GaragePi v2: Temperature and Light

In my previous post, Garage Pi - My Raspberry Pi Playground, I described the Raspberry Pi-based system I had setup in my garage to detect when the garage door was left open and turn on an LED inside the house. Since then, I've expanded the system by:

  • replaced the red LED inside the house with an RGB LED
  • added a new custom board with a photo-resistor and temperature sensor

The photo-resistor can be used to determine relative light levels (its resistance value decreases as the amount of light hitting its surface increases). I will use this to determine if the light bulb inside the garage has been left on and turn on the green LED if so.

The temperature sensor is exactly what it sounds like -- it provides the current temp. Currently, I'm not doing anything with this value (aside from printing it to the console), but v3 of this project will include a database to store all of the various sensors' values over time, including the temperature, followed shortly by v4 which will add a web UI to the system.

In this post, I'll document what I did to hook-up the photo-resistor and the temperature sensor. If you haven't already, I suggest at least skimming through my original post.

Make sure to also check out my next post GaragePi v3: Data Persistence and Visualization and Other Small Improvements

Adding a Temperature Sensor

Since the Pi doesn't have any analog inputs, a TMP36 module (common for Arduino-based projects) will not work. Instead, I used a DS18B20 Thermometer I picked up on Amazon.com (2 for about $3 -- though you can get them cheaper on ebay).

The device has three pins:

  • Voltage (3.3V-5V)
  • Data
  • Ground

It uses the "1-wire" standard, meaning you can have multiple sensors and connect all of their data pins to the same input pin. Each device has a unique serial number, which is used to access the data. For this device, I won't read the pin directly. Instead, I'll load a couple of linux kernel modules that will read the device data and provide a virtual text file with the sensor's data.

I relied heavily on the AdaFruit DS18B20 tutitorial when putting together my hardware and software for this module.

The hardware is pretty straight-forward. Connect the voltage pin to 3.3V, the ground pin to ground and the data pin to one of your open GPIO ports. You'll also need to put a pull-up resistor between the data and voltage pins. I read that this needs to be between 4.7k ohms and 10k ohms, so I used a spare 56k ohm resistor I had.

I connected the data pin to GPIO pin 4, though it doesn't seem to matter which pin you use.

Here, you can see the sensor on my breadboard. It's a really small chip, so it's hard to make out with all the wires and the resistor.

Adding a Photo-Resistor

A photo-resistor, or photocell, is a resistor that changes its resistance when exposed to light. The more light, the less it's resistance. But since, unlike an Arduino, the Pi doesn't have an analog input where I can directly measure the resistance, I added a 1uF capacitor connected to ground that will basically suck up the voltage coming across the resistor until it fills the capacitor, at which point our GPIO pin will see a HIGH voltage signal. Then I time how long it takes fill the capacitor instead of measuring the resistance directly. The more resistance (ie: the less light), the longer it will take before I get a HIGH voltage signal.

The circuit is again pretty simple. I went to RadioShack and bought the only 1uF capacitor they had in stock, which was a 50V 1uF PET capacitor. It's important to note that these differ from your typical electrolytic capacitor in that PET's don't have polarity and can be connected in any direction. If you're using an electrolytic capacitor, make sure to connect it with the negative pin going to ground. My circuit diagram shows an electrolytic capacitor since I couldn't find a PET module in Fritzing.

So the circuit is this: connect one pin of the photocell to 3.3V, and the other connects to your GPIO pin and to your capacitor. The other leg of the capacitor connects to ground.

When we get to the software, you'll see that I have to first empty the capacitor each time I perform a reading by setting my input pin to LOW.

The Circuits

Here's the logical diagram for both the temperature and photo-resistor circuits:

Here's the completed breadboard:

And for those following along, here's the full circuit diagram for my GaragePi, including the Ultrasonic Range Finder module from my original post and the new RGB LED.

Going Off-Breadboard

Just like with my range finder circuit, I soldered these two circuits onto a PCB so that I could mount them in the garage. I used an old 4-wire cable that originally was intended to connect your computer's CD-ROM to your soundcard (I knew I kept all those old wires for something).

And here it is mounted in the garage along with the other components. I have the new temperature and light board mounted on the left side of the picture, facing the light bulb (just off camera) and as shaded as I could from the window (to reduce false-positive readings from sunlight). You can see the sonic range finder module in the back right and the Pi in the middle. The gray cable heading off to the left is the telephone cable running to the inside LED. The white wires go from the garage door opener to it's safety sensors and are unrelated to the GaragePi, aside from me hanging the LED wiring on it.

The Inside LED

One other change I made was to replace the red LED I had mounted inside with a Red-Green-Blue tri-color LED. This also me to illuminate a different color for different issues. Currently, I show red if the garage door is open and green if the light is left on. One issue I have currently is that the green LED is much brighter than the other two. I could fix this by adding a larger resistor to that LED's pin, but not today.

Since I had used cat3 2-line telephone cable for the run between the LED and the Pi, I had 4 wires already in place that I could use - I just had to remove the old LED and feed the new one's wires through the same path.

The Code

In addition to adding the light and temperature sensor logic, I made two other big changes to the code:

  1. I split the Controller classes into separate files and moved the init() logic into the constructors (__init__())
  2. I put the code up on GitHub

Since the code is now on GitHub, I won't post most of it here.

The PhotocellController class

Source code on GitHub

The logic for getting a reading starts with putting the pin into output mode and writing LOW to the pin. This has the effect of discharging the capacitor. Then, I put the pin back into input mode and time how long it stays in the LOW state. I also put in a timeout because, at least for the size of resistor I'm using, it takes a VERY long time to fill the capacitor at night when the light is off. When the light is on, I usually get readings in under 300, so there's a large margin there.

def resistanceTime(self):
    print("    Reading photocell")
    timeout = 3000
    reading = 0
    GPIO.setup(self.pin, GPIO.OUT)
    GPIO.output(self.pin, GPIO.LOW)
    time.sleep(0.1)

    GPIO.setup(self.pin, GPIO.IN)
    # This takes about 1 millisecond per loop cycle
    while (GPIO.input(self.pin) == GPIO.LOW):
        reading += 1
        if reading > timeout:
            break
    print("    Light Reading: {}".format(reading))
    return reading

The TempSensorController class

Source code on GitHub

Again, this code was based in large part on the AdaFruit walk through.

In the __init__(), I take a sensor index (in the event I ever add a 2nd or 3rd temp sensor), then I call modprobe to load the os modules needed to read these values. I then go find the virtual files that store the readings.

I've noticed a few times that the very first time I run the script after a reboot, the device files aren't loaded yet when I try to read them and an exception is thrown. Immediately re-running the script works fine. This is something I'll need to fix in the future.

def __init__(self, sensorIndex):
    self.sensorIndex = sensorIndex
    print("Initializing Temperature Sensor")

    os.system('modprobe w1-gpio')
    os.system('modprobe w1-therm')

    base_dir = '/sys/bus/w1/devices/'
    device_folder = glob.glob(base_dir + '28*')[self.sensorIndex]
    self.device_file = device_folder + '/w1_slave'

The other methods in this class, _readTempRaw() and readTemp() are reading the values out of the virtual file and parsing them. The temperature is stored as Celsius, so there's also logic to convert it to Fahrenheit and the return value is both temperature scales. I won't post the code here -- head to GitHub or the AdaFruit site if you want to see it.

Main loop

I did a lot of refactoring of the main loop and broke out the garage door check and the light check into separate functions.

For the light check, if the photocell's response time is faster than a constant value, then I consider the light to be on. To come up with the constant, I ran the tests over and over with the light and off to see what values came back. I learned two important things:

  • The longer the photocell is exposed to light, the less resistance it has -- even if the light remains constant. This means that when the light is initially turned on, the values coming back close to 300, but within about a minute, they are closer to 50.
  • I have a CFL bulb in the garage, which takes a while to warm up in the cold of winter -- sometimes up to 3 minutes before reaching full brightness. This means that when I first turn on the light after it's cooled, the light level may not be high enough to trigger my alert.

I also added an instance of the LED controller for the currently unused blue LED. This was needed to ensure the LEDs pin was set to LOW. Initially I didn't have this and I saw a very slight blue glow from the LED due to a very low voltage existing on the pin if it wasn't explicitly set to LOW.

So here's what the main class looks like now. Again, you can find the full source out at my GitHub repo for this project