Average Electric Draw on the 30 minute mark

I found bits and pieces of this problem in other solutions but can’t piece them together.

My electric company charges peak usage fees based on 30-minute averages, Mon-Fri from 2pm-8pm. They only utilize the averages taken at the hour and half-hour point (example 2:30pm, 3:00pm, 3:30pm, 4:00pm, etc.)

I have a sensor monitoring electricity use and was able to build a sensor showing the 30-minute average. I would like to track the highest peak average during the monthly billing cycle but get stuck on two aspects:

  1. I only need the 30-minute average at the hour and half-hour point. The max value of the sensor is not accurate as the 30-minute average might reach a high at 22 or 24 minutes past the hour and come down by the time the reading is taken at 30 minutes past. Thus the sensor max is not accurate.

  2. I only need to record Mon-Fri from 2:00pm - 8:00pm. Technically the first data point would be at 2:30pm and the last at 8:00pm.

Any tips or suggestions would be much appreciated. Thank you in advance.

If you already have a sensor that does a trailing 30-minute average, then you just need a trigger-based template sensor to grab that sensor’s value at the appropriate times.

For the trigger, you could just add a list of every time from 2:30-8:00 that you want the snapshot taken, or you could use a /30 time pattern trigger and in the template for the state you could check if the time is within the desired time window

Thank you. I’m not 100% sure how to set that up but will look into it further.

If the trigger-based template sensor updates every 30 minutes, will it keep the highest value or just keep overwriting itself?

Make a statistics sensor with a 30 min max_age.

You’ll have a template, so you can decide. You can just grab the current stats sensor value (available as this.state) or compare it against the current sensor value using the max function.

I appreciate the responses but honestly have no idea how to do any of this.

I’m trying to figure out a “trigger-based template sensor” from the earlier response. I assume I need to do something in the template.yaml file but the example referenced shows the trigger of a time pattern which I can’t figure out how to adjust to my needs nor what to use for the state.

If I get that figured out, I’m pretty sure it will just show the most recent 30 minute average from the hour or half past the hour mark but won’t retain the highest value of the month. Your response says since I’m using a template, I can compare the result against the current sensor value using the max function. I’m sorry but I have no clue how to do that.

I know this is all voluntary and appreciate everyone trying to help. I’ve spent hours attempting to figure this out but will have to keep working on it.

Give me the source entity ID.

I can type out a solution later this weekend. It’s mother’s day tomorrow, so it’s a busy weekend with family.

In the meantime, someone else with time can take a go at it.

If you continue, at least try my two steps. I’m not sure why you’re trying to do everything in one sensor.

First get the 30 min average as a stats sensor, then make a template sensor to take a snapshot of that sensor.

This is done. Here is what I’m using:

sensor:
  - platform: average
    name: 'SRP 30 Minute'
    duration:
      minutes: 30
    entities: 
      - sensor.vueenergy_123_1min

This is what I’m asking for help with.

1 Like

I think I came up with an alternate working solution. EDIT: Didn’t work

  1. I created a number helper. This can be displayed in the front end, easily reset with other automations, etc.
  2. The existing “sensor.srp_30_minute” had an attribute showing the max rating. I used this attribute as a trigger in an automation which updates the number helper. Two conditions in the automation make sure it only runs weekdays and between the hours 1400-2000 ending in minutes 00 or 30. Since the automation will only trigger when a new max occurs and the number helper will only be updated during the peak hours, the result will be the highest peak usage. I can use a separate automation to reset the high each month (billing cycle).

This is what the automation looks like:

alias: "SRP 30 Minute Peak Value"
description: ""
trigger:
  - platform: state
    entity_id:
      - sensor.srp_30_minute
    attribute: max_value
condition:
  - condition: state
    entity_id: binary_sensor.workday
    state: "on"
  - condition: template
    value_template: |
      {{
       now().hour >= 14 and now().hour <= 20 and 
      (now().minute == 0 or now().minute == 30) 
       }}
action:
  - service: input_number.set_value
    metadata: {}
    data:
      value: "{{states('sensor.srp_30_minute')}}"
    target:
      entity_id: input_number.srp_30_minute_peak
mode: single
1 Like

That will work, but I think you can simplify it.

Here is my version using a trigger-based template sensor:

template:
  - trigger:
      - platform: time_pattern
        # matches when minutes are divisible by 30, which is on the hour and on every 30 minutes past the hour
        minutes: "/30"
    sensor:
      - name: "SRP 30 Minute Peak"
        state: >-
          {% if 14 <= now().hour <= 20 %}
            {{ state_attr('sensor. srp_30_minute', 'max_value') }}
          {% else %}
            {# retain the current value #}
            {{ this.state }}
          {% endif %}

Side-note: If this FR was implemented, the solution could be more elegant by using a condition.

Correction: I erroneously previously wrote to use the statistics platform, but you cannot get a max value with it – only the mean. I see you’ve opted for the third-party GitHub - Limych/ha-average: Average Sensor for Home Assistant, which is a good choice (I use it for other things too). Just point this out for others coming across this thread.

your solution will only trigger if the max_value attribute happens to change within the one-minute time period at the start and middle of every hour.

This solution would take the max of each 30-minute window, rather than the average of each 30-minute window. It’s also missing the weekday criteria and will also trigger at 8:30pm. I’d modify it to this:

template:
  - trigger:
      - platform: time_pattern
        # matches when minutes are divisible by 30, which is on the hour and on every 30 minutes past the hour
        minutes: "/30"
    sensor:
      - name: "SRP 30 Minute Average"
        state: >-
          {% if today_at('14:30') <= trigger.now <= today_at('20:01') and trigger.now.isoweekday() <=5 %}
            {{ states('sensor.srp_30_minute') }}
          {% else %}
            {# retain the current value #}
            {{ this.state }}
          {% endif %}

Then, if the ultimate desire is to get the maximum of these readings from each calendar month, you can set up another trigger-based-template sensor to do that. But there’s two different possible solutions here:

  1. A sensor which reports the max value during the current month, and bumps up the value each time a higher number is seen. Throughout the month you’ll see this sensor ratchet up and at some point it will hold steady. The only time you can be certain it holds the max value of the entire month is if you look at its state after 8pm on the last weekday of the month. The first day of the next calendar month the value will reset.
  2. A sensor which reports the max value of the previous month and therefore holds that value all month long. Upon the first day of the new month it will update its value to report the max value of the previous month. You will get one data point per month.

You could set up a sensor which does both also. Here’s that option, where the sensor’s state only changes at the start of every month (option 2 above), and the sensor’s attribute called “current_month_max” tracks the current month’s max and can therefore change many times every month. This template sensor doesn’t require the previous template sensor defined above, so if you aren’t interested in recording a history of the 30-minute averages you can omit that sensor entirely.

template:
  - trigger:
      - platform: template
        value_template: "{{ today_at().day == 1 }}"
      - platform: time_pattern
        minutes: "/30"
    sensor:
      - name: Max Monthly 30 Minute Average Power
        unique_id: ef82671c-6825-4e0b-bb8a-c309ffdbe1e1
        device_class: power
        unit_of_measurement: kW
        state: >
          {% set self_state = this.state if has_value(this.state) else 0 %}
          {{ this.attributes.current_month_max if trigger.platform == 'template' else self_state }}
        attributes:
          current_month_max: >
            {% set current_month_max = this.attributes.current_month_max | default(0) | float %}
            {% if trigger.platform == 'template' %}
              0
            {% elif trigger.now.isoweekday() <= 5 and today_at('14:30') <= trigger.now <= today_at('20:01') %}
              {{ max(current_month_max, states('sensor.srp_30_minute') | float(0)) }}
            {% else %}
              {{ current_month_max }}
            {% endif %}

Right. In that case, the average sensor must then track the mean and not the max, and the max must be taken with every snapshot as I originally understood it (but I assumed OP’s sensor as they defined it was what they wanted): the max average for every 30 min slot.

I think you understood it the same way:

Original:

I’m this case, I would indeed then use the built-in statistics sensor, tracking the mean with a max age of 30 min.

I missed that.

Nice use of trigger.now! TIL. As well as trigger.platform later on.

This is working so far. I added a unique_id and unit_of_measurement but am getting results that update every 30 minutes during the desired time period. Graph below:

I messed this up initially changing the today_at().day == 1 setting from 1 to 11. I thought this would reset the month on the 11th (which is when my billing cycle ends). The first few checkpoints today had no reading so I moved it back to 1 and the last checkpoints have now updated correctly. If there is a way to reset the monthly max on the 11th and not first, please let me know.

The 4:30pm average reading was 73.86kW. I turned off some items around the house and let the solar send some juice back for the 5:00pm reading. My 30 minute average reading at 5:00pm was -2,709.21 but the current_max_month attribtue still showed 73.86. So it is working!

I appreciate the help so much. I’ve learned more than one thing from this and the other comments. Again, much thanks!

That is the correct way to change it to reset on the 11th day of the month. If there was an issue it is unrelated to that change.

I suspect it might be that my code does not account for your power to go negative, and so currently the attribute will remain at zero until a positive number is seen. Perhaps the power was negative during your testing and coincidentally changed to positive around the same time you reverted your change?

This code is nearly identical but the default value for the attribute (which will be the value first populated) will be set to the value of sensor.srp_30_minute instead of setting it to zero.

template:
  - trigger:
      - platform: template
        value_template: "{{ today_at().day == 11 }}"
      - platform: time_pattern
        minutes: "/30"
    sensor:
      - name: Max Monthly 30 Minute Average Power
        unique_id: ef82671c-6825-4e0b-bb8a-c309ffdbe1e1
        device_class: power
        unit_of_measurement: kW
        state: >
          {% set self_state = this.state if has_value(this.state) else 0 %}
          {{ this.attributes.current_month_max if trigger.platform == 'template' else self_state }}
        attributes:
          current_month_max: >
            {% set current_month_max = this.attributes.current_month_max | default(states('sensor.srp_30_minute')) | float %}
            {% if trigger.platform == 'template' %}
              0
            {% elif trigger.now.isoweekday() <= 5 and today_at('14:30') <= trigger.now <= today_at('20:01') %}
              {{ max(current_month_max, states('sensor.srp_30_minute') | float(0)) }}
            {% else %}
              {{ current_month_max }}
            {% endif %}
1 Like