A custom Home Assistant integration for the hai smart showerhead. It listens for the shower head's Bluetooth wake advertisements and reads shower data over a local GATT connection — no cloud, no app account.
The shower head sleeps between showers and only advertises while water is running. This integration treats those advertisements as wake signals:
- Water runs → the shower head advertises → Home Assistant wakes the integration.
- The integration connects over BLE (directly or through an ESPHome Bluetooth proxy) and reads a full snapshot roughly every 10 seconds while the shower is active.
- When the shower ends, the device goes back to sleep. Live values become unavailable; lifetime and last-shower values stay available from cache, including across Home Assistant restarts.
- Home Assistant 2026.7.2 or newer (the version this release is tested against).
- A Bluetooth adapter usable by Home Assistant, or an ESPHome Bluetooth proxy with active connections enabled, in range of the shower.
- A shower head that has been paired at least once with the official hai app so it runs current firmware. Factory firmware exposes a different GATT layout and is not supported; polls against it fail with a "missing required characteristics" error in the logs.
Firmware support is based on the GATT layout observed on app-updated firmware (protocol notes). Optional characteristics (flow rate, battery voltage, lifetime average temperature) are probed per device, and their entities appear only when the firmware actually exposes them.
- In HACS, open ⋮ → Custom repositories.
- Add
https://github.com/taylorfinnell/hai-homeassistantwith category Integration. - Install Hai Smart Shower Head and restart Home Assistant.
- Copy
custom_components/hai/from this repository into<config>/custom_components/hai/. - Restart Home Assistant.
There is no automatic upgrade path. v2 is a rewrite: entity unique IDs, units, and stored state all changed, and no migration is provided. Remove the old integration and add it again.
- In Settings → Devices & services, open the existing hai entry and delete it. This removes its device and all of its entities.
- Install v2 using one of the methods above and restart Home Assistant.
- Run the shower so the head advertises, then add the integration again (see First discovery below).
Recorded history for the v1 entities is not carried over, and any automations, scripts, or dashboard cards that referenced them need repointing at the new entities. Export anything you want to keep before deleting the integration.
Run the shower for a little while. The shower head only advertises with water running, so discovery can take a minute; once seen, Home Assistant shows a discovered hai device under Settings → Devices & services. You can also add it manually via Add integration → hai while the water is running.
| Entity | Unit | Kind | Notes |
|---|---|---|---|
| Current shower temperature | °C | Live | |
| Current shower average temperature | °C | Live | Aggregate; no statistics |
| Current shower volume | mL | Live | Resets per shower |
| Current shower duration | s | Live | |
| Current shower started | timestamp | Live | Only on firmware that exposes it |
| Flow rate | L/min | Live | Only on firmware that exposes it |
| Lifetime volume | L | Retained | Feeds the Water dashboard |
| Lifetime average temperature | °C | Retained | Only on firmware that exposes it |
| Shower count | — | Retained | Lifetime number of completed showers |
| Last shower temperature | °C | Retained | |
| Last shower initial temperature | °C | Retained | How cold the water was at the start |
| Last shower duration | s | Retained | |
| Last shower volume | mL | Retained | |
| Last shower started | timestamp | Retained | |
| Battery voltage | V | Retained | Diagnostic, disabled by default |
| Shower active | on/off | Activity | See below |
| Level 1–4 colour, temperature colour | #RRGGBB |
Config | Writable — see below |
| First/second/third level threshold | L | Config | Writable — see below |
Live entities have values only while a shower is running and the integration has completed a fresh read for that shower. Between showers they are unavailable — that is by design, not a bug.
Retained entities keep their last value while the shower head sleeps and
across Home Assistant restarts. No input_number persistence workaround is
needed anymore.
Shower active turns on with the first wake advertisement. It turns off as soon as a reading reports that no session is in progress, or failing that when Home Assistant declares the device's advertisements stale — which can take several minutes after the water stops. Treat it as a convenience trigger for automations, never as a safety signal.
Water dashboard. Lifetime volume is a water sensor with long-term statistics, so it can be added under Settings → Dashboards → Water. The counter never resets: on firmware 6.11 it read 70,072 L across 1,039 showers, about 67 L each, matching that device's last-shower volume. A 32-bit millilitre counter holds roughly 4.29 million litres — some 63,000 more showers — and the sensor is declared in a way that tolerates a rollover regardless.
The shower head stores three consumption thresholds and five LED colours. They are read once per shower rather than on every poll, so they cost nothing during normal operation.
The head lights one of four colours according to how much water the running
shower has used, with a fifth for temperature. Each is a text entity holding
a #RRGGBB value, so setting one is a matter of typing a hex colour.
They are text entities rather than light entities on purpose. Home Assistant
couples brightness to every RGB colour mode, so a light would carry a
brightness slider that either does nothing or quietly rescales the stored
colour — and five entities in the light domain would be swept up by any
"turn off all the lights" automation, which here would write #000000 over
your configuration.
The three consumption thresholds are writable number entities in litres.
They bound the four colour bands: below the first threshold the head shows
level 1, and above the third it shows level 4.
Contrary to the published protocol notes, this block is XOR-encrypted the same
way the telemetry is. That was settled against firmware 6.11 using the
composite e622150d record, which mirrors the individual characteristics and
so decides them: e6221502 reads 00 02 03 04, decrypts to 1, and the record
independently reports 1. Zero is a special case — the firmware returns plain
zeros for unset values rather than the encrypted form.
Writes need a live Bluetooth connection, so they only work while water is running. Setting a threshold while the shower head is asleep fails immediately rather than queueing — a threshold that silently applied hours later would be worse than a clear error. A threshold that decodes to an impossible volume shows as unavailable, which also blocks writing to it.
Nothing in this integration can erase your shower history or factory-reset the device; those characteristics are deliberately not implemented.
If you want to check what your own firmware reports, download diagnostics from
the device page: the settings block lists every configuration characteristic
with its raw bytes and both candidate decodings.
- Everything unavailable and the device never appears: the shower head only talks while water is running. Run the shower and watch Settings → Devices & services.
- "Missing required characteristics" in the log: the shower head is on factory firmware. Pair it once with the official hai app to update.
- Upgraded from v1 and old entities linger or everything is unavailable: v2 does not migrate v1 config entries. Delete the old integration entry and add it again — see Upgrading from v1.
- A threshold is unavailable: its bytes decoded to a volume no shower could
use, which usually means the encoding assumption is wrong for your firmware.
Check the
settingsblock in diagnostics. - Download diagnostics from the device page to see the last full snapshot, firmware versions (app and bootloader), and polling state.
pip install -r requirements_test.txt
pytest tests/
ruff check .Protocol details live in custom_components/hai/protocol.py behind a typed
snapshot; its tests run without Home Assistant. The
PoC script and GATT notes
document the observed characteristic map.
Thanks to @adizanni's Hydrao integration for the original inspiration for v1 of this integration.