Appreciate the work put in by everyone in the thread to reverse engineer the Meater BLE protocol.
I made some improvements to the ESPHome code above, so I thought I’d share them. The below just needs to be copied into the config of any ESPHome device with Bluetooth capabilities that is near where you’ll be using your Meater. Be sure to insert your device’s MAC address. I found it pretty straightforward to just use the LightBlue app. It prints out a list of nearby Bluetooth devices and their MAC addresses, so just grab the one that says MEATER.
The code above had the temperature values as integers, but then returned 2 decimal places, so the temperature would go from e.g. 70.00 to 71.00. I changed them to floats and reduced to 1 decimal place, as that’s really the limit of precision of this thing.
I also added device_class and state_class to each sensor, which allows Home Assistant to automatically convert units to your defaults. So no need to mess with the temperature calculation math.
I removed some icons and casts to (float), as they seemed extraneous. Not overriding the icon also allows Home Assistant to customize icons in some cases (e.g. if the battery entity is unavailable you’ll get the battery + question mark icon).
As others have mentioned, Meater doesn’t seem to be able to handle the tip temperature being higher than the ambient temperature, so just a reminder if anyone else tests this by putting the tip in some boiling water.
I didn’t however, experience the Meater powering off/disconnecting after an hour. Mine will seemingly keep transmitting until the battery dies. It transmitted for 18 hours overnight, though there were two ~1hr interruptions after ~7hrs and ~12hrs in. Not sure if that’s the device or my ESPHome device disconnecting.
My device is the Meater Plus, with probe MT-PR10 and charger MT-CP01.
ble_client:
- mac_address: **insert your Meater's MAC address here**
id: meater
text_sensor:
- platform: template
name: "Meater Firmware"
id: meater_firmware
sensor:
- name: "Meater Tip Temperature"
platform: ble_client
type: characteristic
ble_client_id: meater
service_uuid: 'a75cc7fc-c956-488f-ac2a-2dbc08b63a04'
characteristic_uuid: '7edda774-045e-4bbf-909b-45d1991a2876'
unit_of_measurement: '°C'
accuracy_decimals: 1
device_class: "temperature"
state_class: "measurement"
notify: true
lambda: |-
float tip_temp = (x[0] + (x[1] << 8) + 8.0) / 16.0;
return tip_temp;
- name: "Meater Ambient Temperature"
platform: ble_client
type: characteristic
ble_client_id: meater
service_uuid: 'a75cc7fc-c956-488f-ac2a-2dbc08b63a04'
characteristic_uuid: '7edda774-045e-4bbf-909b-45d1991a2876'
unit_of_measurement: '°C'
accuracy_decimals: 1
device_class: "temperature"
state_class: "measurement"
notify: true
lambda: |-
uint16_t tip = x[0] + (x[1] << 8);
uint16_t ra = x[2] + (x[3] << 8);
uint16_t oa = x[4] + (x[5] << 8);
uint16_t min_val = 48;
float ambient = (tip + std::max(0., (((ra - std::min(min_val, oa)) * 16. * 589.) / 1487.)) + 8.0) / 16.;
return ambient;
- name: "Meater Battery Level"
platform: ble_client
type: characteristic
ble_client_id: meater
service_uuid: 'a75cc7fc-c956-488f-ac2a-2dbc08b63a04'
characteristic_uuid: '2adb4877-68d8-4884-bd3c-d83853bf27b8'
unit_of_measurement: '%'
device_class: "battery"
state_class: "measurement"
notify: true
lambda: |-
uint16_t battery = (x[0] + x[1]) * 10;
return battery;
- name: "Meater RSSI"
platform: ble_client
type: rssi
ble_client_id: meater
device_class: "signal_strength"
state_class: "measurement"
- id: firmware
platform: ble_client
type: characteristic
ble_client_id: meater
service_uuid: '180A'
characteristic_uuid: '00002a26-0000-1000-8000-00805f9b34fb'
lambda: |-
std::string data_string(x.begin(), x.end());
id(meater_firmware).publish_state(data_string.c_str());
return x.size();