Posts Tagged With Microcontrollers - Musing, Rants & Jumbled Thoughts

Header Photo Credit: Lorenzo Cafaro (Creative Commons Zero License)

In my previous posts, (Garage Pi – My Raspberry Pi Playground, GaragePi v2: Temperature and Light, and GaragePi v3: Data Persistence and Visualization and Other Small Improvements) I described the Raspberry Pi-based system I had setup in my garage to detect when the garage door was left open or the light was left on and turn on an LED inside the house. It also reads the current temperature in the garage. This was all originally written in Python (with a node.js-based website added in the third post), but I wanted to play around with other languages on the Pi, so I followed it up with a port to JavaScript and .Net (using Mono).

I'm not going to go into too much detail in this post, because (as I hope to write up a post here shortly), I've since abandoned the RaspPi platform for this project and converted it over to an Arduino-based solution.

Some general notes:

In both cases, I tried to keep the layout of the code similar to the Python layout to provide better side-by-side comparisons of the codebase. In some cases, this may result in code that doesn't match the languages' general styling.

The hardware didn't change for these -- just the software. I also didn't change the website or MongoDB, which let me switch back and forth between languages and compare results using the same db and website. (I added a "source language" field to the db records to help call out any oddness.)

All of this was done about a year ago (Feb 2015) -- I'm just lazy and never got around to writing it up.

.NET/Mono Implementation

The .NET implementation, using Mono, was extremely straightforward. Now, in all fairness, this is the language I spend all day writing at my day job, so it's clearly the one I'm most familiar with, but this is running on Linux and interacting with the hardware -- this is supposed to be hard!

I used the "raspberry-sharp-io" library to interact with the GPIO pins. I had to add RaspPi 2 pin support and wrote a driver for the 1-wire temp sensor, both of which have since been merged to the main repository (open source pull requests for the win!). I also used a NuGet package for the MongoDB client.

If you're thinking about going this route, this may not be a good guide. Since the time that I wrote this code, Microsoft has shifted .NET in major ways, including (pre-release) support for the next version of the framework to run natively on Linux. Additionally, Windows10 IoT edition will run natively on the RaspPi, so you could do this without Linux. At the time I went through this excersize, there wasn't good GPIO/1-wire support in the Win10 IoT releases -- that's likely changed.

I initial expected the performance of running in .NET/Mono to be bad. While I've lost the raw numbers I collected, the performance generally was very good, both for CPU and memory utilization

You can find my code on GaragePi GitHub repo in the "dotNet" folder: https://github.com/johnmwright/GaragePi

JavaScript Implementation

Wow -- this did not go well. Since several of my sensors rely on precise timing (the Ultrasonic Range Finder in particular), it did not fit well with the JavaScript callback model. I ended up using the r-pi-usonic npm module which is a Node module written in C so that it can better handle the timing. Note: the r-pi-usonic project is no longer maintained..

I also used the rpi-gpio npm module for GPIO pin interactions. For some reason, I had to pull it from a personal clone of the GitHub repo instead of from the npm website, though I don't recall why. I think the version on npm was very stale at the time.

Ultimately, this solution never fully worked for me. It would periodically just die and I never got all the kinks worked it. It also took me significantly more time (like 2 orders of magnitude or more) to implement than Python and .NET combined!

I also investigated projects like Cylon.js, but ended up going deep into a rabbit hole only to hit a major roadblock. Again, this was a year ago, which in JS terms is lifetimes, so your millage may vary.

A big thanks to Clark Sell, Brett Slaski and Brandon Martinez, who worked through some of the uglier JavaScript bits with me. These guys are all on the That Conference Staff and we have a #microcontrollers Slack channel! You should consider going to That Conference is you're reading my post and live anywhere near the US Midwest!

You can find my code on GaragePi GitHub repo in the "JavaScript" folder: https://github.com/johnmwright/GaragePi



In my previous posts, Garage Pi – My Raspberry Pi Playground and GaragePi v2: Temperature and Light, I described the Raspberry Pi-based system I had setup in my garage to detect when the garage door was left open or the light was left on and turn on an LED inside the house. It also reads the current temperature in the garage.

Since then, I've expanded the system by:

  • Persisting the data into a MondgoDB database
  • Adding a separate NodeJS-based website to view the current readings and a graph of recent readings
  • Improved the logic for my SonicController

If you haven’t already, I suggest at least skimming through my previous posts.

Storing to a MongoDB database

I wanted to store the readings in a database so that I can review historical data. And, since one of the drivers of this project was for me to "play" with and learn technologies I haven't used before, I wanted to utilize a Document Database. I also knew that I wanted to port my project to other languages (C# and NodeJS), so wanted a DB that was easily supported by each of those.

Installing the Software

I decided to use MongoDB and installed the version from Raspian (ie: I did apt-get install mongodb). There are several other resources online on how to get things installed and running, so I won't detail them here. I just used the defaults for everything.

Note: I'm running the 'jessie' version of Raspian, which is basically the release candidate for the next version after 'wheezy'. Unless you've specifically upgraded to 'jessie', you're probably running 'wheezy' and may run into some issues with outdated packages.

I also installed pymongo, the mongodb client libraries for Python.

Updating My Code

Persisting the data was actually pretty straightforward.

First, I added my import statement for the client libs:

     from pymongo import MongoClient

Then, in my main loop method, I create an instance of the db client using the db name garagePi_database:

  dbclient = MongoClient()
  db = dbclient.garagePi_database

Note that if this db doesn't already exist, it will be created as soon as you write a record to it.

Next, I create a record object, which is basically a key-value store. I made the choice not to store the raw readings and use the interpreted values instead. (ie: Instead of storing the distance measurement to the garage door, I'm storing whether the door is open or not). This is because I expect these values to vary when I start using other languages and I didn't want to have to deal with that. I'm also storing the language used to store the data.

record = { "doorOpen" : garageDoorIsOpen,
           "lightOn"  : lightIsOn,
           "temp_F"   : temp_f,
           "timestamp": timestamp,
           "sourceLanguage": "python"
}

And finally, I save the record and print it's generated Id:

 readingId = db.readings.insert(record)
 print("    readings posted to db with id {}".format(readingId))

Storing Less Data

Now, that's all great, but I quickly realized this was storing a LOT of records (1 every 5 second, since that's my polling interval), and I didn't want that.

Instead, I really just want to know:

  • when the door is open (or recently closed)
  • when the light is on (or recently turned off)
  • what the temperature is over time

For the first two it's pretty simple logic: if the value is true for garageDoorIsOpen or lightIsOn, or if that value has changed since the last reading, then save the record. This means I also need to keep a copy of the previous reading to determine if the value has changed.

The "temperature over time" requirement is a little more tricky. Ultimately, I decided I really only wanted readings every 15 minutes for the temperature. So I added some logic to determine if the current time's minute value is 0, 15, 30, or 45 and save the record -- but only once per minute. This only kicks-in if the door is closed and the light is off.

Also, since I'm actually writing this post after I completed the port to other languages, I'm storing the timestamp in UTC to avoid date parsing and formatting issues when writing and consuming the timestamps in multiple languages.

So, the culmination of all this logic looks like this:

from datetime import datetime, timedelta
//....

lastRecord = None

try:
  ledRun.turnOn()

  dbclient = MongoClient()
  db = dbclient.garagePi_database

  while True:
    timestamp = datetime.utcnow()
    print("Beginning Sensor Checks {}".format( timestamp))

    //....


    record = { "doorOpen" : garageDoorIsOpen,
               "lightOn"  : lightIsOn,
               "temp_F"   : temp_f,
               "timestamp": timestamp,
               "sourceLanguage": "python"
    }

    shouldSaveRecord = False
    if lastRecord is None:
      print("       + lastRecord is None")
      shouldSaveRecord = True
    else:
      if garageDoorIsOpen or garageDoorIsOpen != lastRecord["doorOpen"]:
        print("       + garageDoorIsOpen differs from lastRecord {}".format(lastRecord["doorOpen"]))
        shouldSaveRecord = True
      if lightIsOn or lightIsOn != lastRecord["lightOn"]:
        print("       + lightIsOn differs from lastRecord {}".format(lastRecord["lightOn"]))
        shouldSaveRecord = True

      alreadyRecordedForThisMinute = timestamp.minute == lastRecord["timestamp"].minute
      if not alreadyRecordedForThisMinute and (timestamp.minute == 0 or timestamp.minute == 15 or timestamp.minute == 30 or timestamp.minute == 45):
        print("       + recording due to 15 minute period")
        shouldSaveRecord = True


    if shouldSaveRecord:
      readingId = db.readings.insert(record)
      print("    readings posted to db with id {}".format(readingId))

    lastRecord = record
    time.sleep(SAMPLE_SPEED)

Adding a Website

You'll see that I've added a new "Website" folder to my GaragePi GitHub repository. I'm not going to dig too deep into how I set this up, as there are so many options out there you can choose from and a lot of in-depth walk-throughs.

I'm using a node.js based site with express as the web framework and jade as a template engine.

Since this is node.js, I have a package.json file that defines all the library dependancies, so you can do npm install to download them.

Additionally, I've defined a start script, so you can do npm start to get the website up and running. There's also a config.json that defines the db name/hostname/port and http port to use.

Note: You may get a warning about mongodb not being able to load js-bson. That's ok and can be ignored.

{ [Error: Cannot find module '../build/Release/bson'] code: 'MODULE_NOT_FOUND' }
js-bson: Failed to load c++ bson extension, using pure JS version

There are a couple of custom bits that I want to describe here, though.

Getting access to the db client

Since the website will be reading the records from the MongoDB instance, it'll need access to the db client. Instead of having each route fetch it's own client, I've modified the app.js file to create a db client object and attach it to every request:

//DB config
var DbServer = mongodb.Server;
var Db = mongodb.Db;
var dbServer = new DbServer(dbconfig.host, dbconfig.port);
var db = new Db(dbconfig.dbName, dbServer, { safe: true });


// Website config
var app = express();


// Make our db accessible to our router
app.use(function (req, res, next) {
    req.db = db;  // <-- attach the db client to the request object
    next();
});

Fetching the Data

Since I'm using Express and Jade, I need to define the route (webpage path) and fetch the db records there. Then, I'll define the view (layout) later.

My routes are in a routes folder, and I used the index.js file that comes with the out-of-the-box install and define the default \\ route. Inside there, I define a function to fetch the db records for the last x days:

var recordsSince = function(db, dataSinceDate, callback) {

    db.open(function(err) {
        if (err) throw err;

        db.collection("readings", function(err, collection) {
            if (err) throw err;

            collection
                .find({ "timestamp": { "$gt": dataSinceDate } })
                .sort({ "timestamp": -1 })
                .toArray(function(err, results) {
                    if (err) throw err;                        
                    db.close();
                    callback(results);
                });
        });
    });
};

Then in the parent function, I call that fetch method to get the last couple of days worth of data, and call-out the most recent one so I can display it separately:

var numDaysToShow = 2;
var currentDate = new Date();
var dataSinceDate = new Date(currentDate.setDate(currentDate.getDate() - numDaysToShow));

recordsSince(req.db, dataSinceDate, function(records) {

    var tempReadings = records;
    var mostRecent = records[0];

    res.render('index', {
        reading: mostRecent,
        readings: tempReadings,
        showRecords: false
    });
});

Note: I have a showRecords variable here to toggle logic in my view to show a table of the raw values. This is useful while troubleshooting my graphing logic.

The res.render('index'...) call tells Jade to render the index view and bind the reading, readings and showRecords variables and make then available in the view.

That's it for the route.

Displaying the Data

Now to display that data. Again, using Jade, I've defined some views in the views folder. I have an error.jade for showing error status (5xx and 4xx HTTP status). There's a layout.jade that has the shared layout for all of the pages, including the page title, script and css includes (I'm including Bootstrap here) and defines a content block for the child pages to populate.

Then, in my index.jade, I define the content for my page using Jade's markup language. Mine is broken into three logical sections: Current Status, Data Graph and Optionally Display the Datatable

Lesson Learned: Jade allows you to use tabs or spaces to start your lines, but not both. The editor I was using defaulted to using tabs, so I frequently got runtime errors saying something like "You used both spaces and tabs". I ended up changing the editor's settings to use four spaces when I hit tab instead of a tab character.

Current Status

The Current Status section displays the most recent record's data via the bound reading value we passed in from the route above. So anywhere you see reading.XXX, that's going to display the XXX property of the bound record.

Also note that you apply CSS styles to an HTML tag using .style notation, so span.glyphicon.glyphicon-download is rendered as <span class="glyphicon glyphicon-download">.

You'll also see conditional logic using case when default logic blocks.

Also note that if you are only displaying the bound field, you can follow the HTML element with = and the bound field, like this dd= reading.timestamp. But if you're adding any other stuff, like additional text, you have to use #{} to reference your bound item, like this: dd #{reading.temp_F} °F

Here's the Current Status section:

  h1 Current Status
  dl.dl-horizontal
    dt Garage Door:
    case reading.doorOpen
      when false
        dd
          span.glyphicon.glyphicon-download
          span &nbsp;Closed
      default
        dd
          span.glyphicon.glyphicon-upload
          span Open
    dt Light:
    case reading.lightOn
      when true
        dd On
      default
        dd Off
    dt Temp:
    dd #{reading.temp_F} °F
    dt Last Record:
    dd= reading.timestamp

Which looks like this:

Data Graph

Then, in the second section, I use the D3 Javascript library to graph the data in the db records.

This consists of two parts: The Jade markup, which pulls in an external javascript file graph.js and the d3 library, creates a <graph /> element, then executes the renderChart() function from the graph.js file, pointing it at the <graph /> element:

  script(src='http://d3js.org/d3.v3.min.js')
  script(src='/javascripts/graph.js' type='text/javascript')

  graph/
  script.
    renderChart('graph', !{JSON.stringify(readings)})

Now, the real work happens in the graph.js file (which is in the public\\javascripts folder of the site). Here, I graph three data points: temperature (as a line graph), whether the garage door was open or not (as a red dot) and if light was one or not (as a yellow dot). Like this:

I'm going to try not to paste the entire file here, but this method took me a while to get right, so I'm going to describe it in some detail for my own reference and for anyone who wants to try to do the same on their own.

The contents of the file can be found in my GitHub repo and I'll just describe pieces here.

Step 1: Set the dimensions of the canvas / graph In this chunk, I define the screen size of the canvas onto which the graph is drawn.

Step 2: Define function to parse the date / time using ISO format Here, I create a method to parse the date values in the database for use by the D3 library to plot the data. Note that the data is stored in UTC in the database in ISODate format, so this parse logic will convert it into a javascript date in the local timezone

// Define function to parse the date / time using ISO format
var parseDate = d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ").parse;

Step 3: Set the display ranges Here, I define the displayable ranges for the x axis (time) and y (for temp) and yDoorAndLight (for door and light values, which use a different scale) axis. Note that I set a floor of 0 for the yDoorAndLight displayable values, but later I convert the false values to -1 for these data points. This will plot them below the visible graph, thus effectively not plotting the false values.

Step 4: Define the axes Here, I define the actual displays for the x and y axis (what the tick values are, if the axis line is on the left/right or top/bottom, etc). Note that I call .scale(x) and .scale(y) to scale the axis based on the display ranges defined above.

Note that this doesn't draw the axes -- it just defines them.

Step 5: Adds the svg canvas Now we start to actually draw stuff on the screen. First, we have to create an svg canvas to draw the other items onto. Note the d3.select(tagName) call (where tagName is the tag passed into the function call from my Jade template). This is where we define where on the page this graph will go (it'll anchor to that provided tagName), how big it is (based on the dimensions we defined in Step 1), etc.

Step 6: change the shape of the data to work with the graphs Here, we walk through all of the data points and modify the value to match how we want them plotted. Specifically, I call the parseDate function we defined in Step 2 to convert the dates into localtime. Then, for the doorOpen and lightOn properties, I convert the true/false values into -1 for false and either 1 or .95 for true values. This will stack them toward the top of the graph canvas, but not on top of each other. In the next step we'll set the domain range for the values to be from 0 to 1, so they'll be graphed with 1 as the very top and 0 as the very bottom.

// change the shape of the data to work with the graphs
data.forEach(function(d) {
    d.timestamp = parseDate(d.timestamp);
    var doorPlotPoint = -1; //outside the domain, so not shown
    if (d.doorOpen) {
        doorPlotPoint = .95;
    }
    d.doorOpen = doorPlotPoint;

    var lightPlotPoint = -1;//outside the domain, so not shown
    if (d.lightOn) {
        lightPlotPoint = 1;
    }
    d.lightOn = lightPlotPoint;


});

Step 7: Scale the range of the data Here, I define the domain of the data for my three axis. This is used by d3 to scale and plot the values. Values that fall outside the domain are not shown. For the temperatures, I set a +/- 10 degree margin above and below the data points to make it look a little nicer and to keep the temperature datapoints from overlapping with the door/light data points.

// Scale the range of the data
var maxTemp = d3.max(data, function(d) { return d.temp_F; });
var minTemp = d3.min(data, function(d) { return d.temp_F; });
x.domain(d3.extent(data, function(d) { return d.timestamp; }));
y.domain([minTemp - 10, maxTemp + 10]);
yDoorAndLight.domain([0, 1]);

Step 8: Add the temp scatterplot Here, I plot dots for each of the temperature readings. This helps define where there is data and where there is just a line (we'll draw that in a minute). This is useful when my collection script dies and I have a big gap in my data.

Notice my call to .data(data). This is how I handoff the data records for it to use. Then I call .attr("cx", function (d) { return x(d.timestamp); }) to tell it what to plot for the x axis and .attr("cy", function (d) { return y(d.temp_F); }) to plot the y axis.

Step 9 & 10: Add the door scatterplot & light scatterplot Similar to the temp scatterplot, I'm plotting the door open/closed dots. The main difference to the temp scatterplot is that I provide yDoorAndLight for the y axis values. I also use differ fill colors for each (red and yellow).

Step 11: Add the temperature valueline path.

Here, I define the valueline function for plotting the temperature values on the graph. I use .interpolate("linear") to connect the dots with a direct line. I then provide functions to the .x() and .y() calls to return the datapoint properties to use from the data records:

// Define the temperature line
var valueline = d3.svg.line()
    .interpolate("linear")
    .x(function(d) { return x(d.timestamp); })
    .y(function(d) { return y(d.temp_F); });

Then I use that function to plot the temperature data as a line:

// Add the temperature valueline path.
svg.append("path")
    .attr("d", valueline(data))
    .attr("stroke", "blue")
    .attr("stroke-width", 2)
    .attr("fill", "none");

Step 12 & 13: Add the X Axis & Y Axis Finally, we draw the actual x and y axis lines. Note that on the y axis, I use .attr("transform", "rotate(-90)") to turn is sideways and add .text("Temperature (°F)"); to provide the axis label.

Tip: In order for the ° character to display correctly, I had to modify my layout.jade file to set the charset to utf-8, like this:

meta(http-equiv='content-type', content='application/xhtml+xml; charset=utf-8')

Optionally Display the Datatable

The final section in the index.jade file will optionally show a datatable with the raw records. Note that it starts with if showRecords, and that in the route I provide a true/false value for showRecords. That will toggle whether this table is shown or not:

  if showRecords
    hr

    div
      table.table.table-striped
        thead
          tr
            th Id
            th Lang
            th LightOn
            th DoorOpen
            th Temp
            th Timestamp
        tbody
          each record, i in readings
            tr
              td #{record._id}
              td #{record.sourceLanguage}
              td #{record.lightOn}
              td #{record.doorOpen}
              td #{record.temp_F}
              td #{record.timestamp}

Improving the SonicController Logic

A made a few tweeks to the logic in my SonicController class since my last post. Specifically, I was getting some weird readings occasionally and needed to smooth those out, and every so often, the call to get a distance reading would never return. So, instead of taking a single distance reading, I'm now taking multiple readings, dropping the high and low reading and averaging out the remaining before returning that back to the caller. I've also added a timeout to keep it from never returning.

You can see the commit with these changes here

And here's my new class:

import RPi.GPIO as GPIO
import time

class SonicController:

  SPEED_OF_SOUND = 34000 #cm/s

  def __init__(self, triggerPin, echoPin):
    self.triggerPin = triggerPin
    self.echoPin = echoPin

    print("Initializing Ultrasonic Range Finder")

    GPIO.setup(self.triggerPin, GPIO.OUT, pull_up_down = GPIO.PUD_DOWN)
    GPIO.setup(self.echoPin, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)

    GPIO.output(self.triggerPin, False)
    print("Waiting For Sensor To Settle")
    time.sleep(2)

  def _readDistanceOnce(self):

    print("    Distance Measurement In Progress")
    READING_TIMEOUT = 2 #sec
    maxTime = time.time() + READING_TIMEOUT

    GPIO.output(self.triggerPin, True)
    time.sleep(0.00001)
    GPIO.output(self.triggerPin, False)


    pulse_start = time.time()
    while GPIO.input(self.echoPin)==0 and pulse_start < maxTime:
      pulse_start = time.time()

    pulse_end = time.time()
    while GPIO.input(self.echoPin)==1 and pulse_end < maxTime:
      pulse_end = time.time()

    if pulse_end > maxTime:
      print("  PULSE READ TIMED OUT")

    pulse_duration = pulse_end - pulse_start
    roundtrip_duration = pulse_duration * self.SPEED_OF_SOUND
    one_way_distance = roundtrip_duration/2
    print("    Distance: {0:0.2f} cm".format(one_way_distance))
    return one_way_distance

  def readDistance(self):

    #
    # Take multiple readings in order to counter the affects of
    # bad data due to non-realtime OS.  Take a bunch of readings,
    # throw out the min and max, then average the rest.
    # 
    numReadingsToTake = 8
    print("    Taking {} Distance Measurements".format(numReadingsToTake))
    measurements = []
    for x in range(0, numReadingsToTake):
      thisReading = self._readDistanceOnce()
      measurements.append(thisReading)

    maxReading = max(measurements)
    minReading = min(measurements)
    measurements.remove(maxReading)
    measurements.remove(minReading)

    average = sum(measurements)/len(measurements)

    print("    Average Distance: {0:0.2f} cm".format(average))
    return average

  def teardown(self):
    print("Tearing down Ultrasonic Range Finder")
    GPIO.output(self.triggerPin, False)


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



Back in October, my wife picked up on my subtle hints that I wanted a Raspberry Pi (by "subtle", I mean "Hey, here's an Amazon link to exactly what I want").

I decided to make a device to determine when our garage door was left open -- something that happens way more often than I'd like. Initially, this will light-up an LED inside the house, but eventually it may send me an alert on my phone, etc. It could (and will be) extended to also check and record the temperature, ambient light (to determine if the lights were left on), be controllable via a web UI and other fun little projects.

I thought I'd share my experiences here, as well as catalog my designs, etc, for my own future reference. Be sure to check out my follow-up posts: GaragePi v2: Temperature and Light and GaragePi v3 Data Persistence and Visualization and Other Small Improvements

For this initial project, I'll be using an HC-SR04 Ultrasonic Range Finder mounted in the ceiling of the garage. When the garage door is closed, the distance measurements will be several feet (either to the top of a car, if one is parked under it, or to the floor of the garage). If the garage door is open, the distance will be just a few inches, as the garage door slides up under the range finder. When the Pi detects the door is open, it will turn on a light inside.

Starting with the Raspberry Pi Kit

I started off with a nice little kit from CanaKit (sold via Amazon). At the time I got it (and still at the time I'm writing this), it was on sale at Amazon for about $70 and comes with a Rapberry Pi B+, case, power supply, USB WiFi adapter, MicroSD card, small bread board with GPIO interface board and cable, and a ton of resistors, LEDs, buttons, etc.

This is a great little starter kit, especially for the on-sale price (a Pi alone costs $35), and I'd recommend it to folks looking to jump in.

I first assembled all the kit's parts and did some small little "get to know your Pi" projects lighting up LEDs, etc. The great thing about the breadboard is you can build things up and tear them down without any permanent connections, so you can ticker all you want.

The Pi came with the NOOBS (New Out Of the Box Setup) bootstrapper OS, which I used to install Raspian, a Debian Linux distro specifically for the Raspberry Pi. I have a lot of experience in the past with Debian's Linux distro, so this was an easy choice for me.

Adding an Ultrasonic Range Finder

So for the detection of an open door, I'll be using an HC-SR04 module. It has a pair of speakers and microphones that will send off an ultrasonic pulse and measure the amount of time it takes before the sound waves bounce off an object and return to the device's microphone. It has four pins:

  • 5V Vcc
  • Trigger
  • Echo
  • Ground

While you can go to RadioShack and pay $25 for this guy, you can pick it up for much, much cheaper on eBay. You can get one for about $1.50 with free shipping from China. In my case, I found one in Plainfield, IL (just down the street from me), so I paid a higher $3 and got it in two days. Hint: eBay's Advanced Search options let's you search for shippers within a set range from your zipcode.

I used this ModMyPi guide as my primary reference, along with a bunch of other sites, such as the RaspberryPi StackExchange community Q&A site.

The basic usage is this: You send a short (10 micro-second) high voltage signal to the Trigger pin to initiate the pulse measurement. It will then set the Echo pin to high (5V) for the same amount of time it took for the sound pulse to bounce off something and return. You have to time how long the Echo pin is set to high, then use the speed of sound to determine the distance. (Don't forget that the sound has to make a round-trip, so it's actually double the one-way distance).

IMPORTANT: The range finder uses 5V for it's high signal, but the Raspberry Pi can only accept a 3.3V input, so you must split the voltage to prevent overloading the Pi's GPIO circuits.

To do this, you need a voltage divider circuit on the Echo pin. The basic idea is that the Echo pin connects to a resistor (R1), then on the other side of the resistor the circuit has two paths: one to the Pi's GPIO pin, the other goes through another resistor (R2) and connects to ground. The ratio of resistance values of the first (R1) and second (R2) resistors determines how much of the voltage makes it's way to the GPIO pin. In our case, we want the end voltage to be approx 3.3V. You can use this voltage calculator to do the heavy lifting for you. In my case, my kit came with 180 Ohm and 10K Ohm resistors, so I headed to the local RadioShack and looked through their inventory, plugging in the values to that website until I found a set of resistors that I could use to supplement my existing ones.

And an LED

The other important component is an LED that gets turned on when the garage door is opened. The kit came with several 5mm LEDs, so I just used one of those. It's important to put a resistor in the circuit with the LED, otherwise you'll burn out the LED in about 500ms flat. Also, LEDs are polar, meaning that current must pass through it in a specific direction. Usually, the LED will have one shorter leg or a flattened side indicating the Ground pin.

The Circuits

Tip: There's a free circuit design tool called Fritzing which comes with a ton of pre-designed components you can use, including the Raspberry Pi B+, breadboards, Arduinios, etc. This lets you layout your circuits and export them for others to see.

Here's the logical layout I used: (I tried to keep the wire colors and layout close to what I used on the breadboard for your reference).

And here's the actual breadboard:

Going Off-Breadboard

Once I got this all working the way I wanted using the breadboard, I decided to turn it into a more permanent solution. I purchased a "learn to solder" kit and a few tools from Frys, and female-to-female jumper cables and header pins, and some PCM board from RadioShack, as well as some old cabling in my closet from computers long forgotten.

The end result is something more easily mounted in the garage, while easily disconnected from the Pi (so that I move them around in the garage as needed). It also allows me to keep the breadboard available for other projects.

You may notice, too, that I have an LED on this board. This was added later and is on while the Python script is running. This allows me to quickly be able to see if the script is running or not without having to ssh into the Pi.

Here, you can see it mounted on the garage ceiling rafters. I used two nails with rubber motherboard standoffs to keep it way from the wood, plus a "high-tech" rubberband to keep it from falling off the nails from the vibrations as the garage door opens and closes. The ribbon cable runs back to the Pi, which is an easier-to-reach physical location.

The Inside LED

Next, I took the LED intended for the inside of the house, soldered the resistor to the LED lead, then connected the other lead and the lead/resistor to some Cat5 wires and covered the soldier connections with heat-shrink. Then, the LED was mounted on the inside of our entry door, with the wires running through the hollow space above the door frame and coming out in the garage.

I ran Cat3 (telephone) cable from the Pi's mounting location (next to the garage door opener) to the entry door. Ininitally, I tried to splice the wires, but that failed (bad connection). So I rigged up a connection using an old telephone line splitter. It doesn't look very good right now, but I'll clean it up eventually.

Mounting the Pi

And finally, to tie it all together, I mounted the Pi itself in a location where I could easily reach it (to take it down and play with it). The case has two screw mounting holes, but with the vibrations from the garage door opening/closing, it would fall off, so I added another rubberband to keep it in place. You can see where I connected the IDE cable coming from the GPIO pins to the range finder cable on the right and to the cat3 cable running to the inside LED on the left.

The Code

All this hardware is great, but it's not really worth anything until you have code running on the Pi to control it. So here we go...

I started out using Python, mainly because 1) there is a great GPIO library available and 2) lots of existing blogs and how-to articles exist that use Python (and the GPIO library). I later tried to port the logic to other languages, including C# (using mono) and JavaScript (using Node.js). I'll write more about that experiment in another post.

I'm going to assume the reader isn't familiar with Linux or Python, so I'm going to walk through the code a little at a time. I'll post the full program at the end.

The first line - a linux directive

#!/usr/bin/python

For scripting languages on Debian (and most *nix operating systems), you can add a "pound bang" (#!) line at the beginning of the script that tells the command shell which program to use to interpret the script. In this case, I provide the path to python. Now, instead of using the full command line: /usr/bin/python garagePi.py, I can mark the script as executable (using the command chmod +x garagePi.py) and execute it directly: .\\garagePi.py.

Note: that's a little bit of a lie. Since the Python GPIO library needs root access to manipulate the GPIO pins, you must run the script as root (using sudo), so the actual command I use is sudo ./garagePi.py.

Import external libraries

import RPi.GPIO as GPIO
import time

I use two Python libraries:

  • RPi.GPIO, a library for manipulating the Raspberry Pi GPIO pins.
  • time, a library for dealing with time

The RPi.GPIO library is available as a Debian/Raspian package, so the best way to get it is to use apt-get python-rpi.gpio. Note: It seems most recent Raspian releases include this package in the base install, so you likely already have it.

By adding "as GPIO" to the RPi.GPIO import, that let's me use the "GPIO" alias to reference the library in my code.

Set the PIN Mode

GPIO.setmode(GPIO.BCM)

Ok, this one is important to understand: There are different ways people number the GPIO pins used on the Raspberry Pi.

Some people use the "Board" method, which is the physical layout of the GPIO pins on the board going across and down, starting with pin1 in the upper left (3.3V), then pin2 across from it (5V), then pin3 underneath pin1, and pin4 under pin2 and so on. Basically, the left side are odd pins with 1 on top and the right side are even pins with 2 on top.

The other method is "BCM" mode, which is based on how the computer itself sees the pins. These are typically prefixed with "GPIO" on reference diagrams. For example, Board pin 8 is BCM pin GPIO14.

There's also the "WiringPi" numbering system. I haven't taken the time to fully understand where it get's it numbers (the author explains it here), but several online resources and libraries use the WiringPi library under the hood and rely on this numbering scheme.

Here's an interactive diagram that will show you each of these numbering systems, which can be very helpful when porting from one system to another. The kit I purchased also came with a handy reference card for the pinouts.

In my Python script, I've set the mode to BCM. Alternatively, you could use setmode(GPIO.BOARD) to go with the Board layout.

Some Constants - Magic Numbers and Pins

Here, I set some constants I will use later, like the speed of sound, how far away my garage door is from the sensor when the door is open and how often I want to check the door's status.

SPEED_OF_SOUND = 34000 #cm/s    
DISTANCE_TO_CLOSED_DOOR = 70 #cm  - is actually about 60 but I get readings up to 68 sometimes
SAMPLE_SPEED = 5 #seconds

Then some more constants, but this time it's the GPIO pin numbers I'm using. Remember, these are the BCM pin numbers since I set the mode to GPIO.BCM.

# GPIO pin numbers
TRIG = 23
ECHO = 24
LED_OPEN = 12

Initialize the Hardware Modules

I have a separate controller class for each of the hardware modules, which includes a init() and teardown() method, plus hardware-specific methods. I'll detail these classes in a minute. Here, I create the class objects and initialize the hardware for each.

led = LedController()
led.init()

sensor = SonicController()
sensor.init()

The Main Loop

Now, the real work -- In an infinite loop (while True), get a distance reading and if the distance is less than that of the closed door, then turn on the LED. Then, sleep for our SAMPLE_SPEED before checking again.

try:
  while True:
    distance = sensor.readDistance()
    if distance < DISTANCE_TO_CLOSED_DOOR:
      print "    Door open"
      led.turnOnDoorLed()
    else:
      print "    Door closed"
      led.turnOffDoorLed()

    time.sleep(SAMPLE_SPEED)
except KeyboardInterrupt:
    print "keyboard interrupt caught"
finally:
  sensor.teardown()
  led.teardown()
  # Finally, we clean our GPIO pins to ensure that all inputs/outputs are reset
  GPIO.cleanup()
  print "exiting"

Now, you'll notice that the whole thing is wrapped in a try/except/finally block. There are a number of things that could go wrong and throw an Exception, such as not running as root or hitting Cntl-C while the program is running. Cntl-C will throw a KeyboardInterupt and is the easiest way to exit the program when running on a console.

In any case, the finally block logic will run, which will call the teardown() methods on the hardware modules to cleanup the Pin state, and then will call GPIO.cleanup(), which will cleanup the GPIOs so that other apps can use them (including this script in a future run). This includes clearing the input/output state of the Pins. Not doing this will likely result in the next run throwing an exception saying that something else has already configured the GPIO pins.

The SonicController class

I created a separate class for each of the hardware modules to isolate the logic. This will come in handy later as I add more hardware.

In this case, there are three functions in my SonicController:

  • init() for initializing the hardware
  • teardown() for shutting down the hardware
  • readDistance() to actually get a distance reading

    class SonicController:
    

init()

The initialization will set the pin modes (input vs output) for the Trigger and Echo pins. For each, I also instruct the library to use the Pi's built-in Pull-Down resistors. This will keep the voltage on the pins set to low until we explicitly set it high. Initially, I didn't do this and I found that when the temperature in my garage fell below about 20*F, I started getting false-high readings on the pins as the voltage was somewhere between high and low and the GPIO was reading it has (ambiguously) high.

Then, we'll set the Trigger pin to False (low voltage), since this is the expected voltage level when idle. Just in case the pin was previously high (or ambiguously mid-voltage), we let the sensor settle down after setting the Trigger to low. I do this by sleeping for a short period.

  def init(self):

    print "Initializing Ultrasonic Range Finder"

    GPIO.setup(TRIG, GPIO.OUT, pull_up_down = GPIO.PUD_DOWN)
    GPIO.setup(ECHO, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)

    GPIO.output(TRIG, False)
    print "Waiting For Sensor To Settle"
    time.sleep(2)

teardown()

The teardown method just sets the Trigger to False (low voltage) to make sure we leave the model in a sane state.

  def teardown(self):
    print "Tearing down Ultrasonic Range Finder"
    GPIO.output(TRIG, False)

readDistance()

This is the core logic for the Ultrasonic Range finder. I start by logging a message to the console with a timestamp. This comes in handy when things don't work the way you expect.

  def readDistance(self):   
    print "Distance Measurement In Progress " + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())    

Now, I tell the module I want it to take a reading. This is done by setting the Trigger pin to True (high voltage) for approx 10uS (microseconds).

    GPIO.output(TRIG, True)
    time.sleep(0.00001)
    GPIO.output(TRIG, False)

Once the sensor takes it's reading, it will then set the Echo pin to high voltage for the same duration as it took the sound pulse to return to the model. So we must wait until the Echo pin goes high, then time how long it stays high.

Note, however, that Raspian is a time-sharing operating system. This means that our script may not have control of the CPU when the Pin's voltage changes. This has a couple of side effects:

  1. The Echo pin's voltage may already be set to high if our script didn't get CPU time soon enough
  2. Our timings are going to be somewhat inaccurate, since the pin will likely go to low voltage while we're not on the CPU. Given that the speed of sound is pretty darn fast, every microsecond counts - so don't expect super-high accuracy here. (If you want higher accuracy, go to a more real-time system such as the Arduino)

So here, I set the pulse_start to the current time, then go into a busy-wait while loop and keep updating the pulse_start time for as long as the Echo pin is low (zero).

pulse_start = time.time()
while GPIO.input(ECHO)==0:
  pulse_start = time.time()

Once the pin goes high (one), I do a similar busy-wait to set the pulse_end variable

while GPIO.input(ECHO)==1:
  pulse_end = time.time()      

And finally, we take the difference between the two time readings to determine how long it took the sound pulse to make the roundtrip. Multiply that by the speed of sound to determine the roundtrip distance traveled and divide that in half to find the one-way distance.

pulse_duration = pulse_end - pulse_start
roundtrip_duration = pulse_duration * SPEED_OF_SOUND
one_way_distance = roundtrip_duration/2
print "    Distance: %.2f cm" %one_way_distance
return one_way_distance

LedController class

Here again I created a separate class for the LED hardware "module" to isolate the logic. In this case, there are four functions in my LedController:

  • init() for initializing the hardware
  • teardown() for shutting down the hardware
  • turnOnDoorLed() to turn the LED on
  • turnOffDoorLed() to turn the LED off

    class LedController:
    

init()

This one's pretty simple. Set the mode as an output and move on

  def init(self):
    print "Initializing LED"
    GPIO.setup(LED_OPEN, GPIO.OUT)

teardown()

Make sure the LED is off.

  def teardown(self):
    print "Tearing down LED"
    self.turnOffDoorLed()

turnOnDoorLed()

Set the voltage to True (high) so that the light comes on.

  def turnOnDoorLed(self):
    print "    Turning LED on"
    GPIO.output(LED_OPEN, True)

turnOffDoorLed()

Set the voltage to False (low) so the light goes off.

  def turnOffDoorLed(self):
    print "    Turning LED off"
    GPIO.output(LED_OPEN, False)

Full Source

Here's the full source file.