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>
This commit is contained in:
commit
ad9c457ea7
24 files changed
+2178
No files matched your search
@@ -0,0 +1,164 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 980 600" width="980" height="600" role="img" aria-labelledby="t1">
|
||||
<title id="t1">System architecture: six HW-390 probes over Cat5 to an ESP32-S3, published to Home Assistant over MQTT</title>
|
||||
<style>
|
||||
.bg { fill: #fcfcfa; }
|
||||
.box { fill: #f3f4f6; stroke: #16181d; stroke-width: 1.6; }
|
||||
.boxhi { fill: #eef2ff; stroke: #1d4ed8; stroke-width: 1.8; }
|
||||
.boxpw { fill: #fff3ec; stroke: #c2410c; stroke-width: 1.8; }
|
||||
.boxha { fill: #ecfdf3; stroke: #15803d; stroke-width: 1.8; }
|
||||
.ttl { font: 600 19px ui-sans-serif, system-ui, 'Segoe UI', Roboto, sans-serif; fill: #16181d; }
|
||||
.h { font: 600 13.5px 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; }
|
||||
.mb { font: 600 11px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; fill: #1d4ed8; }
|
||||
.sig { fill: none; stroke: #1d4ed8; stroke-width: 1.5; }
|
||||
.cat5 { fill: none; stroke: #1d4ed8; stroke-width: 8; stroke-linecap: round; }
|
||||
.pwr { fill: none; stroke: #c2410c; stroke-width: 2.2; }
|
||||
.data { fill: none; stroke: #15803d; stroke-width: 2.2; }
|
||||
.dash { stroke-dasharray: 5 4; }
|
||||
</style>
|
||||
|
||||
<rect class="bg" x="0" y="0" width="980" height="600"/>
|
||||
<text class="ttl" x="20" y="30">Soil Moisture Node — system architecture</text>
|
||||
<text class="s" x="20" y="49">Up to 6 HW-390 v2.0 probes per node · one Cat5 run · multi-node safe via MAC-derived ID</text>
|
||||
|
||||
<!-- ============ probes ============ -->
|
||||
<text class="h" x="20" y="80">Field sensors</text>
|
||||
<g>
|
||||
<rect class="box" x="20" y="90" width="152" height="44" rx="5"/>
|
||||
<text class="h" x="32" y="110">HW-390 #1</text><text class="m" x="32" y="126">sealed · 5mA</text>
|
||||
<rect class="box" x="20" y="146" width="152" height="44" rx="5"/>
|
||||
<text class="h" x="32" y="166">HW-390 #2</text><text class="m" x="32" y="182">sealed · 5mA</text>
|
||||
<rect class="box" x="20" y="202" width="152" height="44" rx="5"/>
|
||||
<text class="h" x="32" y="222">HW-390 #3</text><text class="m" x="32" y="238">sealed · 5mA</text>
|
||||
<rect class="box" x="20" y="258" width="152" height="44" rx="5"/>
|
||||
<text class="h" x="32" y="278">HW-390 #4</text><text class="m" x="32" y="294">sealed · 5mA</text>
|
||||
<rect class="box" x="20" y="314" width="152" height="44" rx="5"/>
|
||||
<text class="h" x="32" y="334">HW-390 #5</text><text class="m" x="32" y="350">sealed · 5mA</text>
|
||||
<rect class="box" x="20" y="370" width="152" height="44" rx="5"/>
|
||||
<text class="h" x="32" y="390">HW-390 #6</text><text class="m" x="32" y="406">sealed · 5mA</text>
|
||||
</g>
|
||||
<text class="s" x="20" y="437">Each: XC6206 LDO 5V→3.3V on-board.</text>
|
||||
<text class="s" x="20" y="452">Upper PCB epoxy-sealed; prongs bare.</text>
|
||||
|
||||
<!-- ============ cat5 funnel ============ -->
|
||||
<text class="h" x="186" y="80">Cat5 24AWG</text>
|
||||
<text class="s" x="186" y="96">8 conductors · T568B</text>
|
||||
<path class="sig" d="M172,112 Q212,112 244,252"/>
|
||||
<path class="sig" d="M172,168 Q212,168 244,252"/>
|
||||
<path class="sig" d="M172,224 Q216,224 244,252"/>
|
||||
<path class="sig" d="M172,280 Q216,280 244,252"/>
|
||||
<path class="sig" d="M172,336 Q212,336 244,252"/>
|
||||
<path class="sig" d="M172,392 Q212,392 244,252"/>
|
||||
<path class="cat5" d="M244,252 L300,252"/>
|
||||
|
||||
<!-- ============ breakout ============ -->
|
||||
<rect class="boxhi" x="300" y="112" width="184" height="302" rx="6"/>
|
||||
<text class="h" x="314" y="136">RJ45 breakout</text>
|
||||
<text class="s" x="314" y="153">at MCU enclosure</text>
|
||||
|
||||
<rect x="314" y="166" width="156" height="96" rx="4" fill="#ffffff" stroke="#1d4ed8" stroke-width="1.2"/>
|
||||
<text class="h" x="326" y="187">6× RC low-pass</text>
|
||||
<text class="m" x="326" y="205">10 kΩ series</text>
|
||||
<text class="m" x="326" y="221">0.1 µF to GND</text>
|
||||
<text class="m" x="326" y="237">at the ADC pin</text>
|
||||
<text class="m" x="326" y="253" fill="#c2410c">MANDATORY §3.1</text>
|
||||
|
||||
<rect x="314" y="274" width="156" height="58" rx="4" fill="#ffffff" stroke="#374151" stroke-width="1.2"/>
|
||||
<text class="h" x="326" y="294">Star ground</text>
|
||||
<text class="m" x="326" y="312">single point, no</text>
|
||||
<text class="m" x="326" y="326">daisy-chaining</text>
|
||||
|
||||
<text class="m" x="314" y="356" fill="#c2410c">pin 2 → +5V (switched)</text>
|
||||
<text class="m" x="314" y="373" fill="#374151">pin 1 → GND</text>
|
||||
<text class="m" x="314" y="390" fill="#1d4ed8">pins 3-8 → 6 signals</text>
|
||||
|
||||
<!-- breakout to mcu -->
|
||||
<path class="sig" d="M484,252 L522,252" marker-end="url(#arrowb)"/>
|
||||
|
||||
<!-- ============ esp32 ============ -->
|
||||
<rect class="boxhi" x="530" y="112" width="180" height="302" rx="6"/>
|
||||
<text class="h" x="544" y="136">ESP32-S3</text>
|
||||
<text class="s" x="544" y="153">YD-ESP32-S3 (WROOM-1)</text>
|
||||
|
||||
<rect x="544" y="166" width="152" height="122" rx="4" fill="#ffffff" stroke="#1d4ed8" stroke-width="1.2"/>
|
||||
<text class="h" x="556" y="186">ADC1 only</text>
|
||||
<text class="s" x="556" y="201">ADC2 is owned by Wi-Fi</text>
|
||||
<text class="m" x="556" y="221">GPIO4-9 moisture 1-6</text>
|
||||
<text class="m" x="556" y="238">GPIO10 battery÷2</text>
|
||||
<text class="m" x="556" y="255">GPIO14 rail MOSFET</text>
|
||||
<text class="m" x="556" y="272">GPIO48 status LED</text>
|
||||
|
||||
<rect x="544" y="300" width="152" height="100" rx="4" fill="#ffffff" stroke="#374151" stroke-width="1.2"/>
|
||||
<text class="h" x="556" y="320">Firmware</text>
|
||||
<text class="m" x="556" y="338">16× oversample</text>
|
||||
<text class="m" x="556" y="354">trim 10% outliers</text>
|
||||
<text class="m" x="556" y="370">eFuse ADC cal</text>
|
||||
<text class="m" x="556" y="386">2-point curve → %</text>
|
||||
|
||||
<!-- ============ wifi ============ -->
|
||||
<path class="data" d="M710,220 L770,180" marker-end="url(#arrowg)"/>
|
||||
<text class="s" x="716" y="248">Wi-Fi</text>
|
||||
<text class="s" x="716" y="262">MQTT</text>
|
||||
|
||||
<!-- ============ broker + HA ============ -->
|
||||
<rect class="boxha" x="790" y="130" width="170" height="86" rx="6"/>
|
||||
<text class="h" x="804" y="153">MQTT broker</text>
|
||||
<text class="s" x="804" y="170">Mosquitto</text>
|
||||
<text class="m" x="804" y="190">soil/<node>/state</text>
|
||||
<text class="m" x="804" y="206">retained JSON</text>
|
||||
|
||||
<path class="data" d="M875,216 L875,266" marker-end="url(#arrowg)"/>
|
||||
|
||||
<rect class="boxha" x="790" y="270" width="170" height="120" rx="6"/>
|
||||
<text class="h" x="804" y="293">Home Assistant</text>
|
||||
<text class="s" x="804" y="310">auto-discovery, no YAML</text>
|
||||
<text class="m" x="804" y="331">moisture 1-6 %</text>
|
||||
<text class="m" x="804" y="348">battery level %</text>
|
||||
<text class="m" x="804" y="365">wifi signal dBm</text>
|
||||
<text class="m" x="804" y="382">uptime s</text>
|
||||
|
||||
<!-- ============ power path ============ -->
|
||||
<!-- Flow runs right-to-left along the bottom: source -> MOSFET -> breakout -> Cat5 -->
|
||||
<rect class="boxpw" x="530" y="440" width="180" height="76" rx="6"/>
|
||||
<text class="h" x="544" y="462">Power source</text>
|
||||
<text class="m" x="544" y="481">mains: 5V USB / buck</text>
|
||||
<text class="m" x="544" y="497">remote: 18650 + solar</text>
|
||||
<text class="m" x="544" y="512">→ boost to 5V</text>
|
||||
|
||||
<path class="pwr" d="M530,470 L492,470" marker-end="url(#arrowr)"/>
|
||||
|
||||
<rect class="boxpw" x="300" y="440" width="184" height="76" rx="6"/>
|
||||
<text class="h" x="314" y="462">Switched 5V rail</text>
|
||||
<text class="m" x="314" y="481">P-FET high-side</text>
|
||||
<text class="m" x="314" y="497">HIGH = sensors on</text>
|
||||
<text class="m" x="314" y="512">floating = off</text>
|
||||
|
||||
<path class="pwr" d="M392,440 L392,418" marker-end="url(#arrowr)"/>
|
||||
|
||||
<!-- GPIO14 gate control: out the right of the MCU, under both boxes, up into the FET -->
|
||||
<path class="pwr dash" d="M690,414 L730,414 L730,548 L430,548 L430,522" marker-end="url(#arrowr)"/>
|
||||
<text class="m" x="560" y="542" fill="#c2410c">GPIO14 gate</text>
|
||||
|
||||
<!-- ============ legend ============ -->
|
||||
<line x1="20" y1="575" x2="46" y2="575" stroke="#1d4ed8" stroke-width="2.2"/>
|
||||
<text class="s" x="54" y="579">analog signal</text>
|
||||
<line x1="152" y1="575" x2="178" y2="575" stroke="#c2410c" stroke-width="2.2"/>
|
||||
<text class="s" x="186" y="579">power</text>
|
||||
<line x1="248" y1="575" x2="274" y2="575" stroke="#15803d" stroke-width="2.2"/>
|
||||
<text class="s" x="282" y="579">network / data</text>
|
||||
<line x1="378" y1="575" x2="404" y2="575" stroke="#c2410c" stroke-width="2.2" stroke-dasharray="5 4"/>
|
||||
<text class="s" x="412" y="579">control</text>
|
||||
|
||||
<defs>
|
||||
<marker id="arrowb" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto">
|
||||
<path d="M0,0 L9,4.5 L0,9 z" fill="#1d4ed8"/>
|
||||
</marker>
|
||||
<marker id="arrowr" 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="arrowg" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto">
|
||||
<path d="M0,0 L9,4.5 L0,9 z" fill="#15803d"/>
|
||||
</marker>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.9 KiB |
Reference in new issue
Block a user