Hello! First time posting, but, since I spent a great deal of time trying to find a solution myself, I figured I’d make a post here for anyone else.
The Problem
I got a Hatch Baby Grow baby changing scale. This scale uses Bluetooth Low Energy (BLE) and connects to the Hatch Baby app on your phone.
Personally, I hated the app. Its clunky, didn’t integrate with my baby tracking system, and the UI to just use the scale is somewhat buried. And worst yet, it natively does not work with Home Assistant.
The Solution
After lots of trial and error, the simplest solution to get the Hatch Baby Grow scale to work with Home Assistant was to use an ESP32 BLE client, and parse the raw BLE service characteristics. Some trial and error was required to discover which service ID and characteristic was needed to connect to, and how to parse the raw data. (I highly recommend using the nRF Connect app when doing this investigation yourselves!). For anyone curious, the weight is not in the BLE advertisements, but instead in a service that requires a connection for. It displays the weight in 100ths of a gram, so, it is fairly jittery. There also seems to be a semi-linear error to the value that required correcting.
I also discovered my current solution would drain the battery since the current SDK has the manual connect and disconnect commands broken (see github issue here). So, I lazily opted to use a Battery Eliminator kit for the Hatch baby Grow so I wouldn’t need to worry about battery life, and kept the auto connect logic on. This means the scale is always reporting the weight measurements, which does impact the battery life a lot. I hope to fix this once that issue gets resolved, so you can simply connect to the device, read the weight, then disconnect to save on battery,
And then lastly I added several helper template sensors to calibrate the raw weight values, as the raw values are both not zeroed, but also have a semi-linear error. (More trial and error was also needed to find the correct error correction equation. I assume the original Hatch Baby app has their own way to correct this, so, I had to make a best guess suing my own measurements. Based on the values I recorded, a power regression seemed to be the best fit fix.)
Parts list
- Any ESP32 with a BLE adapter will work. I picked an ESP32-S3 up off of amazon for ~$15, but I’m sure there are better pricing out there elsewhere.
- 3 AA battery eliminator kit (4.5V). I also picked one up off of Amazon for ~$15. I’m also sure there’s better pricing elsewhere too.
ESP32 config
This is the ESPHome config required. Make sure when setting up your project you pick the correct board for whatever device you ended up getting as well.
I also left some commented code in there for some battery optimizations I wanted to have, but because of a current bug, they won’t work, hence the need for the Battery Eliminator adapter.
substitutions:
name: hatch-grow-connector
friendly_name: Hatch Grow Connector
esp32:
variant: ESP32S3
ota:
- platform: esphome
encryption:
esphome:
name: ${name}
name_add_mac_suffix: false
friendly_name: ${friendly_name}
api:
encryption:
key: <your key>
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# uncomment after https://github.com/esphome/issues/issues/6443 is resolved
# esp32_ble_tracker:
ble_client:
- name: Hatch Baby Grow
id: ble_client_hatch_baby_grow
mac_address: <find MAC from nRF Connect App>
# uncomment after https://github.com/esphome/issues/issues/6443 is resolved
# auto_connect: false
sensor:
- platform: ble_client
name: Raw Weight
id: sensor_ble_client_raw_weight
ble_client_id: ble_client_hatch_baby_grow
characteristic_uuid: 02230001-5efd-47eb-9c1a-de53f7a2b232 # discovered using nRF connect App
type: characteristic
service_uuid: 02230000-5efd-47eb-9c1a-de53f7a2b232 # discovered using nRF connect App
icon: "mdi:scale"
device_class: weight
notify: true
state_class: measurement
unit_of_measurement: "g"
lambda: |-
// 1. Safety check: Ensure we received at least 4 bytes to read the first 3 safely
if (x.size() < 4) {
ESP_LOGW("ble_lambda", "Received incomplete data payload");
return {};
}
// 2. Map the 3 data bytes according to little-endian order
uint32_t byte_1 = x[0]; // Least Significant Byte (LSB)
uint32_t byte_2 = x[1]; // Middle Byte
uint32_t byte_3 = x[2]; // Most Significant Byte (MSB)
// byte 4 seems to be a counter, counting up with each data read and restarting. So I ignored it.
// 3. Combine the 3 bytes into a single 32-bit unsigned integer using bitwise shifts
uint32_t raw_weight = (byte_3 << 16) | (byte_2 << 8) | byte_1;
// 4. Log the result to the ESPHome terminal for easy troubleshooting
// ESP_LOGD("ble_lambda", "Parsed 24-bit Raw Weight: %u", raw_weight);
// 5. Send the final value to Home Assistant, divided by 100 to convert from hundredths of a gram to grams
return (float)raw_weight/100.0;
accuracy_decimals: 2
- platform: ble_client
type: characteristic
id: sensor_ble_client_battery
ble_client_id: ble_client_hatch_baby_grow
name: "Battery Level"
service_uuid: '180f'
characteristic_uuid: '2a19'
icon: 'mdi:battery'
unit_of_measurement: '%'
accuracy_decimals: 0
notify: true
entity_category: diagnostic
state_class: measurement
device_class: "battery"
# uncomment after https://github.com/esphome/issues/issues/6443 is resolved
# button:
# - platform: template
# name: "Connect to Hatch Baby Grow"
# on_press:
# then:
# - esp32_ble_tracker.stop_scan:
# - delay:
# seconds: '1'
# - ble_client.connect: ble_client_hatch_baby_grow
# icon: "mdi:bluetooth-audio"
# - platform: template
# name: "Disconnect from Hatch Baby Grow"
# on_press:
# - ble_client.disconnect: ble_client_hatch_baby_grow
# icon: "mdi:bluetooth-off"
Template Sensors
I created these sensors using the GUI (so no YAML, sorry), and they help with both displaying the weight, calibrating the weight, and zeroing the scale.
- A de-bounced raw weight, using a statistics helper using
mean, and amax ageof 3 seconds. This helps filter noise from the jittery sensor. - An input number that will store the zeroed value (essentially, the raw weight when nothing is on the scale).
- min of 0 and max of 1000 seems to work as the raw value hovers around ~550.
- A template sensor that shows the calibrated weight (raw weight, minus the zeroed weight, and then fed through a power regression to error correct)
- These values were determined using calibrated weighs using the original app, and then the raw reads from from the esp32. I found a power regression fit better than linear regression.
{% set zeroed_val = ((states('sensor.family_room_hatch_grow_connector_raw_weight_debounced') | float(default=0.0)) - (states('input_number.hatch_baby_grow_zeroed_value') | float(default=0.0))) %} {{ 0.840326 * (zeroed_val ** 1.00995) }}
- These values were determined using calibrated weighs using the original app, and then the raw reads from from the esp32. I found a power regression fit better than linear regression.
- A template sensor that converts the calibrated weight into pounds.
- A template sensor that displayed the weight in pounds as a readable pounds and ounces.
- A template button that on press records the de-bounced weight to the input number helper above.
What this has allowed me to do
Once I got all the above created, I was able to create some great automations with the scale! For example:
- We have a lamp nearby that will now auto turn on and off whenever the scale is in use.
- I can use Assist to record the weight to our baby tracking app hands free.
- I created a dashboard that replaces the Hatch Baby app, making manual use of the scale much simpler and faster.
edit: fixed ESP config to remove unused bluetooth proxy package in favor of leaner ota/platform config