Module M1 - Climate & VPD
Files: packages/grow_core.yaml (VPD calculation, Phase 2), packages/grow_climate.yaml (setpoints + regulation automation, Phase 3), packages/grow_actuators.yaml (device control proxies, used by this module and the “Devices” dashboard view)
What’s implemented
Phase 2: VPD calculation, live Temperature/Humidity/VPD display, and
historical charts with a time-range selector - see
docs/docs/dashboard-2-climate.md. Purely observational.
Phase 3: day/night setpoints and tolerance bands for RH, Temperature,
and VPD (decision R17), a regulation mode switch, and the regulation
automation itself (automation.grow_climate_regulation).
Setpoints and flags
All nine setpoint/tolerance input_number helpers, input_select.grow_climate_mode,
and the status flags are exposed on Dashboard 2’s “Climate & VPD” view,
under “Climate Setpoints (Phase 3 config)”:
input_boolean.grow_climate_is_daytime- which setpoint set is active. Kept in sync automatically with Module M3 (Light, Phase 6) byautomation.grow_climate_daytime_follows_light: it mirrorsbinary_sensor.grow_light_top_active(packages/grow_light.yaml), so this flips to Day exactly when the Top Light group turns on and to Night exactly when it turns off. You can still set it by hand at any time - it gets overwritten on the next light state change while the toggle below is on.input_boolean.grow_climate_daytime_follows_light- master toggle for the sync above. Defaults on. Turn it off to go back to settinggrow_climate_is_daytimemanually (e.g. Top Light isn’t mapped/enabled yet, or your day/night split shouldn’t follow the light schedule). Unlike the module master switches below, this only ever changes a flag - it never drives an actuator directly - so it’s safe to leave on even before you’ve turned on automatic climate control itself.input_boolean.grow_climate_control_enabled- master safety switch. Defaults off. Nothing is driven automatically until you turn this on - do that only after disabling any oldRH Control VPD-/VPD+automations, so two controllers never fight over the same device.input_select.grow_climate_mode- “RH/Temp Direct” or “VPD Driven”, see below.input_boolean.grow_climate_control_paused- automation-managed, turns on whenever a required sensor/actuator is unavailable (see Safety below).input_text.grow_climate_last_decision- automation-managed, a short human-readable line explaining what the last regulation cycle decided and why (e.g.RH/Temp Direct: RH 68.0% > target 55.0+5.0 -> dehumidifier ON, humidifier OFF.) - the quickest way to understand what the automation is doing without digging through logs.
Regulation modes
RH/Temp Direct - classic hysteresis control straight against the RH and
Temperature setpoints, independently. RH above setpoint + tolerance turns
the dehumidifier on (humidifier off); below setpoint - tolerance turns the
humidifier on (dehumidifier off); inside the band, both off.
VPD Driven - humidity is controlled using VPD itself as the target,
instead of RH directly. VPD above setpoint + tolerance (air too dry
relative to target) turns the humidifier on; below setpoint - tolerance
(too humid) turns the dehumidifier on; inside the band, both off. This is
the better fit for a tent or room with only an exhaust fan and no real way
to control temperature directly - VPD mode keeps the one variable that
actually matters (VPD) on target using only humidity control, without
needing a working Temperature setpoint at all.
Temperature/AC control runs independently of the mode above, in both
cases - it’s a separate axis. Only active if an AC is mapped on Dashboard 1;
cooling-only (turns the AC on above setpoint + tolerance, off at or below
setpoint), since no heater mapping exists in this project.
Safety (decision R7)
Every regulation cycle checks that the sensors it needs (temperature,
humidity, and VPD if in VPD Driven mode) and the actuators it would control
(dehumidifier/humidifier/AC, whichever are mapped) are all available. If
anything required is missing or unavailable, regulation pauses - sets
grow_climate_control_paused on, sends one persistent_notification (not
repeated every cycle), and does not touch any actuator that cycle. It
resumes automatically, with a “resumed” notification, once everything
required is available again.
Trigger cadence
The automation runs on every relevant sensor/setpoint/flag change (near-
instant reaction to real conditions or you adjusting a setpoint), plus a
/5 minute fallback re-check in case a trigger was missed.
Day/Night sync with the Light module (Phase 6)
A second, independent automation - automation.grow_climate_daytime_follows_light
- keeps
grow_climate_is_daytimematched tobinary_sensor.grow_light_top_active(Module M3,packages/grow_light.yaml) wheneverinput_boolean.grow_climate_daytime_follows_lightis on (the default): Top Light on -> Daytime Active on -> Day setpoints; Top Light off -> Daytime Active off -> Night setpoints. It reacts on every light state change and once at Home Assistant startup, and runs regardless of whethergrow_climate_control_enableditself is on, since it only ever flips a flag, never an actuator.
If binary_sensor.grow_light_top_active is unavailable (no Top Light slots
mapped/enabled yet - see docs/docs/module-light.md), it leaves
grow_climate_is_daytime untouched and sends one persistent_notification
rather than guessing, clearing automatically once a definite on/off state
is seen again. Turn grow_climate_daytime_follows_light off at any time to
go back to setting grow_climate_is_daytime by hand - useful if you don’t
have Top Light mapped yet, or if your actual day/night split shouldn’t
follow the light schedule 1:1.
Bug fix (2026-08-27) - this affected you if this sync never seemed to
work: binary_sensor.grow_light_top_active didn’t actually exist under
that entity_id before this fix (see docs/docs/module-light.md’s “Bug
fix” note - a template entity’s entity_id comes from its display name,
which for this entity was “Light: Top Light Active”, not from its
unique_id). In practice that meant this automation’s state trigger never
fired on a real light change, and every time it did run (HA startup, or
the follow-toggle turning on) it read the nonexistent entity as
unavailable and just sent the “sensor unavailable” notification instead of
syncing anything - so the day/night sync has likely never actually worked
since it was built, silently. Once the Light module fix is deployed (and,
if you’d already deployed the pre-fix version, the one-time entity rename
in docs/docs/module-light.md’s rollout checklist step 0 is done), this
starts working immediately - no changes needed here in grow_climate.yaml
itself.
Device control proxies (packages/grow_actuators.yaml)
Since the real actuator behind each function can change any time you
re-map it on Dashboard 1, both the regulation automation above and the
Dashboard 2 “Devices” view (manual on/off buttons for every mapped device,
see docs/docs/dashboard-2-climate.md) go through a set of stable template
switches (switch.grow_actuator_<function>) instead of the raw mapped
entity_id directly. See the comments in that file for the full rationale -
same pattern as the Phase 2 pass-through sensors, applied to switches.
Known issue (fixed in code, needs a one-time manual cleanup on your instance)
Four of the thirteen switch.grow_actuator_* proxies (Under Canopy 1/2,
Exhaust, Circulation) were originally given friendly names that didn’t
slugify to the entity_id the dashboard expects (e.g. “Grow Actuator:
Exhaust Fan” auto-generates switch.grow_actuator_exhaust_fan, not
switch.grow_actuator_exhaust) - Home Assistant derives entity_id from the
name at creation time, unique_id alone doesn’t control it. This showed up
as those four Devices-view tiles displaying “Entity not found” instead of
the intended name. The names in packages/grow_actuators.yaml are now
fixed to match, but because those four entities already got created once
under the wrong entity_id, redeploying the fixed YAML alone won’t rename
them - Home Assistant recognizes them by unique_id on reload and keeps
whatever entity_id they already have.
One-time fix after redeploying: go to Settings -> Devices & Services
-> Entities, search for grow_actuator_under_canopy_light and
grow_actuator_exhaust_fan / grow_actuator_circulation_fan, delete those
4 stale entities, then reload the Template integration (or restart). They
get recreated fresh under the correct entity_id, matching what the
dashboard already references.
Rollout checklist before enabling automatic control
- Deploy
packages/grow_climate.yamlandpackages/grow_actuators.yaml, check config, restart. - Set your day/night RH/Temp/VPD setpoints and pick a regulation mode on Dashboard 2.
- Disable any pre-existing
RH Control VPD-/VPD+automation so it doesn’t fight the new one. - If Module M3 (Light,
packages/grow_light.yaml) is deployed and at least one Top Light slot is mapped/enabled, leavegrow_climate_daytime_follows_lighton and confirminput_boolean.grow_climate_is_daytimeflips at the same moment the Top Lights do - otherwise turn it off and set Daytime Active yourself. - Watch
input_text.grow_climate_last_decisionand the “Devices” view for a while withgrow_climate_control_enabledstill off, to sanity-check what it would decide. - Turn
grow_climate_control_enabledon, ideally while you can watch the tent for the first cycle or two.