Twitter controlled power switches with a Raspberry Pis

A programmable remote power outlet switch is a great way to control a huge number of interactive installations and projects: turn on your garden water sprinklers with an SMS, play a horn when you receive a Tweet, turn on a light when your website goes down, start playing music when you enter the room … there are endless possibilities.

To give you an idea of the potential, we’re going to show you how to make a Twitter controlled power switch that activates for 10 seconds when you Tweet a certain hashtag.

Overview

To realise these ideas, all you need are a few relatively cheap off-the-shelf parts:

  • Raspberry Pi: we’ll use a Raspberry Pi B+ to power the application. With this you will need to buy a power supply and a SD card (ideally with the OS - called NOOBS - pre-installed). It’s worthwhile also getting a case. I’ve used this case in the past, which has enough space to enclose the below PiMote controller board.
  • PiMote: for the remote power outlet we’ll use a PiMote. These are a line of purpose-built devices by Energenie that attach directly to a Raspberry Pi GPIO pins and allow you control the included plug adapter over radio via software. They have a great range of 10m - 30m (which can be extended by soldering on an antenna).

— The PiMote controller board and remote socket from Energenie

— Everything you need to get your remote switch setup

We’ll create a Python application on our Raspberry Pi that will run on boot-up of our device. This application will use the public Twitter stream API to listen for Tweets with a particular hashtag. When a Tweet is received, we will use the PiMote to turn on and off the remote power switch for 10 seconds. Easy!

Installing the PiMote board

The first thing to do is attach the PiMote controller board to the GPIO pins on the Raspberry Pi. Make sure that you have aligned it correctly. Looking closely, you will be able to see a small ‘1’ and ‘2’ beside the top-most pins of the PiMote board. These should connect to the ‘1’ (3.3v) and ‘2’ (5.0v) pins on the RPi. For more information you should read the Energenie manual but the following image should make it clear:

— The PiMote fits neatly onto the RPi's GPIO pins

Setting up the Raspberry Pi

To get the RPi setup, you should first read the great documentation on their website. We are going to be connecting to our RPi directly over SSH on the local network. There are alternative approaches if you are not able to do this. If you aren’t comfortable with any of these you can simply connect a monitor, mouse and keyboard and mouse and use the RPi directly.

First, you need to download a copy of the custom Raspberry Pi, Debian-based OS called “Raspbian”. You can get it via torrent or direct download on the Raspberry Pi website.

Once you have it downloaded, unzip it:

unzip ~/Downloads/2016-05-10-raspbian-jessie.zip

then you need to format the micro SD card with the image. We’re using OSX so the following approach is specific to that operating system. You can find guides for other OSs via the RPi documentation above.

Please be aware that the commands you need to run here will differ from what we run. It's important to read the RPi walkthrough above carefully or you could damage a drive on your laptop

First find out what disk your SD card has been assigned.

diskutil list

In our case it’s disk ‘3’ but we will refer to it as disk ‘X’ to avoid any copy and paste disasters!

Then you need to write the downloaded .img to the card:

sudo dd bs=1m if=~/Downloads/2016-05-10-raspbian-jessie.img of=/dev/rdiskX

this will take a few minutes and you may not notice any output on the command line while you are waiting. When the command finishes executing, your SD card is ready!

Connect your RPi to the power supply as well as to your network over Ethernet. You will then need to find what IP your RPi has been assigned on the network. Once you have that, simply SSH in:

ssh pi@192.168.X.X

with the default password raspberry

Creating our Python environment

We’re going to write a simple Python application that will listen to the Twitter API stream and communicate with the PiMote board via the GPIO pins to enable and disable the remote power switch. Before we do this, we’re going to update our system and also install some dependancies:

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install python-dev

When creating Python applications, it’s recommended to use virtualenv and virtualenvwrapper to create isolated Python environments. This means that instead of installing the new libraries into your global Python path, you encapsulate the libraries required for this project within a sealed-off environment. You don’t have to use this but it makes life much easier.

To install them:

sudo pip install virtualenv virtualenvwrapper

then add the two following lines to your ~/.bashrc file:

export WORKON_HOME=~/.virtualenvs/
source /usr/local/bin/virtualenvwrapper.sh

and reload bash:

source ~/.bashrc

This will place all your new virtual environments in the folder ~/.virtualenvs/

Now create a new virtual environment for our project:

mkvirtualenv twitter_switch
cdvirtualenv

and a new directory to hold our application:

mkdir twitter_switch

lets add it to our virtual environments path so that we can call it from the python interpreter:

# This command is a feature of virtualenvwrapper which makes it
# easy to add folders to the path
add2virtualenv twitter_switch
cd twitter_switch

We also need to install some Python libraries to help us. The easiest way to do that is using a pip requirements file, so create a file called requirements.txt:

nano requirements.txt

with the following:

oauthlib==1.1.1
requests==2.10.0
requests-oauthlib==0.6.1
six==1.10.0
tweepy==3.5.0

then run pip:

pip install -r requirements.txt

Finally, because we are interacting with GPIO pins, we will need to run out applications as root. So we need to install the GPIO python libraries to the root python, not our virtualenv’s python:

sudo pip install RPi.GPIO

and then symlink it back from our root python to our virtualenv packages:

sudo ln -s /usr/lib/python2.7/dist-packages/RPi ~/.virtualenvs/twitter_switch/lib/python2.7/site-packages/

Creating our remote switch application

Now that we have a sane environment with all our dependancies installed, let’s first create a Python module that can interact with our remote power switch.

The PiMote board on the Raspberry Pi can actually control up to 4 of the remote power sockets that come with it from Energenie. Whether you are just using one socket or four, you need to actually configure each socket manually before using them.

With this in mind, our Python module will allow us to do the following:

  • configure up to four sockets to interact with our Raspberry Pi
  • turn one or all sockets on indefinitely
  • turn one or all sockets off
  • turn one or all sockets on for a specified number of seconds
  • get the status of a single socket

Create a new file called switch.py with the following code:

import time
import logging
import RPi.GPIO as GPIO
from threading import Timer

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
logger.addHandler(handler)

"""
This module is intended to interface with the Energie Pi-Mote GPIO board
attached to the Raspberry Pi. It is written as a singleton module.

Code is based on the following:

https://github.com/MiniGirlGeek/energenie-demo/blob/master/energenie.py
https://energenie4u.co.uk/res/pdfs/ENER314%20UM.pdf
"""
PIN_D0 = 11
PIN_D1 = 15
PIN_D2 = 16
PIN_D3 = 13

PIN_ENABLE_MODULATOR = 22
PIN_MODULATOR_MODE_SELECT = 18

# Set the pins numbering mode
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)

# Signal bit maps
#
# These are the various codes that identify
# which switch we are trying to talk to and
# what we want it to do.
#
# [Turn all switches on] = 1011
# [Turn switch 1 on]     = 1111
# [Turn switch 2 on]     = 1110
# [Turn switch 3 on]     = 1101
# [Turn switch 4 on]     = 1100
POWER_ON = 1
POWER_OFF = 0
SIGNALS_BITMAP = {
    POWER_ON: ['1011', '1111', '1110', '1101', '1100'],
    POWER_OFF: ['0011', '0111', '0110', '0101', '0100']
}

# Tell the GPIO what pins are going to be used as outputs
# - the pins that are going to the encoder on the Energenie board: d0-d3
# - the pin used to enable the modulator
# - the pin used for mode selection on the modulator (OOK/FSK)
GPIO.setup(PIN_D0, GPIO.OUT)
GPIO.setup(PIN_D1, GPIO.OUT)
GPIO.setup(PIN_D2, GPIO.OUT)
GPIO.setup(PIN_D3, GPIO.OUT)
GPIO.setup(PIN_ENABLE_MODULATOR, GPIO.OUT)
GPIO.setup(PIN_MODULATOR_MODE_SELECT, GPIO.OUT)

# Disable the modulator and reset the pins
GPIO.output(PIN_ENABLE_MODULATOR, False)
GPIO.output(PIN_MODULATOR_MODE_SELECT, False)
GPIO.output(PIN_D0, False)
GPIO.output(PIN_D1, False)
GPIO.output(PIN_D2, False)
GPIO.output(PIN_D3, False)

timer = None

def _set_encoder_pins(mode, socket):
    bits = SIGNALS_BITMAP[mode][socket]
    GPIO.output(PIN_D3, int(bits[0]))
    GPIO.output(PIN_D2, int(bits[1]))
    GPIO.output(PIN_D1, int(bits[2]))
    GPIO.output(PIN_D0, int(bits[3]))


def _toggle_modulator():
    time.sleep(0.1)
    GPIO.output(PIN_ENABLE_MODULATOR, True)
    time.sleep(0.25)
    GPIO.output(PIN_ENABLE_MODULATOR, False)


def _switch(mode, socket):
    global timer
    if timer:
        timer.cancel()
        timer = None
    _set_encoder_pins(mode, socket)
    _toggle_modulator()


def status(socket=0):
    state = "{}{}{}{}".format(GPIO.input(PIN_D3),GPIO.input(PIN_D2), GPIO.input(PIN_D1), GPIO.input(PIN_D0))
    if state == SIGNALS_BITMAP[POWER_ON][socket]:
        return POWER_ON
    if state == SIGNALS_BITMAP[POWER_OFF][socket]:
        return POWER_OFF
    return -1


def on(socket=0, seconds=None):
    """ Turn one or all sockets on. """
    if seconds:
        global timer
        if not timer:
            def callback():
                _switch(POWER_OFF, socket)
                return
            _switch(POWER_ON, socket)
            timer = Timer(seconds, callback)
            timer.start()    
    else:
        _switch(POWER_ON, socket)

def off(socket=0):
    """ Turn a single socket off. """
    _switch(POWER_OFF, socket)


if __name__ == "__main__":
    """ Configure a single socket. """
    socket = int(raw_input("""Configuring A Socket With A Number:

First plug the socket into the wall and hold the green button for
5 seconds until it begins flashing.

Now enter a number between 1 and 4 to associate that socket with that number:"""))
    if socket > 4 or socket < 1:
        print "ERROR: You must enter a number between 1 and 4"
    else:
        on(socket=socket)

As mentioned above, we first need to configure our socket so that we know what number it corresponds to. To do this, we plug the socket in and press-and-hold the green button until the red light starts flashing. With the socket in this mode we can send a signal to it to assign it a number.

To do so, open the python interpreter and assign the socket a number by simply sending out an on command:

sudo ~/.virtualenvs/twitter_switch/bin/python
> import switch
> switch.on(socket=1)

Now our socket is socket number 1. Now we can play with it

> switch.off(socket=1)
> switch.on(socket=1, seconds=10)
> switch.status(socket=1)

Creating our Twitter listener

Now that we can turn our remote switch on and off, let’s create a module that uses the Twitter API to detect certain hashtags.

The first thing we need to do is set up a Twitter application on the Twitter developer site so that we can gain access to the API. Create a new application on https://apps.twitter.com. After creating your application you will have four keys and tokens you need to insert into the application: consumer key, consumer secret key, access token and access token secret.

Now create the module twitter.py:

import logging
import logging.handlers
import json
from datetime import datetime

import tweepy

import switch

"""
A script that listens to the Twitter Stream API via Tweepy and monitors it for
one or more hashtags.
"""

logger = logging.getLogger(__name__)
logFormatter = logging.Formatter("%(asctime)s [%(levelname)-5.5s] %(message)s")
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(logFormatter)
logger.addHandler(consoleHandler)
logger.setLevel(logging.DEBUG)

TWITTER_CONSUMER_KEY = ""
TWITTER_CONSUMER_SECRET = ""
TWITTER_ACCESS_TOKEN = ""
TWITTER_ACCESS_SECRET = ""

SEARCH_TERMS = ["#rpi_turnmeon", ]


class TweetListener(tweepy.StreamListener):

    """
    A tweet listener.

    This listens to the Twitter public stream API for any instance of the
    terms I've defined in our search_terms.txt file. When it sees one, it
    runs the associated dispatchers
    """

    def on_connect(self):
        logger.info("[Twitter Hashtag Monitor Started] @ {}".format(datetime.now()))
        logger.info("[Consumer Key] {}".format(TWITTER_CONSUMER_KEY))
        logger.info("[Consumer Secret] {}".format(TWITTER_CONSUMER_SECRET))
        logger.info("[Access Token] {}".format(TWITTER_ACCESS_TOKEN))
        logger.info("[Access Secret] {}".format(TWITTER_ACCESS_SECRET))

    def on_data(self, raw_data):
        data = json.loads(raw_data)
        logger.info("[Energie] Switching Power Switch: %s" % data['text'])
        switch.on(0, seconds=10)
        return True

    def on_error(self, status):
        logger.error("[Twitter API] Error (Status {})".format(status))
        return

    def on_limit(self, track):
        logger.error("[Twitter API] Limit Reached {}".format(track))
        return

    def on_timeout(self):
        logger.error("[Twitter API] Timeout")
        return

    def on_disconnect(self, notice):
        logger.error("[Twitter API] Disconnect")
        return


if __name__ == "__main__":
    try:
        logger.info("[Search Terms] {}".format(SEARCH_TERMS))
        auth = tweepy.OAuthHandler(TWITTER_CONSUMER_KEY,
                                   TWITTER_CONSUMER_SECRET)
        auth.set_access_token(TWITTER_ACCESS_TOKEN, TWITTER_ACCESS_SECRET)
        twitterStream = tweepy.Stream(auth, TweetListener())
        twitterStream.filter(track=SEARCH_TERMS)
    except (KeyboardInterrupt, SystemExit):
        logger.info("Closing Twitter Stream monitor")

now run the module:

sudo ~/.virtualenvs/twitter_switch/bin/python twitter.py

and you should see the following output:

2016-05-17 13:47:45,124 [INFO ] [Search Terms] ['#rpi_turnmeon']
2016-05-17 13:47:46,175 [INFO ] [Twitter Hashtag Monitor Started] @ 2016-05-17 13:47:46.174649
2016-05-17 13:47:46,178 [INFO ] [Consumer Key] 'XXX'
2016-05-17 13:47:46,182 [INFO ] [Consumer Secret] 'XXX'
2016-05-17 13:47:46,185 [INFO ] [Access Token] 'XXX'
2016-05-17 13:47:46,189 [INFO ] [Access Secret] 'XXX'

and you’ll see that we are listening to the hashtag #rpi_turnmeon. If you Tweet this hashtag now, you should see the following:

2016-05-17 13:48:31,877 [INFO ] [Energie] Switching Power Switch: Test #rpi_turnmeon

and your switch should turn on. Woohoo!

Make our application load on boot-up

With everything set up, we now need to have our twitter listener run when we boot the Raspberry Pi. An easy way to do that is to install supervisor. This is a process control system (written in Python) to allow us to start/stop & manage our applications. To install:

sudo apt-get install supervisor

Now let’s make a configuration file for supervisor:

vim supervisor.conf

with the following:

[program:twitter_switch]
numprocs=1
user=root
autostart=true
autorestart=true
command=/home/pi/.virtualenvs/twitter_switch/bin/python /home/pi/.virtualenvs/twitter_switch/twitter_switch/twitter.py
directory=/home/pi/.virtualenvs/twitter_switch/twitter_switch/
environment=PATH="/home/pi/.virtualenvs/twitter_switch/bin"
stdout_logfile=/home/pi/.virtualenvs/twitter_switch/logs/supervisord.twitter_switch.out.log
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=2
stderr_logfile=/home/pi/.virtualenvs/twitter_switch/logs/supervisord.twitter_switch.error.log
stderr_logfile_maxbytes=1MB
stderr_logfile_backups=2

Now create the associated log files:

mkdir ~/.virtualenvs/twitter_switch/logs
touch ~/.virtualenvs/twitter_switch/logs/supervisord.twitter_switch.error.log
touch ~/.virtualenvs/twitter_switch/logs/supervisord.twitter_switch.out.log

now symlink it to the supervisor configuration folder:

sudo ln -s ~/.virtualenvs/twitter_switch/twitter_switch/supervisor.conf /etc/supervisor/conf.d/twitter_switch.conf

now run the control application:

sudo supervisorctl
> reread
> update
> status

Now you should be able to plug out your RPi and plug it back in. After a minute allowing it boot up you should be able to Tweet and have your power socket turn on for 10 seconds!