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>
5.9 KiB
Wiring & Breakout
Implements PRD §2 (cabling) and §3.1–3.2 (signal integrity, ground bounce).
1. Cat5 pinout
Standard T568B colours. Power and ground share one twisted pair so the supply loop area stays small (PRD §2.2).
| RJ45 pin | Colour | Function | MCU side |
|---|---|---|---|
| 1 | Orange/White | GND | GND |
| 2 | Orange | +5V | Switched 5V rail |
| 3 | Green/White | Analog signal 1 | GPIO4 (ADC1_CH3) |
| 6 | Green | Analog signal 2 | GPIO5 (ADC1_CH4) |
| 4 | Blue | Analog signal 3 | GPIO6 (ADC1_CH5) |
| 5 | Blue/White | Analog signal 4 | GPIO7 (ADC1_CH6) |
| 7 | Brown/White | Analog signal 5 | GPIO8 (ADC1_CH7) |
| 8 | Brown | Analog signal 6 | GPIO9 (ADC1_CH8) |
Known tradeoff: six signals over three pairs means each pair carries two signals rather than a signal and its return, which is the worst case for pair-to-pair crosstalk. It is acceptable here only because these are slow DC levels and the RC filters below roll off everything above a few hertz. If you ever repurpose this harness for anything with edges in it, re-plan the pairing.
2. RC low-pass filter — mandatory (PRD §3.1)
One per analog line, on the RJ45 breakout board, between the jack and the ESP32 pin:
RJ45 signal ──[ 10 kΩ ]──┬── GPIO (ADC1)
│
[ 0.1 µF ] ceramic, X7R
│
GND
Placement is the whole point: the capacitor must sit at the ESP32 end of the resistor, physically close to the pin. Put it at the jack end and you have filtered the cable but left the ADC pin exposed to everything picked up on the last few centimetres of board trace.
- 10 kΩ + 0.1 µF → τ = 1 ms. Fine for soil, which moves over minutes.
- 10 kΩ + 1 µF → τ = 10 ms. Quieter; still far faster than the sample burst.
The series resistor also raises source impedance above what the SAR ADC would like. The capacitor is what fixes that — it supplies the sampling charge — so do not fit the resistor without it.
3. Ground integrity (PRD §3.2)
- Every splice at the sensor end: soldered + heat-shrink, or a high-retention Wago. Twist-and-tape connections corrode and drift the ground reference.
- Budget ~5 mA per HW-390, so ~30 mA total. One 24 AWG conductor carries this with negligible drop over a normal garden run.
- Run one GND back to a single point at the MCU board. Do not daisy-chain sensor grounds through each other — that is exactly the shared-return path that makes one probe's current show up as another probe's reading.
4. Sensor power switch (battery profile)
SENSOR_POWER_GATE_PIN (GPIO14) is HIGH for sensors on. A 3.3 V GPIO
cannot drive a P-channel MOSFET's gate on a 5 V rail hard enough to fully turn
it off, so an inverting stage is required:
+5V ──────────┬──────────────┬─── source
│ │
[100kΩ] [ P-ch MOSFET ] e.g. AO3401
│ │
GPIO14 ──[10kΩ]──┤ gate └─── drain ──→ Cat5 pin 2 (+5V to sensors)
│
[ 2N7002 N-ch ] drain ──┘
│
GND
- GPIO14 HIGH → 2N7002 conducts → P-FET gate pulled low → rail on.
- GPIO14 LOW or floating → 100 kΩ pulls the gate to 5 V → rail off.
The pull-up is what makes deep sleep safe: pins float during sleep, and this guarantees a floating pin means off rather than undefined.
5. Battery & solar (PRD §4.2)
The 18650 cannot feed the sensors directly. A cell runs 3.0–4.2 V, and the HW-390's XC6206 LDO needs roughly 3.4 V of headroom to hold a clean 3.3 V. Wire the cell straight to the Cat5 5 V pair and the probes brown out and drift as the cell discharges — readings that look like the soil drying out. Put a boost converter to 5 V on the switched side of the MOSFET.
Solar panel (6V) ──→ TP4056 (with DW01 protection) ──→ 18650
│
├──→ ESP32-S3 5V/VIN
│
└──→ [P-FET] ──→ boost 5V ──→ Cat5 pin 2
Battery sense divider into GPIO10:
VBAT ──[ R1 1MΩ ]──┬── GPIO10 (ADC1_CH9)
│
[ R2 1MΩ ] ┬── [1µF]
│ │
GND GND
1 MΩ/1 MΩ draws ~2 µA, which is the right order next to the ESP32's ~10 µA deep-sleep current. The obvious 100 kΩ/100 kΩ pair draws ~21 µA — more than the sleeping MCU, and enough to dominate your winter power budget. The 1 µF cap is not optional at this impedance: it supplies the ADC's sampling charge.
BATTERY_DIVIDER_RATIO = 2.0 in include/config.h assumes R1 == R2. Measure
the actual cell voltage with a meter and adjust the ratio to trim out resistor
tolerance.
6. Sensor sealing (PRD §2.3)
Every HW-390's upper PCB — components, exposed board edges, and the splice — sealed with marine epoxy or conformal coating. Only the capacitive prongs stay bare. This is the single most common reason these probes die in a season; an unsealed board wicks moisture up under the silkscreen and the readings drift long before anything looks visibly corroded.