BEN://
--:--:-- UTC
BEN://PROJECTS/ESP32-SMART-DESK

Automation

ESP32 Smart Desk

Replacing a cheap standing desk's handset with an ESP32 so Home Assistant knows how high it is, can move it to an exact height, and stands me up when I've been sitting too long. No soldering, no new parts.

active

Started

Oct 26, 2026

Updated

Oct 27, 2026

Repository

View →

Live

—

ESP32ESPHomeC++Home AssistantmmWave presence
#home-assistant#hardware#reverse-engineering#homelab

Problem

I have a sit/stand desk and I don't stand at it. The motor works fine; the problem is me. I wanted the desk to notice I'd been sitting too long and stand me up, then lower itself again later, without me having to remember.

The desk is a generic one: an MXKZH06 control box under the top and an MXSK05_A handset with Up, Down, four presets and a three-digit height display.

The original handset: Up, presets 1–4, Down

I gave myself three rules:

Finding out what the handset actually says

The handset plugs into the control box with what looks like an Ethernet plug. Four of the eight positions are used.

Handset plug (right) next to a standard Ethernet plug (left). Only four pins are populated

Inside the handset there's very little: buttons, an LED display, and one chip.

MXSK05_A V1.4 handset board

The chip is a TM1650, a combined LED driver and key scanner

That chip, a TM1650, was the big clue. It's a cheap LED driver and key scanner that talks a two-wire, I2C-like protocol. So the handset isn't sending "go up" commands at all. It's a dumb display and keypad, and the control box is polling it.

The four wires go to pads labelled DAT, K (clock) and GND, plus a 3.3 V supply from the box.

The handset cable's four wires on the PCB pads

I captured the bus with the logic analyzer while pressing buttons. Every ~3.7 ms the box writes the display digits to the handset and reads back one byte saying which key is held. Pressing a preset didn't light up any other wire, just a different key byte on the data line.

Then I found dong4j/desk-gateway, where someone had already reverse-engineered the exact same handset board: the addresses, the key codes, and the odd segment wiring for the display. My captures matched their notes, which took most of the guesswork out. Two details from there that matter a lot:

The plan: the ESP32 is the handset

The obvious approach is to sit an ESP32 next to the handset and fake button presses. That needs soldering onto the buttons, which I'd ruled out.

The better approach is to unplug the handset and have the ESP32 pretend to be it. The control box polls; the ESP32 answers. When Home Assistant wants the desk up, the ESP32 reports "Up is held". And because the box writes the height to the display every few milliseconds, the ESP32 gets the height for free. No separate height sensor needed.

Wiring with no soldering

The handset port takes a normal RJ45 plug, so I cut an Ethernet patch cable, kept the plug end, and put the three wires I needed into a screw-terminal breakout board for the ESP32:

PinSignalESP32
2ClockGPIO18
3GroundGND
4DataGPIO19
13.3 V from the boxnot connected

Clock, data and ground from the cut Ethernet cable in the ESP32's screw terminals

Everything is 3.3 V, so there are no level shifters or resistors. The ESP32's internal pull-ups stand in for the handset's, and it's powered from USB. The handset itself is untouched in a drawer, ready to plug back in.

Firmware

It's ESPHome with one custom C++ header. The ESP32's hardware I2C can't easily act as this particular slave, so the header bit-bangs it: a GPIO interrupt on both pins, pinned to the second core away from Wi-Fi, that

The rest runs in the normal ESPHome loop: decode the digits into a height in inches, and decide which key to hold. Home Assistant shows the same 27.0 the handset would.

The original display, which is what the ESP32 is now decoding

Presets were the wrong idea

My first version pressed the box's own presets for Sit and Stand. It kept almost working:

Then I realised I was overcomplicating it. I already had the live height in inches. So the firmware stopped using presets entirely and now does go-to-height itself:

  1. Hold Up or Down toward the target, watching the height.
  2. Let go a little early, because the desk coasts after release. That margin is a setting ("Stop Early By"), tuned once.
  3. If the desk hasn't moved within four seconds, the box was probably asleep: let go, press again (up to four times). I started with two seconds, which is probably about when a cold box gets the motor going, so the re-press could stop it again. That's my best explanation for a few auto-sits that clicked and went nowhere.
  4. Give up if the height stops changing for three seconds (end of travel or something in the way), or after 40 seconds total.

Sit Height and Stand Height are just number settings in Home Assistant now. "Stand is a bit low" is fixed by typing 42.5 instead of reprogramming anything.

The ESP32's own web page: height, position, sitting time and the controls

Making me stand

The automation lives in Home Assistant, not on the ESP32. The desk only knows whether it's up or down; it doesn't know whether I'm there.

I already had a Zigbee mmWave presence sensor on the desk. A template sensor, "Sitting at desk", is on only when that sensor sees me and the desk is at sitting height. The automation:

  1. When "Sitting at desk" has been on for the sit limit (a slider, currently 30 min), press Stand.
  2. Wait for the desk to report standing.
  3. Wait for the stand duration (another slider).
  4. Press Sit, but only if I'm still there and the desk is still up. It never lowers an empty desk, and if I've already sat down myself it leaves it alone.

There's also a toggle to turn the whole thing off.

GOTCHA

A state trigger with "for 30 minutes" only starts counting when the state changes. After a Home Assistant restart, with me sitting the whole time, the state never changed and the automation never fired. The fix was a once-a-minute trigger that checks how long "Sitting at desk" has actually been on.

Where it is now

It works day to day: Sit and Stand go to the right height from Home Assistant, the desk wakes reliably, and it stands me up after half an hour. The next step is physical buttons and a small OLED on the ESP32, probably in a 3D-printed case, so the desk has a handset again without needing a phone.

If you have the same MXKZH06 / MXSK05_A desk (or one with a TM1650 handset), the repo has the firmware, wiring, Home Assistant automation and protocol notes needed to build your own.

← All projects