text: """
  Support for GTFS (Google/General Transport Format Schema).
  
  For more details about this platform, please refer to the documentation at
  https://home-assistant.io/components/sensor.gtfs/
  """
  import os
  import logging
  import datetime
  import threading
  
  import voluptuous as vol
  
  from homeassistant.components.sensor import PLATFORM_SCHEMA
  from homeassistant.const import CONF_NAME
  from homeassistant.helpers.entity import Entity
  import homeassistant.helpers.config_validation as cv
  
  REQUIREMENTS = ["https://github.com/robbiet480/pygtfs/archive/"
                  "00546724e4bbcb3053110d844ca44e2246267dd8.zip#"
                  "pygtfs==0.1.3"]
  
  _LOGGER = logging.getLogger(__name__)
  
  CONF_DATA = 'data'
  CONF_DESTINATION = 'destination'
  CONF_ORIGIN = 'origin'
  
  DEFAULT_NAME = 'GTFS Sensor'
  DEFAULT_PATH = 'gtfs'
  
  ICON = 'mdi:train'
  
  TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
  
  PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
      vol.Required(CONF_ORIGIN): cv.string,
      vol.Required(CONF_DESTINATION): cv.string,
      vol.Required(CONF_DATA): cv.string,
      vol.Optional(CONF_NAME): cv.string,
  })
  
  
  def get_next_departure(sched, start_station_id, end_station_id):
      """Get the next departure for the given schedule."""
      origin_station = sched.stops_by_id(start_station_id)[0]
      destination_station = sched.stops_by_id(end_station_id)[0]
  
      now = datetime.datetime.now()
      day_name = now.strftime('%A').lower()
      now_str = now.strftime('%H:%M:%S')
  
      from sqlalchemy.sql import text
  
      sql_query = text("""
      SELECT trip.trip_id, trip.route_id,
             time(origin_stop_time.departure_time),
             time(destination_stop_time.arrival_time),
             time(origin_stop_time.arrival_time),
             time(origin_stop_time.departure_time),
             origin_stop_time.drop_off_type,
             origin_stop_time.pickup_type,
             origin_stop_time.shape_dist_traveled,
             origin_stop_time.stop_headsign,
             origin_stop_time.stop_sequence,
             time(destination_stop_time.arrival_time),
             time(destination_stop_time.departure_time),
             destination_stop_time.drop_off_type,
             destination_stop_time.pickup_type,
             destination_stop_time.shape_dist_traveled,
             destination_stop_time.stop_headsign,
             destination_stop_time.stop_sequence
      FROM trips trip
      INNER JOIN calendar calendar
                 ON trip.service_id = calendar.service_id
      INNER JOIN stop_times origin_stop_time
                 ON trip.trip_id = origin_stop_time.trip_id
      INNER JOIN stops start_station
                 ON origin_stop_time.stop_id = start_station.stop_id
      INNER JOIN stop_times destination_stop_time
                 ON trip.trip_id = destination_stop_time.trip_id
      INNER JOIN stops end_station
                 ON destination_stop_time.stop_id = end_station.stop_id
      WHERE calendar.{day_name} = 1
                 AND time(origin_stop_time.departure_time) > time(:now_str)
      AND start_station.stop_id = :origin_station_id
                 AND end_station.stop_id = :end_station_id
      AND origin_stop_time.stop_sequence < destination_stop_time.stop_sequence
      ORDER BY origin_stop_time.departure_time LIMIT 1;
      """.format(day_name=day_name))
      result = sched.engine.execute(sql_query, now_str=now_str,
                                    origin_station_id=origin_station.id,
                                    end_station_id=destination_station.id)
      item = {}
      for row in result:
          item = row
  
      if item == {}:
          return None
  
      today = datetime.datetime.today().strftime('%Y-%m-%d')
      departure_time_string = '{} {}'.format(today, item[2])
      arrival_time_string = '{} {}'.format(today, item[3])
      departure_time = datetime.datetime.strptime(departure_time_string,
                                                  TIME_FORMAT)
      arrival_time = datetime.datetime.strptime(arrival_time_string,
                                                TIME_FORMAT)
  
      seconds_until = (departure_time-datetime.datetime.now()).total_seconds()
      minutes_until = int(seconds_until / 60)
  
      route = sched.routes_by_id(item[1])[0]
  
      origin_stoptime_arrival_time = '{} {}'.format(today, item[4])
      origin_stoptime_departure_time = '{} {}'.format(today, item[5])
      dest_stoptime_arrival_time = '{} {}'.format(today, item[11])
      dest_stoptime_depart_time = '{} {}'.format(today, item[12])
  
      origin_stop_time_dict = {
          'Arrival Time': origin_stoptime_arrival_time,
          'Departure Time': origin_stoptime_departure_time,
          'Drop Off Type': item[6], 'Pickup Type': item[7],
          'Shape Dist Traveled': item[8], 'Headsign': item[9],
          'Sequence': item[10]
      }
  
      destination_stop_time_dict = {
          'Arrival Time': dest_stoptime_arrival_time,
          'Departure Time': dest_stoptime_depart_time,
          'Drop Off Type': item[13], 'Pickup Type': item[14],
          'Shape Dist Traveled': item[15], 'Headsign': item[16],
          'Sequence': item[17]
      }
  
      return {
          'trip_id': item[0],
          'trip': sched.trips_by_id(item[0])[0],
          'route': route,
          'agency': sched.agencies_by_id(route.agency_id)[0],
          'origin_station': origin_station,
          'departure_time': departure_time,
          'destination_station': destination_station,
          'arrival_time': arrival_time,
          'seconds_until_departure': seconds_until,
          'minutes_until_departure': minutes_until,
          'origin_stop_time': origin_stop_time_dict,
          'destination_stop_time': destination_stop_time_dict
      }
  
  
  def setup_platform(hass, config, add_devices, discovery_info=None):
      """Set up the GTFS sensor."""
      gtfs_dir = hass.config.path(DEFAULT_PATH)
      data = config.get(CONF_DATA)
      origin = config.get(CONF_ORIGIN)
      destination = config.get(CONF_DESTINATION)
      name = config.get(CONF_NAME)
  
      if not os.path.exists(gtfs_dir):
          os.makedirs(gtfs_dir)
  
      if not os.path.exists(os.path.join(gtfs_dir, data)):
          _LOGGER.error("The given GTFS data file/folder was not found")
          return False
  
      import pygtfs
  
      split_file_name = os.path.splitext(data)
  
      sqlite_file = "{}.sqlite".format(split_file_name[0])
      joined_path = os.path.join(gtfs_dir, sqlite_file)
      gtfs = pygtfs.Schedule(joined_path)
  
      # pylint: disable=no-member
      if len(gtfs.feeds) < 1:
          pygtfs.append_feed(gtfs, os.path.join(gtfs_dir, data))
  
      add_devices([GTFSDepartureSensor(gtfs, name, origin, destination)])
  
  
  class GTFSDepartureSensor(Entity):
      """Implementation of an GTFS departures sensor."""
  
      def __init__(self, pygtfs, name, origin, destination):
          """Initialize the sensor."""
          self._pygtfs = pygtfs
          self.origin = origin
          self.destination = destination
          self._custom_name = name
          self._name = ''
          self._unit_of_measurement = 'min'
          self._state = 0
          self._attributes = {}
          self.lock = threading.Lock()
          self.update()
  
      @property
      def name(self):
          """Return the name of the sensor."""
          return self._name
  
      @property
      def state(self):
          """Return the state of the sensor."""
          return self._state
  
      @property
      def unit_of_measurement(self):
          """Return the unit of measurement of this entity, if any."""
          return self._unit_of_measurement
  
      @property
      def device_state_attributes(self):
          """Return the state attributes."""
          return self._attributes
  
      @property
      def icon(self):
          """Icon to use in the frontend, if any."""
          return ICON
  
      def update(self):
          """Get the latest data from GTFS and update the states."""
          with self.lock:
              self._departure = get_next_departure(
                  self._pygtfs, self.origin, self.destination)
              if not self._departure:
                  self._state = 0
                  self._attributes = {'Info': 'No more departures today'}
                  if self._name == '':
                      self._name = (self._custom_name or DEFAULT_NAME)
                  return
  
              self._state = self._departure['minutes_until_departure']
  
              origin_station = self._departure['origin_station']
              destination_station = self._departure['destination_station']
              origin_stop_time = self._departure['origin_stop_time']
              destination_stop_time = self._departure['destination_stop_time']
              agency = self._departure['agency']
              route = self._departure['route']
              trip = self._departure['trip']
  
              name = '{} {} to {} next departure'
              self._name = (self._custom_name or
                            name.format(agency.agency_name,
                                        origin_station.stop_id,
                                        destination_station.stop_id))
  
              # Build attributes
              self._attributes = {}
  
              def dict_for_table(resource):
                  """Return a dict for the SQLAlchemy resource given."""
                  return dict((col, getattr(resource, col))
                              for col in resource.__table__.columns.keys())
  
              def append_keys(resource, prefix=None):
                  """Properly format key val pairs to append to attributes."""
                  for key, val in resource.items():
                      if val == "" or val is None or key == 'feed_id':
                          continue
                      pretty_key = key.replace('_', ' ')
                      pretty_key = pretty_key.title()
                      pretty_key = pretty_key.replace('Id', 'ID')
                      pretty_key = pretty_key.replace('Url', 'URL')
                      if prefix is not None and \
                         pretty_key.startswith(prefix) is False:
                          pretty_key = '{} {}'.format(prefix, pretty_key)
                      self._attributes[pretty_key] = val
  
              append_keys(dict_for_table(agency), 'Agency')
              append_keys(dict_for_table(route), 'Route')
              append_keys(dict_for_table(trip), 'Trip')
              append_keys(dict_for_table(origin_station), 'Origin Station')
              append_keys(dict_for_table(destination_station),
                          'Destination Station')
              append_keys(origin_stop_time, 'Origin Stop')
              append_keys(destination_stop_time, 'Destination Stop')
  