Retaining specific states from MQTT sensors across Home Assistant restarts using retained messages
Introduction
One of the great things about Home Assistant is its ability to integrate data from virtually anywhere. This flexibility allows you to choose the best sensor for each use case, regardless of the protocol it uses. From Zigbee and Z-Wave to MQTT and beyond, Home Assistant makes it possible to unify your smart home ecosystem seamlessly.
For example, in my smart home setup, I use Govee H5054 Leak Detectors for water leak detection. These sensors are excellent because they emit a loud audible alarm, are relatively inexpensive (~$10 on sale), have great battery life (1+ years) and of course, communicate with Home Assistant. Although not advertised, these sensors operate over the 433 MHz frequency, which I capture using a Nooelec's RTL-SDR software-defined radio (SDR).
Capturing 433Mhz sensor readings and landing them into Home Assistant
To process the 433 MHz signals, I run the outstanding rtl_433 project on a Raspberry Pi. This tool collects data from the SDR and forwards it to my MQTT broker (I use EMQX, though Mosquitto is another popular option). From there, Home Assistant subscribes to the MQTT topics using an YAML-defined MQTT sensor and updates the states of my entities accordingly.
The challenge
However, integrating these sensors into Home Assistant presents a challenge: the default behavior of MQTT. Since MQTT is a publish/subscribe protocol, Home Assistant only updates a sensor’s state when a new message is published while it is subscribed to the topic. After a restart, the sensor’s state defaults to ‘Unknown’ until the next reading is emitted by the Govee. For sensors like the H5054, which only send battery reports infrequently (triggered by a significant change, like a 5% drop), this means the state could remain ‘Unknown’ for months. This leaves a gap in reporting until the next reading comes in, only to create another gap starting the next Home Assistant restart or MQTT sensor reload.
Having all rtl_433 messages come in as retained creates other issues
One option is to configure rtl_433 to publish all MQTT messages with the retain
flag set to true
. This means that the most recent message for each topic is always available to new MQTT clients, including Home Assistant after a restart. However, this approach has a significant downside. If you have 433 MHz remotes or buttons, their ‘pressed’ states will also be retained, potentially triggering automations upon every Home Assistant restart. This is clearly not ideal. It also means you retaining all sorts of things that have no value (such as timestamps, randomly generated message IDs, etc)
My approach: selectively retaining MQTT messages by republishing them with HA
To solve this, I have an automation to selectively retain MQTT messages for specific sensor readings, such as battery levels and 'no leak' readings, while avoiding retained messages for other events like leak alerts (or button presses on other sensors that also come in via rtl_433)
Here’s how it works:
Battery level entity state retention
For battery levels, I created a Home Assistant automation that listens to the topic where the sensor publishes its battery reports. When a matching message is received, the automation extracts the battery level and republishes it to the same topic with the retain
flag set to true
. This ensures that Home Assistant always has the last known battery level available, even after a restart.
alias: Retain Govee water battery level message
description: Republish Govee Water battery level message back to MQTT with retain flag
triggers:
- topic: rtl_433/Govee-Water/#
trigger: mqtt
payload: Battery Report
value_template: "{{ value_json.event }}"
conditions: []
actions:
- data:
topic: "{{ trigger.topic }}/battery_ok"
payload: |
{{ trigger.payload_json.battery_ok }}
retain: true
action: mqtt.publish
mode: single
'No leak' entity state retention
For water leak detection, the H5054 sensors emit ‘wet’ or 'not wet' readings. To ensure the default state remains ‘dry’ without retaining every message, I publish a retained wet_detect=0
message whenever a ‘dry’ reading is received. This way, Home Assistant assumes the default state is ‘not wet’ when it connects and only triggers leak alerts when the sensor publishes a wet_detect=1
message (indicating a leak is present).
alias: Retain Govee Water dry message
description: Republish Govee Water dry message back to MQTT with retain flag
triggers:
- topic: rtl_433/Govee-Water/#
trigger: mqtt
payload: 0
value_template: "{{ value_json.detect_wet }}"
- topic: rtl_433/Govee-Water/#
trigger: mqtt
payload: Button Press
value_template: "{{ value_json.event }}"
conditions: []
actions:
- data:
topic: "{{ trigger.topic }}/detect_wet"
payload: |
{{ trigger.payload_json.detect_wet }}
retain: true
action: mqtt.publish
mode: single
When a client (in this case, Home Assistant) connects to MQTT and subscribes to the topics, they receive the retained messages and can load initial states.
Conclusion
By selectively applying the retain
flag through Home Assistant automations, I’ve been able to create a setup that ensures reliable state retention for key sensors while avoiding the downsides of globally retained MQTT messages. This approach provides the best of both worlds: accurate state tracking across restarts and minimal risk of unintended automation triggers.
This method has significantly improved the reliability and usability of my smart home, and I hope it inspires others to tackle similar challenges in their setups. If you have other creative solutions or enhancements, feel free to share them!