Reading a residential water meter with an ESP32 and a magnetometer, reporting live flow rate and cumulative volume to Home Assistant via ESPHome.
No utility cooperation required, no plumbing modification, no cutting into the water line. The sensor tapes to the outside of the meter body.
Positive-displacement water meters use a nutating disc that drives a magnet. That magnet turns the register dials. A magnetometer taped to the meter body sees the magnet as a rotating field; count the rotations, multiply by a calibrated volume-per-rotation, and you have consumption.
[rotating magnet in meter] --magnetic field--> [QMC5883P] --I2C--> [ESP32]
|
WiFi
|
[Home Assistant]
This works on essentially any positive-displacement meter regardless of what radio or register is attached to it.
Rotation counting, threshold detection, and volume math are handled by tronikos/esphome-magnetometer-water-gas-meter. This repo is the config, the dashboard, and the gotchas.
Tested on a 3/4" positive-displacement residential meter.
| Part | Notes |
|---|---|
| ESP32 WROOM-32 DevKit (30-pin) | Any common clone works |
| GY-271 magnetometer breakout | See the chip warning below |
| 4x female-female jumper wires | Or solder directly |
| USB power supply | 5V, any phone charger |
Four wires. The magnetometer speaks I²C, which needs two data lines plus power and ground — that's the whole connection.
| GY-271 | ESP32 | Purpose |
|---|---|---|
| VCC | 3V3 | Power |
| GND | GND | Ground |
| SDA | GPIO21 | I²C data |
| SCL | GPIO22 | I²C clock |
| DRDY | not connected | Data-ready interrupt, unused |
Female-to-female DuPont jumpers push straight onto both headers, so no soldering is needed beyond attaching the 4-pin header to the breakout if it shipped loose.
The GY-271 board is rated 3–5 V, so 5 V will not damage the magnetometer itself. The problem is the two I²C pull-up resistors on the breakout: they tie SDA and SCL to whatever you feed VCC. Power the board at 5 V and those lines idle at 5 V, driving the ESP32's 3.3 V GPIO pins out of spec. It often appears to work, then degrades the pins over time.
Running VCC from 3V3 puts the whole bus at 3.3 V and the problem disappears. No level shifter and no external pull-ups are needed — the ones on the breakout are already correct for a short bench-length bus.
Nothing about those pins is special. The ESP32 can route I²C to almost any GPIO, and the pin assignment is set in the YAML:
i2c:
sda: GPIO21
scl: GPIO22They are simply the convention — ESPHome's examples and most tutorials use them, so sticking with the default keeps your config comparable to everyone else's. If you need to move them, avoid GPIO6–11 (wired to the internal flash chip) and the strapping pins GPIO0, 2, 12, and 15, which affect boot mode.
Silkscreen on the 30-pin WROOM-32 DevKits is inconsistent. The 3.3 V pin
may read 3V3, 3.3V, or occasionally just 33. GPIO pins may be labeled
D21/D22 or bare 21/22. On the GY-271 side, pin order is usually
VCC, GND, SCL, SDA, DRDY — note that SCL comes before SDA, which is the
opposite of the order people tend to assume, and is the most common wiring
mistake with this board.
If SDA and SCL are swapped, the I²C scan finds nothing at all rather than reporting an error. A silent empty scan is the symptom.
Before flashing the full config, flash a minimal one and read the boot log:
i2c:
sda: GPIO21
scl: GPIO22
scan: trueA working bus prints the device address:
[C][i2c.idf:113]: Results from bus scan:
[C][i2c.idf:119]: Found device at address 0x2C
Found no i2c devices! means one of: SDA/SCL swapped, VCC not connected, a
cold solder joint on the header, or a dead board. Check in that order — swapped
data lines and bad solder joints account for nearly all cases.
The address itself tells you which chip you have, which determines the ESPHome platform. See the next section.
Boards sold as "GY-271 QMC5883L" ship with at least three different chips. Scan the I2C bus before assuming anything:
| Address | Chip | ESPHome platform |
|---|---|---|
0x1E |
HMC5883L | hmc5883l (built in) |
0x0D |
QMC5883L | qmc5883l (built in) |
0x2C |
QMC5883P | external component — see below |
This build got a QMC5883P at 0x2C, which ESPHome does not support natively.
The register map differs from the QMC5883L despite the near-identical name, so
pointing the qmc5883l platform at it silently fails — no error, just no
readings.
The fix is the qmc5883p.yaml overlay in the tronikos package, which pulls in
mazzhead/esphome-components.
It's already wired up in water-meter.yaml.
To scan, flash a minimal config with i2c: { scan: true } and read the boot log.
This was the single biggest factor in getting it working, and it is worth more than any amount of config tuning.
Magnetic field strength falls off with roughly the cube of distance. Halving the gap between the chip and the magnet increases signal by ~8x.
Measured on this meter:
| Sensor position | Signal swing | Result |
|---|---|---|
| ~1 cm off, poorly aligned | ~2.8 uT | Calibration failed: low signal |
| Pressed against the housing, hunted for the hotspot | ~85 uT | Works reliably |
Noise floor on this sensor is roughly 2-4 uT, so the first position was producing a signal barely distinguishable from noise.
Find the hotspot first. Install a magnetometer app on your phone (Physics Toolbox, Sensors, etc.), run water, and sweep the phone slowly across the meter face watching for the largest oscillation. Mount the sensor there. The strongest point is often off-center rather than directly on top.
- Wire it up per the table above.
- Flash a scan config and note the I2C address from the boot log.
- Copy
water-meter.yaml, adjusting thefiles:list if your chip is not a QMC5883P. - Select the axis. In Home Assistant, set the
Axisdropdown to whichever of X/Y/Z shows the largest swing under flow. Watch the ESPHome log'sGot x=... y=... z=...lines withqmc5883p: DEBUG— much easier to read than the HA history graphs. - Calibrate. With water running, trigger the calibration entity. It sets
thresholds from the observed min/max.
Calibration Statuswill reportReadyorFailed: low signal— the latter means go back to placement. - Set volume per half rotation. Note the meter dial reading and the half-rotation count, run a measured volume, then divide gallons used by half-rotations counted. More volume gives a better figure.
- Hide the diagnostic sensors. Set both
hide_*substitutions to'true'and reinstall. The magnetic field sensors publish every ~5 ms and are a real load on the ESP; hiding them improves accuracy at high flow.
A custom:button-card (HACS) showing an animated pump dial — the water level
rises with flow and the impeller spins proportionally.
Add sensor.water_meter_total as a water source under
Settings → Dashboards → Energy.
Watch out: changing Volume per half rotation rescales Total, and with
state_class: total_increasing a decrease reads as a meter reset — HA records
the entire new value as fresh consumption, producing enormous phantom spikes.
Fix via Developer Tools → Statistics → adjust or delete the statistic.
recorder.purge_entities does not clear long-term statistics.
This repository is licensed under the MIT License — see LICENSE.
That covers the contents of this repo only: the ESPHome device configuration, the Home Assistant card, the wiring diagram, and this documentation.
This project does not bundle or redistribute any third-party code. The packages below are fetched at build time from their own repositories and remain under their own licenses:
| Project | License | Role |
|---|---|---|
| tronikos/esphome-magnetometer-water-gas-meter | Apache-2.0 | Rotation counting, calibration, and volume math — the actual meter-reading logic |
| mazzhead/esphome-components | see repo | QMC5883P driver, pulled in by the qmc5883p.yaml overlay |
| custom-cards/button-card | MIT | Renders the flow gauge card |
Nearly all of the difficult work here belongs to tronikos. If this project is useful to you, star that repository — this one is a thin configuration layer, some hardware notes, and a dashboard card on top of it.
Portions of the documentation, the wiring diagram, and the dashboard card SVG were drafted with AI assistance. The hardware findings, measurements, and testing are the author's own.