Skip to content

Instantly share code, notes, and snippets.

@McPolemic
Last active August 29, 2015 14:12
Show Gist options
  • Save McPolemic/25c6a74fb658bb7a3fc9 to your computer and use it in GitHub Desktop.
Save McPolemic/25c6a74fb658bb7a3fc9 to your computer and use it in GitHub Desktop.
Home Automation with Redis Pub/Sub

I've been thinking a lot about home automation lately. Turning lights on when you enter rooms, turning the television on and off from your smart phone, making sure the doors are locked when you're not home - there's a lot of use cases I'm interested in.

I've read a number of articles where people will do one of the above. The order is usually the same:

  1. Get an Arduino/Raspberry Pi.
  2. Wire it up to power.
  3. Turn it on and off with a simple switch.

That didn't excite me very much. Some people went through the trouble of giving it a web front-end, so you could open http://192.168.12.34/ to view or change the device. That's getting closer, but it's a little... user hostile. I don't want to remember random IP addresses, especially if I'll have more than one device in the house ("Was that the garage door I just turned on, or the coffee pot?").

I then came across an article titled "Building a home automation system - The broker and sensors" by Stian Eikeland. He talks about having a central server that all devices can send their information to. It, in turn, will publish all received data. When you want to do something with the data, you simply connect to the central server and subscribe to the events you're interested in.

I have a number of Sonos speakers around the house and decided that would be a good testbed for how this sort of system would work. I can publish what's currently playing on each speaker to the central server. From there, various tools can connect to get the information. Perhaps in the future I'll even send out controlling events.

In lieu of ZeroMQ, I opted for Redis Pub/Sub. No particular reason why other than I already had the Redis gem installed and didn't want to learn a new API on top of trying this out.

First, I built the Sonos watcher. I used the awesome Sonos gem, which took away a large bit of difficulty actually interacting with the speakers. From there, the code was relatively easy:

require 'json'
require 'sonos'
require 'redis'

# Set up some global connections
REDIS = Redis.new()
SONOS = Sonos::System.new

# Publish a message hash to Redis Pub/Sub
def publish(message)
  # Let's print out our raw data to see what's going on in the system.
  puts message
  
  encoded_message = JSON.dump(message)
  REDIS.publish('sonos', encoded_message)
end

# Create a thread for each speaker to watch for changes
listeners = SONOS.speakers.map do |speaker|
  Thread.new do
    puts "Watching #{speaker.name} for changes..."

    # Since we'll always want updates, we'll loop indefinitely
    loop do
      # Sonos puts out a lot of data. Let's get just the data we're interested in.
      event_data = {speaker: speaker.name,
                    is_playing: speaker.is_playing?,
                    song: speaker.now_playing}

      publish(event_data)

      # Sleep for a bit of time to keep from harrassing the speakers
      sleep(0.25)
    end
  end
end

# All our work is done in the above threads, but we don't want to exit.
# We'll just sleep instead.
sleep

After running the listener, we can see some of the output by running redis-cli monitor:

Watching Bedroom for changes...
Watching Kitchen for changes...
Watching Nika's Room for changes...

{:speaker=>"Nika's Room", :is_playing=>true, :song=>{:title=>"Angie", :artist=>"Rockabye Baby!", :album=>"Lullaby Renditions of The Rolling Stones", :info=>"", :queue_position=>"87", :track_duration=>"0:03:06", :current_position=>"0:00:22", :uri=>"x-sonos-spotify:spotify%3atrack%3a61BcO6t63xsc0x4bKNrHeZ?sid=12&flags=32&sn=2", :album_art=>"http://192.168.1.30:1400/getaa?s=1&u=x-sonos-spotify%3aspotify%253atrack%253a61BcO6t63xsc0x4bKNrHeZ%3fsid%3d12%26flags%3d32%26sn%3d2"}}
{:speaker=>"Kitchen", :is_playing=>true, :song=>{:title=>"Frei", :artist=>"Schandmaul", :album=>"Anderswelt", :info=>"", :queue_position=>"2", :track_duration=>"0:04:06", :current_position=>"0:02:54", :uri=>"x-sonosprog-http:station-song%3a1404502%2f16586533.mp4?sid=29&flags=32&sn=4", :album_art=>"http://192.168.1.6:1400/getaa?s=1&u=x-sonosprog-http%3astation-song%253a1404502%252f16586533.mp4%3fsid%3d29%26flags%3d32%26sn%3d4"}}
{:speaker=>"Bedroom", :is_playing=>true, :song=>{:title=>"Prelude No 4 In E Minor Op 28", :artist=>"Frédéric Chopin", :album=>"The Soloist - Classical Masters", :info=>"", :queue_position=>"2", :track_duration=>"0:02:05", :current_position=>"0:01:32", :uri=>"x-sonosprog-http:station-song%3a1395850%2f5496553.mp4?sid=29&flags=32&sn=4", :album_art=>"http://192.168.1.5:1400/getaa?s=1&u=x-sonosprog-http%3astation-song%253a1395850%252f5496553.mp4%3fsid%3d29%26flags%3d32%26sn%3d4"}}
{:speaker=>"Nika's Room", :is_playing=>true, :song=>{:title=>"Angie", :artist=>"Rockabye Baby!", :album=>"Lullaby Renditions of The Rolling Stones", :info=>"", :queue_position=>"87", :track_duration=>"0:03:06", :current_position=>"0:00:22", :uri=>"x-sonos-spotify:spotify%3atrack%3a61BcO6t63xsc0x4bKNrHeZ?sid=12&flags=32&sn=2", :album_art=>"http://192.168.1.30:1400/getaa?s=1&u=x-sonos-spotify%3aspotify%253atrack%253a61BcO6t63xsc0x4bKNrHeZ%3fsid%3d12%26flags%3d32%26sn%3d2"}}
{:speaker=>"Bedroom", :is_playing=>true, :song=>{:title=>"Prelude No 4 In E Minor Op 28", :artist=>"Frédéric Chopin", :album=>"The Soloist - Classical Masters", :info=>"", :queue_position=>"2", :track_duration=>"0:02:05", :current_position=>"0:01:32", :uri=>"x-sonosprog-http:station-song%3a1395850%2f5496553.mp4?sid=29&flags=32&sn=4", :album_art=>"http://192.168.1.5:1400/getaa?s=1&u=x-sonosprog-http%3astation-song%253a1395850%252f5496553.mp4%3fsid%3d29%26flags%3d32%26sn%3d4"}}
{:speaker=>"Kitchen", :is_playing=>true, :song=>{:title=>"Frei", :artist=>"Schandmaul", :album=>"Anderswelt", :info=>"", :queue_position=>"2", :track_duration=>"0:04:06", :current_position=>"0:02:54", :uri=>"x-sonosprog-http:station-song%3a1404502%2f16586533.mp4?sid=29&flags=32&sn=4", :album_art=>"http://192.168.1.6:1400/getaa?s=1&u=x-sonosprog-http%3astation-song%253a1404502%252f16586533.mp4%3fsid%3d29%26flags%3d32%26sn%3d4"}}

We're getting some decent information from just that small amount of code. From here, I created a small program to parse the data and show what was playing in each room.

I ended up really liking the idea of having a single server to publish events to. I can have a collection of small programs all sending and listening for events. It can be as simple as a generic way to get music information, or I can think grander by listening to more events (such as Stian's article above proposing aggregating temperature readings). It was a fun way to spend an hour or so, and I'm hoping I'll build this out into a better, more cogent system.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment