Automation based on traffic and train delay (Waze travel time sensor + ViaggiaTreno)

Ok, so you want an automation that triggers when it’s time to leave for the train station so that you get there when the train arrives (or a little before), taking into account the current estimated travel time.

It seems like sensor.train_10658’s attribute compOrarioArrivoZeroEffettivo (real arrival time) gives you the estimated time the train will arrive at the station. You can get that with state_attr('sensor.train_10658', 'compOrarioArrivoZeroEffettivo)`. But I’m guessing that is a string, so it will have to be converted to something that can be used in an equation.

And sensor.percorso_stazione has the estimated travel time (in minutes, I assume, and since it’s a state it’s also a string.)

So this template will calculate the time to leave, based on those two values, as a string formatted as HH:MM.

{% set arrive = state_attr('sensor.train_0658', 'compOrarioArrivoZeroEffettivo').split(':') %}
{% set travel = states('sensor.percorso_stazione') %}
{{ (((arrive[0]|int)*60+(arrive[1]|int)-(travel|int))*60)|timestamp_custom('%H:%M',false) }}

Now the trick to getting an automation to trigger at a calculated time is to use sensor.time. If you don’t have that configured, then add this to your configuration:

sensor:
  - platform: time_date
    display_options:
      - time

This sensor will update every minute and shows the time also in the HH:MM format.

Putting it all together you get this automation:

automation:
  - alias: Notify when time to leave for train
    trigger:
      platform: template
      value_template: >
        {% set arrive = state_attr(
             'sensor.train_0658', 'compOrarioArrivoZeroEffettivo').split(':') %}
        {% set travel = states('sensor.percorso_stazione') %}
        {{ (((arrive[0]|int)*60+(arrive[1]|int)-(travel|int))*60)
           |timestamp_custom('%H:%M',false) == states('sensor.time') }}
    condition:
       ...

I’ve made some assumptions that might not be correct, so this may still need some tweaking. Let me know what you think. And feel free to ask any questions about what I did.

EDIT: There is, of course, a major flaw in this suggestion so far. If the estimated travel time changes and the estimated arrival time changes, it’s possible this may never trigger. Probably needs to somehow determine if current time is after estimated time to leave. That’s a bit more work. Then, as the estimates change you might get multiple triggers. So still some “head scratching” to do. :slight_smile: But hopefully this is a start.

1 Like

Hey man!

Thanks a lot for the great answer. I’m currently running few tests on this automation. The only problem is that I always have to wait for the train to depart for real, since changing the sensor everytime is a terrible mess. I hope I could use a demo sensor but I actually don’t know how to initialize it and to make it output the string I like, so that I would be able to test the automation everytime I like.

UPDATE: looks like the automation is causing several errors since, if the train hasn’t left yet, the train sensor outputs this string: “Not departed yet” which seems to cause this error:

Value cannot be processed as a number: <state sensor.train_10658=Not departed yet; destinazione=NOVARA, categoria=REG, subTitle=, orarioPartenza=1539791580000, origine=PIOLTELLO LIMITO, icon=mdi:train, orarioArrivo=1539796320000, friendly_name=Suburbano S6 10658, attribution=Powered by ViaggiaTreno Data, compOrarioPartenzaZeroEffettivo=17:53, compOrarioArrivoZeroEffettivo=19:12, unit_of_measurement=, numeroTreno=10658 @ 2018-10-17T10:01:37.844326+02:00> (Offending entity: Not departed yet)

and then, as a side effect, I’m getting flooded by a giant quantity of this error, that gets repeated about every minute:

Error during template condition: UndefinedError: 'None' has no attribute 'split'

I’m trying to do my best to fix it but had no luck until now.

Thanks for the great work, man :wink:

EDIT: by taking a look at the historical data graph of the train sensor, I’ve discovered that it essentially oscillates between 4 states:

  1. Not departed yet
  2. (departed) minutes of delay: e.g. 5 min
  3. (departed but got no server info… seems to happen frequently) No information for this train now
  4. Arrived

so this is an added problem, since it will happen for sure that we get no info from ViaggiaTreno (it’s not a problem with HomeAssistant, it’s a problem with Trenitalia system…) and the train has departed, so we should make something to avoid the automation to wrongly trigger because of this missing info.

You’ll have to share your current automation, because the trigger I suggested does not use the state of sensor.train_10658; it only uses its compOrarioArrivoZeroEffettivo attribute. So there’s no way the trigger I suggested would cause that error.

You’re right, Phil, I made an incorrect assumption but the problem was actually coming from another automation. I’ve made some minor changes on the (working) automation you provide me, just adding the workday sensor and also a triggering interval.

Might be a good idea to implement a time offset so that the announcement of the train arrival will be made few minutes before the actual arrival (you can be busy and may arrive a bit later if you don’t leave at the exact time when the automation triggers).

Apart from this, the automation seems to work perfectly and I would like to thank you a lot for your great work. It will be amazing to not have to check everyday the delay of the train and also get to the railway station in time :heart_eyes:

Here is the slightly modified code (from your original -working- idea):

- alias: Notify when time to leave for train
  trigger:
    platform: template
    value_template: >
      {% set arrive = state_attr('sensor.train_10658', 'compOrarioArrivoZeroEffettivo').split(':') %}
      {% set travel = states('sensor.percorso_stazione') %}
      {{ (((arrive[0]|int)*60+(arrive[1]|int)-(travel|int))*60)
         |timestamp_custom('%H:%M',false) == states('sensor.time') }}
  condition:
    condition: and
    conditions:
      - condition: time
        after: '18:00:00'
        before: '21:00:00'
      - condition: state
        entity_id: binary_sensor.workday_sensor
        state: 'on'
  action:
  - data:
      message: The train is arriving. Leave now to get in time to the railway station.
    service: tts.google_say

Again, thanks a lot for your help, really appreciated.

Glad it’s working for you, and happy to help!

FYI, for the condition part, if you list multiple conditions they will be and'ed, so you don’t need to explicitly use condition: and here. So the following is equivalent to your condition part:

  condition:
    - condition: time
      after: '18:00:00'
      before: '21:00:00'
    - condition: state
      entity_id: binary_sensor.workday_sensor
      state: 'on'

Wow this is awesome! I am using a slightly revised version of this logic to pick my son up at school. For me his school is always done at a fixed time, so I am working backwards from that (55800 is 15:30 in seconds). I also built in a buffer to let me wrap things up before I hit the road.

I am concerned about the major flaw phil pointed out in that if the waze travel time all of a sudden gets longer, then it is possible that it would never fire. I was thinking about how to fix this and came up with some options below. Before I go trying them I was hoping for some feedback from the pros :wink:

  1. Could we change the == to a <=? As phil mentioned this would presumably keep triggering, but could we add a condition so that it must be at least 12 hours (or however many hours you need to get you past midnight) since last triggered? That way it would fire once per day… The buffer comes into play here because without it, it would be challening to make up time.

  2. I tried #1 but i couldn’t get it to fire… Could we create a switch, or switches, and have the value template set the status of the switch(s). Automations could fire based on the status of the switch…

Here is what I have so far with the gaping flaw…

- alias: travel_time_son_school
  trigger:
    platform: template
    value_template: >
      {% set travel = states('sensor.me_to_son_school') %}
      {% set buffer_time = 15 %}
      {{ ((55800)-((buffer_time|int)+(travel|int))*60)|timestamp_custom('%H:%M',false) == states('sensor.time')  }}
  condition: 
    condition: state
    entity_id: binary_sensor.workday_sensor
    state: 'on'
  action:
  - service: notify.mobile_app_iPhone
    data:
      message: Waze son!
      data:
        push:
          sound:
            name: default
            critical: 1
            volume: 1.0  

First off, this topic is way old. I didn’t read through it so I can’t guarantee how valid any of my statements from back then are! lol

Next, generally it’s not a great idea to compare string representations of numbers. E.g., '10' >= '5' is actually false. But in this case, when you’re comparing formatted times where hours < 10 are represented with a leading zero, it’s ok.

Next, a template trigger like this will only fire when the result of the expression changes from not true to true. And it will only fire once. It won’t fire again until it changes to not true and back to true. So continual triggers won’t be a problem.

Lastly to deal with the fact that the travel time estimate can jump up and down, yes you could change the comparison to <=. That will handle the case where the current time never matches exactly the estimated departure time (e.g., the travel estimate jumps up and hence the estimated departure time jumps from in the future to in the past.) But, if it jumps the other way you could certainly get multiple triggers. One way to deal with that is to add a condition that the last_triggered time of the automation must be, say, at least 12 hours ago. Something like:

  condition: 
  - condition: state
    entity_id: binary_sensor.workday_sensor
    state: 'on'
  - >
      {% set last = state_attr('automation.travel_time_son_school', 'last_triggered') %}
      {{ last is none or (now() - last).total_seconds() > 12 * 60 * 60 }}

Yes it is old but seems to have withstood the test of time, and was one of the few useful threads for this kind of dynamic messaging. This is my first template trigger and was expecting it to continuously fire. When it didn’t I thought that it didn’t work. Thanks so much for the condition, that would have taken me a while to figure out on my own.

I’m working on some if clauses for the action to have different messages depending if I’m within or outside of my buffer. When I’m done testing I’ll post back with the entire automation to close the loop and help the next person. Not that I really need this these days given I am home 95% of the time and walk to get him, but I’m learning lots!

1 Like

Here is the relevant part from my automation file. Its still sort of a work in progress but at this point fairly low on the backlog so i wanted to share this for now.
Basically i gave myself a 15 minute buffer to give me time (up to) to wrap up what i’m doing in case i totally loose track of time. Additionally this buffer should compensate for any traffic that would occur suddenly (15 min for me would be at least 50% of my travel time) so if that happened I wouldn’t be late all of a sudden.
Everyday I have to pick him up at 3:30 (55800)
I typically get the else message, to get ready to leave in 15 minutes which is my buffer.
If something happens with traffic and I get the in buffer message it calculates how much time I have left in my 15 minute buffer (subtracting from 15)
If I get the late message it actually means that I don’t have any buffer (<=-15) and it calculates how late I will actually be past 3:30 (absolute value) so I will know if I need to call someone to pick him up.
Took me a while to get the time logic working well, hopefully it will save someone some time.

- alias: travel_time_son_school
  trigger:
    platform: template
    value_template: >
      {% set travel = states('sensor.me_to_son_school') %}
      {% set buffer_time = 15 %}
      {{ ((55800)-((buffer_time|int)+(travel|int))*60)|timestamp_custom('%H:%M',false) <= states('sensor.time')  }}
  condition: 
  - condition: state
    entity_id: binary_sensor.workday_sensor
    state: 'on'
  - > #change to 12 hours below
    {% set last = state_attr('automation.travel_time_son_school', 'last_triggered') %}
    {{ last is none or (now() - last).total_seconds() > 1 * 60 * 60 }}
  action:
  - service: notify.mobile_app_iPhone
    data:
      message: > #replace with 55800 (3:30)
        {% set travel = states('sensor.me_to_son_school') %}
        {% set buffer_time = 15 %}
        {% if ((((55800)-((travel|int)*60)-(now().hour*60*60+now().minute*60+now().second)))/60-(buffer_time|int)) <= -15 %}
        Waze Son! LATE! You must leave right now. You are going to be {{(((((55800)-((travel|int)*60)-(now().hour*60*60+now().minute*60+now().second)))/60)|abs)| round(0) }} minutes late. If you are more than 15 call Partner.
        {% elif ((((55800)-((travel|int)*60)-(now().hour*60*60+now().minute*60+now().second)))/60-(buffer_time|int)) < 0 %}
        Waze Son! You must leave right now you are in the buffer, you only have {{((15-(((((55800)-((travel|int)*60)-(now().hour*60*60+now().minute*60+now().second)))/60-(buffer_time|int))|abs)))| round(0) }} minutes to spare .
        {% else %}
        Waze Son! Get ready to leave in {{((((55800)-((travel|int)*60)-(now().hour*60*60+now().minute*60+now().second)))/60)| round(0)}} minutes. Son doesn't like to wait
        {% endif %}
      data:
        push:
          sound:
            name: default
            critical: 1
            volume: 1.0  

Future improvements include making this only trigger if I am out of my home zone. This is easy enough but for testing (since i’m always home I left it out). I would like to have an actionable command to dismiss, or to call my partner in the case I am late. Also wanted to look at setting up changes to scan interval if I am out, around the time i would normally get the message.

love the work of @drewHA and @pnbruckner. thank you for the template and conditions template also!
i’m very new to templates. i’d actually like to understand the workings of it. is there a reason why you converted the leaving time into seconds?
i was wondering if there’s a formula/template i could use to work out an arrival time based on the current and a custom time input. my plan is to collect statistics for average drive time via home assistant for an average eta drive time with a custom time and use that in a lovelace

So i just stole pnbruckners code and modified, i donèt really know what ièm doing but ill have a crack at answering your quesitons! The main reason is because we are doing math, we want that lowest common denominator. Waze travel time integration spits in seconds and to be the most precise we use seconds. You could probably convert waze to minutes and use minutes and not loose much, but you aren’t gaining much because you are still doing some conversion.

I haven’t used inputs at all, my life is routine and boring :frowning: but this example does use the current time in the form of: (now().hour6060+now().minute*60+now().second) that takes the current time and converts it to seconds to use in the formula. Sorry I can’t help on the input much, but i would imagine it would be pretty similar. You would replace my 55800 with your input (converted to seconds, maybe similar to the now conversion I’m not sure. These should get you started:

Good luck and i’d be interested to see the code when you get it working with the input time as that would be useful for sure.

I’m using the code written by @drewHA to let me know when I need to leave for work, but my automation isn’t triggering on its own. When I run the automation, I receive my notification. So I tried putting the trigger value_template into the Template tool under Developer Tools, and it correctly displays False until it’s time to leave for work and switches to True.

I’ve tried removing conditions for the time being, in hopes of receiving a notification. Not sure what else to try. Does the platform template automatically trigger off of the False/True change or do I need to add an additional trigger or create a helper of some sort?

Are you using the same conditions as me? The condition is that it is a work day, i’m not sure if this works for you depending on your schedule, but it generally did work well for me.

The other condition is that it hasn’t been run in the past hour, which doesn’t sound like it would be met. If you want to post your automation it would be useful to help you further. Plus let us know if it runs without the conditions. The only thing I could think of would be the time. For me its 15:30 so its 156060+30*60 = 55800 yours would likely be something in the 30,000 range if you were trying to get to work for 9. Also check your system time, maybe its off. I don’t use the system time value much and probably wouldn’t noticed if it was set wrong but for this automation. Good luck!

Thank you for your suggestions. Here’s my automation:

- id: '1682723430570'
  alias: Waze Work Travel Notification
  description: ''
  trigger:
  - platform: template
    value_template: ">\n      {% set travel = states('sensor.work_travel_time') %}\n
      \     {% set buffer_time = 5 %}\n      {{ ((24600)-((buffer_time|int(0))+(travel|int(0)))*60)|timestamp_custom('%H:%M',false)
      <= states('sensor.time')  }}"
  condition: []
  action:
  - device_id: f5aa4bac19fc89c5ac135b8e21cbfdc0
    domain: mobile_app
    type: notify
    message: Time to leave for work.
  mode: single

I turned mine off when I went on vacation and forgot to turn them back on. I turned them back on last night and it went off this morning, when it should be going off around 15:00.

My Waze integration sensor is now reading 6 hours for what would really never be more than a 30 minute route. I don’t think it’s the automation but actually the integration. Perhaps something changed? Does your sensor time seem realistic?

Back on April 6 it appears my sensor changed significantly without any configuration change. It went from a mean of 3 to 392 and has been there since.

I thought that it might have been a change from seconds to minutes or some thing but after seeing my values I doubt that’s the case.

I had my Waze configured manually in yaml and région set to US. I was getting errors in logs to I deleted the manual config and did it via gui. I also set région to North America since I am in Canada and also used an gps addressand that seems to have done the trick.

Now I am trying to add a condition that if I am within a certain distance from my office it won’t alert me since I don’t have to pick up when I go into work. I think I’ll add a new sensor for Waze that will calculate the time from me to my office and my condition will be within a minute or two of that time.

I noticed a few things different in your code. I have no clue how much they would impact but I don’t have those >\n and i have a single quote surrounding everything.
I don’t have my int’s defined with the (0).

Did you receive a notification without the conditions? I didn’t have to do anything else than what you have. Given the fact it went off this morning it is still working for me, and I now have my sensor worked out so I’m expecting it will be all good tomorrow.

My sensor times look correct. I’ll try taking out the (0) after my int’s.

I haven’t received any notifications off of this trigger yet, with or without conditions.

They look like they’re supposed to be carriage returns, but I’m not sure why the \n is added to the automation.yaml file. The code in the GUI looks like this:

The screen shot is helpful. Mine is all on one line, which probably isn’t a big deal. I do not have the initial > in mine it starts with what you have on line 2. Maybe that is the issue.

1 Like

Removing the > at the beginning fixed it. Looks like that syntax is only needed in message notifications.

Thank you for the help!