Files
soilMoistureSystem/docs/img/battery-solar.svg
T
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

114 lines
5.9 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 940 520" width="940" height="520" role="img" aria-labelledby="t4">
<title id="t4">Solar and battery power topology with boost converter and battery sense divider</title>
<style>
.bg { fill: #fcfcfa; }
.ttl { font: 600 19px ui-sans-serif, system-ui, 'Segoe UI', Roboto, sans-serif; fill: #16181d; }
.h { font: 600 13px ui-sans-serif, system-ui, 'Segoe UI', Roboto, sans-serif; fill: #16181d; }
.s { font: 11.5px ui-sans-serif, system-ui, 'Segoe UI', Roboto, sans-serif; fill: #6b7280; }
.m { font: 11px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; fill: #374151; }
.box { fill: #f3f4f6; stroke: #16181d; stroke-width: 1.6; }
.boxp { fill: #fff3ec; stroke: #c2410c; stroke-width: 1.8; }
.boxs { fill: #eef2ff; stroke: #1d4ed8; stroke-width: 1.8; }
.pw { fill: none; stroke: #c2410c; stroke-width: 2.2; }
.sigw { fill: none; stroke: #1d4ed8; stroke-width: 1.8; }
.gw { fill: none; stroke: #374151; stroke-width: 1.9; }
.comp { fill: #ffffff; stroke: #16181d; stroke-width: 1.5; }
</style>
<rect class="bg" x="0" y="0" width="940" height="520"/>
<text class="ttl" x="20" y="30">Solar / battery topology — remote node</text>
<text class="s" x="20" y="49">The cell cannot feed the probes directly: an 18650 runs 3.0–4.2V and the HW-390's XC6206 LDO needs ~3.4V of headroom.</text>
<!-- ============ chain ============ -->
<rect class="boxp" x="24" y="96" width="150" height="80" rx="6"/>
<text class="h" x="38" y="120">Solar panel</text>
<text class="m" x="38" y="139">6V / 1–2W</text>
<text class="s" x="38" y="158">sized for winter, not</text>
<text class="s" x="38" y="171">for a sunny afternoon</text>
<path class="pw" d="M174,136 L216,136" marker-end="url(#ar)"/>
<rect class="boxp" x="222" y="96" width="160" height="80" rx="6"/>
<text class="h" x="236" y="120">TP4056 + DW01</text>
<text class="m" x="236" y="139">CC/CV charger</text>
<text class="s" x="236" y="158">protection variant only</text>
<text class="s" x="236" y="171">— no MPPT</text>
<path class="pw" d="M382,136 L424,136" marker-end="url(#ar)"/>
<rect class="boxp" x="430" y="96" width="140" height="80" rx="6"/>
<text class="h" x="444" y="120">18650 Li-Ion</text>
<text class="m" x="444" y="139">3.0 – 4.2 V</text>
<text class="s" x="444" y="158">VBAT node</text>
<!-- split to MCU and boost -->
<circle cx="600" cy="136" r="4" fill="#c2410c"/>
<path class="pw" d="M570,136 L600,136"/>
<path class="pw" d="M600,136 L600,86 L716,86" marker-end="url(#ar)"/>
<path class="pw" d="M600,136 L600,206 L716,206" marker-end="url(#ar)"/>
<rect class="boxs" x="722" y="56" width="192" height="62" rx="6"/>
<text class="h" x="736" y="79">ESP32-S3 · 5V/VIN</text>
<text class="s" x="736" y="97">onboard reg accepts VBAT</text>
<text class="m" x="736" y="112">deep sleep ≈ 10 µA</text>
<rect class="boxp" x="722" y="176" width="192" height="62" rx="6"/>
<text class="h" x="736" y="199">Boost converter → 5V</text>
<text class="s" x="736" y="217">on the SWITCHED side of</text>
<text class="s" x="736" y="231">the P-FET, not before it</text>
<path class="pw" d="M818,238 L818,282" marker-end="url(#ar)"/>
<rect class="boxp" x="722" y="288" width="192" height="54" rx="6"/>
<text class="h" x="736" y="310">P-FET → Cat5 pin 2</text>
<text class="s" x="736" y="329">see power-switch.svg</text>
<!-- ============ battery sense divider ============ -->
<path class="sigw" d="M500,176 L500,222"/>
<circle cx="500" cy="176" r="4" fill="#c2410c"/>
<text class="h" x="300" y="228">Battery sense divider</text>
<rect class="comp" x="480" y="222" width="40" height="52" rx="2"/>
<text class="m" x="528" y="252">R1 1 MΩ</text>
<path class="sigw" d="M500,274 L500,302"/>
<circle cx="500" cy="302" r="4" fill="#1d4ed8"/>
<path class="sigw" d="M500,302 L640,302" marker-end="url(#ab)"/>
<text class="h" x="648" y="299">GPIO10</text>
<text class="s" x="648" y="315">ADC1_CH9</text>
<!-- shunt cap -->
<path class="sigw" d="M566,302 L566,336"/>
<circle cx="566" cy="302" r="3.4" fill="#1d4ed8"/>
<line x1="555" y1="336" x2="577" y2="336" stroke="#16181d" stroke-width="1.8"/>
<line x1="555" y1="342" x2="577" y2="342" stroke="#16181d" stroke-width="1.8"/>
<path class="gw" d="M566,342 L566,368"/>
<use xlink:href="#gnd" href="#gnd" x="566" y="368"/>
<text class="m" x="584" y="342">1 µF</text>
<rect class="comp" x="480" y="302" width="40" height="52" rx="2"/>
<text class="m" x="410" y="332">R2 1 MΩ</text>
<path class="gw" d="M500,354 L500,382"/>
<use xlink:href="#gnd" href="#gnd" x="500" y="382"/>
<!-- ============ notes ============ -->
<rect x="20" y="410" width="900" height="90" rx="5" fill="#fff3ec" stroke="#c2410c" stroke-width="1.4"/>
<text class="h" x="34" y="431">Why 1 MΩ and not the usual 100 kΩ.</text>
<text class="s" x="34" y="450">A 100 kΩ/100 kΩ divider draws ~21 µA continuously — more than the sleeping MCU itself (~10 µA), and enough to dominate</text>
<text class="s" x="34" y="468">the winter power budget. 1 MΩ/1 MΩ draws ~2 µA. The 1 µF cap is not optional at that impedance: it supplies the ADC's sampling charge.</text>
<text class="s" x="34" y="486">Trim BATTERY_DIVIDER_RATIO in config.h against a meter to cancel resistor tolerance.</text>
<defs>
<marker id="ar" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto">
<path d="M0,0 L9,4.5 L0,9 z" fill="#c2410c"/>
</marker>
<marker id="ab" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto">
<path d="M0,0 L9,4.5 L0,9 z" fill="#1d4ed8"/>
</marker>
<g id="gnd">
<line x1="-10" y1="0" x2="10" y2="0" stroke="#374151" stroke-width="1.9"/>
<line x1="-6" y1="4.5" x2="6" y2="4.5" stroke="#374151" stroke-width="1.9"/>
<line x1="-2.5" y1="9" x2="2.5" y2="9" stroke="#374151" stroke-width="1.9"/>
</g>
</defs>
</svg>