ThermoPro Meat Thermometer TP904 Gateway

Hi everyone,

I have bought a rather cheap meat thermometer for my beefer box last year and always wanted to build some esp-based gateway to home assistant since the bluetooth range of this device is rather short (at least for this kind of device). I have banged my head on some esphome configuration, did some research and trail-and-error configuration and want to share my (highly wip) config with you all.

Device: ThermoPro TP904
(ThermoPro Meat Thermometer Digital Grill Thermometer Wireless Roasting Thermometer with 2 Probes for BBQ Smoker Rechargeable Kitchen Thermometer with Alarm Function Supports Mobile Phone Bluetooth 5.0 : Amazon.de: Home & Kitchen) ← no affiliate, just an example

ESPHome Configuration:

... (default part left out for brevity)

esp32_ble_tracker:

ble_client:
  - id: tp904
    mac_address: E4:C1:51:AB:65:57
    auto_connect: false
    on_connect:
      then:
        - logger.log:
            format: "Connected to TP904."

switch:
  - platform: template
    name: "BBQ Thermometer"
    optimistic: true
    turn_on_action:
      - ble_client.connect: tp904
      - ble_client.ble_write:
          id: tp904
          service_uuid: 1086FFF0-3343-4817-8BB2-B32206336CE8
          characteristic_uuid: 1086FFF1-3343-4817-8BB2-B32206336CE8
          value: [0x01, 0x09, 0x8a, 0x7a, 0x13, 0xb7, 0x3e, 0xd6, 0x8b, 0x67, 0xc2, 0xa0]
    turn_off_action:
      - ble_client.disconnect: tp904

sensor:
  - platform: ble_client
    name: "TP904 BLE Parser"
    type: characteristic
    ble_client_id: tp904
    service_uuid: 1086FFF0-3343-4817-8BB2-B32206336CE8
    characteristic_uuid: 1086FFF2-3343-4817-8BB2-B32206336CE8
    notify: true
    lambda: |-
      // accept only full measurement frames
      if (x.size() != 19 && x.size() != 20) return NAN;
      if (x[0] != 0x30) return NAN;

      // ---- Publish raw frame as hex string ----
      //std::string out;
      //char buf[3];
      //for (size_t i = 0; i < x.size(); i++) {
      //  snprintf(buf, sizeof(buf), "%02X", x[i]);
      //  out += buf;
      //}
      //id(probes_raw).publish_state(out);
      
      // Probe 1: chars 11–13 -> 22.6
      uint16_t p1 =
        ((x[5] & 0x0F) * 100) +   // char 11
        ((x[6] >> 4) * 10) +     // char 12
        (x[6] & 0x0F);           // char 13

      // Probe 2: chars 15–17 -> 23.5
      uint16_t p2 =
        ((x[7] & 0x0F) * 100) +  // char 15
        ((x[8] >> 4) * 10) +    // char 16
        (x[8] & 0x0F);          // char 17

      if (p1 <= 999)
        id(tp904_probe1).publish_state(p1 / 10.0f);

      if (p2 <= 999)
        id(tp904_probe2).publish_state(p2 / 10.0f);

      return NAN;

  - platform: template
    name: "TP904 Probe 1"
    id: tp904_probe1
    icon: mdi:temperature-celsius
    unit_of_measurement: "°C"
    accuracy_decimals: 1

  - platform: template
    name: "TP904 Probe 2"
    id: tp904_probe2
    icon: mdi:temperature-celsius
    unit_of_measurement: "°C"
    accuracy_decimals: 1

#text_sensor:
#  - platform: template
#    name: "TP904 Raw Frame"
#    id: probes_raw

You can uncomment the ---- Publish raw frame as hex string ----part and the text_sensor to see the raw message received through the notified characteristic but its very noisy and for development purpose only.

When using it you have to turn on the switch (and sometimes try this 2-3 times until you see real temperature values).
Feel free to use and share your experience as well as improve it.

1 Like