If you already know about simple ASK/OOK 433MHz devices, you probably know they're pretty easy to interact with via software defined radio. It felt like a shame to tie up a whole SDR module just to control some lights, so I wanted to design some hardware to dedicate to this task.

If you've got a similar problem, but don't care about the details of my particular application, just check out the link to GitHub where you can build your own RF transmitter controlled via ESP8266. There will be some interesting content later in this writeup about how manage controlling the RF circuit with MicroPython.


Before I started making any hardware, I had to make sure I could integrate with the Lutron bridge. It turns out the bridge is pretty hackable. It is possible to enable telnet integration which can receive commands and broadcast state change events to connected clients.

Lutron's protocol is documented in the "Lutron® integration protocol", which is a superset of what the Caseta devices actually implement. But it was easy enough to figure out what was happening without reading this document. Each event simply return a line of data like this:

~OUTPUT,12,1,0.00\r\n

Which means an "output event" occured, on device 12, with event type 1, and parameter 0.00 (which in this case means, device 12 was just turned off).

So in this proof of concept, firing up Python, connecting to the bridge on port 23, and listening on the socket, we were good to go:

#!/usr/bin/env python

import telnetlib
import re
import lights

HOST = "192.168.0.123"
LOGIN = "lutron"
PASSWORD = "integration"
EVENT_RE = re.compile('\S*\s?~(\w+),(\d+),(\d+),(\d+.?\d*)')

tn = telnetlib.Telnet(HOST)
    tn.read_until(b"login: ")
    tn.write(LOGIN.encode('ascii') + b"\r\n")

    tn.read_until(b"password: ")
    tn.write(PASSWORD.encode('ascii') + b"\r\n")

    while True:
        recv = tn.read_until(b"\r\n")
        for event in EVENT_RE.findall(str(recv)):
            action, target, var = event[0], event[1], event[3]
            print("Event received: {}@{}: {}".format(action, target, var))
except KeyboardInterrupt:
    print("Closing")
finally:
    tn.close()
Event received: OUTPUT@12 0.00

Next step was figuring out how to decode the RF protocol used to control the string lights. This started with a search of the FCC database using the information found on the back of the remote control confirming that the frequency was 433.92 MHz.

A lot of these devices use really simple modulation schemes, the simplest being known as On-Off Keying or Amplitude Shift Keying (OOK or ASK), which is extremely simple to understand and decode -- at some pre-determined time base the radio module is either screaming as loud as it can at 433.92 MHz for a digital '1' or off for a '0'. Some variants of this encode a '0' as a shorter pulse than a '1', but the idea is the same.

One of the easiest ways to control a device like this without even decoding this signal is to use an SDR module like an RTL-SDR, hackrf, or anything else that uses GnuRadio to record an audio file of the button press with the SDR tuned to the frequency of the transmitter. Then you play that back, and if the device responds then congratulations, you've got a straightforward path ahead!

I'm going to wave my hands a little bit about how to actually decode this data, since there is lot of great information out there about how to do this.

After a proof of concept using my USRP e310 and GnuRadio, controlled via a python script much like the one above, it was time to start designing some hardware. If you'd like to see how I built the flow graph to perform the OOK modulation using GnuRadio, please leave a comment and I'll be happy to post more information about this!


Armed with these two pieces, I had all I needed to realize my dream of not tying up an SDR for such a trivial task as turning a light on and off. I fired up KiCad and got to work.

The module I created consisted of an AC-DC converter (so it could be plugged into a wall), an ESP8266 (I didn't...

Read more »