Using the Govee Bluetooth Thermometer with Home Assistant (Python and MQTT) - Austin's Nerdy Things (2024)

Introduction

Like many other Home Automation enthusiasts, I have been on the lookout for a cheap thermometer/hygrometer that has either WiFi or Bluetooth connectivity. I think I found the answer in the $12 USD Govee Bluetooth Digital Thermometer and Hygrometer. The fact that the device broadcasts the current temperature and humidity every 2 seconds via low energy Bluetooth (BLE) is the metaphorical icing on the cake. Here is a pic of the unit in our garage:

Specifications

This is a pretty basic device. It has a screen that shows the current temperature, humidity, and min/max values. It sends the current readings (along with battery health) every 2 seconds via low-energy bluetooth (BLE). The description on Amazon says it has a “Swiss-made smart hygrometer sensor”. Dunno if I believe that but for $12 it is good enough. The temperature is accurate to +/- 0.54F and humidity is +/- 3% RH. If you use the app, it is apparently possible to read the last 20 days or 2 years of data from the device (I haven’t used the app at all).

Enough with the boring stuff. Let’s get it connected to Home Assistant.

Reading the Govee bluetooth advertisem*nts with Python

I found some sample code on tchen’s GitHub page (link) to help get me going in the right direction.

Without further ado, here is the code I’m using to read the data and publish via MQTT:

observe.py (updated 2022-01-04 with some logging improvements. default logging level is now WARNING, which disables printing every advertisem*nt)

# basic govee bluetooth data reading code for model https://amzn.to/3z14BIi# written/modified by Austin of austinsnerdythings.com 2021-12-27# original source: https://gist.github.com/tchen/65d6b29a20dd1ef01b210538143c0bf4import loggingimport jsonfrom time import sleepfrom basic_mqtt import basic_mqttfrom bleson import get_provider, Observerlogging.basicConfig(format='%(asctime)s.%(msecs)03d - %(name)s - %(levelname)s - %(message)s',datefmt='%Y-%m-%d %H:%M:%S',level=logging.WARNING)# I did write all the mqtt stuff. I kept it in a separate classmqtt = basic_mqtt()mqtt.connect()# writing code on my windows computer, committing, pushing, and then # pulling the new code on the Raspberry Pi is a tedious "debug" process.# there are quite a few errors that spit out from bleson, which as far# as I can tell, isn't super polished. if we set the debug level to# critical, most of those disappear.logging.getLogger("bleson").setLevel(logging.CRITICAL)# basic celsius to fahrenheit functiondef c2f(val): return round(32 + 9*val/5, 2)last = {}# I didn't write this, but it takes the raw BT data and spits out the data of interestdef temp_hum(values, battery, address): global last ######################### # # there is a fix for temperatures below freezing here - # https://github.com/joshgordon/govee_ble_to_mqtt/blob/master/observe.py # I will adjust post code afternoon of 2022-11-28 # ######################### values = int.from_bytes(values, 'big') if address not in last or last[address] != values: last[address] = values temp = float(values / 10000) hum = float((values % 1000) / 10) # this print looks like this: # 2021-12-27T11:22:17.040469 BDAddress('A4:C1:38:9F:1B:A9') Temp: 45.91 F Humidity: 25.8 % Battery: 100 % logging.info(f"decoded values: {address} Temp: {c2f(temp)} F Humidity: {hum} % Battery: {battery}") # this code originally just printed the data, but we need it to publish to mqtt. # added the return values to be used elsewhere return c2f(temp), hum, batterydef on_advertisem*nt(advertisem*nt): #print(advertisem*nt) mfg_data = advertisem*nt.mfg_data # there are lots of BLE advertisem*nts flying around. only look at ones that have mfg_data if mfg_data is not None: #print(advertisem*nt) # there are a few Govee models and the data is in different positions depending on which # the unit of interest isn't either of these hardcoded values, so they are skipped if advertisem*nt.name == 'GVH5177_9835': address = advertisem*nt.address temp_hum(mfg_data[4:7], mfg_data[7], address) elif advertisem*nt.name == 'GVH5075_391D': address = advertisem*nt.address temp_hum(mfg_data[3:6], mfg_data[6], address) elif advertisem*nt.name != None: # this is where all of the advertisem*nts for our unit of interest will be processed address = advertisem*nt.address if 'GVH' in advertisem*nt.name: #print(advertisem*nt) temp_f, hum, battery = temp_hum(mfg_data[3:6], mfg_data[6], address) if temp_f > 180.0 or temp_f < -30.0: return # as far as I can tell bleson doesn't have a string representation of the MAC address # address is of type BDAddress(). str(address) is BDAddress('A4:C1:38:9F:1B:A9') # substring 11:-2 is just the MAC address mac_addr = str(address)[11:-2] # construct dict with relevant info msg = {'temp_f':temp_f, 'hum':hum, 'batt':battery} # looks like this: # msg data: {'temp_f': 45.73, 'hum': 25.5, 'batt': 100} logging.info(f"MQTT msg data: {msg}") # turn into JSON for publishing json_string = json.dumps(msg, indent=4) # publish to topic separated by MAC address mqtt.publish(f"govee/{mac_addr}", json_string)# base stuff from the original gistlogging.warning(f"initializing bluetooth")adapter = get_provider().get_adapter()observer = Observer(adapter)observer.on_advertising_data = on_advertisem*ntlogging.warning(f"starting observer")observer.start()logging.warning(f"listening for events and publishing to MQTT")while True: # unsure about this loop and how much of a delay works sleep(1)observer.stop()

And for the MQTT helper class (mqtt_helper.py):

# basic MQTT helper class. really needed to write one of these to simplify basic MQTT operations# written/modified by Austin of austinsnerdythings.com 2021-12-27# original source: https://gist.github.com/fisherds/f302b253cf7a11c2a0d814acd424b9bb# filename is basic_mqtt.pyfrom paho.mqtt import client as mqtt_clientimport loggingimport datetimelogging.basicConfig(format='%(asctime)s.%(msecs)03d - %(name)s - %(levelname)s - %(message)s',datefmt='%Y-%m-%d %H:%M:%S',level=logging.INFO)mqtt_host = "mqtt.home.fluffnet.net"test_topic = "mqtt_test_topic"# this is really not polished. it was a stream of consciousness project to pound# something out to do basic MQTT publish stuff in a reusable fashion.class basic_mqtt:def __init__(self):self.client = mqtt_client.Client()self.subscription_topic_name = Noneself.publish_topic_name = Noneself.callback = Noneself.host = mqtt_hostdef connect(self):self.client.on_connect = self.on_connectself.client.on_subscribe = self.on_subscribeself.client.on_message = self.on_messagelogging.info(f"connecting to MQTT broker at {mqtt_host}")self.client.connect(host=mqtt_host,keepalive=30)self.client.loop_start()def on_connect(self, client, userdata, flags, rc):print("Connected with result code "+str(rc))# Subscribing in on_connect() means that if we lose the connection and# reconnect then subscriptions will be renewed.#self.client.subscribe("$SYS/#")def on_message(self, client, userdata, msg):print(f"got message of topic {msg.topic} with payload {msg.payload}")def on_subscribe(self, client, userdata, mid, granted_qos):print("Subscribed: " + str(mid) + " " + str(granted_qos))def publish(self, topic, msg):self.client.publish(topic=topic, payload=msg)def subscribe_to_test_topic(self, topic=test_topic):self.client.subscribe(topic)def send_test_message(self, topic=test_topic):self.publish(topic=topic, msg=f"test message from python script at {datetime.datetime.now()}")def disconnect(self):self.client.disconnect()def loop(self):self.client.loop_forever()if __name__ == "__main__":logging.info("running MQTT test")mqtt_helper = basic_mqtt()mqtt_helper.connect()mqtt_helper.subscribe_to_test_topic()mqtt_helper.send_test_message()mqtt_helper.disconnect()

Results

Running this script on a Raspberry Pi 3 shows the advertisem*nts coming in as expected. The updates are very quick compared to the usual 16 second update interval for my Acurite stuff.

And running mosquitto_sub with the right arguments (mosquitto_sub -h mqtt -v -t “govee/#”) shows the MQTT messages are being published as expected:

Getting Govee MQTT data into Home Assistant

Lastly, we need to add a MQTT sensor to get the data importing into Home Assistant:

- platform: mqtt state_topic: "govee/A4:C1:38:9F:1B:A9" value_template: "{{ value_json.temp_f }}" name: "garage temp" unit_of_measurement: "F"

And from there you can do whatever you want with the collected data!

Conclusion

This was a relatively quick post and code development. I really hate the cycle of developing on my Bluetooth-less Windows computer, committing the code, pushing to Git, pulling on the Pi, and running to “debug”. Thus, the code isn’t as good as it can be. I probably did 20-25 iterations before calling it good enough.

Regardless, I think this $12 Govee Bluetooth Thermometer and Hygrometer is a great little tool for collecting data around the house. You don’t need an SDR to get Acurite beacons, and you don’t need to spend a lot either. You just need a way to receive BLE advertisem*nts (basically any Bluetooth-capable device can do this). There is even a 2 pack of just the sensors that I just discovered on Amazon for $24 – 2 pack of Govee bluetooth thermometer and hygrometer. I know there are other Bluetooth devices but they’re quite a bit more expensive.

Disclosure: Some of the links on this post are Amazon affiliate links. This means that, at zero cost to you, I will earn an affiliate commission if you click through the link and finalize a purchase.

Also, at least for me, these are available with same day shipping on Amazon. Scratch that instant gratification itch.

Post Views: 23,001

Using the Govee Bluetooth Thermometer with Home Assistant (Python and MQTT) - Austin's Nerdy Things (2024)

References

Top Articles
Latest Posts
Article information

Author: Pres. Lawanda Wiegand

Last Updated:

Views: 5938

Rating: 4 / 5 (51 voted)

Reviews: 90% of readers found this page helpful

Author information

Name: Pres. Lawanda Wiegand

Birthday: 1993-01-10

Address: Suite 391 6963 Ullrich Shore, Bellefort, WI 01350-7893

Phone: +6806610432415

Job: Dynamic Manufacturing Assistant

Hobby: amateur radio, Taekwondo, Wood carving, Parkour, Skateboarding, Running, Rafting

Introduction: My name is Pres. Lawanda Wiegand, I am a inquisitive, helpful, glamorous, cheerful, open, clever, innocent person who loves writing and wants to share my knowledge and understanding with you.