An approach to both detailed and group level energy management

Thank you @ccpk1 – this is amazing!

For those that have different rates by time of day, I used the following to setup automatic rate tracking for Toronto Hydro.

Input number is used to define rates for each tariff rate.

input_number:
  energy_tou_rate_onpeak:
    name: Energy kWh Cost On-Peak
    icon: mdi:currency-usd
    mode: box
    unit_of_measurement: "CAD/kWh"
    min: 0.001
    max: 1
    initial: 0.17
  energy_tou_rate_midpeak:
    name: Energy kWh Cost Mid-Peak
    icon: mdi:currency-usd
    mode: box
    unit_of_measurement: "CAD/kWh"
    min: 0.001
    max: 1
    initial: 0.113
  energy_tou_rate_offpeak:
    name: Energy kWh Cost Off-Peak
    icon: mdi:currency-usd
    mode: box
    unit_of_measurement: "CAD/kWh"
    min: 0.001
    max: 1
    initial: 0.082
  energy_tou_rate_average:
    name: Energy kWh Cost Average
    icon: mdi:currency-usd
    mode: box
    unit_of_measurement: "CAD/kWh"
    min: 0.001
    max: 1
    initial: 0.10325

A template is used to define the name of the tariff rate, and another is used to retrieve the current tariff rate. The name is important since it will be used in utility_meter to calculate which rates apply.

The energy_tou_tariff state can be adjusted as needed

template:
  - trigger:
      - platform: time_pattern
        seconds: "/1"
      - platform: homeassistant
        event: start
      - platform: event
        event_type: "call_service"
        event_data:
          domain: "template"
          service: "reload"
    sensor:
      - name: energy_tou_tariff
        unique_id: e09d54cbfd1e4511a66a847f25e246ef
        icon: mdi:chart-bar
        state: >
          {% if now().weekday() >= 0 and now().weekday() < 5 %}
            {% if now().month >= 5 and now().month < 11 %}
              {% if now().hour >= 7 and now().hour < 11 %} onpeak
              {% elif now().hour >= 11 and now().hour < 17 %} midpeak
              {% elif now().hour >= 17 and now().hour < 19 %} onpeak
              {% else %} offpeak
              {% endif %}
            {% else %}
              {% if now().hour >= 7 and now().hour < 11 %} midpeak
              {% elif now().hour >= 11 and now().hour < 17 %} onpeak
              {% elif now().hour >= 17 and now().hour < 19 %} midpeak
              {% else %} offpeak
              {% endif %}
            {% endif %}
          {% else %} offpeak
          {% endif %}
      - name: energy_kwh_cost
        unique_id: 97041eea00d0448caf9e300ee08694a5
        unit_of_measurement: "CAD/kWh"
        icon: mdi:currency-usd
        state: >
          {% if is_state('sensor.energy_tou_tariff', 'onpeak') %} {{ states('input_number.energy_tou_rate_onpeak') | float }}
          {% elif is_state('sensor.energy_tou_tariff', 'midpeak') %} {{ states('input_number.energy_tou_rate_midpeak') | float }}
          {% elif is_state('sensor.energy_tou_tariff', 'offpeak') %} {{ states('input_number.energy_tou_rate_offpeak') | float }}
          {% else %} {{ states('input_number.energy_tou_rate_average') | float }}
          {% endif %}

Utility Meter is setup for daily and monthly cycles, and tariffs are defined. These tariff names must match the template sensor values above for automatic rate select. Once implemented, usage is then split into “buckets”, where the sensor name will now include the tariff name as a suffix.

For example, sensor.electric_home_daily_total_energy would become …

  • sensor.electric_home_daily_total_energy_onpeak for onpeak usage
  • sensor.electric_home_daily_total_energy_midpeak for midpeak usage
  • and sensor.electric_home_daily_total_energy for offpeak usage

Actual tariff names can be changed as needed, just make sure they still match. Note the use of yaml anchor on the first value using <<: &utility_meter_tariff_defaults, and all other utility_meter definitions have <<: *utility_meter_tariff_defaults instead.

utility_meter:
  ################  Track daily consumption for each grouping  ###############
  electric_home_daily_total_energy:
    source: sensor.electric_home_total_energy
    cycle: daily
    <<: &utility_meter_tariff_defaults
      tariffs:
        - offpeak
        - midpeak
        - onpeak
  other_daily_total_energy:
    source: sensor.other_total_energy
    cycle: daily
    <<: *utility_meter_tariff_defaults
# ... repeat as needed ...
  ################  Track monthly consumption for each grouping  ###############
  electric_home_monthly_total_energy:
    source: sensor.electric_home_total_energy
    cycle: monthly
    <<: *utility_meter_tariff_defaults
  other_monthly_total_energy:
    source: sensor.other_total_energy
    cycle: monthly
    <<: *utility_meter_tariff_defaults
# ... repeat as needed ...

Since utility_meter will now output usage into multiple “buckets” by tariff, total usage must then be configured to aggregate each tariff rate.

sensor:
  - platform: template
    sensors:
    ################  Calculate daily energy usage for each grouping  ###############
      electric_home_daily_total_energy_usage:
        friendly_name: "Electric Home Daily Total Energy Usage"
        value_template: "{{ states('sensor.electric_home_daily_total_energy_onpeak') | float(2) + states('sensor.electric_home_daily_total_energy_midpeak') | float(2) + states('sensor.electric_home_daily_total_energy_offpeak') | float(2) }}"
        <<: &energy_usage_defaults
          unit_of_measurement: "kWh"
          icon_template: mdi:gauge
      electric_other_daily_total_energy_usage:
        friendly_name: "Electric Other Daily Total Energy Usage"
        value_template: "{{ states('sensor.other_daily_total_energy_onpeak') | float(2)  + states('sensor.other_daily_total_energy_midpeak') | float(2) + states('sensor.other_daily_total_energy_offpeak') | float(2) }}"
        <<: *energy_usage_defaults
# ... repeat as needed ...
    ################  Calculate monthly energy usage for each grouping  ###############
      electric_home_monthly_total_energy_usage:
        friendly_name: "Electric Home Monthly Total Energy Usage"
        value_template: "{{ states('sensor.electric_home_monthly_total_energy_onpeak') | float(2) + states('sensor.electric_home_monthly_total_energy_midpeak') | float(2) + states('sensor.electric_home_monthly_total_energy_offpeak') | float(2) }}"
        <<: *energy_usage_defaults
      electric_other_monthly_total_energy_usage:
        friendly_name: "Electric Other Monthly Total Energy Usage"
        value_template: "{{ states('sensor.other_monthly_total_energy_onpeak') | float(2)  + states('sensor.other_monthly_total_energy_midpeak') | float(2) + states('sensor.other_monthly_total_energy_offpeak') | float(2) * states('input_number.energy_tou_rate_offpeak') | float(2) }}"
        <<: *energy_usage_defaults
# ... repeat as needed ...

In addition, cost must be calculated and aggregated for each tariff as well.

sensor:
  - platform: template
    sensors:
    ################  Calculate daily energy cost for each grouping  ###############
      electric_home_daily_total_energy_cost:
        friendly_name: "Electric Home Daily Total Energy Cost"
        value_template: >-
          {{ max((states('sensor.electric_home_daily_total_energy_onpeak') | float(2) * states('input_number.energy_tou_rate_onpeak') | float(2))
            + (states('sensor.electric_home_daily_total_energy_midpeak') | float(2) * states('input_number.energy_tou_rate_midpeak') | float(2))
            + (states('sensor.electric_home_daily_total_energy_offpeak') | float(2) * states('input_number.energy_tou_rate_offpeak') | float(2)) | round(2), 0.00) }}
        <<: &energy_cost_defaults
          unit_of_measurement: "$"
          icon_template: mdi:currency-usd
      electric_other_daily_total_energy_cost:
        friendly_name: "Electric Other Daily Total Energy Cost"
        value_template: >-
          {{ max((states('sensor.other_daily_total_energy_onpeak') | float(2) * states('input_number.energy_tou_rate_onpeak') | float(2))
            + (states('sensor.other_daily_total_energy_midpeak') | float(2) * states('input_number.energy_tou_rate_midpeak') | float(2))
            + (states('sensor.other_daily_total_energy_offpeak') | float(2) * states('input_number.energy_tou_rate_offpeak') | float(2)) | round(2), 0.00) }}
        <<: *energy_cost_defaults
# ... repeat as needed ...
    ################  Calculate monthly energy cost for each grouping  ###############
      electric_home_monthly_total_energy_cost:
        friendly_name: "Electric Home Monthly Total Energy Cost"
        value_template: >-
          {{ max((states('sensor.electric_home_monthly_total_energy_onpeak') | float(2) * states('input_number.energy_tou_rate_onpeak') | float(2))
            + (states('sensor.electric_home_monthly_total_energy_midpeak') | float(2) * states('input_number.energy_tou_rate_midpeak') | float(2))
            + (states('sensor.electric_home_monthly_total_energy_offpeak') | float(2) * states('input_number.energy_tou_rate_offpeak') | float(2)) | round(2), 0.00) }}
        <<: *energy_cost_defaults
      electric_other_monthly_total_energy_cost:
        friendly_name: "Electric Other Monthly Total Energy Cost"
        value_template: >-
          {{ max((states('sensor.other_monthly_total_energy_onpeak') | float(2) * states('input_number.energy_tou_rate_onpeak') | float(2))
            + (states('sensor.other_monthly_total_energy_midpeak') | float(2) * states('input_number.energy_tou_rate_midpeak') | float(2))
            + (states('sensor.other_monthly_total_energy_offpeak') | float(2) * states('input_number.energy_tou_rate_offpeak') | float(2)) | round(2), 0.00) }}
        <<: *energy_cost_defaults
# ... repeat as needed ...

Finally, an automation is configured to set the tariff rate for each utility_meter. We use an automated group to simplify rate automation.

automation:
  ################  Create and update power groupings for circuits and devices  ###############
  - alias: "Update Utility Groups"
    trigger:
      - platform: homeassistant
        event: start
      - platform: event
        event_type: "call_service"
        event_data:
          domain: "group"
          service: "reload"
    action:
      - service: group.set
        data_template:
          object_id: utility_meters
          entities: >
            {% set ns = namespace(entities=[]) %}
            {% for s in states.utility_meter if s.object_id.endswith('_daily_total_energy') or s.object_id.endswith('_monthly_total_energy') %}
              {% set ns.entities = ns.entities + [ s.entity_id ] %}
            {% endfor %}
            {{ ns.entities }}
  ################  Update power tariffs for all circuits  ###############
  - alias: "Update Power Tariffs"
    trigger:
      - platform: homeassistant
        event: start
      - platform: event
        event_type: "call_service"
        event_data:
          domain: "group"
          service: "reload"
      - platform: state
        entity_id: sensor.energy_tou_tariff
      - platform: time_pattern
        seconds: "/5"
    action:
      - service: utility_meter.select_tariff
        data:
          entity_id: group.utility_meters
          tariff: '{{ states("sensor.energy_tou_tariff") }}'

Edited: simplified yaml using anchors and automatic group creation

3 Likes