Trying to map "time of use" power times to drive other automations

I’m hoping someone has a good idea here - I’m looking for ways to save money (power co now has a new time of use optional plan again, but its REALLY complex) - seems the first step is to figure out how I can correctly map the TOU “zones” into an option-select or similar template sensor which I can then use as an input to other automation.

Secondary goal, would be cool if I could also trigger on “X hours before high cost rate”

Whatever I do, I also want to make sure I try and also fix some edge cases I have with my current setup of automations-triggering-option-select for “morning/day/evening/night” HVAC zones where if an update/reboot “spans” the time it changes causes it to get out of sync and not correctly trigger the change (e.g. if I do an update and its offline from evening to night around 10PM it will stay in evening temps all night long it seems).

Here’s what I need to encode into some kind of sensor - it changes by:

  • Summer weekday (4 rates)
  • Winter weekday (6 rates)
  • Weekend day (2 rates)
  • Some weekday holidays (2 rates, same as weekend)

…and of course there’s almost none of them the same time splits.

My present idea is to try and make a template-sensor with a shitload of nested “if” clauses…can anyone help me find a better way?

I still also will need to work out a “these holidays” sensor too but psudocode I’m thinking:

if holiday or saturday or sunday
  if hour now > X and hour now < Y
    "first rate"
  else
    "second rate"
else
  if summer
    if hour now > X and hour now < Y
    # many IF/ELSE testing hour of "now"
  else
    if hour now > X and hour now < Y
    # many IF/ELSE testing hour of "now"

This feels like its way over-complicated though…but I can’t think o a better way?

@Troon had some great ideas in a similar post here which you can use for inspiration.

1 Like

Oh those are some clever ones as long as times land on whole-hours! I love it!

I’d still probably need some if/elses for figuring out season, weekday, and holiday but I like the idea of using a character position and character mapping to consolidate down the hours thru the day without an absurd number of statements. That’d also let me insert an extra “type” that I could use as an identifier for “its about to change”.

I wonder if anyone has made clever holiday booleans…some (like January 1st) are easy but others are more complex rules like Thanksgiving. I need to work out:
New Year’s Day, Memorial Day, Independence Day, Labor Day, Thanksgiving, and Christmas

Just thought I would share what I came up with so far for anyone else looking for this…I still have to work out the “is it a holiday” test but I THINK this will give me a real time sensor I want.

I know its not ultra-minimized but I wanted it slightly more maintainable for when it inevitably changes. First half computes which tier, second half looks up the named tier and returns the current hour mapping expansion.

Note “isHoliday” still needs to be figured out…but its after 10PM and I gotta get up early for work in the morning so not tonight.

I welcome suggestions or the holiday or any other places that look like they could use significant improvement.

template:
  - sensor:
      - name: "Dominion Power TOU Tier"
        state: >
          {% set isHoliday = false %}
          {% set isWeekend = now().weekday() > 4 %}
          {% if isHoliday or isWeekend %}
            {# Holidays and Weekends #}
            {% set tier='summer_weekday' %}
          {% elif now().month >= 10 or now().month <= 4 %}
            {# October to April #}
            {% set tier='winter_weekday' %}
          {% else %}
            {# May to September #}
            {% set tier='summer_weekday' %}
          {% endif %}
          {{
              {'P':'on_peak','O':'off_peak','S':'super_off_peak'}
                [
                  {'hour_header_ten':'100000000011100000000011',
                   'hour_header_one':'212345678901212345678901',
                   'summer_weekday' :'SSSSSOOOOOOOOOOPPPOOOOOO',
                   'winter_weekday' :'SSSSSOPPPOOOOOOOOPPPOOOO',
                   'holiday_weekend':'SSSSSOOOOOOOOOOOOOOOOOOO'}[tier][now().hour]
                ]
          }}

EDIT: Fixed typo in timing…darn 24 hour clock maths

Here’s what you had but with a check for holidays, and also a few minor fixes/tweaks.

template:
  - sensor:
      - name: "Dominion Power TOU Tier"
        state: >
          {% set today = today_at() %}
          {% set isHoliday = (today.isoweekday() == 4 and today.day > 21 and today.day < 29 and today.month == 11)
            or (today.isoweekday() == 1 and today.day < 8 and today.month == 9)
            or (today.isoweekday() == 1 and today.day > 24 and today.month == 5)
            or ((today.date() | string)[5:] in ['01-01', '07-04', '12-25'])
          %}
          {% set isWeekend = today.weekday() > 4 %}
          {% if isHoliday or isWeekend %}
            {# Holidays and Weekends #}
            {% set tier='holiday_weekend' %}
          {% elif today.month >= 10 or today.month <= 4 %}
            {# October to April #}
            {% set tier='winter_weekday' %}
          {% else %}
            {# May to September #}
            {% set tier='summer_weekday' %}
          {% endif %}
          {{
              {'P':'on_peak','O':'off_peak','S':'super_off_peak'}
                [
                  {'summer_weekday' :'SSSSSOOOOOOOOPPPOOOOOOOO',
                   'winter_weekday' :'SSSSSOPPPOOOOOOOOPPPOOOO',
                   'holiday_weekend':'SSSSSOOOOOOOOOOOOOOOOOOO'}[tier][now().hour]
                ]
          }}

Since you specifically asked for a better way, vs a template sensor with a lot of "if"s, I’ll say it, Node Red. Even if you still want your automations in HA, setting up time frames and holidays etc. is easy, and you can use that in an automation, or do the whole thing in Node Red.

I guess I should have specified YAML better way.

I’ve tried Node Red before but it was incredibly confusing once I got more than a couple things interacting trying to follow all the many interconnections and I couldn’t find any good way to copy-paste duplicate stuff when I wanted to tweak slightly or other stuff (e.g. once I get this I can do a quick copy-paste and turn it into time of day sections for each of my HVAC zones).

Oh wow, I’ll have to study this a bit to better understand how the holiday computation works (probably starting with looking up the definition of some like Thanksgiving moves around) in more detail but that looks like it might just do it!

The 3 moving ones are Thanksgiving (4th Thursday in Nov), Labor Day (1st Monday in Sept), and Memorial Day (last Monday in May). And they are calculated by figuring out how late or early that day of the week can fall.

For example, the 1st Monday in Sept could be as early as Sept 1st, but can’t be any later than Sept 7th because if there is a Monday the 8th then there would have also been Monday the 1st. So we know that if today is a Monday and it is between the first and the 7th of Sept, then it is the first Monday of Sept.

An alternative for the holidays would be to use the holiday integration and in this code you would just check if the state of that sensor is on and the message attribute matches on of the holidays:

{{ is_state('calendar.united_states', 'on') and state_attr('calendar.united_states', 'message') in ["New Year's Day", 'Memorial Day', 'Independence Day', 'Labor Day', 'Thanksgiving', 'Christmas Day'] }}

The Workday integration handles weekdays, weekends and holidays (even those which land on different days).

All you have to do is select your country and define your workdays or in this case, your power company’s definition of a weekday. The tricky part is that the integration exposes a boolean, so a holiday in the middle of the week will simply show the entity as false.

I guess you could get around it by checking if the state of the sensor is false and it’s also a weekday, then assume it’s a holiday. EDIT: or just use the holiday integration as suggested above.

Either way, hopefully it should help you simplify your code.

Do you happen to know what the difference is between your using today_at() and today.things vs many examples I see use now().things to get months/days/hours?

Would the first part testing calendar.united_states is on be required? Presumably my home server won’t be moving around out of my house.

I did start to look at that integration but it seemed extremely confusing with most examples I could find seeming to talk about stuff like birthdays and sounded like it has to be “linked” to some other target calendar.

You can use the template tool to check these out. Developer tools → template

today_at("01:35") returns a datetime object for today (local HA time) at the specified time. If no argument is supplied it assumes “00:00”.

now() returns a datetime object for right now (local HA time) with microsecond resolution.

Paste this in the template tool and observe the result:

{{ now() }}
{{ today_at() }}
{{ today_at("01:30") }}

The calendar entity turns on when the event is active; otherwise the state is off. The message attribute will show either the active entry, or if no event is happening, it will report the next entry. This is why you also need to check the on/off state.

I don’t think it’s worth getting confused over. It’s just a couple button clicks in the UI to create the entity (Integrations → Add integration → Holiday), and then you can just copy the code I wrote (and modify the entity name to your calendar’s entity name). There’s nothing more to it.

Also: the reason you see today.blah instead of today_at().blah is because on the very first line I set a variable named today to the result provided by the today_at() function. I could have called it anything, like myvar, and then I would have used myvar.day and myvar.isoweekday().

It’s more efficient and more robust to set a variable to a fixed value and refer to it, instead of calling the function many times during the execution. There’s also a small chance the date changes during execution and results in errors or unexpected results.

Even though today_at().day and now().day will return the same thing, I use today_at() when I don’t need any of the time-related details.

This is the part I was specifically confused about the difference. So it sounds like they are more/less functionally equivalent for the purpose I was using them for?

Yes. In my case, one is the day of the month from the datetime 2024-08-01 21:05:00.368573-05:00 and the other is the day of the month from the datetime 2024-08-01 00:00:00-05:00

The day of the month from either one is 1.

1 Like