Support for MEATER true wireless cooking thermometer

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();

3 Likes