Files
mattandClaude ad9c457ea7 Initial firmware for ESP32-S3 six-channel soil moisture node
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>
2026-08-14 13:38:41 -07:00

5.9 KiB
Raw Permalink Blame History

Wiring & Breakout

Implements PRD §2 (cabling) and §3.1–3.2 (signal integrity, ground bounce).

System architecture

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)

RJ45 breakout schematic

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)

High-side P-FET switch

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)

Solar and battery topology

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.