#!/usr/bin/python
# -*- coding: utf-8 -*-
"""Simple service for SL (Storstockholms Lokaltrafik)."""
import datetime
import json
import logging
from datetime import timedelta

import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (ATTR_FRIENDLY_NAME, CONF_SCAN_INTERVAL,
                                 CONF_SENSOR_TYPE, CONF_SENSORS, STATE_OFF,
                                 STATE_ON)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import (async_track_point_in_utc_time,
                                         async_track_utc_time_change,
                                         track_time_interval)
from homeassistant.util import Throttle
from homeassistant.util.dt import now

from hasl import (haslapi, fpapi, tl2api, ri4api, si2api,
                  HASL_Error, HASL_API_Error, HASL_HTTP_Error)

__version__ = '2.2.0'
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'hasl'

# Keys used in the configuration.
CONF_RI4_KEY = 'ri4key'
CONF_SI2_KEY = 'si2key'
CONF_TL2_KEY = 'tl2key'
CONF_SITEID = 'siteid'
CONF_LINES = 'lines'
CONF_DIRECTION = 'direction'
CONF_ENABLED_SENSOR = 'sensor'
CONF_TIMEWINDOW = 'timewindow'
CONF_SENSORPROPERTY = 'property'
CONF_TRAIN_TYPE = 'train_type'
CONF_TRAFFIC_CLASS = 'traffic_class'
CONF_VERSION = 'version_sensor'
CONF_USE_MINIMIZATION = 'api_minimization'

LIST_SENSOR_TYPES = ['departures', 'status', 'trainlocation', 'comb', 'tl2']
LIST_SENSOR_PROPERTIES = ['min', 'time', 'deviations', 'refresh', 'updated']
LIST_TRAIN_TYPES = ['PT', 'RB', 'TVB', 'SB', 'LB', 'SpvC', 'TB1', 'TB2', 'TB3']

# Default values for configuration.
DEFAULT_INTERVAL = timedelta(minutes=10)
DEFAULT_TIMEWINDOW = 30
DEFAULT_DIRECTION = 0
DEFAULT_SENSORPROPERTY = 'min'
DEFAULT_TRAIN_TYPE = 'PT'
DEFAULT_TRAFFIC_CLASS = ['metro', 'train', 'local', 'tram', 'bus', 'fer']

DEFAULT_SENSORTYPE = 'departures'
DEFAULT_CACHE_FILE = '.storage/haslcache.json'

# Defining the configuration schema.
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    # API Keys
    vol.Optional(CONF_RI4_KEY): cv.string,
    vol.Optional(CONF_SI2_KEY): cv.string,
    vol.Optional(CONF_TL2_KEY): cv.string,
    vol.Optional(CONF_VERSION, default=False): cv.boolean,
    vol.Optional(CONF_USE_MINIMIZATION, default=True): cv.boolean,

    vol.Required(CONF_SENSORS, default=[]):
        vol.All(cv.ensure_list, [vol.All({

            vol.Required(ATTR_FRIENDLY_NAME): cv.string,

            vol.Required(CONF_SENSOR_TYPE, default=DEFAULT_SENSORTYPE):
                vol.In(LIST_SENSOR_TYPES),

            vol.Optional(CONF_ENABLED_SENSOR): cv.string,

            vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL):
                vol.Any(cv.time_period, cv.positive_timedelta),

            vol.Optional(CONF_SITEID): cv.string,

            vol.Optional(CONF_LINES, default=[]):
                vol.All(cv.ensure_list, [cv.string]),

            vol.Optional(CONF_DIRECTION, default=DEFAULT_DIRECTION):
                vol.All(vol.Coerce(int), vol.Range(min=0, max=2)),

            vol.Optional(CONF_TIMEWINDOW, default=DEFAULT_TIMEWINDOW):
                vol.All(vol.Coerce(int), vol.Range(min=0, max=60)),

            vol.Optional(CONF_SENSORPROPERTY, default=DEFAULT_SENSORPROPERTY):
                vol.In(LIST_SENSOR_PROPERTIES),

            vol.Optional(CONF_TRAFFIC_CLASS, default=DEFAULT_TRAFFIC_CLASS):
                vol.All(cv.ensure_list, [vol.In(DEFAULT_TRAFFIC_CLASS)]),

            vol.Optional(CONF_TRAIN_TYPE, default=DEFAULT_TRAIN_TYPE):
                vol.In(LIST_TRAIN_TYPES)
            })]),
}, extra=vol.ALLOW_EXTRA)


def setup_platform(hass, config, add_devices, discovery_info=None):
    """Setup the sensors."""

    if not hass.data.get(DOMAIN):
        hass.data[DOMAIN] = {}

    sensors = []

    if config[CONF_VERSION]:
        sensors.append(SLVersionSensor(hass))
        _LOGGER.info("Created version sensor for HASL")

    for sensorconf in config[CONF_SENSORS]:

        if sensorconf[CONF_SENSOR_TYPE] == 'departures' or \
           sensorconf[CONF_SENSOR_TYPE] == 'comb':

            sitekey = sensorconf.get(CONF_SITEID)
            si2key = config.get(CONF_SI2_KEY)
            ri4key = config.get(CONF_RI4_KEY)
            if sitekey and ri4key:
                sensorname = sensorconf[ATTR_FRIENDLY_NAME]
                sensors.append(SLDeparturesSensor(
                    hass,
                    si2key,
                    ri4key,
                    sitekey,
                    sensorconf.get(CONF_LINES),
                    sensorname,
                    sensorconf.get(CONF_ENABLED_SENSOR),
                    sensorconf.get(CONF_SCAN_INTERVAL),
                    sensorconf.get(CONF_DIRECTION),
                    sensorconf.get(CONF_TIMEWINDOW),
                    sensorconf.get(CONF_SENSORPROPERTY),
                    config.get(CONF_USE_MINIMIZATION)
                    ))

                _LOGGER.info("Created departures sensor %s...", sensorname)
            else:
                _LOGGER.error("Sensor %s is missing site, si2key or ri4key",
                              sensorconf[ATTR_FRIENDLY_NAME])

        if sensorconf[CONF_SENSOR_TYPE] == 'status' or \
           sensorconf[CONF_SENSOR_TYPE] == 'tl2':

            tl2key = config.get(CONF_TL2_KEY)
            if tl2key:
                sensorname = sensorconf[ATTR_FRIENDLY_NAME]
                sensors.append(SLStatusSensor(
                    hass,
                    tl2key,
                    sensorname,
                    sensorconf.get(CONF_ENABLED_SENSOR),
                    sensorconf.get(CONF_SCAN_INTERVAL),
                    sensorconf.get(CONF_TRAFFIC_CLASS),
                    config.get(CONF_USE_MINIMIZATION)
                    ))

                _LOGGER.info("Created status sensor %s...", sensorname)
            else:
                _LOGGER.error("Sensor %s is missing tl2key attribute",
                              sensorconf[ATTR_FRIENDLY_NAME])

        if sensorconf[CONF_SENSOR_TYPE] == 'trainlocation':
            train_type = sensorconf.get(CONF_TRAIN_TYPE)
            if train_type:
                sensorname = sensorconf[ATTR_FRIENDLY_NAME]
                sensors.append(SLTrainLocationSensor(
                    hass,
                    sensorname,
                    train_type,
                    sensorconf.get(CONF_SCAN_INTERVAL),
                    sensorconf.get(CONF_ENABLED_SENSOR),
                    ))

                _LOGGER.info("Created train sensor %s...", sensorname)
            else:
                _LOGGER.error("Sensor %s is missing train_type attribute",
                              sensorconf[ATTR_FRIENDLY_NAME])

    add_devices(sensors)


class SLTrainLocationSensor(Entity):
    """Trafic Situation Sensor."""
    def __init__(self, hass, friendly_name, train_type,
                 interval, enabled_sensor):

        self._hass = hass
        self._fpapi = fpapi()
        self._name = friendly_name
        self._interval = interval
        self._enabled_sensor = enabled_sensor
        self._train_type = train_type
        self._data = {}

        self.update = Throttle(interval)(self._update)

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def icon(self):
        """ Return the icon for the frontend."""
        return None

    @property
    def device_state_attributes(self):
        """ Return the sensor attributes."""
        return {'type': self._train_type, 'data': json.dumps(self._data)}

    @property
    def state(self):
        """ Return the state of the sensor."""
        return self._train_type

    def _update(self):

        if self._enabled_sensor is not None:
            sensor_state = self._hass.states.get(self._enabled_sensor)

        if self._enabled_sensor is None or sensor_state.state is STATE_ON:

            try:
                apidata = self._fpapi.request(self._train_type)

            except HASL_Error as e:
                _LOGGER.error("A communication error occured while "
                              "updating train location sensor: %s", e.details)
                return

            except Exception as e:
                _LOGGER.error("A error occured while"
                              "updating train location sensor: %s", e)
                return

        self._data = apidata

        _LOGGER.info("Update completed %s...", self._name)


class SLVersionSensor(Entity):
    """HASL Version Sensor."""
    def __init__(self, hass):

        self._hass = hass
        self._haslapi = haslapi()
        self._name = 'HASL Version'
        self._version = __version__
        self._py_version = self._haslapi.version()

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def icon(self):
        """ Return the icon for the frontend."""
        return None

    @property
    def device_state_attributes(self):
        """ Return the sensor attributes."""
        return {'hasl': self._version, 'pyHasl': self._py_version}

    @property
    def state(self):
        """ Return the state of the sensor."""
        return self._version + "/" + self._py_version


class SLStatusSensor(Entity):
    """Trafic Situation Sensor."""
    def __init__(self, hass, tl2key, friendly_name,
                 enabled_sensor, interval, type,
                 minimization):

        self._tl2api = tl2api(tl2key)
        self._datakey = 'tl2_' + tl2key
        self._interval = interval
        self._hass = hass
        self._name = friendly_name
        self._enabled_sensor = enabled_sensor
        self._type = type
        self._sensordata = []
        self._lastupdate = '-'
        self._cachefile = hass.config.path(DEFAULT_CACHE_FILE)
        self._minimization = minimization

        if not hass.data[DOMAIN].get(self._datakey):
            hass.data[DOMAIN][self._datakey] = ''

        self.update = Throttle(interval)(self._update)

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def icon(self):
        """ Return the icon for the frontend."""
        return 'mdi:train-car'

    @property
    def device_state_attributes(self):
        """ Return the sensor attributes."""
        return self._sensordata

    @property
    def state(self):
        """ Return the state of the sensor."""
        return self._lastupdate

    def getCache(self, key):
        try:
            jsonFile = open(self._cachefile, 'r')
            data = json.load(jsonFile)
            jsonFile.close()

            return data.get(key)
        except:
            return {}

    def putCache(self, key, value):
        try:
            jsonFile = open(self._cachefile, 'r')
            data = json.load(jsonFile)
            jsonFile.close()

            data[key] = value
        except:
            data = {'' + key + '': value}

        jsonFile = open(self._cachefile, 'w')
        jsonFile.write(json.dumps(data))
        jsonFile.close()

    def _update(self):

        if self._enabled_sensor is not None:
            sensor_state = self._hass.states.get(self._enabled_sensor)

        if self._enabled_sensor is None or sensor_state.state is STATE_ON:

            _LOGGER.info("Starting to update TL2 for %s...",
                         self._name)

            # Object used to create our object.
            newdata = {}

            # Use some nice translations for the statuses etc.
            statuses = {
                'EventGood': 'Good',
                'EventMinor': 'Minor',
                'EventMajor': 'Closed',
                'EventPlanned': 'Planned',
                }

            # Icon table used for HomeAssistant.
            statusIcons = {
                'EventGood': 'mdi:check',
                'EventMinor': 'mdi:clock-alert-outline',
                'EventMajor': 'mdi:close',
                'EventPlanned': 'mdi:triangle-outline'
                }

            trafficTypeIcons = {
                'ferry': 'mdi:ferry',
                'bus': 'mdi:bus',
                'tram': 'mdi:tram',
                'train': 'mdi:train',
                'local': 'mdi:train-variant',
                'metro': 'mdi:subway-variant'
                }

            # If the same API have already made the request in within
            # the specified interval then use that data instead of
            # requesting it again and spare some innocent credits from dying.
            cacheage = self._hass.data[DOMAIN][self._datakey]
            if not cacheage or now(self._hass.config.time_zone) \
                    - self._interval > cacheage or not self._minimization:

                try:
                    apidata = self._tl2api.request()
                    apidata = apidata['ResponseData']['TrafficTypes']

                    self.putCache(self._datakey, apidata)
                    self._hass.data[DOMAIN][self._datakey] = \
                        now(self._hass.config.time_zone)

                    _LOGGER.info("Updated cache for %s...", self._name)

                except HASL_Error as e:
                    _LOGGER.error("A communication error occured while "
                                  "updating TL2 sensor: %s", e.details)
                    return

                except Exception as e:
                    _LOGGER.error("A error occured while "
                                  "updating TL4 API: %s", e)
                    return

            else:
                apidata = self.getCache(self._datakey)
                _LOGGER.info("Reusing data from cache for %s...",
                             self._name)

            # Return only the relevant portion of the results.
            for response in apidata:
                type = response['Type']
                if self._type is None or type in self._type:
                    statustype = ('ferry' if type == 'fer' else type)
                    newdata[statustype + '_status'] = \
                        statuses.get(response['StatusIcon'])
                    newdata[statustype + '_status_icon'] = \
                        statusIcons.get(response['StatusIcon'])
                    newdata[statustype + '_icon'] = \
                        trafficTypeIcons.get(statustype)

                    for event in response['Events']:
                        event['Status'] = statuses.get(event['StatusIcon'])
                        event['StatusIcon'] = \
                            statusIcons.get(event['StatusIcon'])

                    newdata[statustype + '_events'] = response['Events']

            # Attribution and update sensor data.
            newdata['attribution'] = "Stockholms Lokaltrafik"
            newdata['last_updated'] = \
                self._hass.data[DOMAIN][self._datakey].strftime('%Y-%m-%d' +
                                                                '%H:%M:%S')
            self._sensordata = newdata
            self._lastupdate = newdata['last_updated']

            _LOGGER.info("TL2 update completed for %s...", self._name)


class SLDeparturesSensor(Entity):
    """Departure board for one SL site."""

    def __init__(self, hass, si2key, ri4key, siteid,
                 lines, friendly_name, enabled_sensor,
                 interval, direction, timewindow, sensorproperty,
                 minimization):
        """Initialize"""

        # The table of resulttypes and the corresponding units of measure.
        unit_table = {
            'min': 'min',
            'time': '',
            'deviations': '',
            'refresh': '',
            'update': '',
            }

        if si2key:
            self._si2key = si2key
            self._si2api = si2api(si2key, siteid, '')
            self._si2datakey = 'si2_' + si2key + '_' + siteid

        self._ri4key = ri4key
        self._ri4api = ri4api(ri4key, siteid, 60)
        self._ri4datakey = 'ri2_' + ri4key + '_' + siteid
        self._hass = hass
        self._name = friendly_name
        self._lines = lines
        self._siteid = siteid
        self._enabled_sensor = enabled_sensor
        self._sensorproperty = sensorproperty
        self._departure_table = []
        self._deviations_table = []
        self._direction = direction
        self._timewindow = timewindow
        self._nextdeparture_minutes = '0'
        self._nextdeparture_expected = '-'
        self._lastupdate = '-'
        self._interval = interval
        self._unit_of_measure = unit_table.get(self._sensorproperty, 'min')
        self._cachefile = hass.config.path(DEFAULT_CACHE_FILE)
        self._minimization = minimization

        if not hass.data[DOMAIN].get(self._ri4datakey):
            hass.data[DOMAIN][self._ri4datakey] = ''

        if self._si2key:
            if not hass.data[DOMAIN].get(self._si2datakey):
                hass.data[DOMAIN][self._si2datakey] = ''

        # Setup updating of the sensor.
        self.update = Throttle(interval)(self._update)

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def icon(self):
        """ Return the icon for the frontend."""

        if self._deviations_table:
            return 'mdi:bus-alert'

        return 'mdi:bus'

    @property
    def state(self):
        """ Return number of minutes to the next departure """

        # If the sensor should return minutes to next departure.
        if self._sensorproperty is 'min':
            if not self._departure_table:
                return '-'
            return self._departure_table[0]['time']

        # If the sensor should return the time at which next departure occurs.
        if self._sensorproperty is 'time':
            if not self._departure_table:
                return '-'
            expected = self._departure_table[0]['expected'] or '-'
            if expected is not '-':
                expected = \
                    datetime.datetime.strptime(self._nextdeparture_expected,
                                               '%Y-%m-%dT%H:%M:%S')
                expected = expected.strftime('%H:%M:%S')
            return expected

        # If the sensor should return the number of deviations.
        if self._sensorproperty is 'deviations':
            return len(self._deviations_table)

        # If the sensor should return if it is updating or not.
        if self._sensorproperty is 'refresh':
            if self._enabled_sensor is None or sensor_state.state is STATE_ON:
                return STATE_ON
            return STATE_OFF

        if self._sensorproperty is 'updated':
            if self._lastupdate is '-':
                return '-'
            return refresh.strftime('%Y-%m-%d %H:%M:%S')

        # Failsafe
        return '-'

    @property
    def device_state_attributes(self):
        """ Return the sensor attributes ."""

        # Initialize the state attributes.
        val = {}

        # Format the next exptected time.
        if self._departure_table:
            expected_time = self._departure_table[0]['expected'] or '-'
            expected_minutes = self._departure_table[0]['time'] or '-'
            if expected_time is not '-':
                expected_time = \
                    datetime.datetime.strptime(expected_time,
                                               '%Y-%m-%dT%H:%M:%S')
                expected_time = expected_time.strftime('%H:%M:%S')
        else:
            expected_time = '-'
            expected_minutes = '-'

        # Format the last refresh time.
        refresh = self._lastupdate
        if self._lastupdate is not '-':
            refresh = refresh.strftime('%Y-%m-%d %H:%M:%S')

        # Setup the unit of measure.
        if self._unit_of_measure is not '':
            val['unit_of_measurement'] = self._unit_of_measure

        # Check if sensor is currently updating or not.
        if self._enabled_sensor is not None:
            sensor_state = self._hass.states.get(self._enabled_sensor)

        if self._enabled_sensor is None or sensor_state.state is STATE_ON:
            val['refresh_enabled'] = STATE_ON
        else:
            val['refresh_enabled'] = STATE_OFF

        # Set values of the sensor.
        val['attribution'] = 'Stockholms Lokaltrafik'
        val['departures'] = self._departure_table
        val['deviations'] = self._deviations_table
        val['last_refresh'] = refresh
        val['next_departure_minutes'] = expected_minutes
        val['next_departure_time'] = expected_time
        val['deviation_count'] = len(self._deviations_table)

        return val

    def parseDepartureTime(self, t):
        """ weird time formats from the API,
        do some quick and dirty conversions. """

        try:
            if t == 'Nu':
                return 0
            s = t.split()
            if len(s) > 1 and s[1] == 'min':
                return int(s[0])
            s = t.split(':')
            if len(s) > 1:
                rightnow = now(self._hass.config.time_zone)
                min = int(s[0]) * 60 + int(s[1]) - (rightnow.hour * 60 +
                                                    rightnow.minute)
                if min < 0:
                    min = min + 1440
                return min
        except Exception:
            _LOGGER.warning("Failed to parse departure time (%s) ", t)
        return 0

    def getCache(self, key):
        try:
            jsonFile = open(self._cachefile, 'r')
            data = json.load(jsonFile)
            jsonFile.close()

            return data.get(key)
        except:
            return {}

    def putCache(self, key, value):
        try:
            jsonFile = open(self._cachefile, 'r')
            data = json.load(jsonFile)
            jsonFile.close()

            data[key] = value
        except:
            data = {'' + key + '': value}

        jsonFile = open(self._cachefile, 'w')
        jsonFile.write(json.dumps(data))
        jsonFile.close()

    def _update(self):
        """Get the departure board."""

        # If using external sensor, get its value.
        if self._enabled_sensor is not None:
            sensor_state = self._hass.states.get(self._enabled_sensor)

        # If we dont have external sensor or it is ON then proceed.
        if self._enabled_sensor is None or sensor_state.state \
                is STATE_ON:

            self._update_ri4()

            if self._si2key:
                self._update_si2()

            self._lastupdate = now(self._hass.config.time_zone)

    def _update_ri4(self):
        errorOccured = False
        _LOGGER.info("Starting to update RI4 for %s...", self._name)

        cacheage = self._hass.data[DOMAIN][self._ri4datakey]
        if not cacheage or now(self._hass.config.time_zone) \
                - self._interval > cacheage or not self._minimization:

            try:
                departuredata = self._ri4api.request()
                departuredata = departuredata['ResponseData']

                self.putCache(self._ri4datakey, departuredata)
                self._hass.data[DOMAIN][self._ri4datakey] = \
                    now(self._hass.config.time_zone)

                _LOGGER.info("Updated cache for %s...", self._name)

            except HASL_Error as e:
                _LOGGER.error("A communication error occured while "
                              "updating SI2 sensor: %s", e.details)
                errorOccured = True

            except Exception as e:
                _LOGGER.error("A communication error occured while "
                              "updating RI4 API: %s", e)
                errorOccured = True

        else:
            try:
                departuredata = self.getCache(self._ri4datakey)

                _LOGGER.info("Reusing data from cache for %s...",
                             self._name)
            except Exception as e:
                _LOGGER.error("A error occured while retreiving "
                              "cached RI4 sensor data: %s", e)
                errorOccured = True

        if not errorOccured:
            departures = []

            iconswitcher = {
                'Buses': 'mdi:bus',
                'Trams': 'mdi:tram',
                'Ships': 'mdi:ferry',
                'Metros': 'mdi:subway-variant',
                'Trains': 'mdi:train',
                }

            for (i, traffictype) in enumerate(['Metros', 'Buses', 'Trains',
                                               'Trams', 'Ships']):

                for (idx, value) in enumerate(departuredata[traffictype]):
                    direction = value['JourneyDirection'] or 0
                    displaytime = value['DisplayTime'] or ''
                    destination = value['Destination'] or ''
                    linenumber = value['LineNumber'] or ''
                    expected = value['ExpectedDateTime'] or ''
                    groupofline = value['GroupOfLine'] or ''
                    icon = iconswitcher.get(traffictype, 'mdi:train-car')
                    if int(self._direction) == 0 or int(direction) \
                            == int(self._direction):
                        if self._lines == [] or linenumber \
                                in self._lines:
                            diff = self.parseDepartureTime(displaytime)
                            if diff < self._timewindow:
                                departures.append({
                                    'line': linenumber,
                                    'direction': direction,
                                    'departure': displaytime,
                                    'destination': destination,
                                    'time': diff,
                                    'expected': expected,
                                    'type': traffictype,
                                    'groupofline': groupofline,
                                    'icon': icon,
                                    })

            self._departure_table = sorted(departures,
                                           key=lambda k: k['time'])

            _LOGGER.info("RI4 update completed for %s...", self._name)

    def _update_si2(self):
        errorOccured = False
        _LOGGER.info("Starting to update SI2 for %s...", self._name)

        cacheage = self._hass.data[DOMAIN][self._si2datakey]
        if not cacheage or now(self._hass.config.time_zone) \
                - self._interval > cacheage or not self._minimization:

            try:
                deviationdata = self._si2api.request()
                deviationdata = deviationdata['ResponseData']

                self.putCache(self._si2datakey, deviationdata)
                self._hass.data[DOMAIN][self._si2datakey] = \
                    now(self._hass.config.time_zone)

                _LOGGER.info('Updated cache for %s...', self._name)

            except HASL_Error as e:
                _LOGGER.error("A communication error occured while "
                              "updating SI2 sensor: %s", e.details)
                errorOccured = True

            except Exception as e:
                _LOGGER.error("A error occured while "
                              "updating SI2 sensor: %s", e)
                errorOccured = True

        else:
            try:
                deviationdata = self.getCache(self._si2datakey)

                _LOGGER.info("Reusing data from cache for %s...",
                             self._name)

            except Exception as e:
                _LOGGER.error("A error occured while retreiving "
                              "cached SI2 sensor: %s", e.details)
                errorOccured = True

        if not errorOccured:
            deviations = []
            for (idx, value) in enumerate(deviationdata):
                deviations.append({
                    'updated': value['Updated'],
                    'title': value['Header'],
                    'fromDate': value['FromDateTime'],
                    'toDate': value['UpToDateTime'],
                    'details': value['Details'],
                    'sortOrder': value['SortOrder'],
                    })

            self._deviations_table = \
                sorted(deviations, key=lambda k: k['sortOrder'])

            _LOGGER.info("SI2 update completed for %s...", self._name)

[((990, 1017), 'logging.getLogger', 'logging.getLogger', (['__name__'], {}), '(__name__)\n', (1007, 1017), False, 'import logging\n'), ((1743, 1764), 'datetime.timedelta', 'timedelta', ([], {'minutes': '(10)'}), '(minutes=10)\n', (1752, 1764), False, 'from datetime import timedelta\n'), ((2124, 2150), 'voluptuous.Optional', 'vol.Optional', (['CONF_RI4_KEY'], {}), '(CONF_RI4_KEY)\n', (2136, 2150), True, 'import voluptuous as vol\n'), ((2167, 2193), 'voluptuous.Optional', 'vol.Optional', (['CONF_SI2_KEY'], {}), '(CONF_SI2_KEY)\n', (2179, 2193), True, 'import voluptuous as vol\n'), ((2210, 2236), 'voluptuous.Optional', 'vol.Optional', (['CONF_TL2_KEY'], {}), '(CONF_TL2_KEY)\n', (2222, 2236), True, 'import voluptuous as vol\n'), ((2253, 2294), 'voluptuous.Optional', 'vol.Optional', (['CONF_VERSION'], {'default': '(False)'}), '(CONF_VERSION, default=False)\n', (2265, 2294), True, 'import voluptuous as vol\n'), ((2312, 2361), 'voluptuous.Optional', 'vol.Optional', (['CONF_USE_MINIMIZATION'], {'default': '(True)'}), '(CONF_USE_MINIMIZATION, default=True)\n', (2324, 2361), True, 'import voluptuous as vol\n'), ((2380, 2418), 'voluptuous.Required', 'vol.Required', (['CONF_SENSORS'], {'default': '[]'}), '(CONF_SENSORS, default=[])\n', (2392, 2418), True, 'import voluptuous as vol\n'), ((7044, 7051), 'hasl.fpapi', 'fpapi', ([], {}), '()\n', (7049, 7051), False, 'from hasl import haslapi, fpapi, tl2api, ri4api, si2api, HASL_Error, HASL_API_Error, HASL_HTTP_Error\n'), ((8709, 8718), 'hasl.haslapi', 'haslapi', ([], {}), '()\n', (8716, 8718), False, 'from hasl import haslapi, fpapi, tl2api, ri4api, si2api, HASL_Error, HASL_API_Error, HASL_HTTP_Error\n'), ((9579, 9593), 'hasl.tl2api', 'tl2api', (['tl2key'], {}), '(tl2key)\n', (9585, 9593), False, 'from hasl import haslapi, fpapi, tl2api, ri4api, si2api, HASL_Error, HASL_API_Error, HASL_HTTP_Error\n'), ((16028, 16054), 'hasl.ri4api', 'ri4api', (['ri4key', 'siteid', '(60)'], {}), '(ri4key, siteid, 60)\n', (16034, 16054), False, 'from hasl import haslapi, fpapi, tl2api, ri4api, si2api, HASL_Error, HASL_API_Error, HASL_HTTP_Error\n'), ((7252, 7270), 'homeassistant.util.Throttle', 'Throttle', (['interval'], {}), '(interval)\n', (7260, 7270), False, 'from homeassistant.util import Throttle\n'), ((7644, 7666), 'json.dumps', 'json.dumps', (['self._data'], {}), '(self._data)\n', (7654, 7666), False, 'import json\n'), ((10094, 10112), 'homeassistant.util.Throttle', 'Throttle', (['interval'], {}), '(interval)\n', (10102, 10112), False, 'from homeassistant.util import Throttle\n'), ((10706, 10725), 'json.load', 'json.load', (['jsonFile'], {}), '(jsonFile)\n', (10715, 10725), False, 'import json\n'), ((10946, 10965), 'json.load', 'json.load', (['jsonFile'], {}), '(jsonFile)\n', (10955, 10965), False, 'import json\n'), ((11154, 11170), 'json.dumps', 'json.dumps', (['data'], {}), '(data)\n', (11164, 11170), False, 'import json\n'), ((15885, 15911), 'hasl.si2api', 'si2api', (['si2key', 'siteid', '""""""'], {}), "(si2key, siteid, '')\n", (15891, 15911), False, 'from hasl import haslapi, fpapi, tl2api, ri4api, si2api, HASL_Error, HASL_API_Error, HASL_HTTP_Error\n'), ((17116, 17134), 'homeassistant.util.Throttle', 'Throttle', (['interval'], {}), '(interval)\n', (17124, 17134), False, 'from homeassistant.util import Throttle\n'), ((21647, 21666), 'json.load', 'json.load', (['jsonFile'], {}), '(jsonFile)\n', (21656, 21666), False, 'import json\n'), ((21887, 21906), 'json.load', 'json.load', (['jsonFile'], {}), '(jsonFile)\n', (21896, 21906), False, 'import json\n'), ((22095, 22111), 'json.dumps', 'json.dumps', (['data'], {}), '(data)\n', (22105, 22111), False, 'import json\n'), ((22660, 22692), 'homeassistant.util.dt.now', 'now', (['self._hass.config.time_zone'], {}), '(self._hass.config.time_zone)\n', (22663, 22692), False, 'from homeassistant.util.dt import now\n'), ((18108, 18185), 'datetime.datetime.strptime', 'datetime.datetime.strptime', (['self._nextdeparture_expected', '"""%Y-%m-%dT%H:%M:%S"""'], {}), "(self._nextdeparture_expected, '%Y-%m-%dT%H:%M:%S')\n", (18134, 18185), False, 'import datetime\n'), ((19416, 19478), 'datetime.datetime.strptime', 'datetime.datetime.strptime', (['expected_time', '"""%Y-%m-%dT%H:%M:%S"""'], {}), "(expected_time, '%Y-%m-%dT%H:%M:%S')\n", (19442, 19478), False, 'import datetime\n'), ((21154, 21186), 'homeassistant.util.dt.now', 'now', (['self._hass.config.time_zone'], {}), '(self._hass.config.time_zone)\n', (21157, 21186), False, 'from homeassistant.util.dt import now\n'), ((23295, 23327), 'homeassistant.util.dt.now', 'now', (['self._hass.config.time_zone'], {}), '(self._hass.config.time_zone)\n', (23298, 23327), False, 'from homeassistant.util.dt import now\n'), ((26944, 26976), 'homeassistant.util.dt.now', 'now', (['self._hass.config.time_zone'], {}), '(self._hass.config.time_zone)\n', (26947, 26976), False, 'from homeassistant.util.dt import now\n'), ((13146, 13178), 'homeassistant.util.dt.now', 'now', (['self._hass.config.time_zone'], {}), '(self._hass.config.time_zone)\n', (13149, 13178), False, 'from homeassistant.util.dt import now\n'), ((22908, 22940), 'homeassistant.util.dt.now', 'now', (['self._hass.config.time_zone'], {}), '(self._hass.config.time_zone)\n', (22911, 22940), False, 'from homeassistant.util.dt import now\n'), ((26557, 26589), 'homeassistant.util.dt.now', 'now', (['self._hass.config.time_zone'], {}), '(self._hass.config.time_zone)\n', (26560, 26589), False, 'from homeassistant.util.dt import now\n'), ((2476, 2508), 'voluptuous.Required', 'vol.Required', (['ATTR_FRIENDLY_NAME'], {}), '(ATTR_FRIENDLY_NAME)\n', (2488, 2508), True, 'import voluptuous as vol\n'), ((2534, 2592), 'voluptuous.Required', 'vol.Required', (['CONF_SENSOR_TYPE'], {'default': 'DEFAULT_SENSORTYPE'}), '(CONF_SENSOR_TYPE, default=DEFAULT_SENSORTYPE)\n', (2546, 2592), True, 'import voluptuous as vol\n'), ((2650, 2683), 'voluptuous.Optional', 'vol.Optional', (['CONF_ENABLED_SENSOR'], {}), '(CONF_ENABLED_SENSOR)\n', (2662, 2683), True, 'import voluptuous as vol\n'), ((2709, 2767), 'voluptuous.Optional', 'vol.Optional', (['CONF_SCAN_INTERVAL'], {'default': 'DEFAULT_INTERVAL'}), '(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL)\n', (2721, 2767), True, 'import voluptuous as vol\n'), ((2846, 2871), 'voluptuous.Optional', 'vol.Optional', (['CONF_SITEID'], {}), '(CONF_SITEID)\n', (2858, 2871), True, 'import voluptuous as vol\n'), ((2897, 2933), 'voluptuous.Optional', 'vol.Optional', (['CONF_LINES'], {'default': '[]'}), '(CONF_LINES, default=[])\n', (2909, 2933), True, 'import voluptuous as vol\n'), ((3002, 3057), 'voluptuous.Optional', 'vol.Optional', (['CONF_DIRECTION'], {'default': 'DEFAULT_DIRECTION'}), '(CONF_DIRECTION, default=DEFAULT_DIRECTION)\n', (3014, 3057), True, 'import voluptuous as vol\n'), ((3139, 3196), 'voluptuous.Optional', 'vol.Optional', (['CONF_TIMEWINDOW'], {'default': 'DEFAULT_TIMEWINDOW'}), '(CONF_TIMEWINDOW, default=DEFAULT_TIMEWINDOW)\n', (3151, 3196), True, 'import voluptuous as vol\n'), ((3279, 3344), 'voluptuous.Optional', 'vol.Optional', (['CONF_SENSORPROPERTY'], {'default': 'DEFAULT_SENSORPROPERTY'}), '(CONF_SENSORPROPERTY, default=DEFAULT_SENSORPROPERTY)\n', (3291, 3344), True, 'import voluptuous as vol\n'), ((3407, 3470), 'voluptuous.Optional', 'vol.Optional', (['CONF_TRAFFIC_CLASS'], {'default': 'DEFAULT_TRAFFIC_CLASS'}), '(CONF_TRAFFIC_CLASS, default=DEFAULT_TRAFFIC_CLASS)\n', (3419, 3470), True, 'import voluptuous as vol\n'), ((3559, 3616), 'voluptuous.Optional', 'vol.Optional', (['CONF_TRAIN_TYPE'], {'default': 'DEFAULT_TRAIN_TYPE'}), '(CONF_TRAIN_TYPE, default=DEFAULT_TRAIN_TYPE)\n', (3571, 3616), True, 'import voluptuous as vol\n'), ((2610, 2635), 'voluptuous.In', 'vol.In', (['LIST_SENSOR_TYPES'], {}), '(LIST_SENSOR_TYPES)\n', (2616, 2635), True, 'import voluptuous as vol\n'), ((2785, 2831), 'voluptuous.Any', 'vol.Any', (['cv.time_period', 'cv.positive_timedelta'], {}), '(cv.time_period, cv.positive_timedelta)\n', (2792, 2831), True, 'import voluptuous as vol\n'), ((2951, 2987), 'voluptuous.All', 'vol.All', (['cv.ensure_list', '[cv.string]'], {}), '(cv.ensure_list, [cv.string])\n', (2958, 2987), True, 'import voluptuous as vol\n'), ((3362, 3392), 'voluptuous.In', 'vol.In', (['LIST_SENSOR_PROPERTIES'], {}), '(LIST_SENSOR_PROPERTIES)\n', (3368, 3392), True, 'import voluptuous as vol\n'), ((3634, 3658), 'voluptuous.In', 'vol.In', (['LIST_TRAIN_TYPES'], {}), '(LIST_TRAIN_TYPES)\n', (3640, 3658), True, 'import voluptuous as vol\n'), ((12745, 12777), 'homeassistant.util.dt.now', 'now', (['self._hass.config.time_zone'], {}), '(self._hass.config.time_zone)\n', (12748, 12777), False, 'from homeassistant.util.dt import now\n'), ((3083, 3098), 'voluptuous.Coerce', 'vol.Coerce', (['int'], {}), '(int)\n', (3093, 3098), True, 'import voluptuous as vol\n'), ((3100, 3123), 'voluptuous.Range', 'vol.Range', ([], {'min': '(0)', 'max': '(2)'}), '(min=0, max=2)\n', (3109, 3123), True, 'import voluptuous as vol\n'), ((3222, 3237), 'voluptuous.Coerce', 'vol.Coerce', (['int'], {}), '(int)\n', (3232, 3237), True, 'import voluptuous as vol\n'), ((3239, 3263), 'voluptuous.Range', 'vol.Range', ([], {'min': '(0)', 'max': '(60)'}), '(min=0, max=60)\n', (3248, 3263), True, 'import voluptuous as vol\n'), ((3513, 3542), 'voluptuous.In', 'vol.In', (['DEFAULT_TRAFFIC_CLASS'], {}), '(DEFAULT_TRAFFIC_CLASS)\n', (3519, 3542), True, 'import voluptuous as vol\n')]