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_lightsums 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_climatesums whichever of these currently report a value, same as Light. - Total - prefers the existing
grow_map_power_mastermapping (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_totalfalls 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
- 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 (staysunknownforever if nothing in the group is mapped, never guesses). - 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-assignsdevice_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. - 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). - Cost:
sensor.grow_energy_total_cost_lifetime/_cycleand the Light/Climate equivalents (6 sensors total) - cumulative kWh (lifetime or this-cycle) multiplied byinput_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 topackages/grow_irrigation.yaml, seemodule-irrigation.md) minus a snapshot baseline captured by the sameautomation.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 againstinput_number.grow_irrigation_total_volume_l- if the Irrigation module isn’t deployed at all, these read
unavailablerather than erroring, and the existing electricity-only cost sensors are completely unaffected.
- if the Irrigation module isn’t deployed at all, these read
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
- 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). - 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), anddashboards/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). - 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).
- 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 - seemodule-irrigation.md) and setinput_number.grow_energy_price_per_literon 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. - 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.
- 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.