Using your Raspberry Pi to Remotely Control Radio Frequency Devices in Your Home

Posted by Renato Recio on October 25, 2015

Home Automation Part 2:
Enhance your Raspberry Pi to communicate RF signals throughout your home.


In our last tutorial, "Utilizing Siri and the Raspberry Pi to Unlock Home Automation", we created a script that let us send commands from our iPhone to the Raspberry Pi. Now that we can fetch Siri commands, we can begin reading those commands and actually start doing stuff with them.

In this tutorial, I will be referencing several different pieces of hardware that will be needed throughout the tutorial. If you plan on following along, I recommend purchasing the following (assuming you already have a RPi and its standard equipment). Note that some of these items are in excess (you won't need 40 Jumper Wires, and if you are comfortable using strictly jumper wires, you won't need a breakout kit either).

Here is what we will need:
  • 1 x Solderless Plug-in BreadBoard ~ $5 total
  • 40 x Male/Male Jumper Wires ~ $3 total
  • 1 x Pi T-Cobbler Breakout Kit ~ $10 total
  • 1 x 433 MHz Transmitter and Receiver ~ $3 total
  • 3 x Etekcity 433 MHz RF Power Switches ~ $7 each, 20$ total.
    Note: You can order packs that have more of these - the more you get, the cheaper they are.

  • 3 x LED-Concepts 433 MHz RF Light Bulb Sockets ~ $7 each, 20$ total.
    Note: You can order packs that have more of these - the more you get, the cheaper they are.

    Whew. That's that. So, with $60 in total, we get everything we need to configure three power outlets and three light bulbs for Part 2 of our Home Automation System. And if we ever want to expand this to more than just three of each, it only costs roughly ~7$ per additional socket / bulb. Not bad right?

    Now let's get down to business. I've seen several different ways that this could be done, but I went ahead with the simplest idea that worked for me. So for this tutorial, we will start by downloading some software from ninjablocks. The programs contained here are designed to communicate with our RF Receiver and Transmitter from the breadboard through the Raspberry Pi GPIO pins. Our transmitter and receiver are tuned to a 433 MHz frequency, and the software will help us send and read the binary data sent to and from this hardware.

    Before we set up the hardware, let's download the packages we need on our RPi:
    git clone git://github.com/ninjablocks/433Utils.git
    
    cd 433Utils/RPi_utils/
    
    git clone git://git.drogon.net/wiringPi
    
    cd wiringPi
    
    git pull origin
    
    ./build
    
    cd ..
    
    make all
    

    That should be everything you need to get started. Now let's go ahead and hook everything up to the RPi. I am going to go through this step by step, so please bear with me. I don't want you to miss anything, as I had to learn this for myself the hard way...

    Step 1: Connect the breakout board to the RPi and breadboard. Make sure your RPi is off; my picture shows it on because I already had mine connected. Also, make sure you push the breakout board down on the breadboard so that all of the pins are fastened inside. Mine needed some strength to push it through.


    Step 2: Connect the transmitter and receiver to the breadboard. Pretty straight forward. Again, make sure the pins are pushed down so that they are sturdy. Also, note that there is a long, hacky looking wire connected to my transmitter. That is my ghetto-rigged antenna - we will talk about that later.


    Step 3: Connect the jumper wires to the breadboard and breakout board.
    Here we will connect three total jumper wires. The first wire will go into the breadboard in the row marked "5V" on the breakout board. The second one is for ground, and will go into the "GND" marked row, and the last is for the GPIO connection and will go into the row marked for "27", which should be GPIO "21" if you have the older RPi.


    Step 4: Connect the jumper wires to the receiver. Use the same jumper wires you connected in Step 3 and connect the other ends to the receiver using the respective labels "GND", "VCC", and "DATA". Note, GND connects to GND, VCC connects to 5V, and DATA connects to 27.


    Step 5: Connect the jumper wires to the transmitter Repeat steps 3 and 4 with the transmitter, with one key difference: you will be using "17" instead of "27" for your DATA pin. If you modify codesend.cpp, you can actually change which pin is used (the pin corresponds to WiringPi pin mapping, so 0 is the default, which is 17 on the Pi). Also note, in the image below, I reused the same wires that I used for my receiver. If you choose to do this, keep in mind you will be switching wires back and forth when we start reading and sending RF signals.


    Step 6: Keep cats away. Job well done - the hardware portion of this tutorial is complete. We have now learned how to connect the RPi to the transmitter and receiver. In the rest of this tutorial, we will be working on figuring out how to use our program to receive and transmit data through our devices.


    Ok, now let's open up the terminal on the RPi. First thing you want to do is cd into 433Utils/RPiUtils. Then, run the program RFSniffer using sudo, and while it is running, take your remotes, bring them close to your receiver (make sure the jumper wires are connected to it), and start pressing the on / off buttons. Each time you press a button, you should see a number generated from the program:

    If it didn't work, I would double check that you are running under sudo, and that all the jumper wires are connected to the receiver properly. If it is working, then you successfully have unique integers that represent specific actions (turning on or off your power switch).
    So when you press the "ON" button for device 1, what is happening here is that your remote is sending radio waves with high and low frequencies, which are then interpretted as 1's and 0's. When the transmission is sent and received, the program processes a collection of these 1's and 0's and then formats them in integer representation so that we have a unique code (1136065) that we can use and see in our programs.

    Now to test that this works, we will copy one of the numbers we received using RFSniffer and send it to the power switch using another program called "codesend". Ensure that your transmitter is connected as in Step 5.

    To verify it worked, make sure that your power switch is plugged in and see if you can turn it on or off as a result of this command. I've found the signal isn't very strong, so for best transmission strength, cut out the inside of a Cat5 cable and solder it to the antenna portion of the transmitter chip. You can see how I did the same thing except without the soldering in Step 2. Using this method, I found that I can easily get signals throughout my entire home.

    If you made it this far, it's time to implement it in our program. For me, I really liked the idea of using a minimal number of words that will trigger an action if they are found in a voice command. For example, if a voice command reads as "Turn on the stupid ugly living room lights!", then we should be able to trigger the action for turning on the living room lights by simply matching the words "On" and "Living". By minimizing the number of key words needed, we remove the chance of any error or misinterpretation by Siri when we read the commands in our Python script.

    What we need to do now is record all the unique numbers from RFSniffer, note what they do, and map them to their intended functionality in a YAML file. Make a file called commands.yaml, and copy this code to the contents, replacing the key words (such as "living" and "bed") with whichever key words you want for your voice commands. Also, note that the numbers shown are dummy codes - replace them with the RFSniffer codes that you want to correspond to those actions:
    'office on': 1231231
    'office off': 1231231
    'tv on': 1231231
    'tv off': 1231231
    'living on':
      - 12312
      - 12312
    'living off':
      - 12312
      - 12312
    'bed on': 1231231
    'bed off': 1231231
    'coffee start': 2131231
    'coffee finish': 2131231

    The key words can be whatever you choose, but I'd recommend keeping them all lower case because our program is designed to match against Siri data that is lower case. Take note that the YAML allows you to make a list value by prefixing each list item with a hyphen and entering them on new lines. As you can see, 'living on' and 'living off' have multiple values because my living room has multiple power outlets that I want to control using the 'living on' and 'living off'commands.

    Now, assuming the commands.yaml file is located in the same directory as your Python file from our first tutorial, we can continue modifying our script to accommodate our new commands.

    First, go ahead and add PyYAML to your Python library using pip (assuming pip is installed as well):
    sudo pip install PyYAML

    Now open up your script from the last tutorial, and start by adding the import and loading the YAML file into a Python dict, which we will call "commands":
    import yaml
    commands = yaml.load(open('commands.yaml'))
    

    Then, make a function called execute_command(). The goal of this function will be to find the key words that match the data we sent in from Siri. This data can be something like "turn on the living room lights."
    def execute_command(data):
        for key in commands.keys():
            split_keys = key.split(' ')
            filtered = filter(lambda x: x in data, split_keys)
            if filtered == split_keys:
                send_codes(list(commands[key]))
                break
    

    Our last step is to actually send the codes that we wrote in our YAML file, which the execute_command() function calls once it finds matching key words. This function will concatenate a send command for each code that we send for that action. In my example, the only time we will send more than one code is for 'living on' and 'living off', where there are two codes for each action. We will use Python's subprocess module to send them as shell commands, similar to how we executed the program earlier in this tutorial.
    def send_codes(codes):
        command = ''
        for code in codes:
            command += 'sudo ./codesend {code};'.format(**locals())
        p = subprocess.Popen(command,
                             shell=True,
                             stdout=subprocess.PIPE,
                             cwd='/home/pi/433Utils/RPi_utils')

    Make sure that you put the correct directory for 'cwd' if the location of your RPi_utils folder is different than mine. This lets the subprocess know where to execute the codesend program from.

    At this point, we can actually test our program to verify it works. In its entirety, it should look something like this:
    import imaplib
    import time
    import subprocess
    import email
    import yaml
    
    last_checked = -1
    commands = yaml.load(open('commands.yaml'))
    
    def execute_command(data):
        for key in commands.keys():
            split_keys = key.split(' ')
            filtered = filter(lambda x: x in data, split_keys)
            if filtered == split_keys:
                send_codes(list(commands[key]))
                break
    
    def send_codes(codes):
        command = ''
        for code in codes:
            command += 'sudo ./codesend {code};'.format(**locals())
        p = subprocess.Popen(command,
                             shell=True,
                             stdout=subprocess.PIPE,
                             cwd='/home/pi/433Utils/RPi_utils')
    
    def fetch_siri_command(mail):
        global last_checked
        mail.list()
        mail.select("Notes")
        result, uidlist = mail.search(None, "ALL")
        latest_email_id = uidlist[0].split()[-1]
        if latest_email_id == last_checked:
            return
        last_checked = latest_email_id
        result, data = mail.fetch(latest_email_id, "(RFC822)")
        voice_command = email.message_from_string(data[0][1]).get_payload()
        return voice_command.strip().lower()
    
    
    def main(username, password):
        mail = imaplib.IMAP4_SSL('imap.gmail.com', 993)
        mail.login(username, password)
        while True:
            try:
                command = fetch_siri_command(mail)
                if command:
                    execute_command(command)
            except Exception as exc:
                print("Received an exception while running: {exc}"
                      "\nRestarting...".format(**locals()))
            time.sleep(1)
    
    
    if __name__ == '__main__':
        main('gmail_username', 'gmail_password')
    


    That is everything! To run your program, save this program as anything (i.e. driver.py) and make sure you put this in some folder along with commands.yaml. Then, simply execute by typing
    python driver.py

    Once it is running, you can leave it on and begin ordering Siri to follow your commands. "Note, turn on the living room lights right now!" If it worked, you can sit back, grab a drink, and pat yourself on the back for a job well done. Otherwise, happy bug hunting.

    Click here to find relevant file(s) for this tutorial.

    In my next few blogs, I plan to write tutorials that introduce some different concepts such as

    • How to set up a voice-controlled stereo system that will play almost any song instantly.
    • How to control infrared devices such as your TV with your voice-controlled Raspberry Pi.
    • How to set up a Home Security System with your Raspberry Pi.

    Thanks for reading! Be sure to comment below with any questions or advice!