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:

ceiling-fan-rx-flowgraph

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:

ceiling-fan-ask

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:

ceiling-fan-tx-flowgraph

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

15 thoughts on “Reverse engineering a ceiling fan”

  1. Hey,
    I have tried to make flowgraph for bladeRF using gnuRadio but I was facing issue in deciding the sample rates. So can you tell me on what factors should sample rates depend.

    Thanks

  2. Very impressive stuff! Out of curiosity, did you try 0110100000100 ? There’s just that gap in the pattern between ‘off’ and ‘low’ that pegs to be poked!

    1. The same thought occurred to me, but only after I had left. I’ll try that out the next time I’m over and see if anything happens. Hopefully it’s not the “detonate” command. 🙂

      1. Also, a little more info on the preamble. 011010 is the prefix shown on all of the messages here, but that depends on the DIP package inside. If it is set to 0000, the preamble will be 010000
        1111 is 011111, and so on.
        It seems the anatomy of the packet is
        01(preamble)xxxx(dip switches)0(unk)1(fan hi)1(fan med)1(fan low)1(fwd/rev)1(on/off)1(light)

  3. Great post. One question: I do not understand the addition of the Signal Source block with the cosine wave at -100k freq. Can you explain what the function of that is?

    1. To avoid the noise spike at the center of the receiver’s passband, I tune 100 kHz below the frequency I want to listen to, then mix in a -100 kHz signal to shift the input signal down by 100 kHz so my target signal is centered at 0 Hz.

  4. This is a great tutorial with lots of good info.
    I was hoping you could help me with something.
    I have a rtl-sdr usb dongle. I’ve been playing with it for a while.
    I have a fan remote, I looked up the frequency and tuned to it.
    I can see the wave spike when I press the button in both Gqrx and GNU-Radio.

    But, what I would like to do at this point is create some sort of script,
    probably Python since that’s what GNU-radio will generate,
    and all I want it to do at this point is print a text output to my shell of what button is being pressed.

    Do you have any example, tutorials, or links that might be able to help me with this?

    Any info will help.
    Thank you.

  5. Excellent article. 1 issue: I actually do not really be aware of inclusion from the Transmission Resource prevent using the cosine influx in -100k freq. Are you able to clarify the particular functionality of this is actually?

  6. Hey,

    Great tutorial, this was exactly what I was looking for. I managed to follow along and get the same results from my Hampton Bay ceiling fan. It was virtually the same except for the dip switches were positioned differently so the first four bits were 0101 instead of 0110.

    I did wonder if you were able to switch the light off using the same signal. I have no problem turning the light on, but when I repeat the signal it doesn’t turn off. Curious to know whether you experienced this as well?

Leave a Reply

Your email address will not be published. Required fields are marked *