Skip to content
49 changes: 36 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,45 +7,68 @@ Show departure information from ctabustracker (Chicago Transit Authority).
This plafrom is based of the findings of [@SilvrrGIT](https://github.com/SilvrrGIT)
in this [forum post](https://community.home-assistant.io/t/cta-bus-tracker-sensor/92416)

**HA 0.86.0 or newer:**
## Installation
### HACS
Add this as a custom repository and restart your HA instance.

### Manual
To get started put `/custom_components/ctabustracker/sensor.py` here:
`<config directory>/custom_components/ctabustracker/sensor.py`

**Older versions**

To get started put `/custom_components/ctabustracker/sensor.py` here:
`<config directory>/custom_components/sensor/ctabustracker.py`

## Example configuration.yaml

```yaml
sensor:
platform: ctabustracker
api_key: 'dfshkdkf7333ykgdfk73'
type: 'bus'
lines:
- route: 151
stop_id: 77
- stop_id: 77
departures: 2
name: 'Union Station'

sensor:
platform: ctabustracker
api_key: 'lk38vjklrj4nj'
type: 'train'
lines:
- stop_id: 3045
departures: 2
name: 'Logan Square'
```

**Configuration variables:**
### Configuration variables

key | type | description
:--- | :--- | :---
**platform (Required)** | string | The platform name.
**api_key (Required)** | string | Your [API key](https://www.transitchicago.com/developers/bustracker/)
**api_key (Required)** | string | [CTA Bus API Key](https://www.transitchicago.com/developers/bustracker/) *or* [CTA Train API Key](https://www.transitchicago.com/developers/traintracker/)
**type** (Required) | string | Transit type: ["bus", "train"]
**lines (Required)** | list | List of lines you want to track.

> **Note:** Bus times and train times each required a different API key.

**Lines configuration**
### Lines configuration

key | type | description
:--- | :--- | :---
**route (Required)** | string | Route number (`rt`)
**stop_id (Required)** | string | Stop ID (`stpid`)
**departures (Optional)** | int | Number of future departures.
**name (Optional)** | list | List of lines you want to track.
**name (Optional)** | list | Name of the HA sensor

## Change Log
### 0.0.3
[@ludeeus](https://github.com/ludeeus): Initial release.

### 0.0.3 (on HACS)
[@jonochocki](https://github.com/jonochocki): Added easy install via HACS support.

### 0.0.4
[@smcpeck](https://github.com/smcpeck): Added support for train lines.
- `route` config variable is no longer supported since the APIs don't need it was a "bus only" API parameter.

***
contributor | support
:--- | :---
[@ludeeus](https://github.com/ludeeus) | [![BuyMeCoffee](https://camo.githubusercontent.com/cd005dca0ef55d7725912ec03a936d3a7c8de5b5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6275792532306d6525323061253230636f666665652d646f6e6174652d79656c6c6f772e737667)](https://www.buymeacoffee.com/ludeeus)
[@smcpeck](https://github.com/smcpeck) | [![BuyMeCoffee](https://camo.githubusercontent.com/cd005dca0ef55d7725912ec03a936d3a7c8de5b5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6275792532306d6525323061253230636f666665652d646f6e6174652d79656c6c6f772e737667)](https://www.buymeacoffee.com/shaunmcpeck)
1 change: 1 addition & 0 deletions custom_components/ctabustracker/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
###
9 changes: 9 additions & 0 deletions custom_components/ctabustracker/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"domain": "ctabustracker",
"name": "CTA Bus Tracker",
"version": "0.0.4",
"documentation": "https://github.com/smcpeck/sensor.ctabustracker",
"dependencies": [],
"codeowners": ["@ludeeus","@jonochocki","@smcpeck"],
"requirements": []
}
95 changes: 74 additions & 21 deletions custom_components/ctabustracker/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
Show departure information from ctabustracker (Chicago Transit Authority).

For more details about this component, please refer to the documentation at
https://github.com/custom-components/sensor.ctabustracker/
https://github.com/smcpeck/sensor.ctabustracker/
"""
from datetime import timedelta
import datetime as dt
import logging
import requests

Expand All @@ -16,30 +16,32 @@
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle

__version__ = '0.0.3'
__version__ = '0.0.4'

_LOGGER = logging.getLogger(__name__)

CONF_API_KEY = 'api_key'
CONF_DEPARTURES = 'departures'
CONF_LINES = 'lines'
CONF_ROUTE = 'route'
CONF_STOP_ID = 'stop_id'
CONF_TYPE = 'type'

RESOURCE = "http://ctabustracker.com/bustime/api/v2/"
ENDPOINT = "getpredictions?key={}&rt={}&stpid={}&format=json"
BUS_RESOURCE = "http://ctabustracker.com/bustime/api/v2/"
BUS_ENDPOINT = "getpredictions?key={}&stpid={}&format=json"
TRAIN_RESOURCE = "http://lapi.transitchicago.com/api/1.0/"
TRAIN_ENDPOINT = "ttarrivals.aspx?key={}&stpid={}&max={}&outputType=JSON"

TIME_BETWEEN_UPDATES = timedelta(seconds=60)
TIME_BETWEEN_UPDATES = dt.timedelta(seconds=60)

LINES = vol.Schema({
vol.Required(CONF_STOP_ID): cv.string,
vol.Required(CONF_ROUTE): cv.string,
vol.Optional(CONF_DEPARTURES, default=1): cv.positive_int,
vol.Optional(CONF_NAME): cv.string,
})

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_API_KEY): cv.string,
vol.Required(CONF_TYPE): vol.In(["bus", "train"]),
vol.Required(CONF_LINES): vol.All(cv.ensure_list, [LINES]),
})

Expand All @@ -49,40 +51,63 @@ async def async_setup_platform(
"""Set up the sensor platform."""
api_key = config[CONF_API_KEY]
lines = config[CONF_LINES]
transit_type = config[CONF_TYPE]

dev = []
for line in lines:
api = CtaBusData(api_key, line)
api = None
if transit_type == "bus":
api = CtaBusData(api_key, line)
elif transit_type == "train":
api = CtaTrainData(api_key, line)

for departure in range(0, line['departures']):
dev.append(CtaBusSensor(api, departure, line))
dev.append(CtaSensor(api, transit_type, departure, line))

async_add_entities(dev, True)


class CtaBusSensor(Entity):
class CtaSensor(Entity):
"""Representation of a Home Assistant sensor."""

def __init__(self, api, departure, config):
def __init__(self, api, transit_type, departure, config):
"""Initialize the sensor."""
self.api = api
self.departure = departure
self.config = config
self._state = None
self.transit_type = transit_type
postfix = '' if departure == 0 else str(departure)
self._name = "{} {}".format(
self.config.get('name', 'CTA '+self.config['route']), postfix)
self.config.get('name', 'CTA ' + self.config['stop_id']), postfix)

def update(self):
"""Get the latest information."""
try:
self.api.update()
data = self.api.data
_LOGGER.debug(f"[{self.transit_type}] SENSOR.update() data = {data}")

if data:
self._state = data[self.departure].get('prdctdn')
if self.transit_type == "bus":
self._state = data[self.departure].get('prdctdn')
elif self.transit_type == "train":
prediction = data[self.departure]
pred_time = dt.datetime.strptime(prediction["prdt"], "%Y-%m-%dT%H:%M:%S")
arr_time = dt.datetime.strptime(prediction["arrT"], "%Y-%m-%dT%H:%M:%S")
minutes_left = int((arr_time-pred_time).total_seconds()/60)
if minutes_left <= 1:
minutes_left = "DUE"
self._state = minutes_left
else:
self._state = "Bad type"
_LOGGER.warning("Bad transit_type configured")
else:
self._state = self._state
except Exception: # pylint: disable=W0703
self._state = "No data"
_LOGGER.warning("No CTA data")
except Exception as ex: # pylint: disable=W0703
self._state = None
_LOGGER.debug(self._state)
_LOGGER.error(ex)

@property
def name(self):
Expand All @@ -97,8 +122,36 @@ def state(self):
@property
def icon(self):
"""Set sensor icon."""
return 'mdi:bus-clock'
return {'bus':'mdi:bus-clock','train':'mdi:train'}[self.transit_type]

class CtaTrainData:
"""Get the latest data and update the states."""

def __init__(self, api_key, config):
"""Initialize the data object."""
self.api_key = api_key
self.config = config
self.api = "{}{}".format(
TRAIN_RESOURCE, TRAIN_ENDPOINT.format(
self.api_key, self.config['stop_id'], self.config['departures']))
self._data = None

@Throttle(TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data from ctabustracker."""
try:
self._data = requests.get(
self.api).json().get('ctatt', {}).get('eta', {})
except Exception as error: # pylint: disable=W0703
_LOGGER.error(error)
self._data = self._data
return self._data

@property
def data(self):
"""Holds data."""
return self._data


class CtaBusData:
"""Get the latest data and update the states."""
Expand All @@ -108,8 +161,8 @@ def __init__(self, api_key, config):
self.api_key = api_key
self.config = config
self.api = "{}{}".format(
RESOURCE, ENDPOINT.format(
self.api_key, self.config['route'], self.config['stop_id']))
BUS_RESOURCE, BUS_ENDPOINT.format(
self.api_key, self.config['stop_id']))
self._data = None

@Throttle(TIME_BETWEEN_UPDATES)
Expand All @@ -118,10 +171,10 @@ def update(self):
try:
self._data = requests.get(
self.api).json().get('bustime-response', {}).get('prd', {})
_LOGGER.debug(self._data)
except Exception as error: # pylint: disable=W0703
_LOGGER.error(error)
self._data = self._data


@property
def data(self):
Expand Down
5 changes: 5 additions & 0 deletions hacs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "CTA Bus Tracker",
"render_readme": true,
"domains": ["sensor"],
}
4 changes: 4 additions & 0 deletions resources.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
"https://raw.githubusercontent.com/smcpeck/sensor.ctabustracker/master/custom_components/ctabustracker/__init__.py",
"https://raw.githubusercontent.com/smcpeck/sensor.ctabustracker/master/custom_components/ctabustracker/manifest.json"
]