Reverse engineering a ceiling fan

Tonight I was visiting a friend of mine, and noticed a strange looking switch on the wall. My friend explained that it was a wireless controller for his ceiling fan. Since we’re both radio geeks, and I happened to have my BladeRF with me, I got the idea to reverse engineer it.

The first step was to figure out what frequency the controller was transmitting on. The BladeRF makes that a fairly easy task, since it has a bandwidth of 28 MHz. I fired up gqrx to get a nice waterfall view of all that bandwidth. My first guess was that the signal might be on the 902-928 MHz band, and sure enough, I spotted a signal popping up at 911.24 MHz whenever I pressed a button on the controller. But it was quite weak, which led me to suspect it might be a harmonic. Indeed, when I tuned lower I found a very strong signal at 303.747 MHz, and I could easily detect it from across the room.

The next step was to check what modulation scheme the controller used. Most simple devices like this are using either on-off keying or frequency-shift keying. Zooming in on the signal in gqrx, I saw only a single peak, which suggested on-off keying.

I knew my trusty RTL-SDR dongle would be more than capable of receiving and demodulating the signal, so I threw together a very simple GNU Radio flow graph to show me the amplitude of the 303.747 MHz signal over time:


Here’s what I saw on the scope, once I set it to trigger on a rising edge and pressed the “light” button on the ceiling fan controller:


The transmission was short enough that I could just read the bits off visually: 1011011001011001001001001001001001011. And by measuring the time from the start to the end of those bits, I worked out that the symbol rate was about 3211 baud.

In fact, all the buttons generated very similar 37-bit patterns:

off:   1011011001011001001001001001001011001
low:   1011011001011001001001001011001001001
med:   1011011001011001001001011001001001001
high:  1011011001011001001011001001001001001
light: 1011011001011001001001001001001001011

The bits were repeated for as long as a button was held, with about another 37 bits worth of zeroes between each repetition.

Given this information, it was trivial to build a flow graph to transmit an on-off keying signal using the BladeRF:


My first attempt was unsuccessful, but it turned out the problem was just that the output gain wasn’t set high enough. Bringing it up to about 15 dB was sufficient to reliably control the ceiling fan!

The whole reverse engineering project took only about a half an hour, which really demonstrates the power of software-defined radio.

I’ve already added the receiver and transmitter to my sdr-examples repository on Github:

Receiver: ceiling_fan_rx.grc
Transmitter: ceiling_fan_tx.grc

Update: Looking at the bit patterns above, it is apparent that the bits come in groups of three: either 001 or 011. Presumably, 001 represents a baseband 0, and 011 represents a baseband 1. That is, a narrow pulse represents a zero and a wide pulse represents a one. That would make the baseband bit patterns as follows:

off:   0110100000010
low:   0110100001000
med:   0110100010000
high:  0110100100000
light: 0110100000001

Digital amateur TV on 70cm, 33cm and 23cm

I love my BladeRF! It’s a very versatile SDR transceiver, and I’ve used it to receive and transmit all sorts of signals. Most recently I got it transmitting DVB-T digital television signals on the amateur radio bands, with my trusty NooElec TV28T serving as the receiver. (It is a TV tuner, after all, so why not use it as one for once?) In this post, I’ll show you how to replicate what I’ve done.

First off, you’ll need two laptops running Linux: one to transmit, and one to receive. The transmit laptop needs to have the latest version of GNU Radio installed. If you’re running Ubuntu, the easiest way to get that done is to use OZ9AEC’s package archive. At a command prompt, run the following:

sudo add-apt-repository ppa:gqrx/snapshots
sudo apt-get install gnuradio gnuradio-dev gqrx libboost-all-dev libcppunit-dev swig liblog4cpp5-dev

Once that’s done, you’ll need to install YO3IIU’s DVB-T package for GNU Radio:

git clone
cd gr-dvbt
mkdir build
cd build
sudo make install
sudo ldconfig
cd ..

Next, grab my collection of SDR examples:

git clone

Included in that collection is, a script written by W6RZ that lets you transmit DVB-T from the command line using a BladeRF. Since amateur stations typically operate at much lower power than commercial broadcasters, I’ve modified it to use the lowest available bit rate, which should maximize the distance at which the signal can be received. (If you want to experiment with higher bit rates, you can change the “channel_mhz”, “mode”, “code_rate”, “constellation” and “guard_interval” variables. You’ll also need to adjust the mux rate of your transport stream, which can be calculated using W6RZ’s dvbrate.c.) The script is configured to transmit at a centre frequency of 441 MHz, so be sure to attach a suitable 70cm antenna to your BladeRF’s TX port before transmitting.

The script expects to be given an MPEG transport stream as input. Fortunately, we can produce one in real time using avconv. It can record video from the laptop’s webcam and audio from the laptop’s microphone, and encode them into a suitable transport stream. To let avconv and talk to each other, we’ll create a fifo:

mkfifo in.fifo

Then we launch and tell it to read from the fifo:

sdr-examples/ in.fifo

You’ll see some output, but nothing will be transmitted yet because no data is arriving in the fifo. To fix that, open a second terminal window and run avconv like so. Be sure to replace XXXXXX with your own call sign, which will be displayed in the lower right corner of the video.

avconv -f alsa -i pulse -f video4linux2 -s 640x480 -i /dev/video0 -vf drawtext=fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf:text="XXXXXX":x=440:y=420:fontsize=48:fontcolor=white@0.6:box=1:boxcolor=black@0.2 -vcodec mpeg2video -s 640x480 -r 60 -b 4000000 -acodec mp2 -ar 48000 -ab 192000 -ac 2 -muxrate 4524064 -mpegts_transport_stream_id 1025 -mpegts_service_id 1 -mpegts_pmt_start_pid 0x1020 -mpegts_start_pid 0x0121 -f mpegts -y in.fifo

You may need to install additional packages so that avconv has access to all the codecs it needs. If all goes well, your two terminal windows should look like this:


Now, over to the receiving laptop, which will use an RTL-SDR dongle to pick up the signal. Since support for the RTL2832 chip was only recently added to the Linux kernel, you’ll want to be running a recent Linux distribution such as Ubuntu 13.10. Make sure you have vlc installed:

sudo apt-get install vlc

Then launch vlc like so:

vlc dvb://frequency=441000000:bandwidth=6

If all goes well, you’ll see your video and hear your audio!


Now that you’ve succeeded on the 70cm band, you may want to try this on the 33cm and 23cm bands as well. Unfortunately, the Linux drivers for the RTL-SDR dongle currently limit its maximum frequency to 862 MHz, a bit below the 33cm band. Until the drivers get updated (I’ve already submitted a patch request), you can work around the problem by patching the kernel modules on your receiving laptop using the script in my sdr-examples repository:

sudo sdr-examples/

If everything worked correctly, the script should print out “Success!” twice. If you saw that, then reboot, and you should now be able to tune all the way up to 1750 MHz. On the transmitting laptop, change the “center_freq” variable to 913000000 for 33cm or 1279000000 for 23cm, put an appropriate antenna on your BladeRF’s TX port, and fire up and avconv again. On the receiving laptop, fire up vlc again, putting the appropriate value in for the “frequency” parameter.

In my experiments, I found that the BladeRF put out the most power on the 33cm band. I was able to receive the signal all around the house, using a rubber duck 33cm antenna on the BladeRF and the RTL-SDR dongle’s stock antenna. I’ve had a QSO with VA3DGN on 70cm. To get the signal beyond my house, I hooked the BladeRF up to a Down East Microwave 70cm 25 watt power amplifier.

Have fun with DVB-T! I’d love to hear back if you make any contacts.