Read Govee Temperature Humidity BLE Advertisements with esp32 and esphome!

Tags: #<Tag:0x00007f73969c47d8>

Hey Folks!

Inspired by custom component https://github.com/Home-Is-Where-You-Hang-Your-Hack/sensor.goveetemp_bt_hci I wanted to use these awesome sensors throughout my house but I have sections of the house I knew the BLE advertisements would not be able to reach my PC that is running home assistant so I wanted to see if it would be possible to use ESP32 bluetooth capability to read these advertisements. There’s already multiple ESPHome components that do something similar but I am a n00b at C++ and it was quite daunting trying to start writing my own component for esphome. Luckily, we dont have to do that as ESPHome has a built in feature/callback that can parse such messages with a lambda function.

Relevant ESP Home config:

esp32_ble_tracker:
  on_ble_manufacturer_data_advertise:
    - mac_address: A4:C1:38:11:22:33 #update to your govee device mac address
      manufacturer_id: '0001'
      then:
        - lambda: |-
            const int basenum = (int16_t(x[2]) << 16) + (int16_t(x[3]) << 8) + int16_t(x[4]);
            ESP_LOGD("ble_adv", "goveesensor basenum: (%d)", basenum);
            const float temperature = basenum / 10000.0f;
            const float humidity = (basenum % 1000) / 10.0f;
            const float battery_level = uint16_t(x[5]) / 1.0f;
            ESP_LOGD("ble_adv", "  Temperature: %.2f°C", temperature);
            ESP_LOGD("ble_adv", "  Humidity: %.2f", humidity);
            ESP_LOGD("ble_adv", "  Battery Level: %.0f percent", battery_level);
            id(govee1_temp).publish_state(temperature);
            id(govee1_humidity).publish_state(humidity);
            id(govee1_battery).publish_state(battery_level);

  #use this section to determine manufacturer id(mine was '0001'), can be commented out after initial discovery
  on_ble_advertise:
    - mac_address: A4:C1:38:11:22:33 #update to your govee device mac address
      then:
        - lambda: |-
            ESP_LOGD("ble_adv", "New BLE device");
            ESP_LOGD("ble_adv", "  address: %s", x.address_str().c_str());
            ESP_LOGD("ble_adv", "  name: %s", x.get_name().c_str());
            ESP_LOGD("ble_adv", "  Advertised service UUIDs:");
            for (auto uuid : x.get_service_uuids()) {
                ESP_LOGD("ble_adv", "    service uuid %s", uuid.to_string().c_str());
            }
            ESP_LOGD("ble_adv", "  Advertised service data:");
            for (auto data : x.get_service_datas()) {
                ESP_LOGD("ble_adv", "    service data uuid - %s: (length %i)", data.uuid.to_string().c_str(), data.data.size());
            }
            ESP_LOGD("ble_adv", "  Advertised manufacturer data:");
            for (auto data : x.get_manufacturer_datas()) {
                ESP_LOGD("ble_adv", "    mfg data uuid %s: (length %i)", data.uuid.to_string().c_str(), data.data.size());
            }

sensor:
  - platform: template
    name: "Master Bath Temperature"
    id: govee1_temp
    unit_of_measurement: "°C"
    accuracy_decimals: 2
  - platform: template
    name: "Master Bath Humidity"
    id: govee1_humidity
    unit_of_measurement: '%'
    accuracy_decimals: 2
  - platform: template
    name: "Master Bath Sensor Battery Level"
    id: govee1_battery
    unit_of_measurement: '%'
    accuracy_decimals: 0

Note(s):
I’ve confirmed this works with Govee H5101 model, which based on the lovely work by @Thrilleratplay should also work with a slight change in message format for Govee models 5102 and 5072 as well other models should be fairly straightforward to adapt, assuming they use similar structure.
Be sure to comment with your code if you find a new model working with a similar method!

2 Likes

I was able to do the same for the H5074. Same concept, different lambda due to the differences in the H5074 data encoding. This has been working for several weeks without issue. I use the humidity sensors to turn on the exhaust fans to run automatically to keep the humidity down when the shower is used. Working well so far.

sensor:
#  - platform: wifi_signal
#    name: "WiFi Signal Strength"
#    update_interval: 60s
  
  - platform: template
    name: "Guest Bathroom Humidity"
    id: guest_bathroom_humidity
    unit_of_measurement: '%'
    icon: "mdi:water-percent"
  - platform: template
    name: "Master Bathroom Humidity"
    id: master_bathroom_humidity
    unit_of_measurement: '%'
    icon: "mdi:water-percent"
  - platform: template
    name: "Master Bathroom Temperature"
    id: master_bathroom_temperature
    unit_of_measurement: 'F'
    icon: "mdi:thermometer"
  - platform: template
    name: "Master Bathroom RSSI"
    id: master_bathroom_rssi
    unit_of_measurement: 'dB'
    icon: "mdi:wifi"
  - platform: template
    name: "Master Bathroom Battery"
    id: master_bathroom_battery
    unit_of_measurement: '%'
    icon: "mdi:battery"

esp32_ble_tracker:
  on_ble_advertise:
    - mac_address: XX:XX:XX:XX:XX:XX
      then:
        - lambda: |-
            for (auto data : x.get_manufacturer_datas()) {
                if(data.data.size()==7) {
                  uint16_t hum_lsb = data.data[3] + (data.data[4] << 8);
                  float humidity= float(hum_lsb)/100.0;
                  int16_t temp_lsb = data.data[1] + (data.data[2] << 8);
                  float temperature = float(temp_lsb)/100*9.0/5.0 + 32.0;
                  int16_t battery=data.data[5];
                  int16_t rssi=x.get_rssi();
                  id(master_bathroom_humidity).publish_state(humidity);
                  id(master_bathroom_temperature).publish_state(temperature);
                  id(master_bathroom_rssi).publish_state(rssi);
                  id(master_bathroom_battery).publish_state(battery);

                }
            }
1 Like

I’m happy to see this working – I have a 5075 and I’m hoping to adjust what you all have done to make it work for it. Of course, I need to do some research to figure out what is happening here. I’d love to move this from the component and to the esp32 to be able to centralize the sensor and have more consistent readings.

Bought several H5051 sensors recently. The only update I had to make to @John.McDowell script was checking the data size was ==9 instead of ==7. Really appreciate the work put in to get this going!

esp32_ble_tracker:
  on_ble_advertise:
    - mac_address: XX:XX:XX:XX:XX:XX
      then:
        - lambda: |-
            for (auto data : x.get_manufacturer_datas()) {
              if(data.data.size()==9) {
                uint16_t hum_lsb = data.data[3] + (data.data[4] << 8);
                float humidity= float(hum_lsb)/100.0;
                int16_t temp_lsb = data.data[1] + (data.data[2] << 8);
                float temperature = float(temp_lsb)/100*9.0/5.0 + 32.0;
                int16_t battery=data.data[5];
                int16_t rssi=x.get_rssi();
                id(govee_h5051_1_humidity).publish_state(humidity);
                id(govee_h5051_1_temperature).publish_state(temperature);
                id(govee_h5051_1_rssi).publish_state(rssi);
                id(govee_h5051_1_battery).publish_state(battery);
              }
            }

This is great. Thanks all for the work you put in.

Does this work for negative temperature numbers? I have the H5075, and when the temperature goes below 0C, it get super weird numbers using the encoding described in https://github.com/Thrilleratplay/GoveeWatcher

@mmohoney cc

I do not understand how to receive the manufacturer_id. I have a 5072, and am able to see it get discovered:

[16:53:35][D][ble_adv:947]: New BLE device
[16:53:35][D][ble_adv:948]:   address: *redacted*
[16:53:35][D][ble_adv:949]:   name: GVH5072_3528
[16:53:35][D][ble_adv:950]:   Advertised service UUIDs:
[16:53:35][D][ble_adv:952]:     - EC:88
[16:53:35][D][ble_adv:954]:   Advertised service data:
[16:53:35][D][ble_adv:958]:   Advertised manufacturer data:
[16:53:35][D][ble_adv:960]:     - EC:88: (length 6)
[16:53:35][D][ble_adv:960]:     - 00:4C: (length 23)

Can anyone help me interpret the manufacturer data? Am I supposed to convert those values or is something else wrong?

It is stored in two’s complement from @Thrilleratplay script. Two’s Complement if you are interested. I’ll see if I can update the esp script this week if I have time.

@bline79

I have H5072s (as well as H5051s). Just got it working right now. Should note is late here and I did about 20 mins of verification so far. The manufacture data you care about is EC88.

     - mac_address: XX:XX:XX:XX:XX:XX
       manufacturer_id: 'EC88'

However you can use the built in function to give you back manufacture data and parse by length if you want to go that route as well. Either way will work, and using the UUID is probably more correct. Here is a working snippet you can paste in below

    - mac_address: XX:XX:XX:XX:XX:XX
      then:
        - lambda: |-
            for (auto data : x.get_manufacturer_datas()) {
              if(data.data.size()==6) {
                const int basenum = (int16_t(data.data[1]) << 16) + (int16_t(data.data[2]) << 8) + int16_t(data.data[3]);
                const float temperature = (basenum / 10000.0f)*9.0/5.0 + 32.0;
                const float humidity = (basenum % 1000) / 10.0f;
                const float battery_level = uint16_t(data.data[4]) / 1.0f;
                int16_t rssi=x.get_rssi();
                id(govee_h5072_7_humidity).publish_state(humidity);
                id(govee_h5072_7_temperature).publish_state(temperature);
                id(govee_h5072_7_rssi).publish_state(rssi);
                id(govee_h5072_7_battery).publish_state(battery_level);
              } 
            }
1 Like

@WhichWayWazzit - wanted to ping you with the same question - does this method for the H5101 work for negative numbers temperatures?