Desky Standing Desk (ESPHome) [Works with Desky, Uplift, Jiecang, Assmann & others]

Rocka84 here :wave:

Before you spent to much time on my project you linked above, I wanted to let you that I deprecated it shortly after it was done just to reimplement it again slightly differently :smiley:

The new code is implemented as an ESPHome Component: esphome_components/components/jiecang_desk_controller at master · Rocka84/esphome_components · GitHub

Here is an example how it’s used: esphome_components/example_jiecang_desk_controller.yaml at master · Rocka84/esphome_components · GitHub

It’s basically the same approach but implemented in a (I hope) more future proof way. And it’s easier to install as the component is served via github.

Nice. Thanks for getting back to us.

I’ll have a tinker over the coming days. I might detail some instructions on how to build the rj12 dongle (for newbies) and you’re welcome to add that to your project etc.

I’ll probably link to your project as a preferred option for those with a rj12 port once I’ve done testing etc.

Wow! I feel a bit honored :sweat_smile:
I didn’t announce the project because I’m not sure if it’s better or worse than the multiple alternatives. But I’m happy you found it and seem to like it :grin:

This motivates me to add some photos of the little dongle I created to the readme after work. I have also created a case design with onshape that I could link from there.

1 Like

Hello again!

tl;dr: new: number entities, recommended config here.

Inspired by the project actually being used I spent some time on it this weekend :grin:

Thanks to the information collected by Mahko_Mahko I was able to add two essential commands to my project.

Stop command
It is now possible to add a stop button and/or use the stop() command in lambdas. In my case the desk stops immediately, so no soft stop unfortunately. (I guess it’s like an emergency break so it’s for security reasons?)

Go to specified height
There now is a method goto_height(float height) to specify a height in centimeters that the desk will then move to. This enabled the next new feature.

Number entities
Starting with the current height of the desk I added number entities which allow to show and control a numeric value.
In other words you can now use a numeric input to control the height of the desk!
I plan to implement this for other values too like the height in percent or the stored positions.

Everything is still optional and all previous features are still available. So you can choose to use numbers, sensors, buttons and/or lambdas in any combination. :slight_smile:

You can find a full blown example config here.
A recommended config is also available.

Let me know what you think! Here or via GitHub Issues!


Thanks for sharing. I have been tinkering with this but I seem to be getting plauged by weird issues which I think relate to cheap RJ12 cables and cheap D1 mini’s… Still working through it.

@pereira98 gets the credit for a lot of that info.

1 Like

Here’s my bare bones “yaml only” version.

People getting started can try this as a basic implementation if they like, and move over to yours for more advanced features.

Two goals:

  1. Make it easy for newbies to get started (although my weird board config might confuse some)
  2. Make it easy for beginners to tweak the sensor if needed (we’ve had some different protocol variants pop up and this reduces reliance on cpp developers).

Feel free to borrow the pics etc. I would have done it with a D1 mini to keep it simple for people but my d1 mini’s are dodgy and I had a spare C3.

 # You probably won't need the stuff under platformio_options: and framework: , I'm just using a weird board.
  name: "jiecang-desk-controller-rj12-c3"
  friendly_name: Desk Controller RJ12 C3 
  comment: Desk Controller for Jiecang Controllers via RJ12 port
    board_build.flash_mode: dio 
    priority: 0
      - cmdFetchHeightValue # Request height on boot.
  board: esp32-c3-devkitm-1
    type: esp-idf
      variant: ESP32C3 # lolin c3 pico pinout |
  ssid: !secret wifi_ssid
  password: !secret wifi_password
    static_ip: 192.168.1.XXX  # I like to set static IPs.

  level: VERBOSE   # Required for uart debug messages.

  tx_pin: GPIO21 
  rx_pin: GPIO20 
  baud_rate: 9600
    direction: BOTH
    dummy_receiver: true
      delimiter: [0x7E]
      # timeout: 5ms
        # Use mulcmu's method for reading uart without a custome component:
      - lambda: |-
          UARTDebug::log_int(direction, bytes, ',');                // Log the message as int. Good for height message checks.
          UARTDebug::log_hex(direction, bytes, ',');                // Log the message in hex. Good for checking against protocol documentation.
          ESP_LOGD("custom", "Bytes size: %d", bytes.size());       // Logs how many bytes in the message, useful for protocol and message identification.
          if (direction == UART_DIRECTION_RX) 
              if (bytes.size() == 9)                                // Only parse messages with 9 bytes.
                  // Do some validation that it is a height message by checking some bytes.
                  if (bytes[0] == 0xF2 && bytes[1] == 0xF2 && bytes[2] == 0x01 && bytes[8] == 0x7E) 
                      int height = (bytes[4] * 256) + bytes[5]; 


  - platform: template
    name: "Desk Height"
    id: desk_height
    update_interval: never
  - platform: uptime
    icon: mdi:sort-clock-descending
    name: Uptime
    id: uptime_sensor
    update_interval: 5s
    accuracy_decimals: 0
    unit_of_measurement: s
  - platform: template
    name: "Go To Height cm"
    id: go_to_height_value
    optimistic: true
    min_value: 80.0 
    max_value: 250.0
    step: 0.1

  - platform: uart
    id: "cmdStop"
    name: "Stop"
    data: [0xF1, 0xF1, 0x2B, 0x00, 0x2B, 0x7E]
  # Memory Presets  
  - platform: uart
    id: "cmdGotoMemory1"
    name: "Go To Memory 1"
    data: [0xF1, 0xF1, 0x05, 0x00, 0x05, 0x7E]
  - platform: uart
    id: "cmdGotoMemory2"
    name: "Go To Memory 2"
    data: [0xF1, 0xF1, 0x06, 0x00, 0x06, 0x7E]
  - platform: uart
    id: "cmdGotoMemory3"
    name: "Go To Memory 3"
    data: [0xF1, 0xF1, 0x27, 0x00, 0x27, 0x7E]
  - platform: template
    name: "Go to specific height X"
        - uart.write:
            data: !lambda |-
              float height_cm = id(go_to_height_value).state;
              int height_mm = static_cast<int>(height_cm * 10); // Convert cm to mm
              std::vector<uint8_t> bArr(8);
              bArr[0] = 0xF1;
              bArr[1] = 0xF1;
              bArr[2] = 0x1B;
              bArr[3] = 0x02; 
              bArr[4] = static_cast<uint8_t>(height_mm / 256);
              bArr[5] = static_cast<uint8_t>(height_mm % 256);
              bArr[6] = static_cast<uint8_t>((bArr[2] + bArr[3] + bArr[4] + bArr[5]) % 256); // Calculate checksum 
              bArr[7] = 0x7E;
              return bArr;

  - platform: uart
    id: "cmdDown"
    name: "Nudge Down"
    data: [0xF1, 0xF1, 0x02, 0x00, 0x02, 0x7E]
  - platform: uart
    id: "cmdUp"
    name: "Nudge Up"
    data: [0xF1, 0xF1, 0x01, 0x00, 0x01, 0x7E]

  - platform: uart
    id: "cmdMemory1"
    name: "Set Memory 1"
    data: [0xF1, 0xF1, 0x03, 0x00, 0x03, 0x7E]
  - platform: uart
    id: "cmdMemory2"
    name: "Set Memory 2"
    data: [0xF1, 0xF1, 0x04, 0x00, 0x04, 0x7E]
  - platform: uart
    id: "cmdMemory3"
    name: "Set Memory 3"
    data: [0xF1, 0xF1, 0x25, 0x00, 0x25, 0x7E]

  - platform: uart
    id: "cmdFetchHeightValue"
    name: "Fetch Height Value"
    data: [0xF1, 0xF1, 0x07, 0x00, 0x07, 0x7E]

 # If you need anything more than the above you should probably move across to another method / project.

  # - platform: uart
    # id: "cmdFetchAllTime"
    # name: "Fetch All Time"
    # data: [0xF1, 0xF1, 0xAA, 0x00, 0xAA, 0x7E]
  # - platform: uart
    # id: "cmdFetchHeightRange"
    # name: "Fetch Height Range"
    # data: [0xF1, 0xF1, 0x0C, 0x00, 0x0C, 0x7E]
  # - platform: uart
    # id: "cmdFetchHeightValue"
    # name: "Fetch Height Value"
    # data: [0xF1, 0xF1, 0x07, 0x00, 0x07, 0x7E]
  # - platform: uart
    # id: "cmdFetchHighestLowestLimit"
    # name: "Fetch Highest Lowest Limit"
    # data: [0xF1, 0xF1, 0x20, 0x00, 0x20, 0x7E]
  # - platform: uart
    # id: "cmdFetchStandTime"
    # name: "Fetch Stand Time"
    # data: [0xF1, 0xF1, 0xA6, 0x01, 0xA7, 0x7E]
  # - platform: uart
    # id: "cmdPatch"
    # name: "Patch"
    # data: [0xF1, 0xF1, 0xA0, 0x00, 0xA0, 0x7E]
1 Like

Hello together!

i´m very new to ESPHome and HomeAssistant but i am very interested to learn it. I own a Loxone Smarthome and have everything automated via Loxone and ioBroker. On the other side there is a HomeAssistant that i use for testing some things and i want to integrate my Ergotopia Desktopia Pro to this with this project here.

As i said, i´m totally new and just installed ESPHome to HomeAssistant. I worked allready with some Wemos D1 mini and have some projects running with other firmware.

Can someone tell me how i should start? Is there a good tutorial? What should i really solder and what YAML and code should i use for this desk?

Here are very much information and i am a little bit confused :slight_smile: Maybe someone have fun to help me out via Chat or something, so that we don´t spam this thread more :slight_smile: I would really like it! I´m from germany so if youre from germany too we can also speak / write in german.

Thank you, David

Hi @solution3,

a quick search revelead this: Desktopia Pro X Smart Control

I didn’t read all of it yet but it sure looks like your desk is using a jiecang controller :wink:
So you may use one of the solutions here that use the RJ12 port.

My solution (see above) should work for the basic features like the height sensor and most buttons. But I’m not sure about all features.
But @Mahko_Mahko’s latest suggestion is more flexible. If you want to use parts of the protocol that are different for your desk from what I implemented, his solution is easier to adapt to your needs.

For getting started on esphome in general you can find a ton of information on Google or YouTube.

btw: I am from Germany but I prefer keeping this conversation in english and public to have the information accessible to as many people as possible.

My goal also was to make it easy for beginners but we seem to have different approaches to reach that goal. :slight_smile:
Too me that means no serial data or (kind of) complicated scripts in the yaml. Just three lines for the external component and a plain as possible configuration for buttons etc. The downfall is that it only supports one version of the protocol.

But I also see the benefit in you configuration. Especially the “no cpp coder needed” argument is hard to beat when it comes to adapting to different versions of the protocol.

While were at it, my 2nd goal was to make it easy to combine the component with other features of esphome.
For example you could easily add some physical buttons to the spare pins of your controller and have them call the lambda-functions, you could create a secondary button interface like that. Or maybe a button that toggles between two desk heights.

Thank you for your answer! Keeping it public and in english is totally fine for me!

So if i understood you and the tutorials right… i can install ESPHome (done), solder de parts togehter and use this config: esphome_components/components/jiecang_desk_controller/jiecang_desk_controller.yaml at a083c17882361c58071b85d45587c410582cda75 · Rocka84/esphome_components · GitHub and it should work in totally Basic?

Yeah I think I agree that your approach “is probably easier” for a beginner. Especially now you have it as an external component, your config is very tight, and your docs are tidy;)

Probably part of me just wanted to tinker too;) And well I could support the yaml based one but not your cpp one if you aren’t so active on it ( my cpp skills are limited).

I’ve linked your project in my OP now. Thanks for contributing to this thread;)

1 Like


Does your desk control box have both a rj12 port and a rj45 port? Or only one? (Which one). Post some photos if you are unsure.

Can you post picture of the label on the control box. Probably it’s a Jiecang but the label typically helps confirm. Like this.

Can you see your control box on the Jiecang site?

If you have the same desk as described in the blog article I mentioned… I can’t promise it but I’m very positive.
So as you already have the d1 mini all you need is an (old) landline phone cable and you could just give it a shot.

But just to be sure: no warranties! Don’t mix up plus and minus :sweat_smile:

1 Like

Thank you again for your answer!

Yes, i will add some photos here and i also found the controll box on the site from jiecang :slight_smile: I have an RJ45 cable but just for that you can confirm this i will also add a photo. @Rocka84 thank you also for your answer, here are the information you asked for :slight_smile:

(As i´m a new user here i am not allowed to upload photos… so here is a wetransfer link. Maybe someone can upload them WeTransfer - Send Large Files & Share Photos Online - Up to 2GB Free).

Additional i allready have the D1 mini and also two RJ45 female breakout connectors so that i can plug in the RJ45 cable from the button-thing and have then screws to connect wires :slight_smile: then i dont have to cut the original cable :slight_smile:


If your control box has both (like 1st two photo’s below), you’re probably better off using the RJ12 port for your smart control and building a dongle for that and then probably just use Rocka84’s project.

If you only have a RJ45 port, probably you should build a pass-through dongle.

Your photo’s


OH SORRY!! I read your question wrong. :slight_smile: YES i have also a RJ12 “F” port on my controller Box.

Then i will solder the cable on it and text again here when its going to configure this stuff :smiley: Thank you!!!

1 Like

should be correct? :slight_smile:

From what I can see it looks good to go!
Next step I would flash a basic ESPHome config via usb and then make sure it powers on ok when plugged into the port.

I had some poor quality RJ12 cables which caused me power issues.

I now have made a new device in ESPHome and edited like this:

  name: ergotopia-david1
  friendly_name: ergotopia-david1
    # don't touch if you don't know what you're doing!
    priority: 0 # when mostly everything else is done
      - lambda: "id(my_desk).request_physical_limits();"
      - delay: 0.1s # give controller a chance to handle the response before sending the next command
      - lambda: "id(my_desk).request_limits();"
      - delay: 0.1s
      - lambda: "id(my_desk).request_settings();"

  - source:
      type: git
    components: [ jiecang_desk_controller ]

  id: uart_bus
  tx_pin: TX
  rx_pin: RX
  baud_rate: 9600

# Enable logging
  baud_rate: 0 # disable logging over uart, required when using the RX/TX pins for the controller

# see full example for more options:
  id: my_desk
      name: "Raise"
      name: "Lower"
      name: "Stop"
      name: "Position 1"
      name: "Position 2"
      name: "Position 3"
      name: "Position 4"
      name: "Height"

# the usual stuff

  board: esp01_1m

# Enable Home Assistant API
    key: "KEY"

  password: "PASSWORD"

  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
    ssid: "Ergotopia-David1"
    password: "PASSWORD"


that should be right? then i just have to flash the device like described in the esp home tutorials by connecting it to my raspberry from home assistant and then it should be found as device, add API Key and should work? :heart_eyes:

1 Like

All looks good to me at a quick look!

1 Like