Maidesite Standing Desk with ESPHome

I suspect that this project will work for the Maidesite desks on the RJ12 port.

I’ve tested it and it works well on my Desky Desk.

The RJ12 dongle is also easier to make than the RJ45 dongle.

Probably you can even get a basic solution with yaml only like this:

Yaml only solution
# 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]