Static snapshot — not a live app. These are saved files, not the running program. Every reading is simulated demo data, frozen at the moment of capture, and the charts are drawn from saved API responses. Buttons, forms and the 5-second live refresh do nothing here. What is for sale is the Flask source you run yourself → · All demos

VPD + Dew Point
Live VPD (kPa) and dew point (°F) from latest Temp/RH using Tetens + Magnus

Monochrome, print-ready documentation. Emphasis on exact formulas, unit handling, payloads, and drop-in code.

Contents

  1. Overview
  2. Inputs & Units
  3. Formulas (Tetens + Magnus)
  4. Target Ranges
  5. Network & Endpoint
  6. Payload Contract
  7. Python Reference Implementation
  8. JavaScript Reference Implementation
  9. Minimal Front-End Widget
  10. Runtime Behavior
  11. Troubleshooting
  12. Validation Checklist
  13. Security & Hardening
  14. Extensions & Variations
  15. Interpreting VPD & Dew Point
  16. Quick Reference

1) Overview

Compute two derived climate metrics for the tent from air temperature and relative humidity:

Implementation uses Tetens for saturation vapor pressure and Magnus for dew point. Constants match the dashboard implementation.

2) Inputs & Units

KeyTypeUnitsNotes
air_temp_ffloat°FFrom your DHTxx/SHTxx/BME280 pipeline. Convert to °C for math.
rh_pctfloat%0–100. Clamp to [1, 100] to avoid log(0) in Magnus.
leaf_temp_cfloat (optional)°CIf you have IR leaf temp, use it for VPD saturation term. Else use air temp.

3) Formulas (Tetens + Magnus)

3.1 Tetens (kPa)

Use for saturation vapor pressure at temperature TC (°C):

// Saturation vapor pressure (kPa)
e_s(Tc) = 0.6108 * exp( (17.27 * Tc) / (Tc + 237.3) )

// Actual vapor pressure (kPa)
e_a = e_s(Tc_air) * (RH / 100.0)

// Vapor Pressure Deficit (kPa)
// Use Tc_leaf if available, else Tc_air
VPD = max(0, e_s(Tc_for_sat) - e_a)

0.6108 and 17.27/237.3 are used in the dashboard. Keep these constants consistent across server and browser.

3.2 Magnus (dew point)

Dew point Td (°C) from air temp and RH:

a = 17.62
b = 243.12  // °C
gamma = ln(RH/100) + (a * Tc_air) / (b + Tc_air)
Td_C = (b * gamma) / (a - gamma)
Td_F = Td_C * 9/5 + 32

Clamp RH to [1, 100] before computing ln(RH/100).

4) Target Ranges

StageVPD Target (kPa)Notes
Seedling/Clones0.6–0.8Higher humidity to reduce stress.
Vegetative0.8–1.2Balanced growth and transpiration.
Flower/Fruiting1.2–1.6Lower humidity to limit mold risk.
Condensation risk: if air_temp_f − dew_point_f < 3–5°F, surfaces may sweat. Increase airflow or reduce RH.

5) Network & Endpoint

Assume your server exposes the latest air temp and RH, and accepts derived metrics:

GET latest envGET http://<host>:5001/api/latest
POST derivedPOST http://<host>:5001/add_data (single reading per request)
Content-Typeapplication/json
IntervalEvery 60 s (or on new sensor sample)
Timeout5 s

6) Payload Contract

6.1 Input example (/api/latest)

{
  "temp": 77.9,           // °F
  "humidity": 58.2,       // % RH
  "light": 123            // lux (example)
}

6.2 Derived POST (/add_data)

{
  "sensor_type": "vpd_kpa",  // or "dew_point_f"
  "reading": 1.12,            // numeric value only
  "plant_id": null            // optional
}
// Post one reading per request.
// Example (dew point): {"sensor_type":"dew_point_f","reading":61.7}

Return HTTP 200/201 on success. Log non-2xx with input values.

7) Python Reference Implementation

# Pure functions with explicit units and clamps.
# Matches the dashboard constants (Tetens + Magnus).

import math
from typing import Tuple

A_TETENS = 17.27
B_TETENS = 237.3   # °C
K0 = 0.6108        # kPa

A_MAGNUS = 17.62
B_MAGNUS = 243.12  # °C

def f_to_c(tf: float) -> float:
    return (tf - 32.0) * (5.0/9.0)

def c_to_f(tc: float) -> float:
    return tc * (9.0/5.0) + 32.0

def tetens_es_kpa(tc: float) -> float:
    # Saturation vapor pressure (kPa) at Tc (°C)
    return K0 * math.exp((A_TETENS * tc) / (B_TETENS + tc))

def dew_point_f_from_air(tf_air: float, rh_pct: float) -> float:
    # Clamp RH to [1,100] to avoid log(0) and nonsense
    rh = max(1.0, min(100.0, rh_pct))
    tc = f_to_c(tf_air)
    gamma = math.log(rh / 100.0) + (A_MAGNUS * tc) / (B_MAGNUS + tc)
    td_c = (B_MAGNUS * gamma) / (A_MAGNUS - gamma)
    td_f = c_to_f(td_c)
    return td_f

def vpd_kpa_from_air(tf_air: float, rh_pct: float, tf_leaf: float | None = None) -> float:
    # Use leaf temperature for saturation term if available; else air temp.
    rh = max(0.0, min(100.0, rh_pct))
    tc_air = f_to_c(tf_air)
    tc_sat = f_to_c(tf_leaf) if tf_leaf is not None else tc_air
    es = tetens_es_kpa(tc_sat)              # saturation at leaf/air
    ea = tetens_es_kpa(tc_air) * (rh/100.0) # actual vapor pressure from air
    vpd = max(0.0, es - ea)                 # never negative
    return vpd

# Example usage with prints for debug:
if __name__ == "__main__":
    tf = 77.9
    rh = 58.2
    print(f"[DEBUG] air_temp_f={tf} rh_pct={rh}")
    dp = dew_point_f_from_air(tf, rh)
    vpd = vpd_kpa_from_air(tf, rh)  # no leaf temp
    print(f"[DEBUG] dew_point_f={dp:.2f} vpd_kpa={vpd:.3f}")

8) JavaScript Reference Implementation

<script>
// Minimal, no dependencies. Mirrors the dashboard implementation.

const A_T = 17.27, B_T = 237.3, K0 = 0.6108;     // Tetens (kPa)
const A_M = 17.62, B_M = 243.12;                 // Magnus (dew point)

function fToC(tf){ return (tf - 32) * (5/9); }
function cToF(tc){ return tc * (9/5) + 32; }
function tetensKPa(tc){ return K0 * Math.exp((A_T * tc) / (B_T + tc)); }

function dewPointF(airTempF, rhPct){
  const rh = Math.min(100, Math.max(1, rhPct));
  const Tc = fToC(airTempF);
  const gamma = Math.log(rh/100) + (A_M * Tc) / (B_M + Tc);
  const TdC = (B_M * gamma) / (A_M - gamma);
  const TdF = cToF(TdC);
  console.log("[dp] Tc=%s rh=%s TdF=%s", Tc.toFixed(2), rh.toFixed(1), TdF.toFixed(2));
  return TdF;
}

function vpdKPa(airTempF, rhPct, leafTempF=null){
  const rh = Math.min(100, Math.max(0, rhPct));
  const Tc_air = fToC(airTempF);
  const Tc_sat = leafTempF==null ? Tc_air : fToC(leafTempF);
  const es = tetensKPa(Tc_sat);
  const ea = tetensKPa(Tc_air) * (rh/100);
  const vpd = Math.max(0, es - ea);
  console.log("[vpd] es=%s ea=%s vpd=%s", es.toFixed(4), ea.toFixed(4), vpd.toFixed(4));
  return vpd;
}
</script>

9) Minimal Front-End Widget

Drop this into your dashboard page. It calls /api/latest and renders live VPD and dew point.

<div id="vpdbox" class="card">
  <strong>Live Air:</strong> <span id="air">--</span> °F,
  <strong>RH:</strong> <span id="rh">--</span> %,
  <strong>VPD:</strong> <span id="vpd">--</span> kPa,
  <strong>Dew Pt:</strong> <span id="dp">--</span> °F
</div>

<script>
async function refreshVPD(){
  try{
    const r = await fetch("api/latest.json", { cache:"no-store" });
    if(!r.ok) throw new Error("HTTP " + r.status);
    const j = await r.json();
    const tf = Number(j.temp);
    const rh = Number(j.humidity);
    const v = vpdKPa(tf, rh);
    const dpf = dewPointF(tf, rh);
    document.getElementById("air").textContent = tf.toFixed(1);
    document.getElementById("rh").textContent  = rh.toFixed(1);
    document.getElementById("vpd").textContent = v.toFixed(2);
    document.getElementById("dp").textContent  = dpf.toFixed(1);
    console.log("[widget] tf=%s rh=%s vpd=%s dp=%s", tf, rh, v, dpf);
  }catch(err){
    console.log("[widget] refresh error:", err);
  }
}
refreshVPD();
setInterval(refreshVPD, 60000);
</script>

10) Runtime Behavior

State Flow

  1. Fetch: Read latest temp and humidity from /api/latest.
  2. Compute: Derive vpd_kpa and dew_point_f.
  3. Publish: POST derived JSON to your store if you persist derived metrics.

Design Notes

  • Use leaf temp for saturation term when you add an IR sensor.
  • Never let VPD go negative. Clamp at zero.
  • Keep constants consistent across Python and JS.

11) Troubleshooting

Spiky VPD

  • Average RH over 30–60 s before deriving.
  • Avoid mixing °C/°F in the same call.

Dew Point Looks Wrong

  • Confirm RH in 0–100%. Clamp to [1,100] for Magnus.
  • Verify temperature conversion (F→C).

Condensation

  • Increase airflow across leaves and walls.
  • Reduce humidifier duty or increase setpoint VPD.

12) Validation Checklist

13) Security & Hardening

14) Extensions & Variations

15) Interpreting VPD & Dew Point

Plant Physiology (What VPD means)

  • Low VPD (< 0.4 kPa): Air is near saturation. Transpiration is slow; stomata may partially close due to low vapor gradient. Risk of mold/mildew increases. Consider lowering RH or increasing temperature/airflow.
  • Vegetative “comfort” (≈ 0.8–1.2 kPa): Healthy transpiration, good nutrient flow. Aim here during veg for balanced growth.
  • Flowering “comfort” (≈ 1.2–1.6 kPa): Slightly drier air suppresses mold risk and tightens internodes. Monitor irrigation—plants drink faster.
  • High VPD (> 1.8 kPa): Air is very dry relative to leaves. Plants can over-transpire, leading to leaf curl, edge burn, or Ca/Mg transport issues. Raise RH or lower temperature.

Dew Point (Condensation & Disease)

  • Safety margin: Track air_temp_f − dew_point_f. When this gap < 3–5°F, surfaces may sweat—prime conditions for botrytis/powdery mildew.
  • Night cycles: As air cools after lights-off, dew point may cross room temperature. Preempt by reducing RH before lights-out or by maintaining gentle airflow.
  • Leaf wetness proxy: You rarely measure leaf wetness—dew point margin is a practical proxy.

Operational Playbook

  • If VPD low: decrease humidifier duty, increase exhaust/air exchange, or raise temp slightly.
  • If VPD high: increase humidifier duty, reduce dehumidifier/exhaust, or lower temp.
  • Use stage targets above; adjust gradually to avoid plant shock.
  • Watch for symptoms: low VPD → droopy/leathery; high VPD → edge curl, rapid wilting between irrigations.

How the Dashboard Helps

  • Live VPD is computed client-side from temp and humidity pulled from /api/latest.
  • Dew point is calculated with Magnus; the hint under VPD classifies the range: “Low (risk of mold)”, “Optimal veg”, “Optimal flower”, or “High (plant stress)”.
  • Use the history charts and light cycle context to interpret spikes at lights-on/off.

16) Quick Reference

ItemValue
Tetens constant (kPa)0.6108
Tetens a17.27
Tetens b (°C)237.3
Magnus a17.62
Magnus b (°C)243.12
F→C(F − 32) × 5/9
C→FC × 9/5 + 32

This document is intentionally black-and-white for clean printing and photocopying.