There is already a great CTA Bus Tracker for those of us lucky enough to live in Chicago and use public transit. I was inspired by this, and the soon-to-arrive renaissance of the after-times to make a train tracker.
I wanted something that would tell me the times to arrival for the next few trains at my nearby Red Line stop. I also made a map that shows me where these trains are, and an alert to warn me if there are any disruptions on that train line. The API supplied by CTA is rich in information, free and well documented. What I used here should work for any other line, too. Ideally someone who programs in python could turn it into an integration. Anyway, I had to learn lots of new details, which was fun. Thought I would save somebody some time by sharing what works.
Let me break down the steps.
1. Apply for an API key.
That can be done for free here. It took less than a minute to receive the key necessary to use the API. While you are waiting, look up the stpid
for your platform here. Scroll down to the table labeled Individual Stop IDs Quick Reference and grab the code in the first column corresponding to your station and direction of travel. Once the key has arrived by email and you have the ID for your stop, make a call to the API from your browser (Firefox is particularly helpful):
http://lapi.transitchicago.com/api/1.0/ttarrivals.aspx?key=xxxxx&stpid=yyyyy&max=5&outputType=JSON
where you can put in your emailed key for xxxxx
and your stop ID for yyyyy
. You should see lots of information about the trains approaching your stop. There is no use continuing if you don’t see this JSON code.
2. Make a sensor template to download all the information from the API:
- platform: rest
resource: http://lapi.transitchicago.com/api/1.0/ttarrivals.aspx?key=xxxxx&stpid=yyyyy&max=5&outputType=JSON
name: Mystation South Trains
value_template: "OK"
json_attributes:
- ctatt
scan_interval: 20
A few things to note. I called this new entity Mystation South Trains
, but you be you. You need that line with value_template: "OK"
because the JSON code returned has more than 255 characters. The saved attribute, ctatt
is actually the mother of all data, so you will be saving everything this way, meaning that you can make one call to the API and get all incoming trains (maximum 5 set here). Finally, I set the scan interval to 20seconds because the CTA is very generous with the number of calls allowed—50,000 per day! I will probably shut off this call at night so as not to spam them. It would be a good idea to check your configuration, and restart HA. Then go to the Develop Tools and see if you have sensor.mystation_south_trains
. The state should just be “OK”, but the state attributes should be rich with data.
3. Make a sensor template to extract out the arrival time for a train.
I will show just the first train, but this code can easily be copied, pasted, and modified to get subsequent trains. Just change .eta[0]
to .eta[1]
for the second train, etc. There are two sensors here, one to get the time of arrival, and the next to calculate the number of minutes until arrival. For the second sensor to work, you need to have the time and date integration.
- platform: template
sensors:
first_red_south_train_time:
friendly_name: "Red Line first southbound train arrival time"
value_template: '{{ states.sensor.mystation_south_trains.attributes["ctatt"].eta[0]["arrT"] }}'
first_red_south_train_time_left:
friendly_name: "Red Line first southbound train time left to arrival"
value_template: >
{% if is_state('sensor.first_red_south_train_time','unavailable') %}
N/A
{% else %}
{{ ((as_timestamp(states('sensor.first_red_south_train_time'))|float
- as_timestamp(states('sensor.date_time_iso'))|float) / 60) |float| round(1) }}
{% endif %}
unit_of_measurement: minutes
I don’t know why, but it is a bit sensitive in how exactly you extract out the information from the JSON (what type of quotes where, when you use states() vs. states., etc), this is the only one that worked for me. It gives the minutes in decimals. If you don’t like tenths of minutes you can change it to round(0)
, or don’t divide by 60 and make unit_of_measurement: seconds
. At this point, you should check you configuration again, restart HA and see if you have these sensors using the Developer’s Tools. If it is working, you can put them in any card that you like.
4. If you want to put the trains on a map.
Then you need to create a new sensor template, some automations, and make sure that you have device_tracker:
in your configuration YAML. These two new sensor templates extract the longitude and latitude for the first train incoming:
- platform: template
sensors:
first_red_south_train_longitude:
friendly_name: "Red Line first southbound train longitude"
value_template: '{{ states.sensor.mystation_south_trains.attributes["ctatt"].eta[0]["lon"] }}'
first_red_south_train_latitude:
friendly_name: "Red Line first southbound train latitude"
value_template: '{{ states.sensor.mystation_south_trains.attributes["ctatt"].eta[0]["lat"] }}'
You could also just delete the first two lines and put these just below the template sensors above. To be safe you could again check your configuration and restart HA, and go to Developer Tools to make sure that you have two new sensors sensor.first_red_south_train_longitude
and sensor.first_red_south_train_latitude
. The automation creates a device_tracker
using the service device_tracker.see
. I will show three trains being tracked. This is because they have to be added in this way, or it fails for some reason.
- alias: first_red_south_train_location
trigger:
platform: state
entity_id:
- sensor.first_red_south_train_latitude
- sensor.first_red_south_train_longitude
- sensor.second_red_south_train_latitude
- sensor.second_red_south_train_longitude
- sensor.third_red_south_train_latitude
- sensor.third_red_south_train_longitude
action:
- service: device_tracker.see
data_template:
dev_id: first_red_south_train_x
gps:
- "{{ states('sensor.first_red_south_train_latitude') }}"
- "{{ states('sensor.first_red_south_train_longitude') }}"
- service: device_tracker.see
data_template:
dev_id: second_red_south_train_x
gps:
- "{{ states('sensor.second_red_south_train_latitude') }}"
- "{{ states('sensor.second_red_south_train_longitude') }}"
- service: device_tracker.see
data_template:
dev_id: third_red_south_train_x
gps:
- "{{ states('sensor.third_red_south_train_latitude') }}"
- "{{ states('sensor.third_red_south_train_longitude') }}"
This will give you device trackers that show up in a Map card. It is modified whenever any of the coordinates of any of the three trains changes. If you have only one train, it should be clear what to delete. It might not be useful, but it looks cool.
5. Use the API to put an alert on your dashboard.
Forgot to put this here the first time. Just need two template sensors, no API key required:
- platform: rest
resource: https://www.transitchicago.com/api/1.0/routes.aspx?routeid=red&outputType=JSON
name: Redline Alerts
value_template: "OK"
json_attributes:
- CTARoutes
- platform: template
sensors:
redline_status:
friendly_name: Red Line status
value_template: '{{ states.sensor.redline_alerts.attributes["CTARoutes"].RouteInfo.RouteStatus }}'
I also added a custom button that flashes on my dashboard only if there are delays. When you click on it, it takes you to the CTA website that has more info. In lovelace:
- type: conditional
conditions:
- entity: sensor.redline_status
state_not: Normal Service
card:
type: 'custom:button-card'
tap_action:
action: url
url_path: 'https://www.transitchicago.com/redline/#alerts'
entity: sensor.redline_status
name: RedLine
icon: 'mdi:subway-variant'
template: alert
styles:
icon:
- animation:
- blink 2s ease infinite
show_state: true
where I used a template for the page:
alert:
show_state: true
styles:
card:
- background-color: 'rgb(3, 169, 244)'
- boxshadow: none;
- overflow: unset
icon:
- color: yellow
- animation:
- blink 2s ease infinite
name:
- color: yellow
state:
- color: yellow
However, there is some bug here, since I shouldn’t need to add that blinking bit twice.
6. What else?
If you don’t live in Chicago, it might still be useful since CTA tries to conform to some Google standard. In case you are not aware, there is an integration with Here, to estimate your travel times between two points, like work and home, using public transit (and biking and driving). You could also use the mapid to get trains going both directions, but then the JSON will be different and you will have to figure out how to modify this. Some day this will be fed into some AI that tracks all my movement, and start telling me what I should be doing in the morning. Maybe.
This can probably all be done more efficiently, so suggestions are welcome in the comments. Also ideas about how to expand this would be great. Hope it helps someone!