# 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.