Last year my wife and I bought a new bed. We decided to go with a bed that contained bed motors so we can move the headrest and legrest up and down. It came with a nice remote control and as always I was wondering what the protocol was of the remote control. After reviewing the manual of the motors on the internet, I’ve figured out that it actually had bluetooth control! This got me hooked. Would it be possible to control the bed with Home Assistant? The answer: Yes!
Smartbed-MQTT
Before diving into my own setup, you should absolutely take a look at the fantastic Home Assistant add-on Smartbed-MQTT by Richard Hopton. Richard is doing an amazing job trying to connect as many different bed motors as possible to Home Assistant through a bluetooth proxy. At the moment, he supports bed motors from Okin/Okimat, Richmat, Linak, Solace, MotoSleep, Reverie, Leggett & Platt, Keeson, Octo, ErgoMotion and Logicdata.
I helped test and contribute support for my own bed model, but I ran into issues with my second motor that prevented full integration through the add-on. Still, if your goal is simply to get your bed into Home Assistant, start with Smartbed-MQTT. Even if your model isn’t supported yet, the repository is an excellent resource for learning about Bluetooth services and control commands.
My Bed: An Okin / Okimat motor
My bed uses the Okimat 4 IPS from DewertOkin GmbH. Each bed motor model behaves slightly differently over Bluetooth, so this configuration may not work with yours. Again, check the Smartbed-MQTT repository—if not for the add-on itself, then for its extensive documentation on control characteristics and message structures.
Note: The latest version of my set-up can be found here.
Bluetooth Discovery
The biggest challenge in this project was discovering the correct Bluetooth services and characteristics. Since I’m relatively new to low-level Bluetooth control, it took some trial and error, but with the right tools, it’s very manageable.
I gathered information from two main sources:
1. Smartbed-MQTT Source Code
Many supported bed models already have their control commands mapped out. Browsing through the code often reveals exactly which service UUIDs and characteristics you need to write to.
nRF Connect (Android / iOS)
Using nRF Connect (Android & iOS), you can monitor BLE traffic from the manufacturer’s app. By pressing buttons in the official mobile app and watching the BLE writes in nRF Connect, you can reverse-engineer the command payloads.
There are many tutorials online, and the process is surprisingly straightforward once you get the hang of it.
Walkthrough of My Setup
I’m using an M5 Atom Lite running ESPHome as a Bluetooth proxy. Below is the relevant part of my configuration.
1. BLE Advertisements
This section scans for the bed motor based on its MAC address and logs all discovered info. It’s purely for debugging: to confirm the proxy sees the correct device.
esp32_ble_tracker:
on_ble_advertise:
- mac_address:
- YOUR MAC ADDRESS
then:
- lambda: |-
ESP_LOGD("ble_adv", "New BLE device");
ESP_LOGD("ble_adv", " address: %s", x.address_str().c_str());
ESP_LOGD("ble_adv", " name: %s", x.get_name().c_str());
ESP_LOGD("ble_adv", " Advertised service UUIDs:");
for (auto uuid : x.get_service_uuids()) {
ESP_LOGD("ble_adv", " - %s", uuid.to_string().c_str());
}
ESP_LOGD("ble_adv", " Advertised service data:");
for (auto data : x.get_service_datas()) {
ESP_LOGD("ble_adv", " - %s: (length %i)", data.uuid.to_string().c_str(), data.data.size());
}
ESP_LOGD("ble_adv", " Advertised manufacturer data:");
for (auto data : x.get_manufacturer_datas()) {
ESP_LOGD("ble_adv", " - %s: (length %i)", data.uuid.to_string().c_str(), data.data.size());
}2. BLE Client Connection
Once connected, a binary sensor is updated indicating the link status. Useful for dashboards and troubleshooting.
ble_client:
- mac_address: YOUR MAC ADDRESS
id: bed_1
on_connect:
then:
- lambda: |-
ESP_LOGD("ble_client_lambda", "Connected to BLE device");
- binary_sensor.template.publish:
id: bed_1_connection_status
state: True
on_disconnect:
then:
- lambda: |-
ESP_LOGD("ble_client_lambda", "Disconnected from BLE device");
- binary_sensor.template.publish:
id: bed_1_connection_status
state: FalseBinary sensor:
binary_sensor:
- platform: template
name: "Bed 1 connection status"
id: bed_1_connection_status
device_class: connectivity
entity_category: diagnostic
device_id: motor_bed_13. Device Information
I also pull in all standard BLE Device Information characteristics (model, serial, firmware, etc.). This is optional but handy, especially when you have multiple motors and want to compare firmware versions.
text_sensor:
- platform: ble_client
ble_client_id: bed_1
service_uuid: '0000180a-0000-1000-8000-00805f9b34fb'
characteristic_uuid: '00002a24-0000-1000-8000-00805f9b34fb'
name: "Model Number"
id: bed_1_model_number
entity_category: diagnostic
icon: mdi:identifier
device_id: motor_bed_1
- platform: ble_client
ble_client_id: bed_1
service_uuid: '0000180a-0000-1000-8000-00805f9b34fb'
characteristic_uuid: '00002a25-0000-1000-8000-00805f9b34fb'
name: "Serial Number"
id: bed_1_serial_number
entity_category: diagnostic
icon: mdi:numeric
device_id: motor_bed_1
- platform: ble_client
ble_client_id: bed_1
service_uuid: '0000180a-0000-1000-8000-00805f9b34fb'
characteristic_uuid: '00002a26-0000-1000-8000-00805f9b34fb'
name: "Firmware Revision"
id: bed_1_firmware_revision
entity_category: diagnostic
icon: mdi:chip
device_id: motor_bed_1
- platform: ble_client
ble_client_id: bed_1
service_uuid: '0000180a-0000-1000-8000-00805f9b34fb'
characteristic_uuid: '00002a27-0000-1000-8000-00805f9b34fb'
name: "Hardware Revision"
id: bed_1_hardware_revision
entity_category: diagnostic
icon: mdi:memory
device_id: motor_bed_1
- platform: ble_client
ble_client_id: bed_1
service_uuid: '0000180a-0000-1000-8000-00805f9b34fb'
characteristic_uuid: '00002a28-0000-1000-8000-00805f9b34fb'
name: "Software Revision"
id: bed_1_software_revision
entity_category: diagnostic
icon: mdi:application
device_id: motor_bed_1
- platform: ble_client
ble_client_id: bed_1
service_uuid: '0000180a-0000-1000-8000-00805f9b34fb'
characteristic_uuid: '00002a29-0000-1000-8000-00805f9b34fb'
name: "Manufacturer Name"
id: bed_1_manufacturer_name
entity_category: diagnostic
icon: mdi:factory
device_id: motor_bed_14. Light Control
The light on my bed is a simple toggle command with no feedback state. Since it also turns off automatically after one hour, exposing it as a “button” is the most reliable option.
# Lights
- platform: template
name: "Bed light"
icon: "mdi:lightbulb-outline"
device_id: motor_bed_1
on_press:
- ble_client.ble_write:
id: bed_1
service_uuid: 62741523-52f9-8864-b1ab-3b3a8d65950b
characteristic_uuid: 62741525-52f9-8864-b1ab-3b3a8d65950b
value: [0x04, 0x02, 0x00, 0x02]5. Movement Controls
Each movement direction is implemented as a button that triggers a repeating script until stopped.
This mirrors how the original remote holds a button down to keep the motor moving.
Buttons:
# Movement
- platform: template
name: "Bed head up"
icon: "mdi:arrow-up-bold"
device_id: motor_bed_1
on_press:
- script.execute:
id: bed_1_move
move_value: 0x01
description: "Head UP"
- platform: template
name: "Bed head down"
icon: "mdi:arrow-down-bold"
device_id: motor_bed_1
on_press:
- script.execute:
id: bed_1_move
move_value: 0x02
description: "Head DOWN"
- platform: template
name: "Bed feet up"
icon: "mdi:arrow-up-bold"
device_id: motor_bed_1
on_press:
- script.execute:
id: bed_1_move
move_value: 0x04
description: "Feet UP"
- platform: template
name: "Bed feet down"
icon: "mdi:arrow-down-bold"
device_id: motor_bed_1
on_press:
- script.execute:
id: bed_1_move
move_value: 0x08
description: "Feet DOWN"
- platform: template
name: "Bed stop"
icon: "mdi:stop-circle-outline"
device_id: motor_bed_1
on_press:
- script.stop: bed_1_moveMovement script:
script:
# Movement script
- id: bed_1_move
mode: restart
parameters:
move_value: int
description: string
then:
- repeat:
count: 100
then:
- ble_client.ble_write:
id: bed_1
service_uuid: 62741523-52f9-8864-b1ab-3b3a8d65950b
characteristic_uuid: 62741525-52f9-8864-b1ab-3b3a8d65950b
value: !lambda |-
return std::vector<uint8_t>{0x02, 0x02, 0x00, static_cast<uint8_t>(move_value)};
- delay: 140ms
- logger.log:
format: "Bed 1: %s"
args: [ 'description.c_str()' ]You can adjust the delay to make the movement feel smoother, but it still won’t be as seamless as the manufacturer’s app. The Smartbed-MQTT add-on handles this more gracefully with fine-tuned timing logic.
Conclusion
Connecting my bed motors to Home Assistant turned out to be one of those projects that starts as a curiosity and ends as a useful home automation upgrade.
If you’re attempting a similar project, I highly recommend:
- Starting with the Smartbed-MQTT add-on
- Using nRF Connect (Android & iOS) to inspect BLE traffic
- Taking the time to properly map out services and characteristics
In the end, bringing your bed into Home Assistant isn’t just a neat trick: it’s a great example of how open-source tools let you take control of hardware that was never designed to be part of a smart home.