IKEA Bekant Standing Desk Mods

I modified the IKEA Bekant sit/stand desk to add memory positions and remote control.

IKEA Bekant

The IKEA Bekant is an adjustable sit-stand office desk. We have two desks in the house, a standard rectangle for me and a corner desk for my wife. We bought these a long time ago when they were the best for value on the market, but there are more options nowadays.

bekant

The Problem

As much as we want to, the standing feature is rarely used. It's not because of any joint pain, as we also have the amazing Ergodriven standing desk mats. After discussing the problem, it boiled down to:

  1. No memory positions and having to do micro-adjustments whenever changes are made
  2. The button is not the easiest to hold and press
  3. Forgetting about the need to stand

My solution to the problem involves using:

Megadesk

Megadesk is an open-source drop-in controller that adds memory positions. It was created by Greg Cormier and is for purchase on Tindie. I found out about this on Tobias Brunner's blog. It is also possible to print the circuit board yourself if you have the know-how.

pcb front

pcb back

Here's a video from Keep things alive on YouTube:

It took a month to arrive from Canada. One of my units was defective and gave out a loud bang. Greg shipped out another one free of charge that arrived in two weeks.

Installation

Unplug the controller unit and unscrew it from the table.

unit

The hardest part of the installation is opening the unit. The plastic needs to be scored with a knife and pried open. I didn't have much luck with both units and resorted to brute force. These three videos show how it is done:

knife

pry

opened 1

opened 2

The Megadesk PCB is then a direct swap.

installed 1

installed 2

I also filed down the sides of my case to make it an easier fit. This may not be an issue with the v10 of the PCB which reduced the size by 1mm in each direction.

file

Controls

The controls for the unit can then be found on GitHub

UP pushes Function
2-10 Memory positions
11 Set the minimum height to the current position
12 Set the maximum height to the current position
14 Recalibration. Desk lowers to the lowest height
15 Reset
16 Newer units, no operation.
17 Toggle audio-feedback mode
18 Toggle both-button mode

Wi-Fi Control

Controlling the Megadesk over Wi-Fi requires connecting a separate microcontroller, typically an ESP8266 or ESP32, to its serial headers. This allows issuing commands remotely to the Megadesk from the microcontroller's web interface or API. The serial commands are documented in detail on GitHub.

The microcontroller can be powered directly by the desk. A step-down converter is used to drop the 24V supplied by the desk to the 3.3V needed by the microcontroller.

I created this wiring diagram following Matthias Schaffer's Megadesk post on Medium.

diagram drawio

D1 Mini

The WeMos LOLIN D1 mini is an ESP8266 microcontroller with USB-C starting from V4.0.0. It can be purchased from their official AliExpress store for $7.50 AUD total at the time of posting. I went with a clone D1 mimi for half the price. There was a voltage regulator issue with clones, but I'm not sure if this applies to newer versions.

d1 mini

ESPHome

ESPHome is an open-source project by Nabu Casa, the company behind Home Assistant, to easily configure and flash ESP8266/ESP32 microcontrollers. It can provide a web interface and API for controlling the microcontroller in other home automation systems.

Using ESPHome, the D1 Mini is flashed to support and interface with the Megadesk over serial. The initial flash is done with USB, but subsequent updates can be done over-the-air (OTA, i.e. Wi-Fi).

A full ESPHome guide is available on the Megadesk GitHub. I made the following improvements to this code, which can be found on my GitHub:

  • Added remaining memory buttons
  • Read and write saved memory positions
  • Cover entity with controls
  • Current, minimum and maximum height sensors
  • Moving binary sensor
  • Icons for all entities
  • Codebase operates on the principle that each YAML file corresponds to exactly 1 device
  • Jinja templating
  • Documentation links and comments

install

d1 mini

select device

installing

dashboard

LM2596S

The LM2596S is an adjustable step-down buck converter from Texas Instruments. It can take the Bekant's 24V output and turn it down to 3.3V for the D1 Mini. It's important to power the D1 Mini from the same source as the Megadesk instead of its USB port or (as I was told by AnAnalogGuy) a ground loop will occur. I bought a clone from AliExpress for $2.50 AUD.

Out of the box, it needs to be adjusted to output 3.3V by turning the screw on while measuring the output with a multimeter. I followed this YouTube video by POWER GEN:

The converter also holds a charge for about 20 seconds after unplugging it, so handle it with care.

lm2596s adjust

Case

Oliver Radke (oradke) designed the perfect case to hold a D1 Mini and LM2596S step-down buck converter. There are two cable channels and screw holes to mount the unit directly to the table. The lid can also be secured with four 2x10mm screws.

case thingiverse

I printed the design with White eSUN PETG, to match the colour of the table, with 20% infill. The total print time was a bit over 3 hours on the Bear MK3.

case

Connectors

Powering both Megadesk and the microcontroller from the desk requires a JST RCY splitter cable. These are also known as RC Servo cables. I purchased two '1 male to 2 female' splitters from AliExpress for $4.50 AUD. As long as there is one male and female, the last split can be anything it will be modified.

  • Female: Power from the desk
  • Male: Power to Megadesk
  • Male: Power to Step-down converter. I modified this to a JST SM connector below.

The cable will also most likely be coloured differently so it's important to mark the right orientation when it's plugged in. I use a bit of masking tape to indicate the way it should be connected.

jst rcy

To make it easy to plug and unplug the microcontroller and step-down converter, I also used JST SM connectors. Two sets of male and female 2-pin cables are required.

  1. Microcontroller to Megadesk
  2. Power to Step-down converter. Optional, see above.

I had 3-pin cables lying around that I de-pined to 2-pin.

jst sm

These cables are a personal preference, but I highly recommend them to make it easier to maintain and move around. It would be possible to achieve the same result with jumpers or soldering a wire directly.

Soldering

I am still using the same soldering equipment from my previous post where I installed a modchip into my Nintendo Switch OLED.

I started by working on the JST RCY connector. I removed the split male connector and cut off one cable as it won't be used. My colour coding will be:

Function Power RC Servo Action
LINBUS Yellow Black Not required. Cut at the split.
GROUND Black Red Solder split to IN-
24V Red White Solder split to IN+

rcy mod

At this point, I chose to connect a male JST connector to the GROUND and 24V wires.

Function RC Servo JST-Connector Action
GROUND Red Black Solder split to IN-
24V White Red Solder split to IN+

rcy jst

Run a JST SM female cable through the case's left channel and solder the black cable to IN- (Ground). The red cable will have to be shortened. Cut it just a bit ahead of where IN+ (24V) is and solder it in.

step down in solder

Connect the step-down converter to the power through the RCY/servo splitter. Adjust the step-down converter until it's outputting 3.3V. See the above section for more details. Power it off when done.

step down test

Run a JST SM cable through the case's right channel and solder it to the D1 Mini.

D1 Mini JST-Connector
RX Yellow
TX Red

d1 data solder

Solder a cable to OUT+ and OUT- on the step-down converter. Use the case's internal cable channels to route the cables. Consider the cable length to allow the lid to close and that it's not too tight and restrictive for future maintenance.

step down out solder

Solder the cables from the previous step to the D1 Mini

Function LM2596S D1 Mini
3.3V OUT+ 3V3
GROUND OUT- Black

d1 power solder

d1 finished

everything soldered

Check the D1 Mini is being wired properly and working by powering the step-down converter from the RCY/servo splitter and accessing the web interface. Power it off when done.

d1 test

Solder a male JST connector onto the Megadesk PCB. The cable must be on the side with the other connectors.

Function JST-Connector
MISO Yellow
SCK Red

megadesk solder

Connect the RCY/servo splitter to the Megadesk PCB and close it back up. Test it still works by connecting it to power and raising/lowering the desk. Power it off when done.

megadesk closed

test megadesk

Connect everything together. Every connector should have a male and a female making it easy to know what goes where. Do a final test raising/lowering the desk manually and through the web interface.

Function Color From To
Power Yellow, Black, Red Desk RCY/Servo Splitter
Power Black, Red RCY/Servo Splitter Step-down converter
Serial Yellow, Red Megadesk ESP Microcontroller

everything connected

esphome web

Finally, close the lid and secure it with screws.

case finished

case flex

On my wife's corner desk, the depth is smaller so I can fit it inside of cable net.

tammy underdesk

On my desk, the cables are too short, so I mounted it with double-sided tape. I could extend the cables or drill holes as well, but the easier solution won.

calvin underdesk

Home Assistant

Adding

Home Assistant should notice ESPHome devices on the network and offer to add them.

discovery

The API key can be copied from the ESPHome dashboard.

api

The device should be added and all non-internal controls made available.

device

Dashboard

I'm using the LinakDesk Card by IhorSyerkov to control the table from my dashboard. It's made for IKEA IDÅSEN desks, however I've created all entities required from ESPHome to make it work.

linak card

cards:
  -
    type: 'custom:config-template-card'
    variables:
      sit_height: parseInt(states['number.megadesk_position_02'].state)
      stand_height: parseInt(states['number.megadesk_position_03'].state)
      min_height: parseFloat(states['sensor.megadesk_minimum_height'].state)
      max_height: parseFloat(states['sensor.megadesk_maximum_height'].state)
      wtf: parseInt(states['sensor.megadesk_height_from_minimum'].state, 10)
    entities:
      - cover.megadesk_controls
      - sensor.megadesk_height_from_minimum
      - binary_sensor.megadesk_moving
    card:
      type: 'custom:linak-desk-card'

      desk: cover.megadesk_controls
      height_sensor: sensor.megadesk_height_from_minimum
      moving_sensor: binary_sensor.megadesk_moving

      min_height: ${min_height}
      max_height: ${max_height}

      presets:
        -
          label: Max
          target: ${max_height}
        -
          label: Stand
          target: ${stand_height}
        -
          label: Sit
          target: ${sit_height}
        -
          label: Min
          target: ${min_height}

Automation

I am using Home Assistant's automations to raise and lower the desk during working hours.

I have a time pattern that triggers the automation each hour. There isn't 'cron' style support yet, so I will use a conditional to check if the time is between 9 AM and 5 PM. I only half-days on Friday, so there is a condition for that as well.

trigger:
  - platform: time_pattern
    hours: /1
    minutes: "0"
    seconds: "0"

condition:
  - alias: Workday
    condition: state
    entity_id: binary_sensor.workday_sensor
    state: "on"

  - alias: Working Hours
    condition: or
    conditions:
      - alias: Monday to Thursday
        condition: time
        after: "09:00:00"
        before: "17:00:00"
        weekday:
          - mon
          - tue
          - wed
          - thu

      - alias: Friday
        condition: time
        after: "09:00:00"
        before: "13:00:00"
        weekday:
          - fri

I have another condition to check if I'm at home by checking if the phone is connected to the Wi-Fi:

-
  alias: At Home
  condition: device
  device_id: abc123
  domain: device_tracker
  entity_id: device_tracker.galaxy_z_fold4
  type: is_home

The last condition is to check the computer is active using the Home Assistant Companion app. It checks if the computer is unlocked and connected to my Alienware AW3821DW monitor. I also check if the microphone and webcam inactive, to not disrupt any meetings I'm in.

- alias: Computer In Use
  condition: and
  conditions:
    - condition: state
      entity_id: binary_sensor.macbook_active
      state: "on"
    - condition: state
      entity_id: binary_sensor.macbook_active
      attribute: Screensaver
      state: "false"
    - condition: state
      entity_id: binary_sensor.macbook_active
      attribute: Locked
      state: "false"
    - condition: state
      entity_id: sensor.macbook_primary_display_name
      state: Dell AW3821DW
    - condition: state
      entity_id: binary_sensor.macbook_audio_input_in_use
      state: "off"
    - condition: state
      entity_id: binary_sensor.macbook_camera_in_use
      state: "off"

I use a Choose script to check whether to raise or lower the desk. The condition checks if the desk is within one centimetre of the saved sitting/standing memory height. The height reported is always within a few millimetres of the saved position in my testing.

action:
  - choose:
    - conditions:
        - condition: template
          value_template: >-
            {% set memory = states('number.memory_position_height_02')|float %}
            {% set current_height = states('number.current_height')|float %}
            {{ memory + 1 > current_height > memory - 1 }}

    - conditions:
        - condition: template
          value_template: >-
            {% set memory = states('number.memory_position_height_03')|float %}
            {% set current_height = states('number.current_height')|float %}
            {{ memory + 1 > current_height > memory - 1 }}

Three actions are triggered afterwards:

  1. Announce the change through the Google Home Mini.
  2. Give myself five seconds to prepare.
  3. Raise/lower the desk.
sequence:
  - service: tts.google_translate_say
    data:
      cache: false
      entity_id: media_player.googlehome3873
      message: The desk is going up
  - delay:
      seconds: 5
  - device_id: abc123
    domain: button
    entity_id: button.megadesk_memory_position_03
    type: press

automation