How to trigger automation BEFORE set time?

That’s what I thought as well. I rather have it easy than more complex than it’s necessary :slight_smile: Earlier I just had three automations, now I get by with two (one for turning on the heater, and another one to turn it off). That’s fine by me, especially since the code is “cleaner” than what I had before! :slight_smile:

Ah ‘Glasshopper’ (1970’s TV reference you may not get) you give me pride in your accomplishments but also humour at your naivety. This too can be done with a service template.
You prove there is life in the old Mutt yet :rofl:
Let me get to a workstation …

Though, what I’d suggested to Gilean was that as we are triggering every minute to test, we even out the granularity of the response, save fuel/energy and do it dynamically.
Ie
We don’t consider any case before 180 mins before set time, we turn off at set time and at any time between we calculate if the heater needs switching.
160 - (Temp * -16) = minutes after the 3hrs before (sounds weird)
So let’s say it’s -10 degrees
160 - (-10 * - 16) = 0 so switch now (ie set time - 180 + 0) 180 mins of heat
-5 degrees : -
160 - (-5 * - 16) = 80 so switch after 80 mins (ie set time - 180 + 80) 100 mins of heat
0 degrees
160 - (0 * - 16) = 160 so switch after 160 mins (ie set time - 180 +160) 20 mins of heat
1 degree
160 - (1 * - 16) = 176 so switch after 176 mins (ie set time - 180 +176) 4 mins of heat
2 degrees
(160 - (2 * - 16) = 192 so is outside time to switch - ignored, 0 mins of heat

This is dynamic, done minute by minute so will adapt to changing weather and assuming linear heat gain should always achieve the same final temp at ‘set time’

Edit: note the ‘funny’ formula is because even at 0 degrees he wanted ‘some heating’ (must be a namby pamby southerner’ :rofl: )
This will also eliminate two of the three evaluations but add a little complication as we are also switching off

1 Like

Definitely too young to get this reference :rofl:

I know that it is possible to do this. To be honest, I just didn’t want to spend more time and make it more complex, because (I think) OPs needs were covered with my sample code. If I were at home right now, I’d probably try to figure it out, however at work it’s a bit hard (and I should be working anyway :rofl:) to do this.

I really like your suggestion of making this whole thing more dynamic and I can’t wait to see your solution :slight_smile:

I like the word “dynamic” :slight_smile:

Well, considering that I live in Finland, I think it’s safe to say I am not “southerner” except from reindeer’s point of view :joy: Anyway, the official reason I want heater still work up until +2C is that “car people” say it’s good for your car, actually you should heat your car always if it’s under +5C but… The unofficial reason is that I also have Defa interior heater that takes care of ice from windshield and moisture if it’s near or just above zero. :slight_smile:

Understood.
If you read burnings note it’s also true that you can ‘overcomplicate’ things.
So just stop if things are going too far.
You have to keep and maintain this stuff after all.
But I think given the course of the above thread you know when to call it a day.
This is now an interlectual excersise for burning as he’s relatively new to this (hard to believe isn’t it ? And I’ve already told him that I’ll be seeking his advice soon :rofl: )
In the UK there is a bridge over the Tyne (River) it was put up for the millennium. It is said that on its southern end it has 8 coats of protective paint. On the northern side, it has ‘none’ “cos its not a pufta” (‘pufta’ is a derogatory term, in this case used to mean ‘namby pamby’ but it ‘can’ be used in a homophobic context also, so thinking about this I should have found another term but this use is authentic ‘geordie’).
Geordies (people from this region) are renown for being tough and often go out in sub-zero temperatures ‘without a coat’

Edit: I also believe, that if we give you a formula and an implementation, you will understand it well enough to tweak the formula to your needs given some time working with it and judging the resultant temperature.

2 Likes

all this is doing is adding strings together safely.

an alternative would have been to use + signs.

(now().date() | string returns the date in this format 2020-30-1
now().date() | string ~ 'T' just adds T at the end for 2020-30-1T
now().date() | string ~ 'T' ~ states('sensor.alarm_clock2_time') we just keep adding to it, ending up with 2020-30-1T08:00
now().date() | string ~ 'T' ~ states('sensor.alarm_clock2_time') ~':00' adding more, ending with a format that as_timestamp can parse naturally: 2020-30-1T08:00:00.

You’d get the same thing with

now().date() | string + 'T' + states('sensor.alarm_clock2_time') + ':00'
3 Likes

Ahha! Now that’s informative post, thank you! I had no idea that as_timestamp needed such format for it to work! Some other solutions have been converting time to integer, then to Unix timestamps, then do the math and then converting back to “normal” time format. I think my old solution used that method but it was very complex…

Yah this is probably one of the easier ways to get a usable datetime objects. But you gotta know the format.

The most astonishing part was that he apparently wrote up that code without testing, and it was the only code that worked straight out of box (except for minor typo with sensor name but that one I could fix myself :smiley: ). I mean, it’s not that I don’t appreciate people helping me because I really do, but for someone who doesn’t understand code much, it can be quite overwhelming to see various different methods of achieving things, and when none of them work because of some minor in syntax and I cannot see it (and template editor is just saying “unknown error” or something like that) :slight_smile:

That is true, except if the code has lots of conversions and () | {* characters, then it’s different story… And unfortunately for this automation, there just has to be those, more or less :slight_smile: For example, in that Burningstone’s conditions, there is [0:5] that I have no idea why and what that does… But still, that code is simple enough for me to adjust it as needed :slight_smile:

That’s one of mine,
It allows you to compare times as strings rather than as timestamps

EDIT: this ONLY works with times in the sameday or if you test for a wrap around (over midnight) when comparing two values

It takes the first 5 characters of the string, maybe this isn’t needed, however I didn’t know if your alarm clock sensor has this “00:00:00” or this “00:00” format.

1 Like

To be precise it takes the beginning of the string from position 0 _ upto but not including position 5
(in effect the first 5)
BUT
you can also do it in reverse with the end of the string, this is useful if your time also has date as this will work with both
[-8:-3]
so
{{ ‘12:34:45’ [-8:-3] }} returns 12:34

Edit This last was robbed kindly donated from pnbruckner

2 Likes

Jinja (the language used for templates in home assistant) is a template engine for Python, so most of things will work the same/similar as in Python.

The [0:5] is called string slicing in python, you can read up on it here in case you are interested.

1 Like

It’s things like these that make me wish there was somewhere a website with lots of (Jinja/Python/YAML) code and user could click anywhere and you could see a small tooltip explaining what that thing does. For example, your explanation with this example would have been perfect for that :slight_smile:

Yeah, but it would need someone like @petro to do it, and his contention is that it’s just jinja wrapped python (true) but it would be nice to have a site with standard examples for various template usages.
Instead “these are just things we pluck from the hedgerows of experience”.
And I also think that petro has enough on his plate :rofl:

And you’d be amazed at how differently some people attack the same problems with different approaches, and we learn new wrinkles all the time

1 Like

you don’t need to include the zero [:5] is the same

Jinja is based on python, don’t know if it’s a wrapped version of it.

I’ve read that Jinja was created for print formatting, so given it’s usage in HA
I think it’s used to pass python up to the sandbox for evaluation
Hence ‘jinja wrapped python’
This is a fairly well educated guess - but it’s still a guess :rofl:

Yeah, it’s meant to serve up information to static forms, like a webpage. Pretty much why it’s used in HA.

So here is my version

automation:
  - alias: 'Turn Car Heat On'
    initial_state: on
    trigger:
      platform: time_pattern
      minutes: '/1'
    condition:
      - condition:  template
        value_template: "{{ states('sensor.sonoffth3_am2301_temperature') | float < 2 }}"
    action:
      - service_template: >
          {% set timenow = as_timestamp(now()) %}
          {% set hr = states('input_number.alarm_clock3_hour') | int %}
          {% set mn = states('input_number.alarm_clock3_minute') | int %}
          {% set settime = as_timestamp(now().strftime("%Y-%m-%d " ~ '{0:0=2d}:{1:0=2d}'.format(hr, mn) ~ ':00')) %}
          {% set starttime = settime - (3*60*60) %}
          {% set slot = (starttime <= timenow < settime) %}
          {% set tempdeg = states('sensor.sonoffth3_am2301_temperature') | float %}
          {% set mins = 160 - (tempdeg * -16) %}
          {% set heatrqtm = starttime + mins * 60 %}
          {% set heat = slot and timenow >= heatrqtm %}
          {% if heat %}
            switch.turn_on
          {% else %}
            switch.turn_off
          {% endif %}
        entity_id: switch.sonoffpow3_2

Given me another 5 Mins and I’ll add some comments to explain it.
You will NEED to test this as it looks okay to me but I only have the template editor to test it as I have no actual entities suitable.

automation:
  - alias: 'Turn Car Heat On'
    initial_state: on
    trigger:
      platform: time_pattern
      minutes: '/1'
    condition:
      # this condition just stops the automation if your temp is 2° or higher, adjust as you see fit (cuts down on further processing)
      # Note: I also considered parsing the start and stop times here but it was nearley as much processing required as is in the service template
      # Note2 : the '>' is not required for a single line template but then it does require quotes
      # Note3 : Note that the quotes are double on the outside and single within
      - condition:  template
        value_template: "{{ states('sensor.sonoffth3_am2301_temperature') | float < 2 }}"
    action:
      - service_template: >
          {# This is a jinja comment and will be ignored by the interpreter #}
          {# ALL the below is indented more than it needs to be to show the comments more clearly #}
          {# This next line (all comments will be as this) gets the time RIGHT NOW and stores it as a HUGE number that has both date and time #}
            {% set timenow = as_timestamp(now()) %}
          {# this gets your input number hour (input_datetime is much easier) and stores as a number in hr #}
            {% set hr = states('input_number.alarm_clock3_hour') | int %}
          {# this gets your input number minute and stores as a number in mn #}
            {% set mn = states('input_number.alarm_clock3_minute') | int %}
          {# this sets your 'settime' as a huge number and stores in settime #}
            {% set settime = as_timestamp(now().strftime("%Y-%m-%d " ~ '{0:0=2d}:{1:0=2d}'.format(hr, mn) ~ ':00')) %}
          {# as we've already done most of the work we just subtract 3 hours from the set time to start our evaluation #}
            {% set starttime = settime - (3*60*60) %}
          {# this tests to see if we are 'in' the 'slot' #}
            {% set slot = (starttime <= timenow < settime) %}
          {# this gets the temperature (as a float, as that will aid in resolution) #}
            {% set tempdeg = states('sensor.sonoffth3_am2301_temperature') | float %}
          {# this gets the minute offset from the start based on the temperature #}
            {% set mins = 160 - (tempdeg * -16) %}
          {# given the 'offset' we now have a new 'start time' calculated, based on temperature #}
            {% set heatrqtm = starttime + mins * 60 %}
          {# this tests that we are (now) in the 'slot' and 'that switch on time' is in the past #}
            {% set heat = slot and timenow >= heatrqtm %}
          {# this uses the last evaluation to determine if we switch 'on' or 'off' #}
            {% if heat %}
              switch.turn_on
            {% else %}
              switch.turn_off
            {% endif %}
        entity_id: switch.sonoffpow3_2

Note: the above code is bulky and could easily be slimmed down, but this way it’s easier to maintain and when you come back to it in 5 years you’ll spend less time scratching your head saying “What the hell does that do ?”

Note: slimming it down is not the same as optimising it. Petro is a specialist at optimising stuff so he may come back with some suggested changes.

Now, Lets see what @Burningstone has to say ? (ie is it concise enough ?) {Edit: it’s 27 lines, the same as burnings but mine switches off if the temperature rises and at the end of the slot. I could also make it shorter but readability would suffer :rofl: )

Edit: note it will switch on (assuming it’s cold enough) every minute, this is not a problem for switches and lights etc because it doesn’t turn them off first so the switch will not even notice.
You could ‘condition test’ to see if the switch was ‘on’ and required ‘on’ OR ‘off’ and required ‘off’ but that would have to go in the condition and again that’s a LOT of additional and unecessary processing.

Edit2: the ‘formula’ is based on a 3 hour assessment period and an assumption that the max we need is 3 hours and the minimum is 20 mins at 2 degrees (1.25° means NO heating, 160 - (1.25 * -16) = 180 so it wont even be considered)

2 Likes

Didn’t read all of it yet, but your value template is missing one curly brace at the beginning and at the end :shushing_face:

1 Like