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

This guy does some good desk reviews and tests including wobble tests.

There is some very cool rj12 port protocol decoding happening here.

Not sure if it applies to all models.

1 Like

I’m posting my progress here

1 Like

@Mahko_Mahko the cmd you tested not all were the new ones I found, some I believe were already known. But great to see you are already testing and something works!

I would like for you guys to test out the new cmd I found ands I can start a compatibility table based on your feedback.

I believe you guys lacked the stop command and the GoToHeight(mm) cmd, this later one works in my desks perfectly with soft stop accurate to the millimeter. I also think I found out how to change the memory presets to a desired hight, independently of were the desk is.

From my findings there are 2 adapters and 2 apps, I could not find an APK for the original app, just for the current one, so not all desks will be compatible with all of the commands I suspect, or maybe not and all of them work, better even, you guys share your findings

There are parts of the code I don’t understand yet, and there are things that I haven’t documented yet, I’m also planning on writing some custom code because I own 2 desks that are side by side, so I want to sync them, maybe even do a script for a celebration mode Tesla like ahah so more is coming than what I have now in my git.

As for my desks they are new here in my country at least, they are from IKEA, the MITTZON series, I believe there are 2 models 60 and 80 cm deep, I own the 80mm deep ones so I can only confirm that these use Jiecang hardware and they support the protocol via the RJ-12 port with no issues, just make sure to use the desk’s provided ground and not just wtv power supply you have for testing. For future compatibility it’s worth pointing out that my desks were bought the current year of 2024, future desks might look the same but with different hardware, who knows?

For users following the IKEA assembly instructions just make sure to pass half of a telephone cable like you did with the controller through the desks cable holes and connect to the F connector, BEFORE YOU ASSEMBLE THE TOP!!!

1 Like

Now about the RJ-45 port. If your goal is to completely upgrade the standard controls of the desk or just to reverse engineer the desk as you did yeah this is the correct port to use. If you don’t and just want to add wireless remote control and status report I think this is not the best option and should be avoided if your desk has the RJ12 port and this one works with the cmd you want to use. I have several concerns with the RJ45 approach:

1st - Warranty: my desks come with 10 years of warranty, so causing damage to any part of the desk is not an option.

2nd - Safety: I haven’t read everything on this forum since at this time I am a little busy but from my understanding you guys managed to control the desk without soft-stop, if I understand this right this could mean that the handset has like a “Lower Level” access to the desk’s controls, for exemple I haven’t managed to move the desk via the RJ12 connect without soft stop, so I suspect the RJ12 port has some kind of user error protection or at least is already designed by the manufacturer for remote access, in order for you guys to fix this again if my understanding is correct, you would need to make some sort of controller that could move the desk to a desired height but the desk already provides this for you. And I hope that the RJ45 port doesn’t let you send the desk higher or lower than possible, from my limited testing the RJ12 port doesn’t let you do nothing of those sorts.

3rd - Practicality: if your desk already provides a connector that is not in use it’s just more practical to go to the store, get a telephone cable and cut it in half, than to make a RJ45 adapter sniffer and put it between desk components. All I used is a raspberry pi pico w ~7€ and a standard telephone cable ~1€ maybe, 10€ max for the setup, all standard stuff fast to connect and go.

4th - Service Reliability: well this is pretty easy to understand, if you keep the standard controls connected always, you don’t loose your desk’s functionality while tinkering with stuff, this is especially important if the desk in question is not used by you, but used by others, like your wife/husband/girlfriend/boyfriend.

1 Like

Hi @pereira98, I definitely see all the benefits with your approach and agree with all of your comments. It’s simpler to wire, requires less hardware, and like you said it just seems like a better overall way to interface. I was initially keen to do what you did but didn’t know how to get the commands;)

I’m happy to help do some testing.

I have my test rig set up and will chip away at it over the coming days/weeks.

I don’t believe all desks have an rj12 port though. So some may still need a pass through set-up.

Next I’ll review where your code is up to and try to get a go to height command working. And I’m also interested whether we can get a height sensor going without using a custom component or custom sensor, perhaps by using this approach.

Well in cases like this, you can easily decompile an apk online but if you want more options and a more professional tool, maybe try Ghidra brought to us by NSA. The base tools that these use I think its mostly Apktool I think Im not sure but then just go throgh the filesystem trying to understand how everything works.

1 Like

I tested a Go to specific height X feature like this, and it worked quite well soft start/stop is nice. Where is your implementation of this - maybe I missed it. ta.

button:
  - platform: template
    name: "Go to specific height X"
    on_press:
      then:
        - uart.write:
            id: uart_bus2
            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 as the sum of specific bytes modulo 256
              bArr[7] = 0x7E;
              return bArr;

number:
  - platform: template
    name: "Go To Height cm"
    id: go_to_height_value
    optimistic: true
    min_value: 0.0
    max_value: 1000.0
    step: 0.1

Edit: And this seems to work ok as a height sensor.

uart:
  - id: uart_bus2
    tx_pin: GPIO19
    rx_pin: GPIO18
    baud_rate: 9600
    debug:
      direction: BOTH
      dummy_receiver: true
      after:
        timeout: 10ms
      sequence:     
      - lambda: |-
          UARTDebug::log_int(direction, bytes, ',');
          ESP_LOGD("custom", "Bytes size: %d", bytes.size());
          if (direction == UART_DIRECTION_TX) {
            if (bytes.size() == 9) {
                int height = (bytes[4] * 256) + bytes[5];
                id(desk_height).publish_state(height);
            }
          }


sensor:
  - platform: template
    name: "Desk Height"
    id: desk_height
    update_interval: never

I don’t have my ESHhome config online since it’s mainly Rocka84’s config, I didn’t spend much time on this besides decompiling the desk’s app yet That’s why I also added his repository link to the GitLab readme.

Nice I’m glad that works since it’s probably one of the most important commands.

1 Like

Thanks. I’m still reviewing Rocka84’s project. I didn’t know about it until you flagged it. It seems quite comprehensive.

I may produce and test a very minimal yaml only project based on it and publish it here that users can more easily adapt if they want/need. And then point them to the project if they want more features.

Hi!
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.

1 Like

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!

3 Likes

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.

Config
 # You probably won't need the stuff under platformio_options: and framework: , I'm just using a weird board.
esphome:
  name: "jiecang-desk-controller-rj12-c3"
  friendly_name: Desk Controller RJ12 C3 
  comment: Desk Controller for Jiecang Controllers via RJ12 port
  platformio_options: 
    board_build.flash_mode: dio 
  on_boot:
    priority: 0
    then:
      - button.press: cmdFetchHeightValue # Request height on boot.
  
esp32:
  board: esp32-c3-devkitm-1
  framework:
    type: esp-idf
    sdkconfig_options: 
      CONFIG_BT_BLE_42_FEATURES_SUPPORTED: y 
      CONFIG_BT_BLE_50_FEATURES_SUPPORTED: y
      CONFIG_ESP_TASK_WDT_TIMEOUT_S: "10" 
      variant: ESP32C3 # lolin c3 pico pinout |  https://arduino-projekte.info/wp-content/uploads/2023/01/Wemos-Lolin-C3-Pico-Pinout.webp
  
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  manual_ip:
    static_ip: 192.168.1.XXX  # I like to set static IPs.
    gateway: 192.168.1.1
    subnet: 255.255.255.0
api:
ota:

logger:
  level: VERBOSE   # Required for uart debug messages.

uart:
  tx_pin: GPIO21 
  rx_pin: GPIO20 
  baud_rate: 9600
  debug:
    direction: BOTH
    dummy_receiver: true
    after:
      delimiter: [0x7E]
      # timeout: 5ms
    sequence:
        # Use mulcmu's method for reading uart without a custome component: https://community.home-assistant.io/t/how-to-uart-read-without-custom-component/491950?u=mahko_mahko
      - 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]; 
                      id(desk_height).publish_state(height);
                  }
              }
          }

        

sensor:
  - 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
    
number:
  - 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

button:
  - 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"
    on_press:
      then:
        - 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]
2 Likes

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?