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

Hey guys!

I’m trying to create an automation that triggers at a dynamically calculated time, based on the train delay and also the traffic sensor from Waze.

Let me be more clear: my father always takes the same train, which, according to the api of ViaggiaTreno, I have configured for tracking as a sensor in my HomeAssistant installation. In addition, I have setup another sensor (Waze Travel Time) that calculates the time needed to get to the train station.

Now, I would like my HomeAssistant to trigger an automation in time to get to the station, so it should check the delay of the train (it’s given by the train sensor of ViaggiaTreno, the only problem is that it seems to output the number of minutes + the unit so something like this: 13 min) and if it is more than x minutes (let’s suppose 10, for convenience), will take in count of this and talk to me (using tts_google_say) when the train is arriving, again, by looking also at the Waze sensor so I won’t be late when the train gets to the station.

Now a concrete example to clarify:

Assumption: sensor of the train 1234 reports a delay of 15 min --> train arrival was scheduled at 19.00, will arrive for real at 19.15
(0. compare the attribute of the sensor of the train (arrival_time) with the actual time) I’m not sure it’s totally needed…

  1. check the travel time to the station (Waze sensor), let’s suppose 5 minutes
  2. calculate the departure time so that I will arrive at the train station when the train gets there (according to its delay), so in this case should be something like this: 19.15 (real arrival) - 5 min (traffic based on the sensor) --> triggers my Google Home Mini (tts_google_say) at 19.10 (maybe a couple of minutes before could be better)

I know it’s a bit complex but I seriously would like this to work since it would help me a lot in my everyday routine.

Here is the core of the automation I’ve tried to setup but seems like it’s not working the part of the delay (I suppose because of the output of the train sensor (digits + min). I think a possible solution would be something like exploding the string based on the space separator between digits and chars but I totally don’t know how.

Any help is truly appreciated.

Thanks guys.

automation 1 (Waze travel time) I’m using this to know if there’s traffic on my way to the station

alias: railway station alert
trigger:
- above: '10'
  entity_id: sensor.percorso_stazione
  platform: numeric_state
condition: # I need it to trigger only in certain hours, not every day
- after: 19:00:00
  before: 21:00:00
  condition: time
- condition: state
  entity_id: binary_sensor.workday_sensor
  state: 'on'
action:
- data:
    message: Seems like that's a bit more traffic than usual, on your way to the train station
  service: tts.google_say

automation 2 (train delay based on ViaggiaTreno)

alias: train delay alert
trigger:
- above: '10'
  entity_id: sensor.train_10658
  platform: numeric_state
condition: # I need it to trigger only in certain hours, not every day
- after: '19:00:00'
  before: '21:00:00'
  condition: time
action:
- data_template:
    message: "Looks like the train is {{states('sensor.train_10658')}} minutes late"
    cache: false
  service: tts.google_say

I’m trying to merge this two automations inside one and do the right modifications to make them work properly.

I’m not sure I completely followed that. Are you saying that automation 1 works, but automation 2 does not? And you think automation 2 is not working because the state of sensor.train_10658 is something like “3 min”?

If so, this shouldn’t be too hard. But first, where is the best place to convert “3 min” to a pure number? E.g., you could do that in the sensor itself, or you could do that in the automation trigger. Do you want the sensor’s state to be what it currently is (i.e., a string like “3 min”), or would it be all around better for the sensor to be a number with a unit_of_measurement?

I’d be happy to provide more concrete help, depending on the answer to the above questions. It might also be helpful if you could post the current config for sensor.train_10658.

Thanks a lot for the answer :wink:

You’re right, the thing is really hard to explain but you got it right. The automation 1 (the one which shows the travel time using Waze sensor is working properly but the other one (ViaggiaTreno) isn’t. I assume, as you correctly said, it is due to the output of the train sensor, which seems to be like this: number + its unit, so min (e.g. “3 min”).

I don’t know what would be the best solution to adress this problem, just choose the easier one. Viewing 3 or 3 min isn’t that important for me. The only thing I would like to see working is the automation, that I will recap here with a code-like example.

alias: check train delay and remember me to get to the train station as the train is arriving
trigger:
- above: '10' #minutes, if these are more than 10, I should consider the train in excessive delay and so make calculations in order to trigger the tts_say based on the train delay and time (Waze) to get to the station
  entity_id: sensor.train10658
  platform: numeric_state
condition: #trigger only in a time gap before the train arrival, with a large offset (in case the train is running hours late)
  - after: '19:00:00'
    before: '21:00:00' #2 hours of delay should be enough, I hope :rofl:
    condition: time
action:
- data_template:
    message: "Looks like the train is running {{states('sensor.train_10658')}} minutes late, you should go to the train station at TIME" #where "TIME" should be calculated based on the new delayed arrival time of the train - the time needed to get to the railway station (based on the waze sensor).
    cache: false
service: tts.google_say

Hope I didn’t make a terrible mess and also that I was able to explain the thing a little better than before.
If you got more question, please feel free to ask.

Here are the two sensors setup I’m currently running:

Waze Travel time sensor:

- platform: waze_travel_time
  name: "percorso_stazione"
  origin: latitude, longitude #my home, also could be "zone.home"
  destination: latitude, longitude #train station
  region: 'EU'

ViaggiaTreno sensor - Italian railroads

- platform: viaggiatreno
  train_id: 10658
  station_id: S01703

Talking about the ViaggiaTreno sensor, according to the API guide I found on the developer’s site, this sensor entity should have these attributes but I can only see these, instead:

A few more considerations:

  • the “not departed yet” status of the sensor should explain us why the automation that should read the delay, doesn’t work: if the train hasn’t departed, like in the upper pic I attached, it shows a string. When the train leaves, it starts showing the delay in the digit + min string.
  • the attributes displayed by the sensor entity are the following (I will try my best in order to translate them for you): real departure time (if the train leaves with delay, this would be the exact departure time), destination, attribution, real arrival time (like real departure, but it’s the arrival), origin, friendly_name, subTitle (sometimes shows the reason of the delay, but very rarely), icon, scheduled departure time, train category (reg means regionale so a train that travels in the same region), unit_of_measurement (looks void), scheduled arrival time, train number.

I totally don’t know why the scheduled arrival and departure times are showing this stupid number: 1539359580000. Looks like it’s not changing, even when train is running.

If I’m not wrong, we should do the subtraction between the compOrarioArrivoZeroEffetivo (or real arrival time, in english) and the sensor.percorso_stazione provided by Waze. That should gave us the right time to leave home in order to get to the train station, based on the traffic on our way and the delay of the train. Also the tts should trigger x (let’s suppose 2 or 5, for simplicity) minutes before that calculation, so that I won’t need to rush to arrive in time, as the Google Home tells me to go.

Thank you for your cooperation :blush:

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.