Industrial bridges · Home Assistant

Template Sensors.

Reading time
~24 min · 4,952 words
Section
All Home Assistant pages

A template sensor is a sensor whose value is calculated from other sensors rather than read from a device: VPD derived from temperature and humidity, daily light integral accumulated from a PAR sensor, dew point, growing degree days, a rolling average of CO2 readings. The derived values are what actually drive decisions: a humidity sensor says the air is moist, while the VPD computed from it says whether the plants are transpiring effectively. The toolkit is Jinja2 plus a short list of calculations worth implementing, and the main hazard is a template that fails quietly and reports wrong-but-plausible values.

Before building templates.

Prerequisites from earlier pages:

Source sensors in place. A template that calculates VPD needs a temperature sensor and a humidity sensor to calculate from. A DLI template needs a PAR sensor. The source entities have to exist first, under known names, producing numeric values in known units. The Your First Sensor page covers adding a first sensor; the ESPHome page covers building custom sensors.

Areas and naming conventions. Per Organizing Home Assistant for a Farm. Template sensors need clear names that match the convention. sensor.greenhouse_zone_1_vpd is meaningful; sensor.template_sensor_1 is not.

Measurement theory comfort. Understanding Sensors covers the units and measurement principles behind the values templates calculate. A grower building a VPD template who does not understand what VPD is, or why it matters, will not know when the template is producing nonsense. Templates amplify both correct and incorrect inputs.

Some Jinja2 tolerance. Templates are Jinja2 expressions. Most agricultural calculations use only the basic subset: arithmetic, a few math functions, conditionals. Reading the examples on this page and adjusting them is enough to get productive. Deep Jinja2 mastery is not needed; deep Jinja2 mastery is also not harmful.

What a template sensor is.

A template sensor is a sensor entity whose state is the output of a Jinja2 template rather than a direct measurement. Home Assistant evaluates the template against current entity states (and other data) and sets the sensor's state to the result.

The shape of a template sensor. A template sensor has a name, a unit of measurement, a device class (optional, enables better dashboard rendering), and the template itself. Example in plain English: "Sensor named Greenhouse Zone 1 VPD, reports kPa, calculated from the temperature and humidity sensors in Greenhouse Zone 1, formula below."

Why template sensors matter in agriculture. Raw sensor data is rarely the most useful view. Temperature and humidity separately are less actionable than VPD together. PPFD instantaneously is less useful than accumulated DLI for the day. The raw data has to be present; template sensors turn it into the values the grower actually watches and the automations actually trigger on.

Template sensors are entities. They appear in the entity registry, on dashboards, in automations, in history graphs, in logbook entries, in the mobile app. Any place a sensor can be used, a template sensor can be used. A VPD template sensor is functionally a VPD sensor. Home Assistant does not distinguish.

Templates are computed when inputs change. For a standard (state-based) template sensor, Home Assistant re-evaluates the template whenever any entity referenced in the template changes state. A VPD template referencing temperature and humidity updates when either source updates. This is efficient for most uses but has implications for templates that reference many entities or that include time-based functions, discussed later under performance.

Jinja2 in Home Assistant.

Jinja2 is a template language. In Home Assistant, Jinja2 expressions live between double curly braces: {{ ... }}. The expression is evaluated and its result becomes the template's output.

Basic arithmetic and functions.

The common operations:

  • Arithmetic: {{ 2 + 3 }}, {{ 10 / 3 }}, {{ 7 * 2 }}.
  • Math functions: abs(), min(), max(), round(), sqrt(), log(), exp().
  • Comparison: ==, !=, >, <, >=, <=.
  • Conditional (ternary): {{ "hot" if temp > 80 else "cool" }}.

Accessing entity states.

The primary function is states(). Useful variants:

  • states('sensor.greenhouse_temp'): returns the current state as a string.
  • states('sensor.greenhouse_temp') | float(0): converts to a float, with a default of 0 if conversion fails. The default is important; sensors can be unavailable, and float without a default on a non-numeric value produces template errors.
  • state_attr('sensor.greenhouse_temp', 'unit_of_measurement'): returns a specific attribute of the entity.
  • is_state('switch.pump', 'on'): returns true or false for a state check.

Filters.

Jinja2's pipe operator applies filters to values. Useful ones in Home Assistant:

  • | float(default): convert to float with a default.
  • | int(default): convert to integer with a default.
  • | round(digits): round to the specified number of decimal places.
  • | default(value): provide a fallback when the value is undefined.

Conditionals.

Full if/else blocks work in Jinja2 beyond the inline ternary:

{% if states('sensor.greenhouse_temp') | float(0) > 85 %}
  hot
{% elif states('sensor.greenhouse_temp') | float(0) > 70 %}
  warm
{% else %}
  cool
{% endif %}

Blocks like this appear less often in sensor templates (which usually evaluate to a single value) and more often in notification message templates.

Testing templates.

Home Assistant provides a template editor in Developer Tools → Template. Paste a template there to see its current value against current entity states. The template editor shows errors, type information, and live updates as source entities change. It is the fastest way to develop a template: get it working in the editor, then move it to a sensor configuration.

State-based versus trigger-based templates.

Home Assistant supports two kinds of template sensors, with different behaviors.

State-based template sensors. The default. The sensor re-evaluates its template whenever any entity referenced in the template changes. A VPD template referencing temperature and humidity updates when either source updates. State-based templates are simple, efficient for most uses, and appropriate for derived values.

Trigger-based template sensors. The sensor updates only in response to specified triggers. The sensor's value does not automatically change when referenced entities change, only when a trigger fires. Trigger-based templates are useful when:

  • The template needs to run on a schedule (every five minutes, say) regardless of when source entities update.
  • The calculation is expensive and should not run on every source update.
  • The value should snapshot at specific moments rather than continuously track inputs.
  • The sensor should depend on time or on events that are not entity state changes.

A trigger-based template sensor can use any of the standard automation triggers: time, time pattern, template, state, platform-specific. The sensor's state updates only when one of those triggers fires.

When to pick which. Most agricultural calculations are correctly state-based: VPD, dew point, delta-T, simple derived metrics. They should track inputs in near-real-time. Some calculations are correctly trigger-based: a DLI reset at start-of-day, a daily average that snapshots once per day, a rolling window that updates on a schedule rather than on every source tick. When in doubt, state-based is usually right.

VPD calculation.

Vapor pressure deficit is one of the most useful derived metrics in greenhouse and indoor growing. It captures the difference between how much moisture the air is holding and how much it could hold at the current temperature: the drying power of the air as plants experience it.

The formula.

VPD in kilopascals, from air temperature and relative humidity:

SVP (kPa) = 0.6108 × exp((17.27 × T) / (T + 237.3))

VPD (kPa) = SVP × (1 − RH / 100)

Where T is air temperature in °C and RH is relative humidity as a percentage (0–100). The formula uses the Magnus-Tetens approximation, which is accurate enough for horticultural purposes across normal growing temperatures.

A worked example.

At 24°C and 65% relative humidity:

SVP = 0.6108 × exp((17.27 × 24) / (24 + 237.3)) SVP = 0.6108 × exp(414.48 / 261.3) SVP = 0.6108 × exp(1.586) SVP = 0.6108 × 4.884 SVP = 2.98 kPa

VPD = 2.98 × (1 − 65 / 100) VPD = 2.98 × 0.35 VPD = 1.04 kPa

A VPD of about 1.0 kPa is in the middle of the typical desirable range for vegetative growth of many crops, so the worked example produces a reasonable number. When the template produces numbers that do not make sense (negative values, numbers far outside the expected 0.4 to 1.6 kPa range), the inputs are the first place to check.

Air VPD versus leaf VPD.

The calculation above is air VPD: the drying power of the air using air temperature. Leaf VPD uses leaf temperature (usually a few degrees cooler than air for a transpiring crop) and captures what the plant's surface actually experiences. For most hobby and small-commercial operations, air VPD is sufficient and is what most published VPD targets refer to. For operations with infrared leaf temperature sensors or tight environmental control on high-value crops, leaf VPD is more precise.

Leaf VPD:

SVP(T_leaf) (kPa) = 0.6108 × exp((17.27 × T_leaf) / (T_leaf + 237.3))

VPD_leaf (kPa) = SVP(T_leaf) − (SVP(T_air) × RH / 100)

Implementing VPD as a template sensor.

template:
  - sensor:
      - name: "Greenhouse Zone 1 VPD"
        unique_id: greenhouse_zone_1_vpd
        unit_of_measurement: "kPa"
        state_class: measurement
        availability: >
          {{ has_value('sensor.greenhouse_zone_1_temperature')
             and has_value('sensor.greenhouse_zone_1_humidity') }}
        state: >
          {% set t = states('sensor.greenhouse_zone_1_temperature') | float %}
          {% set rh = states('sensor.greenhouse_zone_1_humidity') | float %}
          {% set svp = 0.6108 * (2.71828 ** ((17.27 * t) / (t + 237.3))) %}
          {{ (svp * (1 - rh / 100)) | round(2) }}

The template assumes the temperature sensor reports Celsius, matching the formula above. If the sensor reports Fahrenheit, convert inside the template by replacing the temperature line with {% set t = ((states('sensor.greenhouse_zone_1_temperature') | float) - 32) * 5 / 9 %}. The availability block makes the VPD sensor report unavailable when either source is missing, rather than returning a wrong-but-plausible number.

VPD targets for common crops.

Rough VPD target ranges (crop-specific and stage-specific; consult variety guides for production operations):

  • Seedlings and propagation: 0.4 to 0.8 kPa
  • Vegetative lettuce and greens: 0.8 to 1.2 kPa
  • Vegetative tomatoes, peppers, cucumbers: 0.8 to 1.2 kPa
  • Fruiting tomatoes: 1.0 to 1.4 kPa
  • Flowering cannabis: 1.2 to 1.6 kPa

These are starting points. Operations with specific crop expertise tune these within their own programs.

DLI calculation.

Daily light integral is the total photosynthetically active light a crop receives over the course of a day, measured in moles per square meter per day (mol/m²/d). DLI drives growth; tracking it turns a PAR sensor's instantaneous reading into a number that actually connects to yield.

The formula.

DLI (mol/m²/d) = integral of PPFD (μmol/m²/s) over the photoperiod (seconds), divided by 1,000,000.

For the special case of constant PPFD:

DLI = PPFD × photoperiod (seconds) / 1,000,000

At 500 μmol/m²/s over a 12-hour photoperiod (43,200 seconds):

DLI = 500 × 43,200 / 1,000,000 = 21.6 mol/m²/d

Real DLI is rarely constant PPFD over the day. It follows a solar curve in greenhouses, or a mostly-flat profile in indoor operations with possible ramps at the start and end of the photoperiod. The integral is calculated from actual PAR sensor readings over time.

Implementing DLI.

DLI is a classic trigger-based pattern in Home Assistant. The approach:

  1. Use the Integration platform (Riemann sum integrator) to integrate PPFD over time. This produces a running total in μmol/m² that accumulates from the start of the integration period.
  2. Divide the running total by 1,000,000 to convert to mol/m² (the DLI units).
  3. Reset the integrator at the start of each day (or at the start of the photoperiod, if preferred).
sensor:
  - platform: integration
    source: sensor.greenhouse_zone_1_par
    name: Greenhouse Zone 1 light accumulation
    unit_time: s
    method: trapezoidal
    round: 0

utility_meter:
  greenhouse_zone_1_dli_umol:
    source: sensor.greenhouse_zone_1_light_accumulation
    cycle: daily

template:
  - sensor:
      - name: "Greenhouse Zone 1 DLI"
        unique_id: greenhouse_zone_1_dli
        unit_of_measurement: "mol/m²/d"
        state: "{{ (states('sensor.greenhouse_zone_1_dli_umol') | float(0) / 1000000) | round(1) }}"

The Integration platform accumulates PPFD (μmol/m²/s) over time into μmol/m²; unit_time: s integrates against seconds so the units work out. The utility meter zeroes that total once a day. The template divides by 1,000,000 to reach mol/m²/d. Check it against the constant-PPFD case above: 500 μmol/m²/s held for 12 hours should read 21.6.

Once DLI is tracked as a sensor, it becomes available for automations: notify when the DLI target is met, shut off supplemental lighting when the target is reached, summarize the day's DLI at end-of-photoperiod. Lighting Control covers the automation patterns that use DLI.

DLI reset timing.

The most common reset is at local midnight. For operations where the photoperiod does not cross midnight, this is fine. For operations with photoperiods that do cross midnight (some indoor flowering schedules), resetting at the start of the photoperiod makes more sense. Otherwise the DLI for the night portion of the "day" gets cut off at midnight and restarted, producing confusing numbers.

DLI targets for common crops.

Starting points (tune per variety and stage):

  • Lettuce and greens: 12 to 17 mol/m²/d
  • Herbs: 10 to 15 mol/m²/d
  • Tomatoes, peppers: 22 to 30 mol/m²/d
  • Strawberries: 17 to 22 mol/m²/d
  • Cannabis vegetative: 20 to 35 mol/m²/d
  • Cannabis flowering: 35 to 55 mol/m²/d
  • Seedlings and propagation: 8 to 12 mol/m²/d

Dew point and other psychrometric derivations.

Dew point is the temperature at which the air would become saturated if cooled at constant pressure. It is useful for anticipating condensation on plant surfaces (disease pressure) and for identifying when structural surfaces will get wet. A greenhouse where the dew point is approaching the leaf temperature is a greenhouse about to grow pathogens.

The formula.

Magnus-Tetens dew point approximation, given air temperature T (°C) and relative humidity RH (%):

α = ((17.27 × T) / (237.3 + T)) + ln(RH / 100)

T_dew (°C) = (237.3 × α) / (17.27 − α)

A worked example.

At 22°C and 80% RH:

α = ((17.27 × 22) / (237.3 + 22)) + ln(0.80) α = (379.94 / 259.3) + (−0.223) α = 1.465 + (−0.223) α = 1.242

T_dew = (237.3 × 1.242) / (17.27 − 1.242) T_dew = 294.72 / 16.028 T_dew ≈ 18.4°C

So at 22°C and 80% RH, the dew point is about 18.4°C, meaning any surface cooler than 18.4°C starts collecting condensation.

Implementing dew point as a template sensor.

template:
  - sensor:
      - name: "Greenhouse Zone 1 Dew Point"
        unique_id: greenhouse_zone_1_dew_point
        unit_of_measurement: "°C"
        device_class: temperature
        state_class: measurement
        availability: >
          {{ has_value('sensor.greenhouse_zone_1_temperature')
             and has_value('sensor.greenhouse_zone_1_humidity') }}
        state: >
          {% set t = states('sensor.greenhouse_zone_1_temperature') | float %}
          {% set rh = [states('sensor.greenhouse_zone_1_humidity') | float, 1] | max %}
          {% set alpha = ((17.27 * t) / (237.3 + t)) + log(rh / 100) %}
          {{ ((237.3 * alpha) / (17.27 - alpha)) | round(1) }}

Home Assistant's log() is the natural logarithm, which is what the Magnus-Tetens formula needs. Clamping relative humidity to a floor of 1 with [value, 1] | max prevents log(0) from throwing when a humidity sensor briefly reports zero. At 22°C and 80% RH this reads about 18.4°C, matching the worked example.

Other useful psychrometric values.

  • Absolute humidity (grams of water per cubic meter of air): tells the grower how much water is actually in the air, independent of temperature. Useful for dehumidification calculations.
  • Humidity ratio (grams of water per kilogram of dry air): similar purpose, in mass terms.
  • Wet-bulb temperature: the temperature a thermometer wrapped in a wet cloth would read. Used in swamp cooling calculations and in some spray-application decisions.

Each can be computed from temperature and humidity with similar Magnus-Tetens-based formulas. The Climate Control Patterns page covers when each is useful.

Growing degree days.

Growing degree days (GDD) accumulate the effective heat a crop experiences above a base temperature. GDD is used to predict phenological events: when a crop will flower, when pests will reach specific life stages, when harvest readiness is approaching.

The formula.

The simplest method:

GDD (day) = ((T_max + T_min) / 2) − T_base

If the calculated daily GDD is less than zero, it is treated as zero. Accumulated GDD is the sum of daily GDD over the growing period.

Base temperatures.

Base temperatures are crop-specific. Common values (°F and °C):

  • Corn (field): 50°F / 10°C
  • Wheat: 40°F / 4.4°C
  • Tomato: 50°F / 10°C
  • Potato: 45°F / 7.2°C
  • Strawberry: 40°F / 4.4°C
  • Many insect pests: 50°F / 10°C (check specific pest guides)

Some crops use upper thresholds as well: temperature above the upper threshold does not add further heat units. Modified GDD formulas handle these caps.

Implementing GDD as a template sensor.

GDD is a good candidate for a trigger-based template sensor. It does not need to update as inputs change; it needs to update once per day, based on that day's max and min temperatures.

# Daily max and min from the field temperature sensor
sensor:
  - platform: statistics
    name: "Field A temp max today"
    entity_id: sensor.field_a_temperature
    state_characteristic: value_max
    max_age:
      hours: 24
  - platform: statistics
    name: "Field A temp min today"
    entity_id: sensor.field_a_temperature
    state_characteristic: value_min
    max_age:
      hours: 24

# Add one day's growing degree days at 23:59, accumulating across the season
template:
  - trigger:
      - platform: time
        at: "23:59:00"
    sensor:
      - name: "Field A accumulated GDD"
        unique_id: field_a_accumulated_gdd
        unit_of_measurement: "GDD"
        state_class: total_increasing
        state: >
          {% set tmax = states('sensor.field_a_temp_max_today') | float(0) %}
          {% set tmin = states('sensor.field_a_temp_min_today') | float(0) %}
          {% set tbase = 50 %}
          {% set gdd_day = [((tmax + tmin) / 2) - tbase, 0] | max %}
          {{ (this.state | float(0)) + gdd_day }}

This example uses °F with a base of 50 (corn, tomato); change tbase for the crop. The trigger-based sensor reads its own previous value through this.state and adds each night's contribution, so the total accumulates through the season. The [daily_gdd, 0] | max floors a cold day at zero rather than subtracting from the running total. A season reset is a matter of clearing the sensor's state at planting; some growers keep a separate accumulator per planting.

For ornamental and vegetable operations, GDD tracking is more useful for multi-season comparison than for day-to-day decisions. For commodity crops and for pest management (where GDD models predict insect emergence reliably), it is a daily operational tool.

Template attributes.

A template sensor's state is one value. Template attributes let the sensor carry additional data alongside its state.

When attributes are useful.

Attributes are useful for:

  • Carrying metadata: "last updated at," "source sensor," "calculation method."
  • Exposing intermediate values: a VPD sensor might expose the calculated SVP as an attribute for debugging.
  • Grouping related values: a zone-conditions sensor might report its primary metric as state and carry all related conditions as attributes.

How attributes appear.

Attributes are visible in Developer Tools → States for the entity, accessible in automations via state_attr(), and displayable on dashboards through specific cards (or through template cards). They are not history-graphed by default (only the state is), so primary metrics should be the state, not an attribute.

When not to use attributes.

Individual values that each need independent history graphing, independent alerting, or independent dashboard display are better as separate sensors. If the grower wants to alert on VPD specifically, VPD should be a sensor's state, not an attribute of a combined sensor.

Availability and error handling.

Template sensors can produce errors when their source entities are unavailable or their math breaks (divide by zero, log of a negative number, invalid inputs). Handling these cases cleanly prevents cascading failures.

Unavailability propagation.

A template that references an unavailable sensor can be made to report unavailable itself, using availability templates. The alternative (reporting zero, or the last value, or a default) may produce misleading automation behavior. A VPD template that reports 0 kPa because the humidity sensor is unavailable looks like dangerously dry air and could trigger cooling.

Default values with float and int filters.

The pattern states('sensor.temp') | float(0) is convenient but silent. If the sensor is unavailable, the template produces 0 without any indication. For derived values, this often produces nonsense that propagates into automations. The better pattern, in many cases, is to make the template sensor report unavailable when its inputs are unavailable, using the availability field:

availability: >
  {{ has_value('sensor.greenhouse_temp') and has_value('sensor.greenhouse_humidity') }}

A VPD sensor that is unavailable when its source sensors are unavailable is easier to debug than a VPD sensor that reports a wrong-but-plausible value.

Math errors.

Templates with operations that can produce math errors (log of a non-positive number, divide by zero) should guard against the error conditions. A check for positive RH before calculating dew point, a check for non-zero denominator before dividing. Guard failures can return a sentinel value or, better, make the sensor unavailable through the availability template.

Performance considerations.

Most template libraries are small enough that performance does not matter. Some grow large enough to matter.

Templates re-evaluate on input changes. A state-based template re-evaluates whenever any referenced entity changes. A template referencing five sensors that each update every 10 seconds re-evaluates roughly 30 times per minute. This is fine. A template referencing 50 sensors that each update every second re-evaluates 3,000 times per minute, which starts consuming real CPU.

Triggers help for expensive templates. A complex calculation that does not need to update on every source tick should be trigger-based. A daily summary sensor that aggregates across 20 zones does not need to re-run every time any zone's temperature changes. It can run once per hour or once per day.

this is cheaper than repeated template evaluation. Inside a template, repeated references to the same states('...') call re-evaluate each time. Using a Jinja2 set to capture a value once and referring to it multiple times is cheaper:

{% set t = states('sensor.temp') | float(0) %}
{% set rh = states('sensor.humidity') | float(0) %}
{{ 0.6108 * (2.718 ** ((17.27 * t) / (t + 237.3))) * (1 - rh / 100) }}

For a single formula this is a micro-optimization. For complex templates referencing the same entities many times, it is real.

Avoid templates that reference all entities. A template that uses states | list to iterate over every entity in Home Assistant evaluates against thousands of entities on every change. This almost never needs to happen; most such templates can be narrowed to a specific domain or area.

Common failure modes.

Specific template-sensor problems from real deployments.

The template that reported zero for a week. A VPD template referenced sensor.greenhouse_humidity which had been renamed to sensor.greenhouse_zone_1_humidity during a reorganization. The template's | float(0) default caused it to return 0 silently when the entity was missing. The grower thought VPD was just really low. Fix: use has_value() or the availability template field to make the sensor unavailable when inputs are missing rather than returning a default.

The VPD that went negative. The humidity sensor returned 105% briefly during a condensation event; the VPD calculation produced a small negative number; the automation that triggered on "VPD below 0.5" fired when VPD should have been impossible. Fix: clamp inputs to reasonable ranges (RH to 0–100) in the template; test the calculation against edge inputs.

The dew point that reported NaN. A template's ln(RH / 100) failed when humidity briefly read 0. Home Assistant returned a template error. The dew point sensor went unavailable. Automations referencing it received unavailable states. Fix: guard the log operation (if RH > 0 else some fallback), or use availability templates to skip evaluation when inputs are out of valid range.

The DLI that reset mid-photoperiod. A utility_meter was configured to reset daily at midnight, but the operation's photoperiod ran from 6 AM to midnight on one schedule and from 8 PM to 8 AM on another. The midnight reset chopped one operation's DLI in half every day. Fix: choose the reset time based on the photoperiod; a daily reset at start-of-photoperiod (or at a consistent time that falls outside the photoperiod) aligns with operational reality.

The rolling average that lagged behind. A 30-minute rolling average of CO2 was used as the input to a ventilation automation; by the time the average crossed the threshold, the actual CO2 had been high for 20 minutes. Fix: match the smoothing window to the responsiveness needed. For fast-response controls, a shorter window or an unsmoothed input with adequate hysteresis is better.

The template that worked perfectly in the editor and wrong in production. A template tested in Developer Tools → Template used entity states that had just updated. When deployed, the template sometimes evaluated against stale states during startup, producing wrong initial values until inputs refreshed. Fix: test templates including the startup window; expect the first few minutes after a restart to produce unstable values.

The template whose formula was wrong. A VPD template used SVP × RH / 100 instead of SVP × (1 − RH / 100). The sensor reported values that looked like VPD but were actually vapor pressure. The automations based on it drove the operation in the wrong direction. Fix: verify calculations against known test cases before relying on derived sensors. Published VPD at 24°C and 65% RH should be about 1.04 kPa; if the sensor reports something else, the formula is wrong.

The GDD that reset at the wrong time. A GDD accumulator reset at midnight local time. A daylight-saving-time transition caused the automation to fire twice on the spring-forward day and to miss firing entirely on the fall-back day (depending on the direction and logic). Fix: use Home Assistant's native time handling, which handles DST correctly, and test around transition dates.

The template that slowed the system. A monitoring template referenced states | list to count all entities in Home Assistant. With several thousand entities, the template re-evaluated on every state change (effectively continuously), consuming measurable CPU. Fix: narrow the scope of entity-listing templates (filter by domain or area), or make such templates trigger-based so they run on a schedule rather than on every change.

What not to do.

Patterns to avoid.

Don't swallow errors with silent defaults. | float(0) is convenient but dangerous for derived values. A sensor silently reporting zero because its source was unavailable is worse than a sensor reporting unavailable. The availability template is the right tool when inputs may be unavailable.

Don't skip unit checks. Home Assistant does not enforce that a template sensor's declared unit matches what the template actually produces. A VPD template that reports kPa in its declaration but produces Pa from its math is a disaster waiting to happen. Verify units against worked examples.

Don't put all derived values in one huge template sensor with attributes. Each derived value the grower wants to alert on, graph over time, or display on a dashboard should be its own sensor. Attributes are for metadata, not for primary metrics.

Don't over-smooth. A 4-hour rolling average of temperature feels steady on a dashboard but lags real changes by hours. For control-relevant values, match smoothing to the responsiveness the operation actually needs.

Don't assume templates update when you want them to. A state-based template only re-evaluates when a referenced entity changes. If the template includes a time function (now()) but does not reference any entity that updates frequently, the time portion of the template can be stale. Either trigger-based updates or a time-updating entity reference fix this.

Don't skip testing against edge values. 0% humidity, 100% humidity, very cold and very hot temperatures, the moment a sensor is unavailable, the moment after Home Assistant starts. Edge cases reveal the template bugs that average cases hide.

Don't use template sensors as general-purpose scripting. A template sensor is a sensor. Scripts and automations are for procedural logic. A template sensor that implements a complex state machine is in the wrong place.

Don't ignore performance warnings in the Home Assistant logs. Home Assistant sometimes logs warnings about templates that re-evaluate excessively or take unusually long. These warnings get easier to ignore and harder to debug over time. Addressing them early is cheaper than debugging mysterious slowdowns later.

Frequently asked questions.

What is a template sensor in Home Assistant?

A template sensor is a sensor whose value is calculated from other sensors and data rather than read directly from a device. Vapor pressure deficit from temperature and humidity, daily light integral from a PAR sensor, and dew point from air conditions are all template sensors. They appear like any other entity on dashboards, in automations, and in history graphs.

How do you calculate VPD from temperature and humidity?

Vapor pressure deficit uses saturation vapor pressure and relative humidity. In kilopascals: SVP = 0.6108 * exp((17.27 * T) / (T + 237.3)), then VPD = SVP * (1 - RH / 100), where T is air temperature in Celsius and RH is percent humidity. At 75 degrees F (24 degrees C) and 65 percent humidity, VPD is about 1.04 kPa, a typical vegetative-growth value.

How is daily light integral (DLI) calculated?

Daily light integral is the total photosynthetic light received in a day, in moles per square meter. It equals the PPFD integrated over the photoperiod in seconds, divided by 1,000,000. For a constant 500 umol/m2/s over a 12-hour photoperiod, or 43,200 seconds, DLI is 500 times 43,200 divided by 1,000,000, which is 21.6 mol/m2/d. Real light varies, so the value is integrated from actual sensor readings.

What is dew point and why does it matter in a greenhouse?

Dew point is the temperature at which air becomes saturated and condensation forms. Any surface cooler than the dew point starts collecting moisture, which raises disease pressure on plant surfaces. At 72 degrees F (22 degrees C) and 80 percent humidity, the dew point is about 65 degrees F (18.4 degrees C), meaning any surface below that temperature begins to get wet.

What are growing degree days and how are they calculated?

Growing degree days measure the effective heat a crop accumulates above a base temperature, and they help predict flowering, harvest readiness, and pest life stages. The simplest daily formula is ((Tmax + Tmin) / 2) - Tbase, with negative days counted as zero. Base temperatures are crop-specific, for example 50 degrees F for corn and tomato, and daily values are summed across the season.

Why is my Home Assistant template sensor reading zero?

A common cause is the float(0) default. If a source sensor is renamed or becomes unavailable, the template silently returns 0 instead of an error, which can look like a real but wrong reading. Using has_value or an availability template makes the sensor report unavailable when its inputs are missing, which is easier to spot and debug.