Skip to content

Commit b30b865

Browse files
authored
Merge pull request #23 from markaggar/feature/shutoff-valve-docs
Feature/shutoff valve docs
2 parents 2c69d33 + 2282466 commit b30b865

File tree

8 files changed

+574
-45
lines changed

8 files changed

+574
-45
lines changed

README.md

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Water Monitor
44

55
[![Open in HACS](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=markaggar&repository=Water-Monitor&category=integration)
6-
> You must download/copy the integration first (via HACS or manual copy) and restart Home Assistant before you can install the integration from the Settings/Devices and Services
6+
> You must download/copy the integration first (via HACS or manual copy) and restart Home Assistant before you can install the integration from the Devices and Services page under Settings.
77
88
A Home Assistant custom integration for water usage monitoring that provides session tracking, gap handling, hot water analytics, and optional leak detection. Only a Flow sensor is required; a Volume sensor is optional. If you do supply a Volume sensor, Water Monitor will use it directly (ideal if you want volumes to align with the Energy dashboard). Supports multiple instances (works with electricity too!) and full reconfiguration of sensor names and threshold values via the UI.
99

@@ -26,6 +26,10 @@ A Home Assistant custom integration for water usage monitoring that provides ses
2626
- Detects a continuous low-flow “dribble” with seed/persistence timers
2727
- Optional tank refill leak detector
2828
- Detects repeated, similar-sized refills clustered in time (typical symptom of a leaky toilet flapper)
29+
- Shutoff valve support
30+
- Optionally link a shutoff valve entity (switch, input_boolean, or valve)
31+
- Per-detector auto-shutoff toggles: auto-shutoff can be enabled for each leak detector
32+
- Leak sensors will not clear while the valve is off, ensuring you don't miss a leak event
2933
- Upstream sensors health (binary sensor)
3034
- Monitors availability/validity of the configured upstream sensors (flow, volume, and optional hot-water)
3135
- Reconfigurable via Options
@@ -34,7 +38,23 @@ A Home Assistant custom integration for water usage monitoring that provides ses
3438
- Multi-instance safe
3539
- Add multiple instances with different sensors and thresholds
3640
- Synthetic flow testing support
37-
- Optional integration-owned number to inject synthetic GPM for testing (no need to waste actual water).
41+
- Optional integration-owned number to inject synthetic GPM for testing (no need to waste actual water)
42+
43+
## Devices
44+
Here is a list of devices that the community has tested with the integration (submit an issue to add your experience with a device)
45+
46+
| Device | Manufacturer | Works with Integration | Flow Sensor | Volume Sensor | Shutoff Valve | Local API | Flow/Volume Sensor Latency | Plumbing Required | Link |
47+
|--------|--------------|------------------------|-------------|---------------|---------------|-----------|----------------------------|-------------------|------|
48+
| Droplet | Hydrific Water | Y (Flow) | Y | N | N | Y (MQTT) | <3s | N | [link](https://shop.hydrificwater.com/pages/buy-droplet) |
49+
| Flowsmart All-in-one | Yolink | Y (Valve) | N | Y | Y | N | minutes | Y | [link](https://shop.yosmart.com/products/ys5008-20) |
50+
| Titan Water Valve Actuator | Zooz | Y (Valve) | N | N | Y| Y (Zwave) | NA | N | [link](https://amzn.to/4mPD3x8) |
51+
52+
## DISCLAIMER ##
53+
A water flow monitor does not replace the need for leak/moisture sensors placed in strategic locations around your home. If a leak is due to a failure of an appliance (e.g. leaky hose under the sink that only occurs when the faucet is turned on or a sudden failure of a rusty water heater, washing machine, toilet o-ring), water infiltration from outside, or a blocked sewer pipe (speaking from experience), a water flow sensor (and this integration) will not detect those events. It is best suited for wasted water scenarios (e.g. faucet left on, toilet flapper not sealing) or burst pipes (e.g. outside hoses, pipes behind walls) where you cannot practically place a leak/moisture sensor (again, experienced all of those!).
54+
55+
Also, having a controllable valve that enables you or this integration to remotely shut off water to the house in the event of a leak detection (from either this integration or a leak/moisture sensor) could pay for itself many times over if you ever have a leak detected but are not at home to turn the water off manually.
56+
57+
**Finally, your use of this integration means you agree that the author(s) of this integration bear no responsibility for leaks that are not detected or notified, due to any cause. It is important that you do your own testing, particularly ensuring that the parameters you set make sense for your situation, and that any shutoff valves work as expected**.
3858

3959
## Devices
4060
Here is a list of devices that the community has tested with the integration (submit an issue to add your experience with a device)
@@ -71,7 +91,7 @@ Also, having a controllable valve that enables you or an automation to remotely
7191

7292
<img width="364" height="920" alt="image" src="https://github.com/user-attachments/assets/0c773851-d4fa-4782-8683-b673e0701524" />
7393

74-
Setup page (step 1)
94+
### Setup page
7595
- Sensor Name Prefix
7696
- Flow Rate Sensor (required)
7797
- Volume Sensor (optional; if provided, Water Monitor uses it as the source of truth. If omitted, Water Monitor computes volume from the flow sensor.)
@@ -84,10 +104,10 @@ Setup page (step 1)
84104
- Create Low-flow leak sensor (checkbox)
85105
- Create Tank refill leak sensor (checkbox)
86106
- Enable Intelligent Leak Detection (experimental) (checkbox)
107+
- Shutoff Valve Entity (optional)
87108

109+
### Low-flow leak
88110
If “Create Low-flow leak sensor” is checked, you’ll be presented with a second page:
89-
90-
Low-flow leak (step 2)
91111
- Max low-flow threshold (e.g., 0.5 GPM)
92112
- Seed low-flow duration (seconds)
93113
- Leak persistence required to trigger (seconds)
@@ -97,10 +117,10 @@ Low-flow leak (step 2)
97117
- Smoothing window (seconds)
98118
- Cooldown after clear (seconds)
99119
- Clear on sustained high flow (seconds; blank to disable)
120+
- Auto shutoff on trigger (per-detector)
100121

122+
### Tank refill leak
101123
If “Create tank refill leak sensor” is checked, you’ll be presented with a third page:
102-
103-
Tank refill leak (step 2)
104124
- Minimum refill volume (ignore refills smaller than this)
105125
- Maximum refill volume (ignore refills larger than this; 0 disables the cap)
106126
- Similarity tolerance (%) — how close in volume refills must be to count as “similar”
@@ -110,34 +130,38 @@ Tank refill leak (step 2)
110130
- Cooldown after clear (seconds) — optional suppression period before re-triggering
111131
- Minimum refill duration (seconds; 0 disables)
112132
- Maximum refill duration (seconds; 0 disables)
133+
- Auto shutoff on trigger (per-detector)
113134

135+
### Intelligent Leak Detection (experimental)
114136
If “Enable Intelligent Leak Detection” is checked, you’ll be presented with another page:
115-
116-
Intelligent Leak Detection (experimental)
117137
- Occupancy mode input_select (optional)
118138
- Away states (comma-separated, optional)
119139
- Vacation states (comma-separated, optional)
120140
- Enable learning mode (toggle)
141+
- Auto shutoff on trigger (per-detector)
121142

122143
Notes
123-
124144
- CSV fields accept multiple labels separated by commas, e.g. "On Vacation, Returning from Vacation".
125145
- Learning mode is intended for future automation-assisted tuning; you can toggle it via Options or automations.
126146

127-
Reconfiguration
147+
### Synthetic Flow Options
148+
If Enable Synthetic Flow (testing) is enabled, you'll be presented with another page:
149+
- Include synthetic flow in detectors - allow detectors to see synthetic flow
150+
- Include synthetic flow in daily analysis - allow intelligent leak analysis to see synthetic flow
151+
152+
## Reconfiguration
128153

129154
- Open Settings → Devices & Services → Water Monitor → Configure.
130-
- The low-flow leak sensor is optional and can be enabled/disabled at any time:
131-
- Enabling creates the binary sensor.
132-
- Disabling removes the binary sensor on reload.
155+
- The leak sensors are optional and can be enabled/disabled at any time from the main setup page.
156+
- The shutoff valve and auto-shutoff toggles can be changed at any time via Options
133157

134-
Units
158+
## Units
135159

136160
- If a Volume sensor is configured, its unit determines display (gallons/liters) and ensures alignment with the Energy dashboard.
137161
- If no Volume sensor is configured, Water Monitor computes volume by integrating the Flow sensor (default method: Trapezoidal; alternative: Left to match external counters). Units are inferred from the Flow sensor (e.g., GPM → gal, L/min → L).
138162
- Flow sensor units are reflected in the low-flow leak sensor attributes and used for last-session average flow when possible.
139163
- Average flow sensor displays as <volume_unit>/min derived from the volume unit.
140-
164+
141165
## Sensors created
142166

143167
- Last session volume (sensor)
@@ -276,7 +300,20 @@ Notes
276300
- Clearing: when no similar refills occur for the configured idle period; optional cooldown prevents immediate re-triggering.
277301
- Guards: refills below Minimum/shorter than Min duration or above Maximum/longer than Max duration (if set) are ignored to avoid false positives from noise or large draws unrelated to tank refills.
278302

279-
Tuning tips
303+
### Shutoff Valve and Auto-Shutoff Details
304+
305+
- The shutoff valve can be any Home Assistant entity that supports on/off (switch, input_boolean, or valve)
306+
- Each leak detector (low-flow, tank refill) can have auto-shutoff enabled or disabled independently
307+
- When a leak is detected and auto-shutoff is enabled, the valve is turned off automatically
308+
- While the valve is off, leak sensors will not clear, ensuring you don't miss a leak event
309+
- Synthetic flow is automatically zeroed when the valve is off
310+
- The Upstream Health sensor will show the valve as unavailable if it cannot be reached
311+
- Leak sensor attributes:
312+
- **auto_shutoff_on_trigger**: True if auto-shutoff is enabled for this detector
313+
- **auto_shutoff_effective**: True if auto-shutoff is enabled and a valid valve is configured and available
314+
- **valve_off**: True if the valve is currently off
315+
316+
### Tuning tips
280317

281318
- Minimum refill volume: set just below your typical toilet refill to ignore tiny noise.
282319
- Maximum refill volume: set just above a toilet refill to ignore showers/sprinklers; set 0 to disable the cap.

custom_components/water_monitor/__init__.py

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
from homeassistant.helpers import config_validation as cv
55
from homeassistant.core import HomeAssistant, ServiceCall
66
from homeassistant.helpers import device_registry as dr
7+
from homeassistant.helpers.event import async_track_state_change_event
8+
from homeassistant.helpers import entity_registry as er
79

8-
from .const import DOMAIN, CONF_SENSOR_PREFIX
10+
from .const import DOMAIN, CONF_SENSOR_PREFIX, CONF_WATER_SHUTOFF_ENTITY, tracker_signal
11+
import logging
12+
_LOGGER = logging.getLogger(__name__)
913
from .engine import WaterMonitorEngine # new engine
1014

1115
# Platforms provided by this integration
@@ -103,9 +107,83 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
103107
# Create and start engine
104108
domain_data = hass.data.setdefault(DOMAIN, {})
105109
engine = WaterMonitorEngine(hass, entry.entry_id, ex)
106-
domain_data[entry.entry_id] = {"engine": engine, "synthetic_flow_gpm": 0.0}
110+
domain_data[entry.entry_id] = {
111+
"engine": engine,
112+
"synthetic_flow_gpm": 0.0,
113+
"valve_entity_id": ex.get(CONF_WATER_SHUTOFF_ENTITY) or "",
114+
"valve_off": False,
115+
"_unsub_valve": None,
116+
}
107117
await engine.start()
108118

119+
# Track optional water shutoff valve state and react to changes
120+
async def _eval_valve_state(entity_id: str | None) -> None:
121+
try:
122+
data = hass.data.get(DOMAIN, {}).get(entry.entry_id)
123+
if not isinstance(data, dict):
124+
_LOGGER.warning("[valve] No domain data for entry %s", entry.entry_id)
125+
return
126+
valve = data.get("valve_entity_id")
127+
if not valve:
128+
data["valve_off"] = False
129+
_LOGGER.info("[valve] No valve entity configured for entry %s", entry.entry_id)
130+
return
131+
st = hass.states.get(valve)
132+
off = False
133+
if st and st.state not in (None, "unknown", "unavailable"):
134+
dom = valve.split(".")[0]
135+
sval = str(st.state).lower()
136+
_LOGGER.info("[valve] Entity %s state: %s (domain: %s)", valve, sval, dom)
137+
if dom == "valve":
138+
off = sval == "closed"
139+
elif dom in ("switch", "input_boolean"):
140+
off = sval == "off"
141+
else:
142+
_LOGGER.info("[valve] Entity %s is unavailable or unknown", valve)
143+
data["valve_off"] = bool(off)
144+
_LOGGER.info("[valve] Set valve_off=%s for entry %s", bool(off), entry.entry_id)
145+
# Always fire tracker signal so all leak sensors re-evaluate immediately
146+
try:
147+
hass.helpers.dispatcher.async_dispatcher_send(tracker_signal(entry.entry_id), {})
148+
except Exception as e:
149+
_LOGGER.error("[valve] Error firing tracker signal: %s", e)
150+
if off:
151+
# Force synthetic flow number to 0 if present (by unique_id and by explicit entity_id)
152+
try:
153+
ent_reg = er.async_get(hass)
154+
target_uid = f"{entry.entry_id}_synthetic_flow_gpm"
155+
ent = next((e for e in ent_reg.entities.values() if e.platform == DOMAIN and e.unique_id == target_uid), None)
156+
if ent is not None:
157+
await hass.services.async_call(
158+
"number", "set_value", {"entity_id": ent.entity_id, "value": 0}, blocking=False
159+
)
160+
except Exception:
161+
pass
162+
# Always set the explicit entity_id for synth flow if present, using the integration's prefix
163+
try:
164+
prefix = (entry.options.get("sensor_prefix") or entry.data.get("sensor_prefix") or entry.title or "water_monitor").lower().replace(" ", "_")
165+
entity_id = f"number.{prefix}_synth_synthetic_flow_gpm"
166+
await hass.services.async_call(
167+
"number", "set_value", {"entity_id": entity_id, "value": 0}, blocking=False
168+
)
169+
except Exception:
170+
pass
171+
except Exception:
172+
pass
173+
174+
# Subscribe to valve state changes if configured
175+
dd = domain_data.get(entry.entry_id)
176+
valve_ent = dd.get("valve_entity_id") if isinstance(dd, dict) else None
177+
if valve_ent:
178+
async def _on_valve_event(event):
179+
await _eval_valve_state(valve_ent)
180+
try:
181+
dd["_unsub_valve"] = async_track_state_change_event(hass, [valve_ent], _on_valve_event)
182+
except Exception:
183+
dd["_unsub_valve"] = None
184+
# Evaluate once at startup
185+
await _eval_valve_state(valve_ent)
186+
109187
# Register a one-time service to trigger daily analysis on demand
110188
# Useful for testing without waiting until the scheduled time.
111189
if not domain_data.get("services_registered"):
@@ -170,6 +248,13 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
170248
data = domain_data.pop(entry.entry_id, None)
171249
if data and data.get("engine"):
172250
await data["engine"].stop()
251+
# Unsubscribe valve listener
252+
try:
253+
unsub = data.get("_unsub_valve") if isinstance(data, dict) else None
254+
if unsub:
255+
unsub()
256+
except Exception:
257+
pass
173258
# If this was the last engine, remove the on-demand service
174259
any_engines_left = any(
175260
isinstance(v, dict) and v.get("engine") is not None for v in domain_data.values()

0 commit comments

Comments
 (0)