Hello community,
inspired by the reddit article I had decided to try to integrate my Soehnle Shape Sense Connect 200 scale as well to have the data in my dashboard. Searching for an existing integration was not successful and therefore I had decided to do a reverse engineering as well. But my approach to get it done was a little bit different then ForsakenSyllabub8193 and therefore I want to describe the procedure and result here.
Prerequisites
Development has been done on HA 2026.7 with ESPHome Device Builder 2026.7 as well. Furthermore you need the Bluetooth integration in HA. I’m using a housed ESP32 which is named at Amazon " Waveshare ESP32-GEEK Development Board" and this is directly plugged into an old smartphone charger and this is plugged in my sleeping room were the scale is located.
Some coding parts below are the result of prompting with MS CoPilot.
Preparation:
As the Soehnle MyScale app communicates via Bluetooth with the scale, the way to go is via an ESP32 as Bluetooth proxy. How to make the ESP32 a Bluetooth proxy is described here. Add the proxy then to the Bluetooth integration by clicking just on the “Add entry” button. Then open the ESPHome Device Builder app and click the “Create device” button. That creates a new YAML file in the directory root/homeassistant/esphome. Then click on the “Edit” button of your new created device and then on “Install”. This will the run the compilation of a lot of C++ source files and finally create from them the firmware of the Bluetooth proxy. The firmware will be flashed to the ESP32 and after flashing ESPHome Builder starts logging.
The work:
To instantiate a new BLE client as base for HA sensors the MAC address of the Bluetooth device is necessary. The MAC address can be fetched from the Bluetooth integration in HA by just clicking on the three dots of one of the Bluetooth proxies and then selecting “Download diagnostics”. Then searching for keywords like scale, Soehnle or Shape will show the required data in the downloaded JSON file. The line with address is the MAC address. In this case also the service UUIDs are provided
"name": "Shape200",
"address": "XX:XX:XX:XX:XX:XX",
"rssi": -80,
"manufacturer_data": {},
"service_data": {
"0000181d-0000-1000-8000-00805f9b34fb": {
"__type": "<class 'bytes'>",
"repr": "b''"
}
},
"service_uuids": [
"0000181d-0000-1000-8000-00805f9b34fb",
"0000181c-0000-1000-8000-00805f9b34fb",
"0000181b-0000-1000-8000-00805f9b34fb"
],
If the service UUID(s) are not provided it is possible to determine them by using the ESP32_BLE_TRACKER component in ESPHome: Just add the following coding to your existing YAML (enter your MAC address for scale_mac):
substitutions:
scale_mac: "XX:XX:XX:XX:XX:XX"
packages:
esphome.bluetooth-proxy: github://esphome/bluetooth-proxies/esp32-generic/esp32-generic-s3.yaml@main
esp32:
variant: ESP32S3
logger:
level: DEBUG
esp32_ble:
max_connections: 4
ble_client:
- mac_address: ${scale_mac}
id: soehnle_scale
on_connect:
then:
- lambda: |-
ESP_LOGD("Scale", "Connected.");
on_disconnect:
then:
- lambda: |-
ESP_LOGD("Scale", "Connection closed.");
# BLE Tracker Configuration
esp32_ble_tracker:
scan_parameters:
interval: 2000ms
window: 2000ms
active: true
on_ble_advertise:
- mac_address: ${scale_mac}
then:
- lambda: |-
ESP_LOGD("Scale", " name: %s", x.get_name().c_str());
ESP_LOGD("Scale", " Advertised service UUIDs:");
char buf[esphome::esp32_ble::UUID_STR_LEN];
for (auto uuid : x.get_service_uuids()) {
ESP_LOGD("Scale", " - %s", uuid.to_str(buf));
}
ESP_LOGD("Scale", " Advertised service data:");
for (auto service_data : x.get_service_datas()) {
ESP_LOGD("Scale", " Service UUID - %s: (length %i)", service_data.uuid.to_str(buf), service_data.data.size());
}
Save the YAML and click on the Install button. After flashing the firmware to the ESP and while logging runs do a body composition measurement with your scale and then the result in the log will look like (and which confirms the UUIDs from the JSON file):
[20:52:27.770][D][Scale:053]: name: Shape200
[20:52:27.771][D][Scale:054]: Advertised service UUIDs:
[20:52:27.771][D][Scale:057]: - 0x181D
[20:52:27.771][D][Scale:057]: - 0x181C
[20:52:27.771][D][Scale:057]: - 0x181B
[20:52:27.771][D][Scale:059]: Advertised service data:
[20:52:27.771][D][Scale:061]: Service UUID - 0x181D: (length 0)
Now for the service UUIDs the characteristic UUIDs are required. In case of standardized service UUIDs you may ask an AI like MS CoPilot or Anthropics Claude for these UUIDs. If they have no information a further tool like nRF Connect must be used on your mobile device on which also the scale app is running. With the characteristics UUIDs log sensors can be created in the YAML. For this replace parts of the YAML coding by
ble_client:
- mac_address: ${scale_mac}
id: soehnle_scale
sensor:
- platform: ble_client
type: characteristic
ble_client_id: soehnle_scale
id: weight_measurement
name: "Weight Measurement"
service_uuid: "0000181d-0000-1000-8000-00805f9b34fb"
characteristic_uuid: "00002a9d-0000-1000-8000-00805f9b34fb"
notify: true
update_interval: never
lambda: |-
// read raw data
auto weight_data = x;
ESP_LOGD("Scale", "Body Composition Raw (%d bytes):", weight_data.size());
for (int i = 0; i < weight_data.size(); i++) {
ESP_LOGD("Scale", "%02X ", weight_data[i]);
}
uint16_t return_something = weight_data[1];
// Return for ESPHome-Sensor
return return_something;
- platform: ble_client
type: characteristic
ble_client_id: soehnle_scale
id: body_composition_measurement
name: "Body Composition Measurement"
service_uuid: "0000181b-0000-1000-8000-00805f9b34fb"
characteristic_uuid: "00002a9c-0000-1000-8000-00805f9b34fb"
notify: true
update_interval: never
lambda: |-
// get raw data
auto comp_data = x;
ESP_LOGD("Scale", "Body Composition Raw (%d bytes):", comp_data.size());
for (int i = 0; i < comp_data.size(); i++) {
ESP_LOGD("Scale", "%02X ", comp_data[i]);
}
uint16_t return_something2 = comp_data[1];
// Return for ESPHome-Sensor
return return_something2;
In the coding only 2 sensors are used. In general that needs to be done for each valid combination of service and characteristic UUID. But for the Soehnle scale this is sufficient.
Save the YAML file, flash the changes to the ESP. While the logger runs do a scale measurement and the log will look like:
[20:52:40.602][D][ble_sensor:109]: [Weight Measurement] ESP_GATTC_NOTIFY_EVT: handle=0x32, value=0x1e
[20:52:40.606][D][ble_sensor:109]: [Body Composition Measurement] ESP_GATTC_NOTIFY_EVT: handle=0x32, value=0x1e
[20:52:40.606][D][Scale:097]: Body Composition Raw (20 bytes):
[20:52:40.611][D][Scale:099]: 1E
[20:52:40.611][D][Scale:099]: 05
[20:52:40.611][D][Scale:099]: F7
[20:52:40.611][D][Scale:099]: 00
[20:52:40.615][D][Scale:099]: EA
[20:52:40.616][D][Scale:099]: 07
[20:52:40.619][D][Scale:099]: 07
[20:52:40.619][D][Scale:099]: 15
[20:52:40.673][D][Scale:099]: 12
[20:52:40.673][D][Scale:099]: 18
[20:52:40.673][D][Scale:099]: 1B
[20:52:40.673][D][Scale:099]: 01
[20:52:40.673][D][Scale:099]: AA
[20:52:40.673][D][Scale:099]: 0D
[20:52:40.673][D][Scale:099]: 89
[20:52:40.673][D][Scale:099]: 01
[20:52:40.673][D][Scale:099]: 2C
[20:52:40.673][D][Scale:099]: 02
[20:52:40.674][D][Scale:099]: EB
[20:52:40.674][D][Scale:099]: 03
As can be seen, the first sensor does not return data but the second one. While logging is running perform a scale measurement for one or two different person with different composition, note down the scale data and for each case the byte stream from above. Convert the byte sequence in a byte string like 1E:05:F7:00:EA:07:07:15:12:18:1B:01:AA:0D:89:01:2C:02:EB:03
and prompt an AI with your composition data and the corresponding byte string for an analysis of the string and ask which byte composition will return the weight and the muscle, water and fat portion. Sure, it is possible to create the byte string output also by the machine but that requires an additional C++ function which has to be provided in a separate .h file in the ESPHome directory and including the file in the YAML. For those who would like to know that as well, see at the end “Convenience stuff: byte string”.
The result
For the Soehnle Shape Sense Connect 200 scale the sensor part from the YAML coding above turns into (CoPilot has not only identified the data composition but I also asked for the small piece of C++ coding in the lambda)
sensor:
# Measurement sensor
- platform: ble_client
type: characteristic
ble_client_id: soehnle_scale
id: body_composition_measurement
name: "Body Composition Measurement"
service_uuid: "0000181b-0000-1000-8000-00805f9b34fb"
characteristic_uuid: "00002a9c-0000-1000-8000-00805f9b34fb"
notify: true
update_interval: never
lambda: |-
// get raw data
auto comp_data = x;
// calculate data
uint16_t weight_raw = (comp_data[19] << 8) | comp_data[18];
float weight = weight_raw * 0.1f;
id(scale_weight).publish_state(weight);
uint16_t fat_raw = (comp_data[3] << 8) | comp_data[2];
float fat = fat_raw * 0.1f;
id(scale_fat).publish_state(fat);
uint16_t muscle_raw = (comp_data[15] << 8) | comp_data[14];
float muscle = muscle_raw * 0.1f;
id(scale_muscle).publish_state(muscle);
uint16_t water_raw = (comp_data[17] << 8) | comp_data[16];
float water = water_raw * 0.1f;
id(scale_water).publish_state(water);
return weight;
# Sensors to be used in HA
- platform: template
name: "Scale Weight"
id: scale_weight
unit_of_measurement: "kg"
accuracy_decimals: 2
- platform: template
name: "Scale Fat Portion"
id: scale_fat
unit_of_measurement: "%"
accuracy_decimals: 1
- platform: template
name: "Scale Muscle Portion"
id: scale_muscle
unit_of_measurement: "%"
accuracy_decimals: 1
- platform: template
name: "Scale Water Portion"
id: scale_water
unit_of_measurement: "%"
accuracy_decimals: 1
The Soehnle scale transmits the data in little endian format and therefore the bit shift operation (<<) of the high byte and the logical or (|) with the low byte creates a word type which is then converted to a float type by multiplication with 0.1 (HA needs float for the sensors and the scale transmits the data without decimal places). The calculated data are then pushed by the measurement sensor to the HA sensors by the function id(<HA_sensor>).publish_state(<<Calc_part>);
The HA sensor data are written into InfluxDB and I use Grafana to visualize them in my main dashboard. For sure the data can be used for automation like “IF scale_weight > 100 lock fridge” ![]()
Convenience stuff: byte string
Use the File Editor app in HA to create a file scale_utils.h in the directory root/homeassistant/esphome. Copy the following coding in this file and save:
#pragma once
#include <string>
#include <vector>
inline std::string to_hex_colon(const std::vector<uint8_t>& data) {
std::string out;
out.reserve(data.size() * 3);
const char* lut = "0123456789ABCDEF";
for (size_t i = 0; i < data.size(); i++) {
uint8_t b = data[i];
out.push_back(lut[b >> 4]);
out.push_back(lut[b & 0x0F]);
if (i + 1 < data.size()) {
out.push_back(':');
}
}
return out;
}
In the esphome section in your YAML file add the lines
includes:
- scale_utils.h
This includes the file during compilation and hence also scale_utils.h gets compiled.
Finally replace the YAML coding lines
for (int i = 0; i < weight_data.size(); i++) {
ESP_LOGD("Scale", "%02X ", weight_data[i]);
}
by
auto weight_hex = to_hex_colon(weight_data);
ESP_LOGD("Scale", "Body Composition Raw: %s", weight_hex.c_str());
respectively
for (int i = 0; i < comp_data.size(); i++) {
ESP_LOGD("Scale", "%02X ", comp_data[i]);
}
by
auto comp_hex = to_hex_colon(comp_data);
ESP_LOGD("Scale", "Body Composition Raw: %s", comp_hex.c_str());
The log returns then
[17:18:20.324][D][Scale:073]: Body Composition Raw (20 bytes):
[17:18:20.328][D][Scale:075]: Body Composition Raw: 1E:05:FA:00:EA:07:07:17:0E:32:01:01:7E:0D:88:01:22:02:DE:03
You may wonder why the byte string looks different then above? Not really, because measured on different days and time ![]()