A two-ESP32 water leak/usage monitoring system, built for a senior design checkoff. One board listens for the sound of a leak; the other turns that into a live dashboard.
Working on just one board? This branch has the full integrated project. Two scoped branches exist if you only need one half:
water_monitor_hub(dashboard/receiver) andAcoustic_DSP_Node(leak-detection node + its optional real-sensor front ends).
The DSP node produces one window of acoustic samples, runs the real DSP over it (DC removal → RMS → DFT → leak-band energy ratio + spectral centroid → 2-of-3 feature vote → 3-of-5 persistence), and broadcasts the result over ESP-NOW every 2 seconds, along with flow and moisture readings. The hub receives those packets, folds them into its state, classifies the whole system, and serves a dashboard. Traffic is one-way: the hub never commands the node.
Those samples come from one of two sources, switchable at runtime from either UI: a synthetic scenario catalogue, or live hardware (a laptop microphone, plus an Arduino Uno with a DHT11 and HC-SR04). Which one is running changes nothing downstream — the DSP, the ESP-NOW transport, and the hub's parsing and display cannot tell the two apart.
| Path | Board / role | Purpose |
|---|---|---|
Acoustic_DSP_Node/ |
Regular ESP32 (Arduino) | Runs the leak-detection DSP, broadcasts results over ESP-NOW. One-way transmitter; never polled or commanded. |
water_monitor_hub/ |
ESP32-C3 (ESP-IDF) | Receives those packets, classifies overall system state, serves the web dashboard. Also compiles as a native PC executable for development without hardware. |
Uno_Sensor_Bridge/ |
Arduino Uno (optional) | DHT11 + HC-SR04 feeding the DSP node real moisture/flow readings over UART, as an alternative to its synthetic scenario catalogue. |
tools/mic_bridge.py |
Laptop (optional) | POSTs real audio to the DSP node's /audio endpoint as an alternative to its synthetic waveform. |
docs/ |
— | The authoritative guide for running the two/three-board system: wiring, flashing, the demo script, thresholds, troubleshooting. Start with docs/README.md. |
Build-time independence: water_monitor_hub's build only ever reaches into
its own tree. Acoustic_DSP_Node/ is entirely self-contained and does not
require the hub project to build or flash. The only coupling between the two
is the ESP-NOW JSON packet format, documented in
Acoustic_DSP_Node/PACKET_FORMAT.md and validated on the hub side in
water_monitor_hub/core/espnow_receiver.c.
- One-way ESP-NOW link. Broadcast to
FF:FF:FF:FF:FF:FF, NUL-terminated JSON payload, no command channel, no acknowledgement beyond ESP-NOW's own delivery status. Both boards land on the same radio channel by joining the same Wi-Fi network — the node never has to know the hub's MAC address. - Silence is meaningful. A sensor is marked disconnected once ~10 s pass without a packet — there is no explicit "I am offline" message anywhere in the protocol. An ordinary power loss and a simulated fault therefore look identical to the hub, which is deliberate.
- Hub: REAL vs. TEST state. The hub keeps two entirely separate state
stores. Incoming ESP-NOW packets only ever write
REAL_STATE; the dashboard's Testing tab only ever writesTEST_STATE. If the dashboard is in TEST mode, live node data is invisible no matter how well the node is transmitting — the most common "why isn't this working" cause during bring-up. - Node: two interchangeable signal sources.
sensor_input.h's runtime switch picks between the synthetic scenario catalogue and real hardware (mic + Uno). Everything downstream of the sample buffer — DSP, feature extraction, voting, persistence, transport, hub parsing/display — is identical either way and cannot tell which source produced its input. - Hub core is platform-independent.
water_monitor_hub/core/(state machine, classification, persistence, analytics, event log, REST routes) compiles unmodified into both the ESP32-C3 firmware and a native PC executable, through the one abstraction boundary inhal/hal.h. - No reverse control channel, by design. The hub firmware never gains the ability to command the node — that would violate the one-way ESP-NOW contract. Instead, the hub's dashboard browser (which can reach both boards' IPs over Wi-Fi) posts directly to the DSP node's own web console to switch its signal source.
Requires MSYS2 UCRT64 gcc/cmake/ninja on PATH — see
water_monitor_hub/README.md for one-time setup.
cd water_monitor_hub
cmake -B build -G Ninja
cmake --build build
ctest --test-dir build --output-on-failure
Produces build/water_monitor_pc.exe (dashboard on http://127.0.0.1:5000).
cd water_monitor_hub/firmware
idf.py set-target esp32c3 # only on a clean checkout — wipes sdkconfig/Wi-Fi credentials
idf.py menuconfig # Water Monitor Configuration -> Wi-Fi SSID / password
idf.py build
idf.py -p COM<n> flash monitor
Requires ESP-IDF v5.3+ exported into the shell first.
arduino-cli compile --fqbn esp32:esp32:esp32 Acoustic_DSP_Node
arduino-cli upload --fqbn esp32:esp32:esp32 -p COM<n> Acoustic_DSP_Node
No external libraries. Wi-Fi credentials are set in dsp_config.h — must
match the hub's, so both boards join the same AP and land on the same
ESP-NOW channel.
arduino-cli compile --fqbn arduino:avr:uno Uno_Sensor_Bridge
arduino-cli upload --fqbn arduino:avr:uno -p COM<n> Uno_Sensor_Bridge
pip install sounddevice requests numpy
python tools/mic_bridge.py --calibrate --gain 1.0 # tune gain against RMS_THRESHOLD (0.10) before streaming
python tools/mic_bridge.py <node-ip> --gain <value>
- Boot the hub, open its dashboard, confirm it's in REAL mode.
- Boot the DSP node and open its web console (or serial monitor).
- Pick "Slow leak" — within ~2 s the hub's Live DSP telemetry panel shows non-zero RMS, energy ratio, and centroid for it.
- Leave it running ~6 s — persistence crosses its threshold and the panel reports a persistent anomaly.
- Pick "Washing machine" — features move, but no anomaly, proving the detector doesn't just fire on anything noisy.
- Pick "Fault: moisture node 1 offline" — within ~10 s the hub marks it disconnected, having noticed only the silence.
- If the Uno and mic bridge are wired up, switch the node to "Real sensors" and repeat against actual sound, humidity, and water — same pipeline, same packets, only where the samples came from changes.
Full wiring diagrams, threshold tables, and troubleshooting are in
docs/README.md.