Background

In summer 2018, I learned of Elixir from one of the principal architects at Netskope, the company I interned at. He gave me a challenge: implement an Elixir application that reads in a CSV and ingests it into a database of my choice. It took a while, but I think the challenge took me no more than a few days. He then told me about Elixir Nerves, a project that aims to leverage Elixir as the “Operating System” for IoT devices such as the Raspberry Pi. From that point on, I was tremendously excited about the language and what it had to offer.1

Sometime later that Fall, I bought my own standing desk, having used one at Netskope and coming to love that one a lot. When installing the table, I noticed that the communication port between the remote and the controller was an RJ45 jack (same as the ones Ethernet uses), so it got me thinking: Is it possible to reverse engineer the protocol somehow to control the table? Of course, as with all things, someone’s done it before.

Here’s my own attempt to control my table, largely influenced by the linked blog post.

Setting It Up

This was my first attempt at messing with anything hardware. I owned a Raspberry Pi 2 as a birthday present from my friends, so I put that to work. To get started, I also bought a general “Electronics Fun Kit” from Amazon – it came with everything from GPIO cables, LEDs, diodes, capacitors, etc. etc. all for $10!2

The pinout linked blog post described in the blog post was pretty similar to the one on the table I owned. Here was the process I went through:

  1. My PCB didn’t have pinout labels. The pinout also happened to be in a different order.
  2. I then proceeded to connect pins in pairs, knowing that it’d probably be fine due to the low power. I also noticed that a pin was connected to large portions of the board, so I assumed it was the ground.
  3. First, I found the 5v pin since connecting it to the ground caused a soft reset on the board (probably due to shorting).
  4. Next, I found the up and down pins due to the table movement.
  5. With this, I copied the code and started a simple Elixir REPL to figure out the UART pins.
  6. For the REPL pins, I connected the RPi’s Rx (receiving) pin to random remaining pins to see what data came across. Note for those doing this in the future: You must connect your ground pin from your RPi to the ground pin on the board! Not doing so will cause potentially noisy data.
  7. One of the pins gave data, and I marked that as the pin that transmitted data from the controller to the remote.
  8. I repeated this process for the remote -> controller pin, noting which pin gave data when I clicked buttons on the remote.
  9. I then noticed that there was an “M” pin that was labeled in the post, which was responsible for switching on and off the standby mode of the desk. For that, I connected the two pins to ground to see which would toggle the standby mode.
  10. This maps out 7 pins. I still don’t know how the last pin protocol works.

Here’s a picture!

picture

Reverse Engineering the UART Protocol

This is the biggest point of difference between the blog and my own desk. The blog notes that the desk sent messages in 4-byte packets, whereas my desk sent messages in pre-formed 3-byte packages. On inspection, the format was totally different. I recorded some events: <<2, 218, 220>> corresponded to 28.6 inches; <<4, 0, 2>> corresponded to 40 inches. See if you can figure out the conversion! It’s an interesting puzzle that took me a few hours. (Next paragraph has spoilers.)

It turns out the format was quite pathological. Here’s the format: <<tens, lower_bound, upper_bound>>. The format sent has tens corresponding to the height of the table in inches in the tens place. Then, lower_bound and upper_bound correspond to the height of the table in tenths of centimeters. Here’s a sample conversion:

<<2, 218, 220>>:

218 tenths of cms is 8.56 inches. 220 tenths of cms is 8.66. 20 + 8.6 = 28.6

Code

I adapted the code from the blog, adding onto it the ability to control the desk’s height via an Elixir RPC interface. Code is available here. You can see me handling this completely pathological format here.

Controlling the Desk

For my final wiring, I decided to make it a bit more legitimate than having wires hanging everywhere. I bought an RJ45 splitter from Amazon for ~$10, and split the connection from the controller, connecting one end to the current remote and the other to the RPi via a modified Ethernet cable.

I also wrote up a RESTful web server in Elixir Phoenix that directly calls functions on the Nerves device via Distributed Elixir. I then wrote a simple Android app that takes advantage of the Android Device Control [Beta] API.3

Footnotes

  1. I use Elixir in everything now! If you’ve ever used my go links, or if you’re an investor at The Hype Advisor, you’ll have seen some of it in action! 

  2. a nerd’s dream! 

  3. A word of warning on the API: You should read examples on Github; the documentation sucks