In the previous post, we sniffed the on/off codes for RF controlled outlets. For this post, we’ll program an Arduino to transmit the codes and have that controlled by a Raspberry Pi.
Because some precise timing is needed for the transmitter, an Arduino will be used to control the RF transmitter itself. The MagPi article mentioned in the previous post connects the transmitter directly to a Raspberry Pi, and says that the code must be sent several times to ensure that it works. That’s because the Pi runs Linux, which is not a real-time operating system. At any given time, the process that is sending the on/off code through the transmitter may be preempted by some other linux process or service, messing up the code. Instead, we’ll use an Arduino based microcontroller and connect that to the Pi via USB. The Pi will send a signal to the Arduino, which will transmit the code (guaranteed to not be interrupted) and then send an acknowledgement back to the Pi. I happen to have an old Arduino Duemilanove sitting around doing nothing. It’s nice to be using it again.
Below is a picture of the Arduino set up.
The Duemilanove is clearly seen connected to a half breadboard. The long yellow wire is the antenna for the transmitter (read more below). The USB cable connects to the Pi not in the picture. Actually, it is plugged into a 7 port USB hub for the Pi. A close up of the breadboard is below.
There’s more on the breadboard than just the transmitter. On the far left there’s a Dallas One-Wire DS18B20 digital temperature sensor (a remarkable device that deserves its own post soon) connected to the Arduino. Next is the transmitter. It’s very easy to connect – it has only four pins. One goes to +5V from the Arduino, another to ground, another for the data (green jumper wire that goes to pin 3 of the Arduino), and the antenna. More about the antenna later. The rest of the board has the receiver and the audio jack used in the previous post to sniff the codes (I pulled the +5V connections so the receiver is not powered, but it was useful to still have it hooked up for debugging).
The next part is coding up the Arduino and the Raspberry Pi. All of the code is on GitHub. The Arduino code is in the arduino/HomeControl.ino Arduino sketch. This sketch also retrieves the temperature from the one-wire temperature sensor, so to run it as is the OneWire library is needed. The code also can transmit a signal to toggle on/off the light on the ceiling fan in my daughter’s room. I tend not to use that, because it’s a toggle and if the signal is missed, then the light will do the opposite of what it supposed to do at the next signal.
arduino/HomeControl.ino is basically C++ code with a few classes:
RFOutletTransmitter are classes that send signals to the RF transmitter (they really should inherit from a common base class). They basically send highs and lows to the transmitter for certain periods of time, based on the code sniffing explained earlier. Each class has some constants that specify the timings (e.g. a
longTime for the RF outlet is 1750 microseconds). The
transmitCodeString method accepts a string of letters, parses it, figures out the high and low transmit times, and then calls the
transmit method with those times.
transmit actually pulls the transmitter data pin high or low for the specified amount of time. It also lights an LED on the Duemilanove board while transmission is occurring.
OutletCode construct the complicated strings that represent the transmission timings. Then, these classes are instantiated with the specific on/off codes. For example, to turn on outlet one, the code is,
OutletCode s1on( "BAABABBBABBBBBBAL", "BAABABB", 6);
The actual code that gets constructed is
where “M”, “A”, “B”, and “L” are different timings of high and low for the transmitter.
loop of the sketch waits for a “command” to come on the serial port (USB). The commands (different from the transmission codes above) are, for example, “A” to turn on outlet one, “a” to turn off outlet one, “B” to turn on outlet two, and so on. The code then waits half a second (can probably remove that wait) and sends back an acknowledgement on the USB. Looking at this code again, I see some places for improvements (e.g. the transmission code is constructed for each transmission instead of once, but that’s not a big deal).
So basically, the Arduino sits there waiting for a command to come over the USB. When the command comes, it fulfills it, sends an acknowledgement, and waits for the next command.
Raspberry Pi software
The Raspberry pi code is in Python and sends commands over the USB to the Arduino. Two python libraries need to be downloaded and installed: pySerial and lockfile. The easiest way to install them is to use the
pip install pyserial pip install lockfile
pySerial is needed to communicate with the Arduino over USB.
lockfile is needed to ensure that only one linux process is sending USB commands to the Arduino at a time. Before accessing the Arduino over USB, the code will attempt to acquire a “lock”, and will wait if another process has the lock already and won’t proceed until the lock is “freed”. The process of acquiring the lock is complicated (it involves creating a file and then making a “hard link” to it), but it is all contained within the
There are four python files (click on the file names to see the files):
- pi/logfile.py gives a common log file for the other programs. See the python documentation for logging
- pi/transmitter.py does all of the work of talking to the Arduino. Its
sendfunction takes the command to send, acquires the lock as mentioned above (note the
with lock:line), opens the serial connection, sends the command, and waits for the acknowledgement. You can run this file directly. For example, to turn on outlet 1, do
- pi/homeCommand.py is a convenience command to immediately turn on or off an outlet with a human-readable line like
./homeCommand 1 onto, for example, turn on outlet one.
- pi/homeDelay.py is the same as
homeCommand.pybut waits for a random delay before issuing the command. The maximum delay in minutes is given as the third argument. So
./homeDelay 1 on 90will turn on outlet one after waiting for a random amount of time, up to 90 minutes.
Pi crontab file
To make a vacation light timer, we need to turn on and off lights at certain times, or with slightly randomized times. Running linux commands at specified times is a job for cron. You write a special “crontab” file to specify when lights should turn on and off. You can see the currently active crontab file with the
crontab -l command. You can write this out a regular file with
crontab -l > myfile. Alter this file with your editor (pico or vi). You then have to install it into cron with
crontab file where
file is the name of the file. You can also edit the crontab file “in place” with
crontab -e See pi/cron.lights for an example crontab file.
More about locking (and using a Ram disk)
lockfile library mentioned above makes a regular file, and then attempts to make a hard link to it. The hard link is necessary because that is an atomic operation (the file system will allow only one process to create the hard link, making others wait). This eliminates race conditions. For example, say you did locking this way: Check if file “A” exists. If not, “acquire” the lock by creating file A, doing whatever, and then relinquishing the lock by deleting file “A”. If file “A” had existed, then block (wait) until file A is deleted before proceeding. Naively, this would work fine, except in the situation where process 1 acquires the lock by creating file “A”, BUT process 2 comes along while the file system is creating file “A” before it appears in the directory. Process 2, not seeing the file, would the proceed as well, thinking that it had acquired the lock too. The “hard link” creation process is “atomic” for the file system – it will not allow another process to make or check a hard link while it is creating it.
Having all of this file activity occurring on the SD card of your Pi is silly and perhaps detrimental to the card. Fortunately, the Pi has a few special directories as “RAM Disks”. These are filesystems completely in memory (so don’t put anything important there). One such directory is
/run/lock which seems to be made just for lock files. You can verify that it is a ramdisk by doing
pi@raspberrypi ~ $ df -h /run/lock Filesystem Size Used Avail Use% Mounted on tmpfs 5.0M 0 5.0M 0% /run/lock
We just got back from a two week trip. Using the circuit and software described here, I had the Pi controlling three “timer” lights. Before leaving I did quite a bit of testing. To make the signals reliable, I added a quarter wave antenna (9″ wire) to the transmitter (the long vertical yellow wire in the picture above). That made the lights respond to every command every time. As far as I know, everything worked great (we arrived in the evening, and all of right lights were on).