Govee WiFi Water Sensor

Thanks to everyone here for keeping this thread alive, especially @smoysauce :+1:. I finally got my RF Bridge, and the time to work on this. I have flashed tasmota and portisch (not sure if porisch was necessary for these signals, but might as well add it), and I can pickup the leak codes! Just like I always wanted; I’m so happy to finally unplug the bridge!

Seems like we’re still hunting some stray codes for battery, etc. I’ll join the search.

This is a very exhaustive post. I am also seeing a rogue MQTT message with the code ending in FD like this. I have redacted the first 4 digits

05:20:20 MQT: tele/sonoffrfbridge/RESULT = {"Time":"2021-01-10T05:20:20","RfReceived":{"Sync":8780,"Low":320,"High":840,"Data":"xxxxFD","RfKey":"None"}}

I’ll join the party!

I also saw someone achieve the binary sensor via MQTT and yaml. I did the same in Node Red and this is my screenshot

The inside of the switch node looks like this

Here is what I’ve seen so far. I haven’t seen (or looked for) for rogue “FD” payloads but I have seen FD as part of a button push.

Is anyone else seeing the pattern with button push, like this? I need to do a test for what is sent when a battery is inserted.

Does anyone know of a way to safe the tasmota console to a log or log server? For a period of time? That would help determine any keep alive frequency signals sent out by the sensors

Any idea of the frequency of the rogue messages?

Hmm I guess you could log mqtt messages like this: MQTT - Home Assistant. I might try this

For anyone that wants to use rtl_433 (a command line utility that works with RTL-SDR USB dongles that cost around $20), I’ve figured out how to decode the signals sent from these Govee leak sensors and just posted a comment at https://github.com/merbanan/rtl_433/issues/1518#issuecomment-770428323 with my findings. The values received by rtl_433 are completely different from the values reported here for the Sonoff RF bridge. I did figure out the different battery levels, but I don’t have a voltage regulator so I can’t test what signal is transmitted when the battery level gets too low.

I’m using the following command on a Windows computer with the RTL-SDR dongle to run rtl_433 and send the messages received to my MQTT server (change 1.1.1.1 to the IP address of your MQTT server):

rtl_433 -R 0 -X "n=Govee_H5054,m=OOK_PWM,s=440,l=940,g=900,r=9000,bits=48,unique" -F "mqtt://1.1.1.1,events=Govee"

Below is what I have in my Home Assistant config.yaml file. Change #### to the ID for your particular sensor. The “[:9]” part causes Home Assistant to only look at the first 9 characters of the received message.

binary_sensor:
  - platform: mqtt
    state_topic: "Govee"
    name: "Leak sensor"
    value_template: "{{ value_json.data[:9] }}"
    payload_on: "####04040"
    force_update: true
    expire_after: 60 # Expire after 1 minute

At a cost of $8-$11 per sensor (depending on quantity purchased) and about $20 for a RTL-SDR dongle, this is a fantastic and fantastically cheap solution for detecting water leaks. A bundle consisting of the Govee Wi-Fi gateway and 3 sensors sells for $50 on Amazon. But for $4 more, you can instead buy 3 sensors and a RTL-SDR dongle that can be directly connected to a Raspberry Pi running HassIO and you can use the rtl_433 add-on for Home Assistant available at https://github.com/james-fry/hassio-addons/tree/master/rtl4332mqtt). (NOTE: I have rtl_433 running on a separate computer; I am not 100% sure that this custom “flex decoder” will work with the rtl_433 plug-in for Home Assistant.) I’ll also add that the battery life on these sensors is incredible. I’ve had mine for over a year and the included no-name batteries are still at 4 bars (and that’s even after all the testing I did to reverse-engineer the protocol).

3 Likes

thanks for this, I am very new to HA and have an RTL SDR but do not know how to install the RTL_433 add on. I am running HA on a raspberry PI where I would like to connect the dongle to. Do you know of any step by step instructions how to install RTL_433?

@mcsquared88 hi – thank you for sharing, this is incredibly helpful and I was able to implement.

It does seem like the sensors goto ‘sleep’ and appear unavailable (although they wake up when button is pushed or detect moisture).

Is there a way you’ve found to keep them to appear to be in “off” mode when they’re asleep? Also, would you mind sharing how you’re pulling in the battery levels? I saw on github you were able to track that as well. Thanks to your work, looks like its now being implemented directly in rtl_433, very cool.

Thanks, my config below – even with payload_not_available and payload_off it still shows up as unavailable after a bit.

  - platform: mqtt
    state_topic: "Govee"
    name: "Basement Utility Leak Sensor"
    device_class: moisture
    value_template: "{{ value_json.data[:9] }}"
    payload_on: "XXXX04040" 
    payload_not_available: "off"
    payload_off: "off"
    force_update: true
    expire_after: 60 # Expire after 1 minute
    off_delay: 60 # Off after 1 minute

Normally, it can be hours or even days between when the Govee sensors send any sort of signal. (I believe there’s a heartbeat signal, but I haven’t yet figured out how often it’s sent.) But in the configuration you shared, the “expire_after” line makes the sensor go offline after 60 seconds. That’s why your sensors keep appearing as offline. As I don’t yet know how often the sensors send their heartbeat signal, I can’t advise on what you should set the expire_after value to.

Here’s what I currently have in my configuration.yaml file. I don’t use the expire_after value at all, so the sensor doesn’t show as offline. But I’m not yet monitoring battery levels, so if a battery dies or there’s suddenly too much interference and the rtl-sdr can no longer receive a signal from one of my sensors, I won’t know about it.

binary_sensor:
# Govee leak detectors
  - platform: mqtt
    name: "Leak sensor - Upstairs toilet"
    state_topic: "Govee/2352"
    json_attributes_topic: "Govee/2352"
    value_template: "{{ value_json.data[4:8] }}"
    payload_on: "0404"
    payload_off: "0505"

I’ve configured this as someone previously suggested: A leak event changes the state to “on”, and a button press changes the state to “off”. To get battery levels, you’d have to define another sensor with different payload values. Or you could define a regular (non-binary) sensor with multiple values, including the different battery levels.

I’m running my rtl-sdr on a Windows box and I didn’t want to bother setting up a development environment to build a Windows binary for rtl_433 with the new Govee sensor code included. Once I figure out the heartbeat interval and there’s a Windows binary of rtl_433 with the Govee sensor code included, I’ll figure out the best way to implement things in HA and I’ll post an updated example configuration.yaml that will (correctly) show the sensor as offline if the heartbeat signal isn’t received in the expected interval and also (hopefully) implements the battery values in a way that can be used in scripts and automations to alert you when a sensor has a low battery.

@mcsquared88 very helpful, thank you. I see the formatting is a bit different, did you change the command line (below) for running rtf_433 in order to separate into different ID’s? Appreciate your work on this, its very cool and cost effective method.

Ahh yes. Apologies. The command line I’m using has “events=Govee[/id]” rather than “events=Govee”. That splits each sensor into a separate MQTT sub-topic based on the device’s ID. I figured that would be necessary since the battery messages are separate from the other events, but I never got around to implementing the battery stuff in HA.

After testing these for awhile, I have decided to go all in on them.

They work really well, and with our tweaks to rtl_433 (hopefully they accept our patch!), this an extremely viable and reasonably priced solution.

So far I have brought in the modified rtl_433 into my VM running HA, using the rtl_433 hass addon:

The config I currently use for it:

output      mqtt://192.168.1.20:1883,user=abc,pass=xyz
protocol    180
convert     si
report_meta newmodel

I am still working on adding in the sensors, but for example, here is one:

  - platform: mqtt
    name: "Water Heater Leak Sensor State"
    unique_id: sensor.water_sensor_38645_state
    state_topic: "rtl_433/+/devices/Water-H5054/38645/event"
    expire_after: 10

And a trivial alarm/automation:

- alias: Water Heater Leak Sensor 
  trigger:
    - platform: state
      entity_id: sensor.water_heater_leak_sensor_state
      to: "Water Leak"
  action:
    - service: notify.mobile_app_pixel_3a_xl
      data:
        message: "{{ states('sensor.time') }}: {{ trigger.to_state.attributes.friendly_name }}: Water Leak!"

With this set up so far, when I put my fingers on the top of the water sensor (to trigger it), I get an alert on my phone within 1 second.

As @seasideCT mentioned above, the one thing I haven’t figured out, and I don’t like, is the “unavailable” value that gets set.

I use the expire_after because otherwise we will see a “Water Leak” value remain forever, (well, until a heartbeat/button press) which I don’t like either.

It is too bad we can’t change the “unavailable” text to something like “clear” instead.

I am not a HA guru at all, so I am still trying to learn how best to resolve this issue.

@ScottK1

I was able to get the ‘unavailable’ message removed by doing the following:

  1. Recompiled rtl_433 using this repository, where the Govee water sensors were added as a device
    https://github.com/skilau/rtl_433 … eventually I assume it’ll be incorporated into the main branch.

  2. Run rtl_433 as follows:
    rtl_433 -R 55 -R 180 -F “mqtt://192.168.1.4:1883,user=XXX,pass=XXX,retain=0,devices=rtl_433[/id]”

In the complied rtl_433 binary, the Govee sensors are device #180

  1. Setup sensor as follows:
binary_sensor:
  - platform: mqtt
    state_topic: "rtl_433/XXXX/event"
    name: "Basement Utility Leak Sensor"
    device_class: moisture
    payload_on: "Water Leak"
    payload_off: "Button Press"

It’s a little cleaner solution for me, as I have other rtl_433 temperature sensors in the house. The water leak sensors are dry until they detect a leak, I reset them manually by pushing the button.

I hope that’s helpful.

1 Like

Anybody running this in a hass addon with more than one protocol? I’m currently runing the darwindata fork of the old james fry addon. Based on what I learned from the discussions so far, I hacked together the following adjustment to the rtl2mqtt.sh file for the addon to read my acurite sensors and the govee leak sensors.

/usr/local/bin/rtl_433 -R 40 -X "n=Govee,m=OOK_PWM,s=440,l=940,g=900,r=9000,bits=48,unique" -F json -F "mqtt://192.168.1.13:1883,user=user,pass=pass,retain=0,devices=rtl433[/id]" | while read line

It works and it preserves my other acurite sensors with the -R 40 (they are duplicated though in mqtt due to the custom -X decoder), but every button push or water detection comes through on the same mqtt topic of rtl433/govee/null, like this button push on sensor:

null = {“time” : “2021-04-30 15:34:12”, “model” : “Govee”, “count” : 9, “num_rows” : 21, “len” : 48, “data” : “92fc05050740”}

A water detection comes through as a similar data code, but instead of 0505 (button) it is 0404. I’m hoping there is a better way than my primitive hack. I can’t seem to get a binary sensor working on the null ID, so at this point all I can do is see the events in mqtt.

I’m certain I’m doing many things wrong.

Update: I found a way to pull the sensor trip into home assistant with this binary sensor. I’m still stuck with null as the device ID in mqtt but I am able to trip the sensor and reset it with the button. Replace #### with the first 4 characters from the data code the sensor sends.

binary_sensor:
platform: mqtt
state_topic: “rtl433/Govee/+”
name: “Leak sensor 1”
value_template: “{{ value_json.data[:8] }}”
payload_on: “####0404
payload_off: “####0505

1 Like

I just started out with Home Assistant and this was one of my first real projects to get set up. Everyone’s information here and a few other places were invaluable in getting this working. However, it seemed a bit piecemeal where I was grabbing bits of information from different comments and pages trying to put it all together. So hopefully I can assemble it a little bit for someone else like me who’s not as familiar with the individual parts of getting something like this running.

My setup has Home Assistant and all of the relevant services running in docker-compose, so I don’t have access to any of the add-ons. I picked up the Nooelec NESDR Mini 2+ RTL-SDR dongle and 10 of the Govee water sensors.

My first hurdle was getting rtl_433 running so I could receive the messages from the devices. As I mentioned, I have no access to add-ons which seems to be where most suggestions lead to. Exploring that and a few others that seemed like they added some unnecessary complexity for docker, I discovered GitHub - hertzg/rtl_433_docker: Repository containing multiarch docker images definitions of rtl_4 which just builds and runs the binary with no special logic or fluff, since I found out rtl_433 already supports MQTT. The doc suggests using the specific usb bus but that can change, so I just gave the whole thing.

#docker-compose

  rtl-433-mqtt:
    image: hertzg/rtl_433
    restart: unless-stopped
    volumes:
      - /host/path/to/rtl-433-mqtt:/etc/rtl_433
    devices:
      - /dev/bus/usb:/dev/bus/usb
    environment:
      - TZ=America/New_York

Note: as of writing, the latest image is missing a library. I’m using tag alpine-3.13-21.05 until it’s fixed.

#rtl_433.conf

#config_file
config_file /etc/rtl_433/govee_rm433.conf

report_meta level
report_meta noise
report_meta stats
report_meta time:iso:usec:utc:tz
report_meta protocol

output mqtt://mosquitto:1883,user=USER,pass=PASS,retain=1,events=rtl_433[/model][/id][/subtype]

convert si

The important parts are the link to the second config file and the output, which defines the MQTT broker and the topic. Subtype is one of the few variables supported for value expansion for the topic, and it’s what I use for grouping the messages as one of sensor, battery, or heartbeat.

#govee_rm433.conf

decoder {
    name=Govee_H5054,
    modulation=OOK_PWM,
    short=440,
    long=940,
    reset=9000,
    gap=900,
    bits=48,
    get=id:@0:{16},
    get=subtype:@16:{16}:[1285:sensor 1028:sensor        1022:battery 999:battery    997:battery    950:battery    956:battery    964:battery    923:battery 514:heartbeat],
    get=event:@16:{16}:[  1285:button 1028:leak_detected 1022:empty   999:25_percent 997:50_percent 950:75_percent 956:75_percent 964:75_percent 923:full    514:heartbeat],
    unique
}

I got this decoder from this comment on the rtl_433 github, but modified it so that I could use retain and have the values saved individually on separate topics. So my entities’ values would survive restart.

Now on the home assistant side I made two entities for each of my physical sensors, a binary_sensor and a battery sensor.

#binary_sensors.yaml

- platform: mqtt
  name: "Water Heater Water Sensor"
  state_topic: "rtl_433/Govee_H5054/ID/sensor"
  unique_id: "Govee_H5054_ID"
  icon: mdi:water
  payload_on: "leak_detected"
  payload_off: "button"
  value_template: "{{ value_json.event }}"
  json_attributes_topic: "rtl_433/Govee_H5054/ID/+"
  json_attributes_template: >
    {
      "id": "{{ value_json.id }}",
      "model": "{{ value_json.model }}",
      {%- if value_json.subtype == "heartbeat" %}
        "heartbeat": "{{ as_timestamp(value_json.time) | timestamp_custom("%Y-%m-%d %H:%M:%S.%f") }}",
      {%- endif %}
      "time": "{{ as_timestamp(value_json.time) | timestamp_custom("%Y-%m-%d %H:%M:%S.%f") }}"
    }

Like others, I use the button to turn it off instead of a timeout. I want to make sure someone goes and checks on it. I like having the id and model as attributes for if I need to reference things in the future. Also, I plan on making an automation to send a notification to check on the device if time is older than a month. I haven’t seen any heartbeat messages so I probably won’t be able to rely on that.

#sensors.yaml

- platform: mqtt
  name: "Water Heater Water Sensor Battery"
  state_topic: "rtl_433/Govee_H5054/ID/battery"
  unique_id: "Govee_H5054_ID"
  device_class: battery
  unit_of_measurement: '%'
  value_template: >
      {%- if value_json.event == "full" -%}
        100
      {%- elif value_json.event == "75_percent" -%}
        75
      {%- elif value_json.event == "50_percent" -%}
        50
      {%- elif value_json.event == "25_percent" -%}
        25
      {%- elif value_json.event == "empty" -%}
        0
      {%- endif %}
  json_attributes_topic: "rtl_433/Govee_H5054/ID/+"
  json_attributes_template: >
    {
      "id": "{{ value_json.id }}",
      "model": "{{ value_json.model }}",
      {%- if value_json.subtype == "heartbeat" %}
        "heartbeat": "{{ as_timestamp(value_json.time) | timestamp_custom("%Y-%m-%d %H:%M:%S.%f") }}",
      {%- endif %}
      "time": "{{ as_timestamp(value_json.time) | timestamp_custom("%Y-%m-%d %H:%M:%S.%f") }}"
    }

image

Then I set up a simple automation to watch for any of them turning on.

#automation

alias: Water Sensor Alarm
description: ''
trigger:
  - platform: state
    entity_id:
      - binary_sensor.water_shutoff_water_sensor
      - binary_sensor.water_heater_water_sensor
    to: 'on'
    from: 'off'
condition: []
action:
  - service: script.emergency_alarm
    data:
      title: Water Detected
      message: >-
        Water was detected by the {{ state_attr(trigger.entity_id,
        'friendly_name') }}
mode: single

This calls a script I set up for any emergency messages, which sends TTS and an alarm to my wife and I.

#script

alias: Emergency Alarm
description: Send an emergency alert notification to all devices and TTS the message
mode: queued
max: 10
icon: mdi:alert
fields:
  title:
    description: Title of the notification
    example: Alarm!
  message:
    description: The message content
    example: The house is on fire!
sequence:
  - service: notify.all_devices
    data:
      title: '{{ message }}'
      message: TTS
      data:
        ttl: 0
        priority: high
        channel: alarm_stream_max
  - service: notify.all_devices
    data:
      title: '{{ title }}'
      message: '{{ message }}'
      data:
        ttl: 0
        priority: high
        channel: alarm_stream_max

I don’t know if that was all overly verbose, but hopefully it saves someone else a bit of time.

1 Like

Thanks to CodingSquirrel’s detective work, I’ve managed to get the rtl_433 hassio addon by pbkhrv working beautifully with minimal configuration and easy access to multiple (or all) sdr protocols. Just use the cofiguration in the post above me, and save the config to config/rtl_433/rtl_433.conf. I was unable to get any battery readings. Maybe they are infrequent?

Modify the govee sensor line in rtl_433.conf to read the following, and also save govee_433.conf to this location:

config_file /config/rtl_433/govee_433.conf

To avoid logging tire pressure readings and other random nonsense from my neighborhood, I also modify rtl_433.conf to limit the protocols it logs to acurite (protocol 40) and govee (via the custom file above) by adding this to the config file. You can add others by repeating the same thing multiple times.

protocol 40

For my leak notification, I use the following:

- id: leak
  alias: WATER Leak!
  trigger:
    platform: state
    entity_id: binary_sensor.leak_guest_bedroom, binary_sensor.leak_kitchen_sink, binary_sensor.leak_dishwasher, binary_sensor.leak_master_bath
    to: 'on'
  action:
    service: notify.mobile_app_pixel
    data_template:
      message: '{{ trigger.to_state.attributes.friendly_name }} detected water'

Hey all.

I picked up a few of the Govee sensors and base station.
I was reading the above chat but didnt see where anyone was modding / flashing the Govee hub.

From what I can tell it uses a ESP8266EX with a CMT2210LB for the RF decoding. It looks like the demodulated data from the CMT chip is routed to pin 10 of the ESP8266EX.

Deos anyone know what I would need to get this working?

Note the Serial lines are easily accessible for programming with esphome.

Battery readings are sent when you put batteries in and when the state changes. The easiest way to get one is to remove and put back in one of the batteries. Conveniently, this means it’s sent when you first remove the pull tab. So if you have retain set to true then you don’t even have to open it up when setting up a new one (assuming you have rtl_433 configured and running first).