Skip to content

Module M6 - Energy

Files: packages/grow_energy.yaml (this module), packages/grow_core.yaml (sensor.grow_power_total / _light / _climate - the power pass-through sensors this module builds on, plus the grow_map_power_master / grow_map_energy_light_io / grow_map_energy_climate_io mapping fields), and packages/grow_tracker.yaml (input_text.grow_tracker_cycle_id - what this module watches to know a new grow has started).

Note on phase numbering

Per the Phase 0 plan’s own table this falls under Phase 8 (“Energy & complete Alarm module”), but only the Energy half is built here - the Alarm module (mobile push / persistent notification / email across all three channels, R9) is a separate, later piece of Phase 8, not built yet. Built now at the user’s direct request (“lets continue with the next phase, energy”), the same out-of-order pattern as Irrigation, Light, and Air before it.

What’s implemented

Sub-metering (the Phase 0 plan’s own explicit ask)

The plan itself calls for tracking “nicht nur Gesamtverbrauch, sondern auch einzelne Gerätegruppen” (not just total consumption, but individual device groups too). Clarified directly with the user: 3 groups - Total, Light, and Climate (bundling humidifier, dehumidifier, AC/HVAC, exhaust, etc., per the user’s own grouping: “Climate (Humidifier, dehumidifiere, Hvac, exhaust,etc)”).

Redesigned 2026-08-27 (still before first deployment) from “one power sensor per group” to “one OPTIONAL power sensor per DEVICE, summed automatically into its group” - per direct user follow-up: “i need the option to put the sensors in for every device individually and then put them together into usage groups. otherwise i can just put in one light and one climate device.” Every field below is independently optional; map only the devices you actually have power-monitoring hardware for (e.g. a smart plug that reports its own wattage separately from its on/off switch):

  • Light group (packages/grow_core.yaml, 8 optional mapping fields: grow_map_energy_top_light_<1-4>_io / _under_canopy_<1-4>_io) - sensor.grow_power_light sums whichever of these currently report a value; an unmapped light simply contributes nothing.
  • Climate group (7 optional mapping fields: grow_map_energy_dehumidifier_io / _humidifier_io / _ac_io / _exhaust_io / _intake_fan_io / _circulation_io / _co2_valve_io) - sensor.grow_power_climate sums whichever of these currently report a value, same as Light.
  • Total - prefers the existing grow_map_power_master mapping (M0, a whole-circuit/mains meter, has existed since Phase 1) if it’s mapped and reporting, since a dedicated meter is the most accurate source and also catches any load the per-device sensors don’t individually cover. If Power Master isn’t mapped (or is temporarily unavailable), sensor.grow_power_total falls back to Light-group + Climate-group automatically - confirmed directly with the user (AskUserQuestion) as the preferred behavior over always-calculating from devices, or showing both side-by-side.

Every group/device is fully optional/dormant if left unmapped - exactly like every other optional sensor in this project (compare the CO2 sensor in packages/grow_air.yaml). A group starts reporting a real (possibly partial) total the moment at least one of its devices is mapped and available - it doesn’t wait for full coverage.

Power -> kWh -> cost pipeline

  1. Power (W), live: sensor.grow_power_total / _light / _climate (packages/grow_core.yaml) - group-level sensors, each summing whichever of that group’s per-device power sensors (sensor.grow_power_<device>, 15 total) currently report a value. Same guarded pass-through pattern as every other optional sensor in this project (stays unknown forever if nothing in the group is mapped, never guesses).
  2. Cumulative kWh: each of the 3 group-level power sensors feeds a native Home Assistant integration (Riemann sum) sensor - sensor.grow_energy_total_kwh / _light_kwh / _climate_kwh. This is Home Assistant’s own built-in mechanism for exactly this conversion, not custom math, and it auto-assigns device_class: energy / state_class: total_increasing - which means you can optionally add these 3 sensors to Home Assistant’s own Settings -> Dashboards -> Energy page for its native kWh graphs, as a bonus on top of everything below. These 3 sensors never reset on their own and accumulate for as long as this module is deployed.
  3. This-cycle kWh: sensor.grow_energy_total_kwh_cycle / _light_kwh_cycle / _climate_kwh_cycle - the lifetime kWh reading minus a baseline snapshot captured at the start of the current grow cycle (see below).
  4. Cost: sensor.grow_energy_total_cost_lifetime / _cycle and the Light/Climate equivalents (6 sensors total) - cumulative kWh (lifetime or this-cycle) multiplied by input_number.grow_energy_price_per_kwh.

Currency

input_select.grow_energy_currency - EUR / USD / GBP, defaulting to EUR to match the user’s German-language Home Assistant instance, changeable at any time from Dashboard 1’s “Energy: Master Config” section (moved there 2026-08-29, together with grow_energy_price_per_kwh and grow_energy_price_per_liter, so all one-time pricing setup lives alongside the rest of Dashboard 1’s configuration fields - the Energy view on Dashboard 2 keeps only the live cost/usage figures and the cycle-baseline reset button). This is a label only - the price you enter in grow_energy_price_per_kwh is assumed to already be in whichever currency you’ve selected; picking a different currency doesn’t convert the number, it just changes which symbol the dashboard shows next to it.

Why cost figures don’t carry a unit_of_measurement/device_class: monetary: verified against Home Assistant’s own docs that a template sensor’s unit_of_measurement must be a static string - it cannot be templated based on the currently-selected currency. Rather than hardcode one currency permanently (which would silently mislabel the figure the moment you switch currencies), the cost sensors are plain numbers and the Energy view’s markdown cards template the currency symbol in live instead (markdown templating has no such restriction). The sensors’ icons are still templated to match the selected currency, since icon templating is supported.

“Cost” is a recomputed estimate, not a true price-over-time integral: it’s cumulative kWh (lifetime or this-cycle) multiplied by whatever price is currently configured. If you change the price, the entire figure recalculates immediately using the new price - older kWh isn’t remembered as having cost the old price. Tracking historical tariff changes over time was out of scope for this build; this is the same practical-estimate tradeoff the project has made elsewhere (e.g. grow_air.yaml’s CO2 safety ceiling being a simple threshold rather than a full PID controller).

This-cycle re-baselining

automation.grow_energy_capture_cycle_baseline watches input_text.grow_tracker_cycle_id (packages/grow_tracker.yaml) and, whenever it changes to a new real value, snapshots each group’s current lifetime kWh reading into input_number.grow_energy_<group>_kwh_baseline. “This cycle” kWh/cost is then always lifetime - baseline, so replanting a new grow automatically starts “money spent on this grow” back at zero without touching the lifetime figures. A manual script.grow_energy_reset_cycle_baseline (button on the dashboard) does the exact same capture on demand, for marking a fresh starting point without actually starting a new grow cycle (e.g. testing, or after moving Home Assistant).

If this module is deployed mid-cycle, “this cycle” and “lifetime” simply read identically until the next real cycle change - there’s no way to retroactively know consumption from before the module existed, the same limitation every other module in this project has for its own history.

No master enable/disable switch

Unlike every other module here, Energy doesn’t have a shadow-mode _control_enabled switch. It only ever reads sensors and does arithmetic - it never commands an actuator - so there’s nothing unsafe about it always running. Same reasoning as grow_climate_daytime_follows_light’s default-on exemption from the shadow-mode pattern.

Nutrient-solution cost + combined “Total Grow Cost” (2026-08-27)

Requested as “make a cost tracker out of it” plus a dedicated nutrient price field. Interpreted as an umbrella scope expansion (Energy becomes a general grow-cost tracker covering electricity and nutrients), not a module rename - no file/module rename was made; correct me if a hard rename was actually wanted.

  • input_number.grow_energy_price_per_liter - price per liter of nutrient solution, same currency-label convention as the electricity price.
  • Scope: confirmed directly with the user (AskUserQuestion) - all irrigation volume counts as nutrient solution dosed, scheduled and manual alike, both pumps. No separate “plain water vs. nutrient run” distinction exists in the Irrigation module to split on, so this is the simplest faithful reading of “counts up the amount of fertilizer you are using.” If you actually run plain-water flushes through the same pumps, those will be counted as nutrient cost too - there’s no way around that without Irrigation gaining a per-event “this was plain water” flag, which wasn’t part of this request.
  • sensor.grow_energy_nutrient_volume_cycle - this-cycle liters, using the same baseline-subtraction pattern as the kWh sensors: input_number.grow_irrigation_total_volume_l (the lifetime accumulator added to packages/grow_irrigation.yaml, see module-irrigation.md) minus a snapshot baseline captured by the same automation.grow_energy_capture_cycle_baseline / manual reset-button flow already used for kWh, extended to also snapshot nutrient volume.
  • sensor.grow_energy_nutrient_cost_lifetime / _cycle - volume × price per liter.
  • sensor.grow_energy_total_grow_cost_lifetime / _cycle - electricity cost + nutrient cost combined, each side defaulting to 0 if its own source is unavailable, so a grow with e.g. only power monitoring (no Irrigation module deployed) still shows a meaningful combined figure instead of going fully unavailable.
  • Graceful degradation: every nutrient/combined sensor uses has_value() guards against input_number.grow_irrigation_total_volume_l
    • if the Irrigation module isn’t deployed at all, these read unavailable rather than erroring, and the existing electricity-only cost sensors are completely unaffected.

Entity-id correctness (a lesson from a real bug earlier this project)

Every entity in this file (and the 15 new per-device power pass-through sensors in packages/grow_core.yaml) was named with the Light Timeline bug in mind (docs/docs/module-light.md, MEMORY.md item 47): a template/ platform entity’s entity_id is generated from its name at first creation, not from unique_id. The native integration sensor platform has no object_id/default_entity_id escape hatch at all (confirmed against Home Assistant’s own docs), so each integration sensor here is named to slugify exactly to its intended entity_id (e.g. name: "Grow Energy Total kWh" -> sensor.grow_energy_total_kwh). Every template: sensor in this file and the new per-device sensors in grow_core.yaml (which do support it) additionally set default_entity_id: explicitly, rather than relying on the name matching by convention alone a second time. Verified programmatically via the same slugify-audit script used to root-cause the original Light Timeline bug - no mismatches among any of the new Energy entities.

Dashboard (Energy view, Dashboard 2)

A status banner (which groups are actively tracking - via the Power Master meter or calculated from mapped devices - and last decision), a “Cycle Baseline” entities card (a manual “reset this cycle baseline” button, last decision - the currency and price fields that used to sit in this card moved to Dashboard 1’s “Energy: Master Config” section on 2026-08-29, see the Currency section above), a live power-draw tile row (Total / Light / Climate, in W), a “Money Spent on This Grow” section and a “Money Spent - Lifetime” section (both formatted with the selected currency symbol, kWh + cost per group), a “Nutrient Solution Cost” section (this-cycle and lifetime liters + cost), a “Total Grow Cost (Electricity + Nutrients)” section (the combined this-cycle and lifetime figures), a banner showing how many of the Light/Climate group’s devices are currently mapped (n/8 Light, n/7 Climate), and a simple power-draw line chart (native history-graph, same no-HACS-dependency pattern used elsewhere in this dashboard) with a note about optionally adding the kWh sensors to Home Assistant’s own Energy dashboard. Individual per-device power readings are NOT shown on the dashboard by design (confirmed with the user) - they only feed the Light/Climate/Total group totals, keeping this view from growing to 15+ extra tiles.

Rollout checklist

  1. Deploy the updated packages/grow_core.yaml (adds 1 required-ish mapping field - Power Master, likely already set from Phase 1 - plus 15 OPTIONAL per-device power-sensor mapping fields and their 15 per-device pass-through sensors, and redefines the Light/Climate/Total pass-through sensors as sums/fallbacks), packages/grow_mapping_tool.yaml (adds the 15 fields to the search-based mapping picker - this also fixes a gap where the Intake Fan mapping added in the Air module’s v2 follow-up was missing from this picker), and the (unchanged-internally) packages/grow_energy.yaml - check config, restart (new helper entities need one).
  2. Re-import dashboards/dashboard_1_configuration.yaml (adds 2 new “Energy Sub-Metering” entities cards, 15 rows total), dashboards/dashboard_2_control_panel.yaml (adds the “Energy” view), and dashboards/dashboard_3_log.yaml (adds the 15 new fields, plus the previously-missing Intake Fan mapping field, to the “Entity Mapping Changes” logbook card and adds the “Energy Configuration & Automation Status” card).
  3. On Dashboard 1, confirm “Power Master” is mapped if you have a whole-circuit/mains meter (it may already be, from Phase 1) - this is preferred for Total when present. Then map power sensors for whichever individual Light/Climate devices you actually have monitoring hardware for under the two new “Energy Sub-Metering” cards - each is independently optional, map only what you have; an unmapped device just contributes nothing to its group, it doesn’t block the others. If you map NO individual devices and NO Power Master, Energy tracking stays fully inactive (same “empty mapping = skip silently” pattern as every other optional sensor in this project).
  4. On Dashboard 1’s “Energy: Master Config” section, set your currency and current price per kWh (moved there from the Energy view on 2026-08-29). 4a. Optional - nutrient cost: if you also want fertilizer/nutrient spend tracked, deploy the updated packages/grow_irrigation.yaml (adds the lifetime volume accumulator - see module-irrigation.md) and set input_number.grow_energy_price_per_liter on Dashboard 1’s “Energy: Master Config” section. If Irrigation isn’t deployed, or you leave the price at 0, the nutrient and combined “Total Grow Cost” figures simply stay at 0/unavailable - the electricity-only tracking above is completely unaffected either way.
  5. Watch the Energy view’s status banner (confirms whether Total is sourced from Power Master or calculated from mapped devices, plus a live device-mapped count for Light/Climate) and Dashboard 3’s “Energy Configuration & Automation Status” logbook card for a day to confirm power draw and kWh are accumulating as expected.
  6. If you ever want to mark a fresh cost-tracking starting point without starting an actual new grow cycle, use the “Reset ‘This Cycle’ Baseline Now” button on the Energy view - this also re-baselines nutrient volume if Irrigation is deployed.