Water leaks are a surprisingly useful problem to explore when learning about the Internet of Things (IoT). They involve real-world sensor input, microcontroller programming, network communication, message processing and alert handling.
A simple water-contact sensor can detect unexpected moisture, but building a connected monitoring system requires several components working together.
In this tutorial, we'll create an educational water leak detection prototype using an ESP32, a digital water sensor, Wi-Fi and MQTT.
We'll also discuss sensor debouncing, network failures, security and potential improvements.
What We'll Build
Our prototype will:
- Read a digital water-contact sensor.
- Distinguish between wet and dry readings.
- Connect an ESP32 to a Wi-Fi network.
- Publish sensor states to an MQTT broker.
- Send an updated message when the confirmed state changes.
- Publish periodic status messages.
- Allow another application to subscribe to those messages.
The architecture is:
Water Contact Sensor
|
v
ESP32
|
Wi-Fi
|
v
MQTT Broker
|
v
Monitoring Application
|
v
Dashboard / Alert
This is a monitoring prototype, not a certified water protection system.
1. Hardware Requirements
You'll need the following components:
| Component | Purpose |
|---|---|
| ESP32 development board | Reads sensor data and manages communication |
| Digital water-contact sensor module | Detects water contact |
| Jumper wires | Connects the components |
| Breadboard | Supports prototyping |
| USB cable | Powers and programs the ESP32 |
| Computer | Runs the development environment |
| MQTT broker | Receives published sensor messages |
For this example, use a digital sensor module with an output that is compatible with the ESP32's 3.3 V GPIO input.
Do not connect a 5 V output directly to an ESP32 GPIO pin.
We will assume that our particular sensor produces a LOW signal when water is detected. Your sensor may behave differently, so check its documentation before connecting it.
2. Connecting the Water Sensor
For an appropriately rated digital sensor module, an example connection is:
ESP32 Water Sensor
3.3V ------------ VCC
GND ------------ GND
GPIO27 ------------ DO
DO represents the sensor module's digital output.
The ESP32 will read the value using digitalRead().
Keep the electronics and power connections away from water. Only the appropriately designed sensing portion should be exposed during testing.
3. Set Up the Development Environment
We'll use the Arduino IDE with the ESP32 board support package.
Install the appropriate ESP32 board package and select the correct development board.
Next, install the PubSubClient MQTT library through the Arduino Library Manager.
We'll use:
-
WiFi.hfor Wi-Fi connectivity -
PubSubClient.hfor MQTT messaging
You'll also need an MQTT broker.
For local experimentation, you can run Mosquitto on another computer connected to the same trusted network.
Configure the broker to accept connections from the ESP32, using appropriate test credentials and network restrictions.
4. Understanding MQTT
MQTT is a publish/subscribe messaging protocol.
Instead of sending sensor readings directly to every application, a device publishes messages to a broker.
Other applications subscribe to topics and receive those messages.
For example, our ESP32 can publish to:
home/water-sensor-01/status
A sample payload might be:
{
"device_id": "water-sensor-01",
"water_detected": true
}
This separation makes it easier to connect different monitoring applications without changing the sensor hardware.
5. ESP32 Water Leak Monitoring Code
The following Arduino example reads a digital sensor and publishes its confirmed state using MQTT.
Replace the placeholder connection settings before uploading.
#include <WiFi.h>
#include <PubSubClient.h>
// Network configuration
const char* WIFI_SSID = "YOUR_WIFI_NAME";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
// Local MQTT broker
const char* MQTT_HOST = "192.168.1.100";
const int MQTT_PORT = 1883;
const char* MQTT_USER = "YOUR_MQTT_USER";
const char* MQTT_PASSWORD = "YOUR_MQTT_PASSWORD";
// MQTT topic
const char* STATUS_TOPIC =
"home/water-sensor-01/status";
// Hardware
const int SENSOR_PIN = 27;
// Change this if your sensor is active HIGH.
const int WET_LEVEL = LOW;
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
// Simple sensor stability filtering
const int REQUIRED_READINGS = 3;
bool confirmedWet = false;
bool lastRawWet = false;
bool hasConfirmedState = false;
int matchingReadings = 0;
unsigned long lastSample = 0;
unsigned long lastPublish = 0;
const unsigned long SAMPLE_INTERVAL = 500;
const unsigned long PUBLISH_INTERVAL = 30000;
void connectWiFi() {
if (WiFi.status() == WL_CONNECTED) {
return;
}
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
}
void connectMQTT() {
if (WiFi.status() != WL_CONNECTED) {
return;
}
if (mqttClient.connected()) {
return;
}
mqttClient.connect(
"esp32-water-sensor-01",
MQTT_USER,
MQTT_PASSWORD
);
}
void publishStatus() {
if (!mqttClient.connected() ||
!hasConfirmedState) {
return;
}
String payload = "{";
payload += "\"device_id\":\"water-sensor-01\",";
payload += "\"water_detected\":";
payload += confirmedWet ? "true" : "false";
payload += "}";
bool published = mqttClient.publish(
STATUS_TOPIC,
payload.c_str(),
true
);
if (published) {
lastPublish = millis();
}
}
void readSensor() {
bool rawWet =
digitalRead(SENSOR_PIN) == WET_LEVEL;
if (rawWet == lastRawWet) {
if (matchingReadings < REQUIRED_READINGS) {
matchingReadings++;
}
} else {
lastRawWet = rawWet;
matchingReadings = 1;
}
if (matchingReadings >= REQUIRED_READINGS) {
if (!hasConfirmedState ||
rawWet != confirmedWet) {
confirmedWet = rawWet;
hasConfirmedState = true;
Serial.println(
confirmedWet ?
"Water detected!" :
"Sensor is dry."
);
publishStatus();
}
}
}
void setup() {
Serial.begin(115200);
pinMode(SENSOR_PIN, INPUT);
WiFi.mode(WIFI_STA);
mqttClient.setServer(
MQTT_HOST,
MQTT_PORT
);
connectWiFi();
}
void loop() {
static unsigned long lastConnectAttempt = 0;
unsigned long now = millis();
if (now - lastConnectAttempt >= 5000) {
lastConnectAttempt = now;
connectWiFi();
connectMQTT();
}
if (mqttClient.connected()) {
mqttClient.loop();
}
if (now - lastSample >= SAMPLE_INTERVAL) {
lastSample = now;
readSensor();
}
if (hasConfirmedState &&
now - lastPublish >= PUBLISH_INTERVAL) {
publishStatus();
}
}
How the Code Works
First, the ESP32 connects to the configured Wi-Fi network.
After connecting, it attempts to establish an MQTT connection using the configured broker address.
The program reads the sensor approximately every 500 milliseconds.
Instead of accepting every reading immediately, it requires three consistent observations before confirming the wet or dry state.
When the confirmed state changes, the device attempts to publish an MQTT message.
It also attempts periodic status updates.
The message uses the retained-message flag, allowing a new MQTT subscriber to receive the broker's most recently retained status.
However, this simple example has limitations.
It does not guarantee message delivery during a network outage, and a retained message may become stale if the device disconnects.
For production systems, a separate device availability topic, persistent event identifiers and more robust reconnection logic would be useful.
The basic WiFiClient connection in this example is also unencrypted. It is intended only for a trusted local development network. A deployed system should use TLS-capable communication, properly managed credentials and appropriate broker access controls.
6. Subscribe to Sensor Messages
Once the ESP32 starts publishing, another MQTT client can subscribe to its status topic.
For example, using Mosquitto's command-line client:
mosquitto_sub \
-h 192.168.1.100 \
-p 1883 \
-u "YOUR_MQTT_USER" \
-P "YOUR_MQTT_PASSWORD" \
-t "home/water-sensor-01/status" \
-v
Replace the broker address and credentials with your test environment's values.
When the sensor detects water, a message similar to the following should appear:
{
"device_id": "water-sensor-01",
"water_detected": true
}
When the sensor returns to its dry state:
{
"device_id": "water-sensor-01",
"water_detected": false
}
A separate monitoring application could use these messages to update a dashboard or trigger notifications.
7. Why Debouncing Matters
Physical sensors are not always perfectly stable.
A sensor exposed to intermittent moisture might alternate between wet and dry readings over a short period.
If every individual reading triggers an alert, the monitoring application could generate unnecessary notifications.
Our simple example requires three consecutive matching readings before confirming a state.
This provides basic filtering, but it is not a complete solution.
More advanced designs might use:
- Time-based validation
- Moving averages for analogue sensors
- Separate wet and dry thresholds
- Sensor health checks
- Configurable recovery periods
The correct filtering strategy depends on the actual sensor and its intended operating conditions.
8. Handling Network Failures
One of the challenges in connected-device development is deciding what happens when communication stops.
A water sensor may detect moisture while Wi-Fi is unavailable.
In that situation, an internet-based alert may never reach the user.
A more robust implementation could include a local buzzer that activates independently of Wi-Fi.
Other improvements include offline event buffering, connection-health reporting and a monitoring service that recognises when a device has stopped transmitting.
These features become especially important when the monitoring system is expected to operate without someone regularly checking it.
9. Security Considerations
IoT devices are often installed in environments where they may operate for long periods.
Security therefore needs attention beyond the initial prototype.
Important considerations include:
Encrypted communication: Protect sensor messages with suitable transport encryption when communicating outside a restricted test environment.
Credential management: Avoid publishing working passwords or embedding shared credentials in public repositories.
Topic permissions: Restrict which MQTT topics a device can access.
Firmware updates: Establish a maintenance process for security fixes.
Network segmentation: Isolate IoT devices where appropriate.
Data minimisation: Avoid collecting unnecessary information about property occupants.
Even relatively simple devices should follow basic security practices.
10. Testing the Monitoring System
Before relying on the prototype, test its behaviour under different conditions.
A useful test plan includes:
- Confirming correct readings when the sensor is dry.
- Safely testing water contact according to the sensor manufacturer's instructions.
- Checking that MQTT messages contain the expected state.
- Repeating the wet/dry transition to examine debouncing.
- Disconnecting Wi-Fi and observing reconnection behaviour.
- Restarting the MQTT broker.
- Testing the system after a power interruption.
- Checking what happens when the sensor cable is disconnected.
The final test is particularly important.
A basic digital input may not distinguish a disconnected sensor from a valid dry reading.
Production equipment should use appropriate fault-detection techniques rather than assuming that no alarm means no problem.
11. Real-World Applications
A connected water-monitoring system could be useful in several environments.
For example, sensors may be positioned near washing machines, beneath kitchen sinks or around accessible pipework.
Property management applications could combine multiple sensor readings into a monitoring dashboard.
A monitoring application could also maintain an event history to help maintenance teams understand recurring problems.
However, identifying water contact is only one part of dealing with a plumbing issue.
The actual fault may involve damaged pipework, a leaking fitting or another component requiring physical inspection.
Businesses involved in property maintenance, such as Nobel Build & Repairs Ltd, operate in an area where reliable leak information and timely repair coordination can be valuable.
This illustrates how software monitoring can complement practical building maintenance without replacing professional diagnosis.
12. Future Improvements
This prototype can be extended in several directions.
Potential improvements include:
- Multiple ESP32 sensor nodes
- A web-based monitoring dashboard
- Mobile push notifications
- Battery status reporting
- Sensor calibration interfaces
- TLS-secured MQTT connections
- Offline message buffering
- Device health and availability reporting
- Home Assistant integration
- Historical event visualisation
For a more advanced system, developers could investigate suitable flow sensors or professionally installed automatic shut-off equipment.
However, automated control of water supplies introduces additional safety and reliability considerations.
Conclusion
An ESP32-based water leak monitoring prototype is a useful project for learning about real-world IoT development.
It combines sensor interfacing, Arduino programming, network connectivity, MQTT messaging and basic event processing.
It also highlights broader engineering challenges such as sensor reliability, cybersecurity, network failures and monitoring architecture.
Although the prototype is straightforward, developing it into a dependable system requires careful testing and more advanced failure handling.
The most useful IoT projects are those that solve clearly defined problems while accounting for the limitations of both hardware and software.
Top comments (0)