MCU-only Arduino library for pulsed two-electrode wetness, soil-moisture trend, surface wet/dry state, calibration, diagnostics, CSV/JSON logging, and anti-corrosion Hi-Z rest behavior.
Why β’ Quick Start β’ Hardware β’ Installation β’ API β’ Examples β’ Validation β’ Docs
Most wetness and soil-moisture experiments start simple: two electrodes, one analog input, one GPIO, and a lot of noisy readings. PulseWetProbe turns that rough prototype into a reusable Arduino library with:
| What you need | What the library gives you |
|---|---|
| Simple soil/wetness readings | Beginner wrappers like beginSoil() and beginWetDry() |
| Less electrode stress | Pulsed excitation plus Hi-Z rest between readings |
| Better repeatability | Dry/wet calibration, optional multi-point calibration, board-aware ADC defaults |
| Cleaner logs | Fixed-buffer CSV output and optional JSON on larger boards |
| Debug visibility | Quality score, state labels, diagnostic flags, noise/stability/drift indicators |
| Cross-board behavior | AVR-safe Tiny mode, ESP32 touch guard, SAMD/Renesas/RP2040/STM32/Teensy-friendly defaults |
Important
PulseWetProbe reports trend-oriented wetness and conductivity-response indicators. It is not a certified RWIS sensor, laboratory EC meter, salinity meter, freezing-point detector, or corrosion-proof hardware solution. Validate your own electrode geometry, medium, cable length, enclosure, and environment before using the readings for decisions.
Use the beginner wrapper first. Replace the calibration values with your own measured dry/wet readings.
#include <PulseWetProbe.h>
PulseWetProbe probe;
void setup() {
Serial.begin(115200);
// sense pin, excitation pin
probe.beginSoil(A0, 7);
// replace these with your measured dry/wet raw values
probe.calibrateDryWet(120, 850);
Serial.println(F("wetnessPercent,state,quality"));
}
void loop() {
PwpReading r = probe.read();
Serial.print(r.wetnessPercent(), 1);
Serial.print(F(","));
Serial.print(r.stateName());
Serial.print(F(","));
Serial.println(r.qualityScore);
delay(probe.nextIntervalMillis());
}Typical serial output:
wetnessPercent,state,quality
3.8,dry,94
28.6,damp,88
71.2,wet,82
95.5,saturated,76
PulseWetProbe samples a two-electrode path using short excitation pulses, then returns the pins to a safer resting state. The library separates level, differential behavior, and trend diagnostics so you can log meaningful signals instead of hiding everything behind one raw ADC value.
flowchart LR
A[Electrodes / surface / soil] --> B[Pulsed excitation]
B --> C[ADC / touch acquisition]
C --> D[Noise filters]
D --> E[Calibration]
E --> F[Wetness % + state]
E --> G[CSV / JSON output]
D --> H[Diagnostics]
H --> I[Quality, flags, noise, drift]
| Signal | Meaning | Use it for |
|---|---|---|
rawForward / rawReverse |
Forward/reverse acquisition values | Hardware debugging, polarity asymmetry checks |
rawAverage |
Average raw response | Basic trend logging |
filtered |
Filtered sensor response | Calibration and stable display values |
normalizedWetness |
Calibrated 0.0β1.0 wetness estimate | UI, thresholds, dashboards |
wetnessPercent() |
User-friendly 0β100% wrapper | Serial output and beginner projects |
differentialIndex |
Forward/reverse imbalance | Cable/electrode/polarization trend checks |
conductivityTrend |
Heuristic trend/change score | Relative conductivity-response experiments, not EC |
qualityScore |
0β100 confidence-style score | Flagging readings that deserve attention |
stateName() |
dry, damp, wet, saturated, unstable, needs_calibration |
Human-readable output |
Arduino / MCU
GPIO excitation pin ββ[ series resistor ]ββ Plate A
Analog sense pin βββββββββββββββββββββββββ Plate B
GND/reference ββ[ high-value bleed ]ββββ Plate B (optional but recommended when dry node floats)
Recommended starting points:
| Part | Why it matters | Typical starting point |
|---|---|---|
| Series resistor on excitation path | Limits current during accidental shorts and wet conductive conditions | Start high; tune for your ADC range and electrode geometry |
| High-value bleed/reference path on sense node | Prevents floating readings when the electrodes are dry or disconnected | Use a high value so it does not dominate the measurement |
| Short, shielded, or twisted wiring when possible | Reduces pickup/noise on high-impedance analog nodes | Especially important outdoors or near motors/relays |
| Enclosure and ESD protection | Outdoor electrodes can collect static, water, salts, and cable transients | Validate for your deployment environment |
Caution
Do not connect electrodes directly to unknown high-energy sources. Keep current limited, verify the board pin limits, and review docs/ELECTRODE_SAFETY.md, docs/EMC_ESD_GUIDE.md, and docs/HARDWARE_WIRING_AND_SCHEMATICS.md before field use.
| Mode | API | What happens | Best for |
|---|---|---|---|
| High/low pulsed two-plate | beginTwoPlate(sense, excite) or beginSoil() |
One pin drives HIGH/LOW pulses, the other pin is sensed | Simple Arduino projects, low pin count, AVR-safe examples |
| Reversible two-plate | beginReversibleTwoPlate(plateA, plateB) |
Plate roles are swapped during sampling | Better electrode role balancing when both pins are safe for drive/sense |
| ESP32 touch wetness | beginEsp32TouchWetness(touchPin) |
Uses ESP32 touch hardware when available | ESP32 boards with verified touch-capable GPIOs |
- Download the release ZIP from GitHub.
- Open Arduino IDE.
- Go to Sketch β Include Library β Add .ZIP Library...
- Open File β Examples β PulseWetProbe β SimpleSoilStarter.
- Set your pins and calibration values.
- Upload and open Serial Monitor.
- Open Tools β Manage Libraries... or Sketch β Include Library β Manage Libraries...
- Search for PulseWetProbe.
- Install the latest version.
- Open an example from File β Examples β PulseWetProbe.
After Library Manager acceptance:
arduino-cli lib install PulseWetProbeBefore acceptance, install manually from a local ZIP or clone this repository into your Arduino libraries folder.
Before registry publication:
lib_deps = https://github.com/Parvaz-Jamei/PulseWetProbe-Arduino.gitAfter registry publication:
lib_deps = PulseWetProbe| API | Purpose |
|---|---|
beginSoil(sensePin, excitePin) |
Soil/moisture trend starter defaults |
beginWetDry(sensePin, excitePin) |
Faster surface wet/dry state sensing |
beginConductivityTrend(sensePin, excitePin) |
Relative conductivity-response trend logging; not EC/salinity precision |
beginEsp32TouchWetness(touchPin) |
ESP32 touch wetness path when board and pin support touch |
calibrateDryWet(dryRaw, wetRaw) |
Simple two-point calibration |
read() |
Take one reading and return a PwpReading |
wetnessPercent() |
Latest wetness as 0β100% |
stateName() |
Latest state as readable text |
nextIntervalMillis() |
Conservative adaptive sampling interval |
probe.enableBoardDefaults();
probe.beginTwoPlate(A0, 7);
probe.setProfile(PwpProfile::SOIL);
probe.setExcitation(PwpExcitation::PULSED_HIGH_LOW);
probe.filters().setPreset(PwpFilterPreset::INDUSTRIAL_STABLE);
probe.acquisition().setSettlingMicros(100).setDummyReads(2).setBurstSamples(8);
probe.calibration().setDryWet(120, 850);| Profile | Intent |
|---|---|
PwpProfile::SOIL |
Soil-moisture trend sensing |
PwpProfile::WETNESS |
Surface wet/dry response |
PwpProfile::CONDUCTIVITY_TREND |
Relative conductivity-response trend experiments |
PwpProfile::SURFACE_BRINE_TREND |
Brine-like surface trend experiments; not salinity/freezing-point detection |
PwpProfile::DIAGNOSTIC |
More diagnostic visibility for testing and validation |
| Preset | Behavior |
|---|---|
PwpFilterPreset::RESPONSIVE |
Faster response, less smoothing |
PwpFilterPreset::STABLE |
Balanced smoothing |
PwpFilterPreset::INDUSTRIAL_STABLE |
More conservative output for noisy wiring/environments |
Calibration is required because the raw ADC response depends on electrode size, spacing, material, board ADC range, wiring, soil/surface material, and environmental conditions.
- Wire the electrodes safely.
- Run
examples/ManualCalibrationConsoleorexamples/CsvLogger. - Capture a stable dry raw value.
- Capture a stable wet/reference raw value.
- Apply them:
probe.calibrateDryWet(dryRaw, wetRaw);On non-Tiny profiles, you can add intermediate reference points:
probe.calibration().clearPoints();
probe.calibration().addPoint(120, 0.00f); // dry
probe.calibration().addPoint(420, 0.35f); // damp
probe.calibration().addPoint(850, 1.00f); // wetCalibration profiles can be exported with CRC-16 protection so corrupted profiles are rejected during import.
char line[PWP_CSV_BUFFER_SIZE];
PwpReading r = probe.read();
if (r.toCsv(line, sizeof(line)) > 0) {
Serial.println(line);
}CSV columns:
seq,ms,rawForward,rawReverse,rawDiff,rawAvg,filtered,touchRaw,burstSpread,wetPermille,levelPermille,diffPermille,trendPermille,quality,noise,stability,drift,fouling,state,flags
#if PWP_ENABLE_JSON
char json[PWP_JSON_BUFFER_SIZE];
PwpReading r = probe.read();
if (r.toJson(json, sizeof(json)) > 0) {
Serial.println(json);
}
#endifTip
On AVR/Tiny mode, prefer toCsv(char*, size_t) and avoid heap-heavy String output.
| Board family | Default mode | Notes |
|---|---|---|
| AVR UNO/Nano/Mega | Tiny | Fixed buffers, CSV-first, small RAM behavior |
| megaAVR Nano Every / Uno WiFi Rev2 class | Core | Detected before classic AVR so it is not forced into Tiny mode |
| ESP8266 | Core | One user ADC channel; avoid multi-channel assumptions |
| ESP32 classic/S2/S3/C3/C6 | ESP32-aware | Touch mode only when board/core/GPIO support it |
| SAMD | Core/Full-capable | Uses generic Arduino APIs first |
| Renesas UNO R4 | Core/Full-capable | Board-aware defaults and example compile coverage |
| RP2040 / Mbed RP2040 | Core/Full-capable | Specific RP2040 detection before generic Mbed path |
| STM32 | Core/Full-capable | Generic Arduino APIs first |
| Teensy | Core/Full-capable | Generic Arduino APIs first |
| nRF52 / Mbed / Nano 33 BLE class | Core/Full-capable | Generic Arduino APIs first |
Run this example to see what the library detects on your board:
examples/BoardCapabilityReporter
| Example | Start here when you want to... |
|---|---|
SimpleSoilStarter |
Get a friendly wetness percentage and state quickly |
BasicTwoPlateSoil |
Log calibrated soil trend readings as CSV |
PulsedWetDryPlate |
Detect wet/dry surface response with pulsed excitation |
FilterComparisonLogger |
Compare responsive vs stable filtering |
ManualCalibrationConsole |
Capture dry/wet calibration points from Serial Monitor |
CsvLogger |
Produce compact CSV for spreadsheets, dashboards, or Python plots |
BoardCapabilityReporter |
Print detected board capabilities and compile profile |
DiagnosticsProLogger |
Log quality, flags, noise, drift, fouling indicators |
MultiPointCalibrationConsole |
Use multiple calibration points and CRC profile export |
PowerAwareSampler |
Use adaptive sampling interval logic |
JsonFullModeLogger |
Emit JSON on boards where JSON output is enabled |
Esp32TouchWetness |
Try ESP32 touch wetness with automatic fallback |
ReversibleTwoPlateBalance |
Use true two-electrode role swapping |
PulseWetProbe-Arduino/
βββ src/
β βββ PulseWetProbe.h
β βββ PulseWetProbe.cpp
β βββ PwpConfig.h
β βββ PwpBoardCapabilities.h
βββ examples/
β βββ SimpleSoilStarter/
β βββ CsvLogger/
β βββ DiagnosticsProLogger/
β βββ ...
βββ docs/
β βββ API_GUIDE.md
β βββ BOARD_SUPPORT_MATRIX.md
β βββ HARDWARE_WIRING_AND_SCHEMATICS.md
β βββ ELECTRODE_SAFETY.md
β βββ ...
βββ library.properties
βββ library.json
βββ keywords.txt
βββ CHANGELOG.md
βββ CITATION.cff
βββ README.md
PulseWetProbe includes CI workflows and host smoke tests, but sensor accuracy must be validated for your physical setup.
| Check | Purpose |
|---|---|
| Arduino Lint | Library Manager metadata and layout validation |
| Compile Arduino examples | Ensures examples compile across selected board families |
| Host smoke tests | Regression tests for filtering, CRC/profile behavior, trend logic, and board detection |
| Docs link check | Optional pull-request documentation link validation |
| Evidence | Why |
|---|---|
| Dry/wet CSV logs | Proves your calibration range |
| Repeated runs | Shows repeatability and drift |
| Noise/stability plots | Reveals cable/electrode/environment problems |
| Board and core version | Makes results reproducible |
| Electrode geometry and material | Strongly affects calibration and response |
| Cable length and enclosure notes | Important for EMC, leakage, and outdoor use |
PulseWetProbe is not:
- a certified road-weather / RWIS instrument,
- a laboratory EC meter,
- a salinity meter,
- a freezing-point detector,
- a corrosion-proof hardware system,
- a universal soil-moisture accuracy solution,
- a safety-critical decision system without independent validation.
Use the diagnostics as heuristics until you have validation data for your exact deployment.
| Symptom | Likely cause | Try this |
|---|---|---|
| Always near 0% | Wrong pins, open electrode path, calibration too wide | Check wiring, log raw values, recalibrate |
| Always near 100% | Shorted electrodes, too-low resistance path, wrong wet value | Add/increase series resistance, inspect moisture bridge, recalibrate |
| Random dry readings | Floating analog input | Add a high-value bleed/reference path |
| Very noisy output | Long cable, poor grounding, EMI, unstable contact | Shorten cable, twist/shield wires, use stable filter preset |
| ESP32 touch fails | Board/core/GPIO does not support touch | Use a verified touch-capable pin or fallback to two-plate mode |
| JSON unavailable | Tiny/AVR or JSON disabled | Use CSV buffer output |
| Reversible mode strange on ESP8266 | ESP8266 has one user ADC channel | Prefer the simple two-plate topology |
| Document | What it explains |
|---|---|
docs/API_GUIDE.md |
Public API, beginner wrappers, acquisition/filter/output usage |
docs/BOARD_SUPPORT_MATRIX.md |
Board families, defaults, features, limitations |
docs/HARDWARE_WIRING_AND_SCHEMATICS.md |
Wiring topologies and schematic notes |
docs/ELECTRODE_SAFETY.md |
Current limiting, Hi-Z rest, electrode caveats |
docs/EMC_ESD_GUIDE.md |
EMC/ESD and outdoor wiring guidance |
docs/SENSING_MODEL.md |
Physical sensing model and output semantics |
docs/NOISE_FILTERING.md |
Median, moving average, EMA, trimmed mean, Hampel, outlier rejection |
docs/INDUSTRIAL_CALIBRATION.md |
Dry/wet and multi-point calibration, CRC profiles |
docs/ANTI_CORROSION.md |
Anti-corrosion limits and reversible mode |
docs/POWER_SAVING.md |
Power-aware interval behavior and sleep caveats |
docs/VALIDATION.md |
Suggested compile and hardware validation evidence |
docs/VALIDATION_EVIDENCE.md |
Current evidence status |
docs/LIMITATIONS.md |
Known limits and conservative claim boundaries |
docs/TROUBLESHOOTING.md |
Common wiring, compile, and runtime issues |
docs/RELEASE_CHECKLIST.md |
Release gate checklist |
Before publishing a new release:
-
library.properties,library.json,CITATION.cff, andCHANGELOG.mdhave the same release version. - Arduino Lint passes in strict Library Manager mode.
- All examples compile on the CI matrix.
- Host smoke tests pass.
- README and docs avoid certified RWIS / EC / salinity / freezing-point claims unless supported by independent evidence.
- Hardware validation evidence is attached when hardware performance is highlighted.
- Git tag matches the version, for example
v0.3.7.
Recommended tag flow:
git add README.md CHANGELOG.md library.properties library.json CITATION.cff docs/
git commit -m "docs: polish README for v0.3.7"
git tag v0.3.7
git push origin main --tagsContributions are welcome when they keep the project portable, evidence-based, and Arduino-friendly.
Please read:
Good contributions include:
- board compile reports,
- CSV validation logs,
- documentation improvements,
- low-memory fixes,
- safer examples,
- conservative diagnostics improvements,
- issue reports with board/core version and minimal reproducible sketches.
If you use PulseWetProbe in research notes, publications, technical reports, or validation datasets, please cite the project using CITATION.cff.
PulseWetProbe is released under the MIT License.
Built for practical Arduino sensing experiments: simple enough for a first soil probe, careful enough for field-oriented validation work.
