Implements ESP32_Soil_Moisture_Node_PRD.md on the VCC-GND Studio
YD-ESP32-S3 (ESP32-S3-WROOM-1) using Arduino via PlatformIO, publishing
to Home Assistant over MQTT with auto-discovery.
Two power profiles behind a compile-time switch (PRD 4):
-e mains always on, publishes every 60s
-e battery RTC wake, sample, publish, deep sleep 15min
PRD mitigations:
- All six probes on ADC1 (GPIO4-9); ADC2 is unusable while Wi-Fi is up.
- analogReadMilliVolts() for eFuse ADC calibration rather than raw counts.
- 16 samples per probe, sorted, 10% trimmed from each end, averaged.
- Per-probe two-point calibration; HW-390 output is inverted (dry reads
high), and an inverted config is rejected rather than silently producing
a backwards curve.
- MAC-derived node ID so identical binaries never collide on topics.
Design decisions that diverge from a literal reading of the PRD:
- Out-of-range probes publish null (HA "unknown") instead of clamping to
0%/100%. A cut Cat5 pair reads ~0mV, which clamps to a believable "very
dry" and would drive irrigation on wrong data.
- The battery profile uses expire_after rather than an availability topic;
a sleeping node is offline most of the time and would sit greyed out.
- AWAKE_BUDGET_MS forces deep sleep if a wake cycle overruns, covering
failure modes the per-step network timeouts miss (DHCP never issued,
CONNACK never sent) that would otherwise flatten the cell.
- docs/WIRING.md notes the 18650 cannot feed the probes directly: the
XC6206 LDO needs ~3.4V headroom, so a boost converter is required on the
switched side of the MOSFET.
Both profiles compile clean under -Wall. Not yet run on hardware; pin map,
MOSFET circuit and calibration defaults are unverified against a board.
Co-Authored-By: Claude <noreply@anthropic.com>
80 lines
3.3 KiB
Markdown
80 lines
3.3 KiB
Markdown
# Two-point calibration
|
||
|
||
Implements PRD §3.3. Do this **once per probe**, after sealing them (§2.3) and
|
||
with the probe connected through its final Cat5 run — the cable and the RC
|
||
filter both shift the numbers, so calibrating on the bench with short leads
|
||
produces values that are wrong in the field.
|
||
|
||
## Why per-probe
|
||
|
||
`CAL_AIR_MV` / `CAL_WATER_MV` in `include/config.h` are arrays, not scalars.
|
||
HW-390s vary: each has its own XC6206 LDO, its own coating thickness, and its
|
||
own cable run. Using one shared pair of endpoints for six probes typically
|
||
costs 5–15% of absolute accuracy, which is enough to trigger irrigation on a
|
||
bed that is already wet.
|
||
|
||
The defaults shipped in `config.h` are placeholders. They compile and produce
|
||
plausible-looking output, which makes them dangerous — plausible output is
|
||
exactly what stops you noticing you skipped this step.
|
||
|
||
## Procedure
|
||
|
||
1. Flash the mains build and let the node come up:
|
||
```
|
||
pio run -e mains -t upload -t monitor
|
||
```
|
||
|
||
2. In Home Assistant, find the **Raw mV 1–6** diagnostic entities on the node's
|
||
device page. (These exist for this procedure; set `PUBLISH_RAW_MV 0` in
|
||
`config.h` to hide them once you are done.) You can also read them straight
|
||
off the serial monitor in the `state:` line.
|
||
|
||
3. **Air point (0%).** Probe clean, dry, in open air. Wait ~30 s for the value
|
||
to settle. Record `raw_mv_N` → this is `CAL_AIR_MV[N-1]`.
|
||
|
||
4. **Water point (100%).** Submerge the probe in a glass of water **up to but
|
||
not past the marked line** on the PCB. Do not submerge the electronics.
|
||
Wait ~30 s. Record `raw_mv_N` → this is `CAL_WATER_MV[N-1]`.
|
||
|
||
5. Paste both arrays into `include/config.h` and reflash.
|
||
|
||
## Expected values
|
||
|
||
A healthy 3.3 V HW-390 v2.0 reads roughly:
|
||
|
||
| Condition | Typical |
|
||
|-----------|---------|
|
||
| Air (dry) | 2400–2800 mV |
|
||
| Water | 1000–1400 mV |
|
||
|
||
**Air must read higher than water** — the HW-390's output is inverted. The
|
||
firmware rejects `CAL_AIR_MV <= CAL_WATER_MV` at runtime and logs an error
|
||
rather than silently producing an inverted percentage, because a
|
||
moisture curve running backwards is very easy to miss on a dashboard and would
|
||
tell your irrigation to water dry soil less.
|
||
|
||
## Troubleshooting
|
||
|
||
**Air-to-water spread under ~800 mV.** Usually a probe that is degrading, or
|
||
one whose coating crept onto the prongs. A narrow span makes the percentage
|
||
noisy no matter how much you oversample.
|
||
|
||
**Reads ~0 mV.** No power reaching the probe: check the Cat5 5 V pair, the
|
||
MOSFET gate, and the splice. The firmware reports this as *unknown* rather than
|
||
0%, which is the giveaway.
|
||
|
||
**Reads ~3300 mV (rail).** Signal line shorted to the 5 V pair, or the probe's
|
||
output is disconnected and the ADC pin is floating up.
|
||
|
||
**Reading drifts down over weeks with no rain.** Moisture ingress into an
|
||
imperfectly sealed board. Re-seal per `WIRING.md` §6 and recalibrate.
|
||
|
||
## Fault margin
|
||
|
||
`SENSOR_FAULT_MARGIN_MV` (default 400 mV) sets how far outside the calibrated
|
||
window a reading may sit before it is reported as unavailable instead of being
|
||
clamped. Soil genuinely can read slightly outside the air/water endpoints —
|
||
saturated clay can go below the water point — so the margin exists to avoid
|
||
false faults. Widen it if healthy probes flap to *unknown*; narrow it if real
|
||
failures are being clamped into believable numbers.
|