An Arduino water sensor (commonly sold as the FC-37 or generic rain sensor module) detects moisture by measuring the resistance across exposed parallel copper traces. When water bridges the gaps, resistance drops, allowing more current to flow and yielding a higher analog voltage reading. This guide targets the Arduino Uno R3 and Arduino Nano v3 (ATmega328P) boards, providing a complete, production-ready leak detector that solves the most common failure mode of cheap water sensors: rapid electrolysis and corrosion.

Difficulty: Beginner/Intermediate | Time: 45 Minutes | Cost: ~$18 USD

Parts List and Hardware Specifications

Sourcing the right variants prevents logic-level mismatches and power rail issues. The FC-37 module includes an onboard LM393 comparator for digital output, but we will focus on the raw analog output for granular leak detection.

Component Exact Variant / Model Key Specifications Est. Price (2026)
Microcontroller Arduino Uno R3 (Rev3) or Nano v3 ATmega328P, 5V logic, 10-bit ADC $12.00 - $16.00
Water Sensor FC-37 Rain/Water Module LM393 comparator, Analog/Digital out, 3.3V-5V $2.50 - $4.00
Active Buzzer 5V Active Buzzer Module Integrated oscillator, HIGH trigger $1.50
Wiring Dupont Jumper Wires (M-F and M-M) 24 AWG stranded copper $3.00 / pack
Resistor 10kΩ Pull-down Resistor 1/4W, 5% tolerance (optional but recommended) $0.10

The LM393 comparator on the sensor module is a dual differential comparator. According to the Texas Instruments LM393 datasheet, it can sink up to 16mA on its open-collector output, making it safe to drive directly into an Arduino GPIO pin without a logic level shifter.

Wiring Diagram and Pin Mapping

Standard tutorials wire the sensor VCC directly to the Arduino 5V rail. Do not do this. Constant DC voltage across wet copper traces causes rapid electrolysis, destroying the sensor in weeks. We will wire the sensor power to a digital GPIO pin and toggle it HIGH only during the read cycle.

FC-37 Sensor Pin Arduino Uno R3 Pin Function
VCC Pin 7 (Digital) Switched 5V power (Anti-corrosion)
GND GND Common ground reference
A0 A0 (Analog) Raw moisture voltage (0-5V)
D0 Not Connected Leave floating for analog build

Buzzer Wiring:

  • Buzzer VCC (or I/O) -> Arduino Pin 8
  • Buzzer GND -> Arduino GND
Pro-Tip: If your analog readings fluctuate wildly when the sensor is dry, solder a 10kΩ pull-down resistor between the A0 pin and GND. This prevents the high-impedance ADC pin from acting as an antenna for ambient EMI noise.

Complete Compilable Code with Anti-Corrosion Logic

This code targets the Arduino Uno R3 and Nano v3. It implements the GPIO-switching trick to prevent electrolysis, includes logical fault detection for disconnected sensors, and uses a moving average to filter out EMI noise.

// Arduino Water Sensor Leak Detector
// Target: Arduino Uno R3 / Nano v3 (ATmega328P)

#define SENSOR_POWER_PIN 7
#define SENSOR_ANALOG_PIN A0
#define BUZZER_PIN 8

// Thresholds (0-1023)
#define LEAK_THRESHOLD 450 
#define CRITICAL_THRESHOLD 800
#define FAULT_THRESHOLD 10 // Anything below this is likely a disconnected wire

const int numReadings = 10;
int readings[numReadings];
int readIndex = 0;
long total = 0;
int average = 0;
int faultCount = 0;

void setup() {
  Serial.begin(9600);
  pinMode(SENSOR_POWER_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  
  digitalWrite(SENSOR_POWER_PIN, LOW); // Keep sensor OFF to prevent corrosion
  digitalWrite(BUZZER_PIN, LOW);
  
  for (int i = 0; i < numReadings; i++) {
    readings[i] = 0;
  }
  Serial.println("System Initialized. Monitoring for leaks...");
}

void loop() {
  // 1. Power the sensor only for the duration of the read
  digitalWrite(SENSOR_POWER_PIN, HIGH);
  delay(15); // Allow voltage to stabilize across the RC network
  
  int rawValue = analogRead(SENSOR_ANALOG_PIN);
  
  // 2. Immediately cut power to prevent electrolysis
  digitalWrite(SENSOR_POWER_PIN, LOW);
  
  // 3. Error Handling & Fault Detection
  if (rawValue <= FAULT_THRESHOLD) {
    faultCount++;
    if (faultCount >= 3) {
      Serial.println("FAULT: Sensor disconnected or shorted (Read: 0)");
      // Trigger a distinct error beep
      tone(BUZZER_PIN, 150, 500); 
      delay(1000);
    }
    return; // Skip averaging if sensor is faulted
  } else {
    faultCount = 0; // Reset fault counter on valid read
  }
  
  // 4. Moving Average Filter
  total = total - readings[readIndex];
  readings[readIndex] = rawValue;
  total = total + readings[readIndex];
  readIndex = (readIndex + 1) % numReadings;
  average = total / numReadings;
  
  // 5. Output and Alert Logic
  Serial.print("Raw: "); Serial.print(rawValue);
  Serial.print(" | Avg: "); Serial.println(average);
  
  if (average >= CRITICAL_THRESHOLD) {
    Serial.println("ALERT: CRITICAL LEAK DETECTED!");
    digitalWrite(BUZZER_PIN, HIGH);
  } else if (average >= LEAK_THRESHOLD) {
    Serial.println("WARNING: Moisture detected.");
    // Intermittent beep for minor leak
    tone(BUZZER_PIN, 1000, 200); 
  } else {
    digitalWrite(BUZZER_PIN, LOW);
  }
  
  delay(2000); // Read every 2 seconds
}

Debugging: First Three Things to Check When It Fails

When your serial monitor outputs erratic data or triggers the exact error string "FAULT: Sensor disconnected or shorted (Read: 0)", do not immediately replace the sensor. Follow this ranked diagnostic path.

  1. Verify Switched Power Rail Continuity: The most common mistake is wiring the sensor VCC to the Arduino 5V pin while the code expects it on Pin 7. If Pin 7 is never toggled HIGH, the sensor remains unpowered, and the floating A0 pin will either read 0 (if pulled down) or random noise. Use a multimeter to check for 5V between the sensor VCC and GND while the Arduino is running.
  2. Check for Trace Oxidation: If you previously wired VCC to 5V and left it powered, the copper traces will turn black or green due to electrolysis. Scrape the traces gently with a fiberglass scratch pen or fine sandpaper. If the copper is pitted through to the fiberglass substrate, the module is dead and must be replaced.
  3. Isolate EMI and Floating Pins: If your symptom is erratic jumps (e.g., 14, 890, 2, 1023) rather than a solid 0, your A0 pin is floating during the read cycle. Ensure your jumper wire is fully seated in the Dupont connector. If the issue persists, add the 10kΩ pull-down resistor mentioned in the wiring section.

Extending and Simplifying the Build

Depending on your deployment environment, you may want to strip this project down to its bare essentials or scale it up for smart home integration.

How to Simplify (Digital Output Only):
If you do not care about moisture levels and only need a binary "wet/dry" alarm, bypass the analog code entirely. Wire the sensor's D0 pin to Arduino Pin 2. Use a small flathead screwdriver to turn the blue potentiometer on the LM393 chip until the onboard LED turns off when dry, and on when a drop of water hits the traces. Your code then reduces to a simple digitalRead(2) inside the loop.

How to Extend (IoT and Solenoid Shutoff):
To integrate this into Home Assistant, swap the Arduino Uno for an ESP32 DevKit v1. The ESP32's ADC pins (e.g., GPIO 34) are 3.3V tolerant, so you must power the FC-37 sensor with 3.3V to avoid frying the ESP32's internal ADC. Use the PubSubClient library to publish the averaged moisture data to an MQTT broker. For physical mitigation, wire a 5V relay module to control a 12V DC solenoid water valve on your main supply line, triggering a shutoff when the CRITICAL_THRESHOLD is breached.

Frequently Asked Questions

Can I leave the Arduino water sensor powered on continuously?

No. Leaving standard FC-37 or rain sensor modules powered continuously in a damp environment will destroy them via electrolysis within a few weeks. The DC current causes the copper to ionize and dissolve into the water. Always use the GPIO-switching method provided in the code above, where the sensor is only powered for the 15 milliseconds required to take an analogRead() measurement. For continuous monitoring in industrial settings, you must upgrade to a capacitive soil moisture sensor (like the DFRobot SEN0193) which uses an AC field and has no exposed corroding metal.

Why is my water sensor analog reading fluctuating wildly when dry?

Wild fluctuations (e.g., jumping from 0 to 400 to 12) when the sensor is completely dry are caused by the Arduino's high-impedance ADC pin acting as an antenna for ambient electromagnetic interference (EMI) from nearby AC mains wiring or switching power supplies. Because the dry sensor has near-infinite resistance, the A0 pin is effectively "floating." Soldering a 10kΩ pull-down resistor between the A0 signal line and GND provides a path for stray induced currents to bleed off, locking the reading to a stable 0 when dry.

How do I waterproof the Arduino water sensor connections?

The exposed header pins and solder joints on the FC-37 module are highly susceptible to shorting if water splashes the top of the board. Do not use hot glue, as it peels off when exposed to temperature cycles. Instead, mask the sensing traces at the bottom with Kapton tape, and apply a layer of MG Chemicals 419D Acrylic Conformal Coating or a two-part marine epoxy over the top component side and header pins. Remove the Kapton tape once the coating cures, leaving only the intended sensing area exposed to the environment.