As referenced in my earlier article about the Desktopia Pro desk, I wanted to make it controllable in my smart home. More specifically, I wanted to have it in HomeKit so that it could be controlled by touch and voice. While I didn’t get it directly into HomeKit — mainly because of the missing “desk” accessory type — I am now able to control it from my Apple devices and even via Siri!

Reverse engineering the Desktopia desktop app

When I want to move my desk up or down, I normally have to press the hardware key for the height I want. I use the memory buttons for easy switching between the heights. Memory position 1 is my sitting position and my standing height is saved to memory position 2.

This is also what is possible using the Desktopia app. The app has been in beta for a year or so, is currently in version 0.0.2 and therefore still has a few rough edges. However, because it is built in JavaScript using Electron, I was able to decompile it and see what commands it sends to the desk.

Terminology

To start, I want to clarify a few words so you understand what I mean by them. The controller is the box that converts the commands into physical movement by actuating the motor. The front panel is the visible part with buttons that you press to move the desk.

The connection

The computer is connected to the desk controller using a USB-A-to-RJ12 cable. I assume it sends the same commands as the front panel does. It doesn’t really matter as it is connected to the desk’s controller directly and therefore doesn’t actually need the front panel at all. It is helpful for debugging, though, as the panel reflects the state of the desk at all times.

Both devices are communicating via a serial connection over the cable. From my limited experience with such connections I think it is using a standard configuration with a baudrate of 9600. The controller has an off-the-shelf chip for handling the USB connection.

The command protocol

The commands that both parties send back and forth always follow the following pattern. It includes two header bytes, a command byte, the command’s data bytes and ends with a footer byte.

While the footer byte is always 0x7E, the app sends 0xF1 as its header bytes to the desk and receives back messages that start with 0xF2 from the desk.

The values for command bytes are not unique across both senders so depending on the source of the command it might have a different meaning.

Hence, a message from the desktop app would look like this:

0xF1
0xF1
<command byte>
<command data bytes>
0x7E

Please keep in mind that the following description is from what I could gather from the source code of the desktop app and playing around with it for my use case. There is no official documentation for these commands and I might be interpreting them wrong. Feel free to send feedback.

Communication

Before the desk is ready to respond to commands, it needs to be woken up. To do so, we send the 0x07 command and wait for the controller to be responsive. I chose one second but it can probably be less than that. After that, we can start sending our commands. Note that while the desk is awake, the front panel will also be lit up.

As long as you keep interacting with the controller, it will stay awake. For this, see the heartbeat command below. After a few seconds without commands, the controller will go back to sleep.

I also noticed the controller would sometimes send me status messages after waking up. I am not sure if I was doing the wake up wrong at the time or why else I sometimes wasn’t getting them. Additionally, I don’t know what the commands are telling me. I guess it might be something about the current desk height, the controller firmware version, or something similar.

Here is a list of command bytes I have identified so far, sent from the desktop app:

Command byte Description Content
0x01 move up 0x00 0x01
0x02 move down 0x00 0x02
0x03 save to memory 1 0x03 0x00 0x03
0x04 save to memory 2 0x04 0x00 0x4
0x05 move to memory 1 0x00 0x05
0x06 move to memory 2 0x00 0x06
0x07 wake up 0x07 0x00 0x07
0x27 move to memory 3 0x00 0x27
0xAA heartbeat 0x04 0x00 0x00 0x00 0x06 (sum of command and content bytes)
0xAB stand reminder vibrate light move (0xAE + vibrate + light + move) [replace each variable with bool, true = 0x01, false = 0x00)

The desktop app sends a heartbeat every few seconds to keep the desk awake. Note that this will also keep the front panel’s display active.

You see that the “move to memory 3” is value-wise apart from the other two “move” commands and that I did not find a “save to memory 3” command. I suppose these were added late in development or when the controller firmware was already shipping.

The list of what the desk sends back:

Command byte Description Content
0x01 current height ? overflowCounter counter ?…

The current height can be caluclated by multiplying the overflowCounter with 256 and adding the counter value to the result. This will be the current height in millimeters.

Again, I didn’t really need this for my purposes.

Architectural overview

Now how do I control my desk using this information? Here is an overview:

[Apple Shortcuts app]
        |
        V
[Raspberry Pi Zero]
        |
        V
[Desktopia Pro X controller]

Bascially I wrote a small python script that runs on the Raspberry Pi Zero to translate network calls into commands for the desk. The Pi is connected via USB to the desk controller and runs a small HTTP service on the local network.

Whenever specific endpoints are called, it opens the serial connection to the controller, wakes the desk up and sends the command. The connection is closed afterwards.

I found that the desk will stay awake for a few seconds and not respond to new connections. This might be a reason why the official desktop app takes a few seconds to quit. It would probably be best to also have some sort of keep-alive mechanism for when requests come in quick succession but for normal use it is enough. I like that the desk is not lit up the entire day as well.

The Shortcuts app allows me to call the HTTP service from my iPhone. Speaking the shortcut names to Siri on my HomePod runs them as well and therefore moves my desk only by voice! The shortcuts also sync across my devices so I can use them from my iPhone or Mac, for example.

I was planning on adding a passthrough functionality to the python script as well, so that commands from the desktop app would be relayed to the desk and vice versa. However, I never really felt the need for the desktop app and its functionalities. Therefore, I’ve had no need to implement that relay.

Conclusion

The Desktopia Pro X controller has more features than its cheapter counterpart. Honestly, most of them are nice and functional but are better fulfilled by other devices, such as an Apple Watch. If you do not own such a device, however, they might come in handy for you.

The most interesting feature is its capability to talk via USB, as it opens the desk up to be connected with other devices. Currently, Ergotopia only offers Windows and macOS apps for this interface. They are built using Electron and are only in beta version 0.0.2. The code I have read shows that Ergotopia is not a software company first. This leads to some questionable technical choices. I think they are well-intentioned, however.

I’d love for the protocol to be documented publicly, so it would make understanding and building upon it easier for projects like this. Allowing advanced users to customize their experience with a very robust product is great. This way, Ergotopia enriches the value of their product. Controlling the height of your desk from a computer or even over the network is a feature that no other desk company seems to offer in my experience.