Building a Codec for Lutron Caseta: Understanding the Protocol

A Caseta Remote and the Raspberry Pi listening for it

What are we doing?

One of my goals when setting out to learn Rust was to find a project small enough to be manageable in a new-to-me language while still being useful and nontrivial. As I've grown my collection of smart home devices, I've also grown tired of needing to get out my phone to change light scenes. And when I've had people over, none of them could change the scenes themselves unless they downloaded the appropriate apps, connected their phones to my wi-fi, and had me grant them the appropriate permissions from my phone.

Not great.

I wanted a familiar and intuitive way to change the lights. When people see a light switch with a dimmer, they instantly understand how to use it; spin the knob or slide the slider, and the lights change.

Lutron's Caseta line of products offers a clean, familiar-looking interface to convert home electrical circuits into smart home accessories. And the Caseta Hub PRO (but sadly not the standard hub) offers a telnet interface that broadcasts button presses and releases that come from caseta devices. And the Lutron Caseta ecosystem offers Pico remotes to allow you to add a switch where there wasn't one before. This combination of intuitive remote controls and a broadcast of remote interactions was perfect for this project.

We just need to figure out how to decode these broadcasts first.

Decoding the Protocol

I have Homebridge running on a raspberry pi. And Homebridge has a plugin for Pico remotes. This project was an invaluable resource as I tried to parse through the contents of the Caseta broadcasts. This snippet in particular shows the types of messages that the homebridge-pico plugin expects to see and the way it responds to them. To simplify my investigation here, I added a local dns entry to point caseta.run at the caseta hub, and then I started to poke at it with nc.

➜  ~ nc -vz caseta.run 23
Connection to caseta.run port 23 [tcp/telnet] succeeded!

Off to a good start so far! Let's try something more than just scanning for the open port.

➜  ~ nc caseta.run 23
login:

Now it's asking us for input! Still promising, so let's pass it the input that it wants.

➜  ~ nc caseta.run 23
login: < redacted >
password: < redacted >
GNET>

It looks like we've authenticated, and now we see a GNET> message. So far, all of this lines up with what that snippet from homebridge-pico seems to expect. What do we see when we push a button on the remote?

➜  ~ nc caseta.run 23
login: < redacted >
password: < redacted >
GNET> ~DEVICE,2,2,3  # on button pressed here
~DEVICE,2,2,4        # on button released here

At this point, we've replicated the kinds of interactions that the homebridge plugin deals with. Using a few other snippets from the homebridge-pico source (like this and this), we can interpret the message here to mean:

  • for remote number two, the on button was pressed
  • for remote number two, the on button was released

Trying a different button yielded a similar result:

~DEVICE,2,4,3   # off button pressed here
~DEVICE,2,4,4   # off button released here

Listening to the Messages

Now that we understand the protocol and the messages it sends, we the information we need to build a client that hooks into the Caseta message broadcasts. This post is starting to get long, so I'll save the codec write up for a Part Two. But if you want to look ahead, this is the project we're going to work through. Stay tuned!