Here I’m going to build a very simple restroom occupancy detector. It’s a working prototype, but it’s probably not quite advanced enough for prime time. Here’s a look at the finished product:
Unfortunately, it would be overwhelming to try and give a comprehensive step by step walkthrough, so I’ll only be covering the major points. All of the tools & software that I’ll be using are well documented, and don’t be afraid to ping me with any questions!
The complete source code is available on Github.
Parts list:
- Raspberry Pi (NOOBS OS installed)
- Magnetic door switches
- Miscellaneous electrical stuff - breadboard, jumpers, resistors, multi-meter, etc. Check out SparkFun!
- Wireless ethernet adapter (only necessary if you don’t have an ethernet connection available).
Software we’ll be using:
- Ruby & Rails (with ActionController::Live)
- Pi Piper - Library for working with Raspberry Pi GPIO from Ruby
- Redis - event publishing/subscription
Playing with the magnetic door switches
First up, let’s check out the magnetic door switches. These particular ones are extremely simple. Essentially, when the two pieces are apart, the switch is OPEN. When the pieces come together, however, the internal magnetics are such that the switch is CLOSED.
To be sure we’re correct, we can set our multi-meter to check for continuity (usually a “sound wave” icon, since it will beep when the circuit is complete).
When the pieces are brought together, the circuit is complete and the multi-meter beeps.
Occupancy Status with Pied Piper and Redis
On the Raspberry Pi, you’ll need to install a recent version of Ruby and Rails. I used Ruby 2.1.1 and Rails 4.0.5. Also, you’ll need to install Redis and add the pi_piper
, puma
, and redis
gems.
source 'https://rubygems.org'
ruby '2.1.1'
gem 'rails', '4.0.5'
gem 'sqlite3'
gem 'sass-rails', '~> 4.0.3'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.0.0'
gem 'jquery-rails'
gem 'redis'
gem 'puma'
gem 'pi_piper'
Within the rails directory, I created a file named listener.rb
. We’ll start this file up in order to listen for Raspberry Pi input/output pins. Here’s what it looks like.
require "pi_piper"
require 'redis'
redis = Redis.new
PiPiper.watch(pin: 7, direction: :in) do
# 1 - door closed
# 0 - door open
if value == 1
redis.set('status', "occupied")
elsif value == 0
redis.set('status', "available")
end
puts "Pin changed from #{last_value} to #{value}"
end
PiPiper.wait
This is setting up RPi pin 7 as an INPUT pin. When the pin’s value changes, we update a Redis entry to reflect the updated status. Pretty simple.
You’ll need privledged access for this, so I’m running it like this: bundle exec rvmsudo ruby listener.rb
. Now for the circuit.
I’m getting the 3.3V and GND from the available RPi pins.
The 330Ω resistor is in there to protect the RPi input incase I set it to output by accident (note, I don’t know how well the resistor actually protects the Pi).
The 10KΩ resistor is a pull-down resistor. When the switch is OPEN, the Pi input will be fed by ground through the 10KΩ resistor. When the switch is CLOSED, the 3.3V will overpower the ground due to the resistance values, and the Pi input will be HIGH.
If you’ve done everything right, you should see the value changing as you move the sensors together and apart:
Displaying the status
Because we’ve now got the status saved in Redis, we can display it very through a Rails action!
DoorStatusSimple::Application.routes.draw do
root to: "pages#status"
get :updates, to: "pages#updates" # we'll use this later
end
For now, our pages controller is very simple. We’ll just fire up Redis and grab the occupancy status.
class PagesController < ApplicationController
def status
redis = Redis.new
@status = redis.get("status") || "unknown"
end
end
Next, we’ll add the template and some styling to bring it together:
<div class="status" data-status="<%= @status %>">
<span class="smaller">Restroom is currently</span>
</div>
[data-status] {
border-radius: 5px;
width: 600px;
margin: 50px auto 0 auto;
color: #fff;
text-align: center;
font-size: 90px;
.smaller {
padding: 4px 0;
color: #333;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
background-color: #fff;
display: block;
font-size: 20px;
}
}
[data-status='occupied'] {
background-color: red;
border: 2px solid red;
&:after {
content: 'OCCUPIED';
}
}
[data-status='available'] {
background-color: green;
border: 2px solid green;
&:after {
content: 'AVAILABLE';
}
}
When you start the Rails server on the Pi and visit “#{PIIPADDRESS}:3000”, you should see something like this:
Unfortunately, the state is fixed and you need to to refresh the page each time you want to see an updated restroom status. That’s OK, but let’s see if we can do better…
Live (as in ActionController::) status updates
For fun, I’m going to use ActionController::Live
to stream occupancy updates to the browser.
First, I’ll need to update the listener to send Redis publications about the status. I’m still going to keep the set
s so that when we first visit the page we’ll get the last known status.
PiPiper.watch(pin: 7, direction: :in) do
# 1 - door close
# 0 - door open
if value == 1
redis.set('status', "occupied")
redis.publish('status_update', "occupied")
elsif value == 0
redis.set('status', "available")
redis.publish('status_update', "available")
end
end
Then I’m going to add the updates
controller action.
class PagesController < ApplicationController
include ActionController::Live
def status
redis = Redis.new
@status = redis.get("status") || "unknown"
end
def updates
response.headers["Content-Type"] = "text/event-stream"
redis = Redis.new
redis.subscribe('status_update') do |on|
on.message do |event, data|
if %w(available occupied).include?(data)
data = { event: "status_update", status: data }
response.stream.write("data: #{data.to_json}\n\n")
end
end
end
rescue IOError
logger.info "Stream closed"
ensure
response.stream.close
end
end
We’ll subscribe to the Redis “status_update” that we publish in listener.rb
, and when a message is received we’ll check if it’s either “available”, or “occupied” - the two statuses that we expect. If so, we’ll write out a bit of JSON to the stream.
Check out the Railscast on ActionController::Live for more information.
Finally, some JavaScript to bring it together.
source = new EventSource('/updates')
source.addEventListener 'message', (e) ->
data = JSON.parse(e.data)
if data.event == "status_update"
$("[data-status]").attr("data-status", data.status)
console.log e
console.log data
We’ll add the /updates
path as a source, and then listen for messages. When a status_update
message comes along, we’ll just change the status
data attribute. Updating this data attribute will change the occupancy styling/text.
Check it out:
Shortcomings & Looking to the next prototype
While cool, this prototype is unfortunately not very practical. For venues with multiple restrooms, we don’t really want to have a separate Raspberry Pi for each. Also, the RPi will require an electrical and ethernet outlet (or wireless adapter).
My plan is to improve on this design by introducing small microprocessors and wireless transceivers into the mix. Each door, instead of having a RPi, would have a small, cheap microprocessor (think ATTiny) and talk to the RPi through a transceiver.
I’ll surely post an article about the next prototype when it’s completed. Be sure to subscribe with the form below so you don’t miss it!