From bf915cf3b4fff16790a99768b09d2a2fdb334bf0 Mon Sep 17 00:00:00 2001 From: Luca Soldi Date: Tue, 22 Mar 2016 08:33:56 +0100 Subject: [PATCH 001/124] Added Raspberry Pi Camera Component --- .../components/camera/raspberry_camera.py | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 homeassistant/components/camera/raspberry_camera.py diff --git a/homeassistant/components/camera/raspberry_camera.py b/homeassistant/components/camera/raspberry_camera.py new file mode 100644 index 00000000000..d3eb547c432 --- /dev/null +++ b/homeassistant/components/camera/raspberry_camera.py @@ -0,0 +1,70 @@ +"""Camera platform that has a Raspberry Pi camera.""" + +import os +import subprocess +import logging + +from homeassistant.components.camera import Camera + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Raspberry Camera.""" + add_devices([ + RaspberryCamera(config) + ]) + + +class RaspberryCamera(Camera): + """Raspberry Pi camera.""" + + def __init__(self, device_info): + """Initialize Raspberry Pi camera component.""" + super().__init__() + + self._name = device_info.get("name", "Raspberry Pi Camera") + image_width = int(device_info.get("image_width", "640")) + image_height = int(device_info.get("image_height", "480")) + image_quality = int(device_info.get("image_quality", "7")) + image_rotation = int(device_info.get("image_rotation", "0")) + timelapse = int(device_info.get("timelapse", "1000")) + + horizontal_flip_arg = "" + horizontal_flip = int(device_info.get("horizontal_flip", "0")) + if horizontal_flip: + horizontal_flip_arg = " -hf " + + vertical_flip_arg = "" + vertical_flip = int(device_info.get("vertical_flip", "0")) + if vertical_flip: + vertical_flip_arg = " -vf" + + image_path = os.path.join(os.path.dirname(__file__), + 'image.jpg') + + # kill if there's raspistill instance + cmd = 'killall raspistill' + subprocess.call(cmd, shell=True) + # start new instance of raspistill + cmd = ('raspistill --nopreview -o ' + image_path + + ' -t 0 ' + '-w ' + str(image_width) + ' -h ' + + str(image_height) + ' -tl ' + str(timelapse) + ' -q ' + + str(image_quality) + str(horizontal_flip_arg) + + str(vertical_flip_arg) + ' -rot ' + str(image_rotation) + + '&') + + subprocess.call(cmd, shell=True) + + def camera_image(self): + """Return raspstill image response.""" + image_path = os.path.join(os.path.dirname(__file__), + 'image.jpg') + + with open(image_path, 'rb') as file: + return file.read() + + @property + def name(self): + """Return the name of this camera.""" + return self._name From 65ef836313cfff3a4f14b4fc802b8c457b7c75f6 Mon Sep 17 00:00:00 2001 From: Luca Soldi Date: Tue, 22 Mar 2016 22:35:09 +0100 Subject: [PATCH 002/124] Check raspistill exisance and check parameters in setup_platform --- .Python | 1 + .../components/camera/raspberry_camera.py | 62 ++++++++++++------- include/python2.7 | 1 + 3 files changed, 41 insertions(+), 23 deletions(-) create mode 120000 .Python create mode 120000 include/python2.7 diff --git a/.Python b/.Python new file mode 120000 index 00000000000..cc24a1e924a --- /dev/null +++ b/.Python @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/Python \ No newline at end of file diff --git a/homeassistant/components/camera/raspberry_camera.py b/homeassistant/components/camera/raspberry_camera.py index d3eb547c432..4f7a417d095 100644 --- a/homeassistant/components/camera/raspberry_camera.py +++ b/homeassistant/components/camera/raspberry_camera.py @@ -3,6 +3,7 @@ import os import subprocess import logging +import shutil from homeassistant.components.camera import Camera @@ -11,8 +12,35 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Raspberry Camera.""" + if shutil.which("raspistill") is None: + _LOGGER.error("Error: raspistill not found") + return None + + horizontal_flip_arg = "" + horizontal_flip = int(config.get("horizontal_flip", "0")) + if horizontal_flip: + horizontal_flip_arg = " -hf " + + vertical_flip_arg = "" + vertical_flip = int(config.get("vertical_flip", "0")) + if vertical_flip: + vertical_flip_arg = " -vf" + + setup_config = ( + { + "name": config.get("name", "Raspberry Pi Camera"), + "image_width": int(config.get("image_width", "640")), + "image_height": int(config.get("image_height", "480")), + "image_quality": int(config.get("image_quality", "7")), + "image_rotation": int(config.get("image_rotation", "0")), + "timelapse": int(config.get("timelapse", "1000")), + "horizontal_flip_arg": horizontal_flip_arg, + "vertical_flip_arg": vertical_flip_arg + } + ) + add_devices([ - RaspberryCamera(config) + RaspberryCamera(setup_config) ]) @@ -23,22 +51,7 @@ class RaspberryCamera(Camera): """Initialize Raspberry Pi camera component.""" super().__init__() - self._name = device_info.get("name", "Raspberry Pi Camera") - image_width = int(device_info.get("image_width", "640")) - image_height = int(device_info.get("image_height", "480")) - image_quality = int(device_info.get("image_quality", "7")) - image_rotation = int(device_info.get("image_rotation", "0")) - timelapse = int(device_info.get("timelapse", "1000")) - - horizontal_flip_arg = "" - horizontal_flip = int(device_info.get("horizontal_flip", "0")) - if horizontal_flip: - horizontal_flip_arg = " -hf " - - vertical_flip_arg = "" - vertical_flip = int(device_info.get("vertical_flip", "0")) - if vertical_flip: - vertical_flip_arg = " -vf" + self._name = device_info["name"] image_path = os.path.join(os.path.dirname(__file__), 'image.jpg') @@ -47,12 +60,15 @@ class RaspberryCamera(Camera): cmd = 'killall raspistill' subprocess.call(cmd, shell=True) # start new instance of raspistill - cmd = ('raspistill --nopreview -o ' + image_path + - ' -t 0 ' + '-w ' + str(image_width) + ' -h ' + - str(image_height) + ' -tl ' + str(timelapse) + ' -q ' + - str(image_quality) + str(horizontal_flip_arg) + - str(vertical_flip_arg) + ' -rot ' + str(image_rotation) + - '&') + cmd = ('raspistill --nopreview -o ' + str(image_path) + + ' -t 0 ' + '-w ' + str(device_info["image_width"]) + + ' -h ' + str(device_info["image_height"]) + + ' -tl ' + str(device_info["timelapse"]) + + ' -q ' + str(device_info["image_quality"]) + + str(device_info["horizontal_flip_arg"]) + + str(device_info["vertical_flip_arg"]) + + ' -rot ' + str(device_info["image_rotation"]) + + ' &') subprocess.call(cmd, shell=True) diff --git a/include/python2.7 b/include/python2.7 new file mode 120000 index 00000000000..3fe034fccc4 --- /dev/null +++ b/include/python2.7 @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7 \ No newline at end of file From 9584f2dab4a3d36003d0f93f39ba2f6700ac14a0 Mon Sep 17 00:00:00 2001 From: LucaSoldi Date: Wed, 23 Mar 2016 14:53:52 +0100 Subject: [PATCH 003/124] Delete .Python --- .Python | 1 - 1 file changed, 1 deletion(-) delete mode 120000 .Python diff --git a/.Python b/.Python deleted file mode 120000 index cc24a1e924a..00000000000 --- a/.Python +++ /dev/null @@ -1 +0,0 @@ -/System/Library/Frameworks/Python.framework/Versions/2.7/Python \ No newline at end of file From 800397dbe4659495e312e3da79fc957b4ba18443 Mon Sep 17 00:00:00 2001 From: LucaSoldi Date: Wed, 23 Mar 2016 14:55:32 +0100 Subject: [PATCH 004/124] Delete python2.7 --- include/python2.7 | 1 - 1 file changed, 1 deletion(-) delete mode 120000 include/python2.7 diff --git a/include/python2.7 b/include/python2.7 deleted file mode 120000 index 3fe034fccc4..00000000000 --- a/include/python2.7 +++ /dev/null @@ -1 +0,0 @@ -/System/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7 \ No newline at end of file From 9d0b15421c2f4bd887d7d835632a03893140e1ec Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 26 Mar 2016 01:02:30 -0700 Subject: [PATCH 005/124] Version bump to 0.17.0.dev0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index dbc20bb9c32..a98e6c85a7e 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ # coding: utf-8 """Constants used by Home Assistant components.""" -__version__ = "0.16.0" +__version__ = "0.17.0.dev0" REQUIRED_PYTHON_VER = (3, 4) # Can be used to specify a catch all when registering state or event listeners. From 863c111449789b406f3a964814c2274bb6689dff Mon Sep 17 00:00:00 2001 From: Harry Kantas Date: Sat, 26 Mar 2016 09:58:16 +0000 Subject: [PATCH 006/124] systemd unit fixes --- script/home-assistant@.service | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/script/home-assistant@.service b/script/home-assistant@.service index 1fffbc3c344..9c4876f9789 100644 --- a/script/home-assistant@.service +++ b/script/home-assistant@.service @@ -7,8 +7,9 @@ After=network.target [Service] Type=simple User=%i -WorkingDirectory=%h -ExecStart=/usr/bin/hass --config %h/.homeassistant/ +# Enable the following line if you get network-related HA errors during boot +#ExecStartPre=/usr/bin/sleep 60 +ExecStart=/usr/bin/hass SendSIGKILL=no [Install] From 2c45d1f27dd0a8516555276214157d9d5323f1b8 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 26 Mar 2016 12:50:12 +0100 Subject: [PATCH 007/124] Upgrade pyowm to 2.3.1 --- homeassistant/components/sensor/openweathermap.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/sensor/openweathermap.py index c42519733ba..8204307759d 100644 --- a/homeassistant/components/sensor/openweathermap.py +++ b/homeassistant/components/sensor/openweathermap.py @@ -11,7 +11,7 @@ from homeassistant.const import CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -REQUIREMENTS = ['pyowm==2.3.0'] +REQUIREMENTS = ['pyowm==2.3.1'] _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = { 'weather': ['Condition', None], diff --git a/requirements_all.txt b/requirements_all.txt index f21b2986030..f7085806dc3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -186,7 +186,7 @@ pynetgear==0.3.2 pynx584==0.2 # homeassistant.components.sensor.openweathermap -pyowm==2.3.0 +pyowm==2.3.1 # homeassistant.components.device_tracker.snmp pysnmp==4.2.5 From 15303fd32dde3829338b9e747ba9faa593402b9e Mon Sep 17 00:00:00 2001 From: Ellis Percival Date: Sat, 26 Mar 2016 16:42:53 +0000 Subject: [PATCH 008/124] Remove unnecessary dependency from TCP binary sensor for #1617 --- homeassistant/components/binary_sensor/tcp.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/binary_sensor/tcp.py b/homeassistant/components/binary_sensor/tcp.py index 4048c884df6..6c00fea8f94 100644 --- a/homeassistant/components/binary_sensor/tcp.py +++ b/homeassistant/components/binary_sensor/tcp.py @@ -9,7 +9,6 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.sensor.tcp import Sensor, DOMAIN, CONF_VALUE_ON -DEPENDENCIES = [DOMAIN] _LOGGER = logging.getLogger(__name__) From ada561df30ea6c288e80d3f1cde32d769bb90799 Mon Sep 17 00:00:00 2001 From: Flyte Date: Sat, 26 Mar 2016 17:33:18 +0000 Subject: [PATCH 009/124] Remove unused import and unnecessary DOMAIN constant from TCP components. --- homeassistant/components/binary_sensor/tcp.py | 3 ++- homeassistant/components/sensor/tcp.py | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/binary_sensor/tcp.py b/homeassistant/components/binary_sensor/tcp.py index 6c00fea8f94..dcf4c3dff7e 100644 --- a/homeassistant/components/binary_sensor/tcp.py +++ b/homeassistant/components/binary_sensor/tcp.py @@ -7,7 +7,8 @@ https://home-assistant.io/components/binary_sensor.tcp/ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.sensor.tcp import Sensor, DOMAIN, CONF_VALUE_ON +from homeassistant.components.sensor.tcp import Sensor, CONF_VALUE_ON + _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/tcp.py b/homeassistant/components/sensor/tcp.py index 931ad7b237a..b3ee42423ec 100644 --- a/homeassistant/components/sensor/tcp.py +++ b/homeassistant/components/sensor/tcp.py @@ -13,8 +13,6 @@ from homeassistant.helpers import template from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import Entity -DOMAIN = "tcp" - CONF_PORT = "port" CONF_TIMEOUT = "timeout" CONF_PAYLOAD = "payload" From 90ec2c274b4b5c3ab6a9caae8d2a68184af422db Mon Sep 17 00:00:00 2001 From: Josh Wright Date: Sat, 26 Mar 2016 16:21:27 -0400 Subject: [PATCH 010/124] Add shebang for script/setup --- script/setup | 1 + 1 file changed, 1 insertion(+) diff --git a/script/setup b/script/setup index 6d3a774dd54..443dee7889f 100755 --- a/script/setup +++ b/script/setup @@ -1,3 +1,4 @@ +#!/usr/bin/env sh cd "$(dirname "$0")/.." git submodule init From c7e49f20d3452ecf83b9e70b6b3bfea0e2470ee3 Mon Sep 17 00:00:00 2001 From: pavoni Date: Sat, 26 Mar 2016 22:20:26 +0000 Subject: [PATCH 011/124] Cast acc to float before comparison. --- homeassistant/components/device_tracker/owntracks.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py index 709312acc0b..0c67bb123b0 100644 --- a/homeassistant/components/device_tracker/owntracks.py +++ b/homeassistant/components/device_tracker/owntracks.py @@ -11,6 +11,7 @@ from collections import defaultdict import homeassistant.components.mqtt as mqtt from homeassistant.const import STATE_HOME +from homeassistant.util import convert DEPENDENCIES = ['mqtt'] @@ -46,8 +47,8 @@ def setup_scanner(hass, config, see): return if (not isinstance(data, dict) or data.get('_type') != 'location') or ( - 'acc' in data and max_gps_accuracy is not None and data[ - 'acc'] > max_gps_accuracy): + max_gps_accuracy is not None and + convert(data.get('acc'), float, 0.0) > max_gps_accuracy): return dev_id, kwargs = _parse_see_args(topic, data) From ee36c36783a1e042bcaf225206d85c92aa409892 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 26 Mar 2016 23:39:56 +0100 Subject: [PATCH 012/124] Add support for sensor classes --- .../components/binary_sensor/arest.py | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/binary_sensor/arest.py b/homeassistant/components/binary_sensor/arest.py index 61e0202fcdb..899dd44a42b 100644 --- a/homeassistant/components/binary_sensor/arest.py +++ b/homeassistant/components/binary_sensor/arest.py @@ -9,7 +9,8 @@ from datetime import timedelta import requests -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import (BinarySensorDevice, + SENSOR_CLASSES) from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -26,6 +27,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): resource = config.get(CONF_RESOURCE) pin = config.get(CONF_PIN) + sensor_class = config.get('sensor_class') + if sensor_class not in SENSOR_CLASSES: + _LOGGER.warning('Unknown sensor class: %s', sensor_class) + sensor_class = None + if None in (resource, pin): _LOGGER.error('Not all required config keys present: %s', ', '.join((CONF_RESOURCE, CONF_PIN))) @@ -45,21 +51,24 @@ def setup_platform(hass, config, add_devices, discovery_info=None): arest = ArestData(resource, pin) - add_devices([ArestBinarySensor(arest, - resource, - config.get('name', response['name']), - pin)]) + add_devices([ArestBinarySensor( + arest, + resource, + config.get('name', response['name']), + sensor_class, + pin)]) # pylint: disable=too-many-instance-attributes, too-many-arguments class ArestBinarySensor(BinarySensorDevice): """Implement an aREST binary sensor for a pin.""" - def __init__(self, arest, resource, name, pin): + def __init__(self, arest, resource, name, sensor_class, pin): """Initialize the aREST device.""" self.arest = arest self._resource = resource self._name = name + self._sensor_class = sensor_class self._pin = pin self.update() @@ -79,6 +88,11 @@ class ArestBinarySensor(BinarySensorDevice): """Return true if the binary sensor is on.""" return bool(self.arest.data.get('state')) + @property + def sensor_class(self): + """Return the class of this sensor.""" + return self._sensor_class + def update(self): """Get the latest data from aREST API.""" self.arest.update() From 90f1b57ed80440a62c704a772de4889f440be7a1 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 17:41:09 -0700 Subject: [PATCH 013/124] Initial GTFS sensor --- homeassistant/components/sensor/gtfs.py | 236 ++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 homeassistant/components/sensor/gtfs.py diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py new file mode 100644 index 00000000000..9271df28820 --- /dev/null +++ b/homeassistant/components/sensor/gtfs.py @@ -0,0 +1,236 @@ +""" +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 + +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle + +_LOGGER = logging.getLogger(__name__) + +REQUIREMENTS = ["SQLAlchemy", "https://github.com/jarondl/pygtfs/archive/" + "d6aea616e50a0f412b90c37dd7808296f1a6d881.zip#" + "pygtfs==0.1.2"] + +ICON = "mdi:train" + +TIME_FORMAT = "%Y-%m-%d %H:%M:%S" + +def get_next_departure(sched, start_station_id, end_station_id): + 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") + + sql_query = """ + 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.{} = 1 AND time(origin_stop_time.departure_time) > time('{}') + AND start_station.stop_id = '{}' AND end_station.stop_id = '{}' + ORDER BY origin_stop_time.departure_time LIMIT 1;"""\ + .format(day_name, now_str, origin_station.id, destination_station.id) + result = sched.engine.execute(sql_query) + item = {} + for row in result: + item = row + + 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_stop_time_arrival_time = "{} {}".format(today, item[4]) + + origin_stop_time_departure_time = "{} {}".format(today, item[5]) + + destination_stop_time_arrival_time = "{} {}".format(today, item[11]) + + destination_stop_time_departure_time = "{} {}".format(today, item[12]) + + origin_stop_time_dict = { + "Arrival Time": origin_stop_time_arrival_time, + "Departure Time": origin_stop_time_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": destination_stop_time_arrival_time, + "Departure Time": destination_stop_time_departure_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): + """Get the GTFS sensor.""" + if config.get("origin") is None: + _LOGGER.error("Origin must be set in the GTFS configuration!") + return False + + if config.get("destination") is None: + _LOGGER.error("Destination must be set in the GTFS configuration!") + return False + + if config.get("data") is None: + _LOGGER.error("Data must be set in the GTFS configuration!") + return False + + dev = [] + dev.append(GTFSDepartureSensor(config["data"], hass.config.path("gtfs"), + config["origin"], config["destination"])) + add_devices(dev) + +# pylint: disable=too-few-public-methods +class GTFSDepartureSensor(Entity): + """Implementation of an GTFS departures sensor.""" + + def __init__(self, data_source, gtfs_folder, origin, destination): + """Initialize the sensor.""" + self._data_source = data_source + self._gtfs_folder = gtfs_folder + self.origin = origin + self.destination = destination + self._name = "GTFS Sensor" + self._unit_of_measurement = "min" + self._state = 0 + self._attributes = {} + 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.""" + import pygtfs + + split_file_name = os.path.splitext(self._data_source) + + sqlite_file = "{}.sqlite".format(split_file_name[0]) + gtfs = pygtfs.Schedule(os.path.join(self._gtfs_folder, sqlite_file)) + + if len(gtfs.feeds) < 1: + pygtfs.append_feed(gtfs, os.path.join(self._gtfs_folder, + self._data_source)) + + self._departure = get_next_departure(gtfs, self.origin, + self.destination) + 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 = name.format(agency.agency_name, + origin_station.stop_id, + destination_station.stop_id) + + # Build attributes + + self._attributes = {} + + def dictForTable(resource): + return dict((col, getattr(resource, col)) \ + for col in resource.__table__.columns.keys()) + + def appendKeys(resource, prefix=None): + for key, val in resource.items(): + if val == "" or val is None or key == "feed_id": + continue + prettyKey = key.replace("_", " ") + prettyKey = prettyKey.title() + prettyKey = prettyKey.replace("Id", "ID") + prettyKey = prettyKey.replace("Url", "URL") + if prefix is not None and prettyKey.startswith(prefix) is False: + prettyKey = "{} {}".format(prefix, prettyKey) + self._attributes[prettyKey] = val + + appendKeys(dictForTable(agency), "Agency") + appendKeys(dictForTable(route), "Route") + appendKeys(dictForTable(trip), "Trip") + appendKeys(dictForTable(origin_station), "Origin Station") + appendKeys(dictForTable(destination_station), "Destination Station") + appendKeys(origin_stop_time, "Origin Stop") + appendKeys(destination_stop_time, "Destination Stop") From 8fe1a9f008e4390a109749afcacbadc69262fc5d Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 17:53:46 -0700 Subject: [PATCH 014/124] Pylint and flake8 --- homeassistant/components/sensor/gtfs.py | 225 +++++++++++++----------- 1 file changed, 119 insertions(+), 106 deletions(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 9271df28820..16d7268afa3 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -9,7 +9,6 @@ import logging import datetime from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -21,101 +20,109 @@ ICON = "mdi:train" TIME_FORMAT = "%Y-%m-%d %H:%M:%S" +# pylint: disable=too-many-locals + + def get_next_departure(sched, start_station_id, end_station_id): - origin_station = sched.stops_by_id(start_station_id)[0] - destination_station = sched.stops_by_id(end_station_id)[0] + """Get the next departure for the given sched.""" + 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") + now = datetime.datetime.now() + day_name = now.strftime("%A").lower() + now_str = now.strftime("%H:%M:%S") - sql_query = """ - 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.{} = 1 AND time(origin_stop_time.departure_time) > time('{}') - AND start_station.stop_id = '{}' AND end_station.stop_id = '{}' - ORDER BY origin_stop_time.departure_time LIMIT 1;"""\ - .format(day_name, now_str, origin_station.id, destination_station.id) - result = sched.engine.execute(sql_query) - item = {} - for row in result: - item = row + sql_query = """ + 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.{} = 1 + AND time(origin_stop_time.departure_time) > time('{}') + AND start_station.stop_id = '{}' AND end_station.stop_id = '{}' + ORDER BY origin_stop_time.departure_time LIMIT 1;"""\ + .format(day_name, now_str, origin_station.id, destination_station.id) + result = sched.engine.execute(sql_query) + item = {} + for row in result: + item = row - 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, + 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) - 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) + seconds_until = (departure_time-datetime.datetime.now()).total_seconds() + minutes_until = int(seconds_until / 60) - route = sched.routes_by_id(item[1])[0] + route = sched.routes_by_id(item[1])[0] - origin_stop_time_arrival_time = "{} {}".format(today, item[4]) + origin_stoptime_arrival_time = "{} {}".format(today, item[4]) - origin_stop_time_departure_time = "{} {}".format(today, item[5]) + origin_stoptime_departure_time = "{} {}".format(today, item[5]) - destination_stop_time_arrival_time = "{} {}".format(today, item[11]) + dest_stoptime_arrival_time = "{} {}".format(today, item[11]) - destination_stop_time_departure_time = "{} {}".format(today, item[12]) + dest_stoptime_depart_time = "{} {}".format(today, item[12]) - origin_stop_time_dict = { - "Arrival Time": origin_stop_time_arrival_time, - "Departure Time": origin_stop_time_departure_time, - "Drop Off Type": item[6], "Pickup Type": item[7], - "Shape Dist Traveled": item[8], "Headsign": item[9], - "Sequence": item[10] - } + 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": destination_stop_time_arrival_time, - "Departure Time": destination_stop_time_departure_time, - "Drop Off Type": item[13], "Pickup Type": item[14], - "Shape Dist Traveled": item[15], "Headsign": item[16], - "Sequence": item[17] - } + 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 + } - 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): """Get the GTFS sensor.""" @@ -136,7 +143,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): config["origin"], config["destination"])) add_devices(dev) -# pylint: disable=too-few-public-methods +# pylint: disable=too-many-instance-attributes,too-few-public-methods + + class GTFSDepartureSensor(Entity): """Implementation of an GTFS departures sensor.""" @@ -186,9 +195,10 @@ class GTFSDepartureSensor(Entity): sqlite_file = "{}.sqlite".format(split_file_name[0]) gtfs = pygtfs.Schedule(os.path.join(self._gtfs_folder, sqlite_file)) + # pylint: disable=no-member if len(gtfs.feeds) < 1: - pygtfs.append_feed(gtfs, os.path.join(self._gtfs_folder, - self._data_source)) + pygtfs.append_feed(gtfs, os.path.join(self._gtfs_folder, + self._data_source)) self._departure = get_next_departure(gtfs, self.origin, self.destination) @@ -211,26 +221,29 @@ class GTFSDepartureSensor(Entity): self._attributes = {} - def dictForTable(resource): - return dict((col, getattr(resource, col)) \ - for col in resource.__table__.columns.keys()) + 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 appendKeys(resource, prefix=None): - for key, val in resource.items(): - if val == "" or val is None or key == "feed_id": - continue - prettyKey = key.replace("_", " ") - prettyKey = prettyKey.title() - prettyKey = prettyKey.replace("Id", "ID") - prettyKey = prettyKey.replace("Url", "URL") - if prefix is not None and prettyKey.startswith(prefix) is False: - prettyKey = "{} {}".format(prefix, prettyKey) - self._attributes[prettyKey] = val + 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 - appendKeys(dictForTable(agency), "Agency") - appendKeys(dictForTable(route), "Route") - appendKeys(dictForTable(trip), "Trip") - appendKeys(dictForTable(origin_station), "Origin Station") - appendKeys(dictForTable(destination_station), "Destination Station") - appendKeys(origin_stop_time, "Origin Stop") - appendKeys(destination_stop_time, "Destination Stop") + 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") From 8a5f60ee23637c6448cdf1ec2f4ce3b974093d3e Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 17:54:48 -0700 Subject: [PATCH 015/124] .coveragerc and requirements_all.txt --- .coveragerc | 1 + requirements_all.txt | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.coveragerc b/.coveragerc index 370d229d87d..3656ad8fde5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -136,6 +136,7 @@ omit = homeassistant/components/sensor/eliqonline.py homeassistant/components/sensor/forecast.py homeassistant/components/sensor/glances.py + homeassistant/components/sensor/gtfs.py homeassistant/components/sensor/netatmo.py homeassistant/components/sensor/neurio_energy.py homeassistant/components/sensor/onewire.py diff --git a/requirements_all.txt b/requirements_all.txt index f7085806dc3..bae7c6475d6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -15,6 +15,9 @@ PyMata==2.07a # homeassistant.components.rpi_gpio # RPi.GPIO==0.6.1 +# homeassistant.components.sensor.gtfs +SQLAlchemy + # homeassistant.components.media_player.sonos SoCo==0.11.1 @@ -87,6 +90,9 @@ https://github.com/denismakogon/rides-python-sdk/archive/py3-support.zip#uber_ri # homeassistant.components.sensor.sabnzbd https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1 +# homeassistant.components.sensor.gtfs +https://github.com/jarondl/pygtfs/archive/d6aea616e50a0f412b90c37dd7808296f1a6d881.zip#pygtfs==0.1.2 + # homeassistant.components.ecobee https://github.com/nkgilley/python-ecobee-api/archive/92a2f330cbaf601d0618456fdd97e5a8c42c1c47.zip#python-ecobee==0.0.4 From 982baaba22af1a171b3c8bc7e6c697ee0908f8c2 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 17:59:38 -0700 Subject: [PATCH 016/124] Annoying missing period --- homeassistant/components/sensor/gtfs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 16d7268afa3..0d6ac803abb 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -227,7 +227,7 @@ class GTFSDepartureSensor(Entity): for col in resource.__table__.columns.keys()) def append_keys(resource, prefix=None): - """Properly format key val pairs to append to attributes""" + """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 From f31ba1186107ffd9ce77cae291bb46699487b6fc Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 18:05:34 -0700 Subject: [PATCH 017/124] Lock SQLAlchemy --- homeassistant/components/sensor/gtfs.py | 3 ++- requirements_all.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 0d6ac803abb..60996baaa41 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -12,7 +12,8 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ["SQLAlchemy", "https://github.com/jarondl/pygtfs/archive/" +REQUIREMENTS = ["SQLAlchemy==1.0.12", + "https://github.com/jarondl/pygtfs/archive/" "d6aea616e50a0f412b90c37dd7808296f1a6d881.zip#" "pygtfs==0.1.2"] diff --git a/requirements_all.txt b/requirements_all.txt index bae7c6475d6..039183eebe7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -16,7 +16,7 @@ PyMata==2.07a # RPi.GPIO==0.6.1 # homeassistant.components.sensor.gtfs -SQLAlchemy +SQLAlchemy==1.0.12 # homeassistant.components.media_player.sonos SoCo==0.11.1 From 116b83b53ff8049b4f65fa1d0549b6d7663cafd4 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 18:25:49 -0700 Subject: [PATCH 018/124] Fix PyGTFS import errors --- homeassistant/components/sensor/gtfs.py | 5 ++--- requirements_all.txt | 9 +++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 60996baaa41..97eaaa5bdeb 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -12,9 +12,8 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ["SQLAlchemy==1.0.12", - "https://github.com/jarondl/pygtfs/archive/" - "d6aea616e50a0f412b90c37dd7808296f1a6d881.zip#" +REQUIREMENTS = ["https://github.com/robbiet480/pygtfs/archive/" + "6b40d5fb30fd410cfaf637c901b5ed5a08c33e4c.zip#" "pygtfs==0.1.2"] ICON = "mdi:train" diff --git a/requirements_all.txt b/requirements_all.txt index 039183eebe7..386c2d8473b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -15,9 +15,6 @@ PyMata==2.07a # homeassistant.components.rpi_gpio # RPi.GPIO==0.6.1 -# homeassistant.components.sensor.gtfs -SQLAlchemy==1.0.12 - # homeassistant.components.media_player.sonos SoCo==0.11.1 @@ -90,9 +87,6 @@ https://github.com/denismakogon/rides-python-sdk/archive/py3-support.zip#uber_ri # homeassistant.components.sensor.sabnzbd https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1 -# homeassistant.components.sensor.gtfs -https://github.com/jarondl/pygtfs/archive/d6aea616e50a0f412b90c37dd7808296f1a6d881.zip#pygtfs==0.1.2 - # homeassistant.components.ecobee https://github.com/nkgilley/python-ecobee-api/archive/92a2f330cbaf601d0618456fdd97e5a8c42c1c47.zip#python-ecobee==0.0.4 @@ -102,6 +96,9 @@ https://github.com/rkabadi/pyedimax/archive/365301ce3ff26129a7910c501ead09ea625f # homeassistant.components.sensor.temper https://github.com/rkabadi/temper-python/archive/3dbdaf2d87b8db9a3cd6e5585fc704537dd2d09b.zip#temperusb==1.2.3 +# homeassistant.components.sensor.gtfs +https://github.com/robbiet480/pygtfs/archive/6b40d5fb30fd410cfaf637c901b5ed5a08c33e4c.zip#pygtfs==0.1.2 + # homeassistant.components.scene.hunterdouglas_powerview https://github.com/sander76/powerviewApi/archive/master.zip#powerviewApi==0.2 From 1fd96296f7fddb94c6e7d7d0cb832ef1c2e36007 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 18:47:54 -0700 Subject: [PATCH 019/124] Use parameterized queries when possible --- homeassistant/components/sensor/gtfs.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 97eaaa5bdeb..bccf0ef2abb 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -32,7 +32,9 @@ def get_next_departure(sched, start_station_id, end_station_id): day_name = now.strftime("%A").lower() now_str = now.strftime("%H:%M:%S") - sql_query = """ + 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), @@ -62,11 +64,13 @@ def get_next_departure(sched, start_station_id, end_station_id): INNER JOIN stops end_station ON destination_stop_time.stop_id = end_station.stop_id WHERE calendar.{} = 1 - AND time(origin_stop_time.departure_time) > time('{}') - AND start_station.stop_id = '{}' AND end_station.stop_id = '{}' - ORDER BY origin_stop_time.departure_time LIMIT 1;"""\ - .format(day_name, now_str, origin_station.id, destination_station.id) - result = sched.engine.execute(sql_query) + 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 + ORDER BY origin_stop_time.departure_time LIMIT 1;""".format(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 From 5a35e4a9baa753e5847b4d9333506713c2f5b304 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 18:52:03 -0700 Subject: [PATCH 020/124] Data source validation --- homeassistant/components/sensor/gtfs.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index bccf0ef2abb..9e198e7ef11 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -142,8 +142,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error("Data must be set in the GTFS configuration!") return False + gtfs_dir = hass.config.path("gtfs") + + if not os.path.exists(gtfs_dir): + os.makedirs(gtfs_dir) + + if not os.path.exists(os.path.join(gtfs_dir, config["data"])): + _LOGGER.error("The given GTFS data file/folder was not found!") + return False + dev = [] - dev.append(GTFSDepartureSensor(config["data"], hass.config.path("gtfs"), + dev.append(GTFSDepartureSensor(config["data"], gtfs_dir, config["origin"], config["destination"])) add_devices(dev) From dac3c9d1b568355aa949dea09677e94322c424cf Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 18:53:21 -0700 Subject: [PATCH 021/124] Pylint and flake8 --- homeassistant/components/sensor/gtfs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 9e198e7ef11..1ecbe9e9db4 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -68,7 +68,7 @@ def get_next_departure(sched, start_station_id, end_station_id): AND start_station.stop_id = :origin_station_id AND end_station.stop_id = :end_station_id ORDER BY origin_stop_time.departure_time LIMIT 1;""".format(day_name)) - result = sched.engine.execute(sql_query,now_str=now_str, + result = sched.engine.execute(sql_query, now_str=now_str, origin_station_id=origin_station.id, end_station_id=destination_station.id) item = {} @@ -145,11 +145,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): gtfs_dir = hass.config.path("gtfs") if not os.path.exists(gtfs_dir): - os.makedirs(gtfs_dir) + os.makedirs(gtfs_dir) if not os.path.exists(os.path.join(gtfs_dir, config["data"])): - _LOGGER.error("The given GTFS data file/folder was not found!") - return False + _LOGGER.error("The given GTFS data file/folder was not found!") + return False dev = [] dev.append(GTFSDepartureSensor(config["data"], gtfs_dir, From 2a194d8861fd6484a601df5f6458c961c7e846a8 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 18:55:49 -0700 Subject: [PATCH 022/124] Use named string formatting for safety --- homeassistant/components/sensor/gtfs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 1ecbe9e9db4..4cd1655e6dc 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -63,11 +63,12 @@ def get_next_departure(sched, start_station_id, end_station_id): 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.{} = 1 + 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 - ORDER BY origin_stop_time.departure_time LIMIT 1;""".format(day_name)) + 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) From 4cbd49921f389b6793ffa4907b546dec173edd4f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 26 Mar 2016 19:03:16 -0700 Subject: [PATCH 023/124] Use HTTP 1.1 --- homeassistant/components/http.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index 7c252385d5d..3453d815044 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -164,6 +164,7 @@ class RequestHandler(SimpleHTTPRequestHandler): # Track if this was an authenticated request self.authenticated = False SimpleHTTPRequestHandler.__init__(self, req, client_addr, server) + self.protocol_version = 'HTTP/1.1' def log_message(self, fmt, *arguments): """Redirect built-in log to HA logging.""" From 2db49ebca56ec1780e670dd38797a69723dc61ca Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 27 Mar 2016 11:48:48 +0200 Subject: [PATCH 024/124] Add connectivity to sensor classes --- .../components/binary_sensor/__init__.py | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 777974905b8..0fed26bd710 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -17,20 +17,21 @@ SCAN_INTERVAL = 30 ENTITY_ID_FORMAT = DOMAIN + '.{}' SENSOR_CLASSES = [ - None, # Generic on/off - 'opening', # Door, window, etc - 'motion', # Motion sensor - 'gas', # CO, CO2, etc - 'smoke', # Smoke detector - 'moisture', # Specifically a wetness sensor - 'light', # Lightness threshold - 'power', # Power, over-current, etc - 'safety', # Generic on=unsafe, off=safe - 'heat', # On means hot (or too hot) - 'cold', # On means cold (or too cold) - 'moving', # On means moving, Off means stopped - 'sound', # On means sound detected, Off means no sound - 'vibration', # On means vibration detected, Off means no vibration + None, # Generic on/off + 'cold', # On means cold (or too cold) + 'connectivity', # On means connection present, Off = no connection + 'gas', # CO, CO2, etc. + 'heat', # On means hot (or too hot) + 'light', # Lightness threshold + 'moisture', # Specifically a wetness sensor + 'motion', # Motion sensor + 'moving', # On means moving, Off means stopped + 'opening', # Door, window, etc. + 'power', # Power, over-current, etc + 'safety', # Generic on=unsafe, off=safe + 'smoke', # Smoke detector + 'sound', # On means sound detected, Off means no sound + 'vibration', # On means vibration detected, Off means no vibration ] # Maps discovered services to their platforms From c63a3311f48dedbfebb2eab10b2d4471e46ecb28 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 27 Mar 2016 08:44:15 -0700 Subject: [PATCH 025/124] Fix broken Python check for Python 2 --- homeassistant/__main__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 02ccc239f2b..5e21119d91f 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -9,8 +9,6 @@ import threading import time from multiprocessing import Process -import homeassistant.config as config_util -from homeassistant import bootstrap from homeassistant.const import ( __version__, EVENT_HOMEASSISTANT_START, @@ -32,6 +30,7 @@ def validate_python(): def ensure_config_path(config_dir): """Validate the configuration directory.""" + import homeassistant.config as config_util lib_dir = os.path.join(config_dir, 'lib') # Test if configuration directory exists @@ -60,6 +59,7 @@ def ensure_config_path(config_dir): def ensure_config_file(config_dir): """Ensure configuration file exists.""" + import homeassistant.config as config_util config_path = config_util.ensure_config_exists(config_dir) if config_path is None: @@ -71,6 +71,7 @@ def ensure_config_file(config_dir): def get_arguments(): """Get parsed passed in arguments.""" + import homeassistant.config as config_util parser = argparse.ArgumentParser( description="Home Assistant: Observe, Control, Automate.") parser.add_argument('--version', action='version', version=__version__) @@ -225,6 +226,8 @@ def setup_and_run_hass(config_dir, args, top_process=False): Block until stopped. Will assume it is running in a subprocess unless top_process is set to true. """ + from homeassistant import bootstrap + if args.demo_mode: config = { 'frontend': {}, From d0503cc02191eb6fec84790b4f88831870932a98 Mon Sep 17 00:00:00 2001 From: Luca Soldi Date: Sun, 27 Mar 2016 20:49:04 +0200 Subject: [PATCH 026/124] Add feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit √ add file_path config √ changed subprocess call --- .../components/camera/raspberry_camera.py | 86 ------------------- homeassistant/components/camera/rpi_camera.py | 86 +++++++++++++++++++ 2 files changed, 86 insertions(+), 86 deletions(-) delete mode 100644 homeassistant/components/camera/raspberry_camera.py create mode 100644 homeassistant/components/camera/rpi_camera.py diff --git a/homeassistant/components/camera/raspberry_camera.py b/homeassistant/components/camera/raspberry_camera.py deleted file mode 100644 index 4f7a417d095..00000000000 --- a/homeassistant/components/camera/raspberry_camera.py +++ /dev/null @@ -1,86 +0,0 @@ -"""Camera platform that has a Raspberry Pi camera.""" - -import os -import subprocess -import logging -import shutil - -from homeassistant.components.camera import Camera - -_LOGGER = logging.getLogger(__name__) - - -def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Raspberry Camera.""" - if shutil.which("raspistill") is None: - _LOGGER.error("Error: raspistill not found") - return None - - horizontal_flip_arg = "" - horizontal_flip = int(config.get("horizontal_flip", "0")) - if horizontal_flip: - horizontal_flip_arg = " -hf " - - vertical_flip_arg = "" - vertical_flip = int(config.get("vertical_flip", "0")) - if vertical_flip: - vertical_flip_arg = " -vf" - - setup_config = ( - { - "name": config.get("name", "Raspberry Pi Camera"), - "image_width": int(config.get("image_width", "640")), - "image_height": int(config.get("image_height", "480")), - "image_quality": int(config.get("image_quality", "7")), - "image_rotation": int(config.get("image_rotation", "0")), - "timelapse": int(config.get("timelapse", "1000")), - "horizontal_flip_arg": horizontal_flip_arg, - "vertical_flip_arg": vertical_flip_arg - } - ) - - add_devices([ - RaspberryCamera(setup_config) - ]) - - -class RaspberryCamera(Camera): - """Raspberry Pi camera.""" - - def __init__(self, device_info): - """Initialize Raspberry Pi camera component.""" - super().__init__() - - self._name = device_info["name"] - - image_path = os.path.join(os.path.dirname(__file__), - 'image.jpg') - - # kill if there's raspistill instance - cmd = 'killall raspistill' - subprocess.call(cmd, shell=True) - # start new instance of raspistill - cmd = ('raspistill --nopreview -o ' + str(image_path) + - ' -t 0 ' + '-w ' + str(device_info["image_width"]) + - ' -h ' + str(device_info["image_height"]) + - ' -tl ' + str(device_info["timelapse"]) + - ' -q ' + str(device_info["image_quality"]) + - str(device_info["horizontal_flip_arg"]) + - str(device_info["vertical_flip_arg"]) + - ' -rot ' + str(device_info["image_rotation"]) + - ' &') - - subprocess.call(cmd, shell=True) - - def camera_image(self): - """Return raspstill image response.""" - image_path = os.path.join(os.path.dirname(__file__), - 'image.jpg') - - with open(image_path, 'rb') as file: - return file.read() - - @property - def name(self): - """Return the name of this camera.""" - return self._name diff --git a/homeassistant/components/camera/rpi_camera.py b/homeassistant/components/camera/rpi_camera.py new file mode 100644 index 00000000000..cda48d1ddfa --- /dev/null +++ b/homeassistant/components/camera/rpi_camera.py @@ -0,0 +1,86 @@ +"""Camera platform that has a Raspberry Pi camera.""" + +import os +import subprocess +import logging +import shutil + +from homeassistant.components.camera import Camera + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Raspberry Camera.""" + if shutil.which("raspistill") is None: + _LOGGER.error("Error: raspistill not found") + return False + + setup_config = ( + { + "name": config.get("name", "Raspberry Pi Camera"), + "image_width": int(config.get("image_width", "640")), + "image_height": int(config.get("image_height", "480")), + "image_quality": int(config.get("image_quality", "7")), + "image_rotation": int(config.get("image_rotation", "0")), + "timelapse": int(config.get("timelapse", "2000")), + "horizontal_flip": int(config.get("horizontal_flip", "0")), + "vertical_flip": int(config.get("vertical_flip", "0")), + "file_path": config.get("file_path", + os.path.join(os.path.dirname(__file__), + 'image.jpg')) + } + ) + + # check filepath given is writable + if not os.access(setup_config["file_path"], os.W_OK): + _LOGGER.error("Error: file path is not writable") + return False + + add_devices([ + RaspberryCamera(setup_config) + ]) + + +class RaspberryCamera(Camera): + """Raspberry Pi camera.""" + + def __init__(self, device_info): + """Initialize Raspberry Pi camera component.""" + super().__init__() + + self._name = device_info["name"] + self._config = device_info + + # kill if there's raspistill instance + subprocess.Popen(['killall', 'raspistill'], + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT) + + cmd_args = [ + 'raspistill', '--nopreview', '-o', str(device_info["file_path"]), + '-t', '0', '-w', str(device_info["image_width"]), + '-h', str(device_info["image_height"]), + '-tl', str(device_info["timelapse"]), + '-q', str(device_info["image_quality"]), + '-rot', str(device_info["image_rotation"]) + ] + if device_info["horizontal_flip"]: + cmd_args.append("-hf") + + if device_info["vertical_flip"]: + cmd_args.append("-vf") + + subprocess.Popen(cmd_args, + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT) + + def camera_image(self): + """Return raspstill image response.""" + with open(self._config["file_path"], 'rb') as file: + return file.read() + + @property + def name(self): + """Return the name of this camera.""" + return self._name From 7cb69ae9d956e259ebd575eb9aa6a7e1c39be681 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 27 Mar 2016 11:57:15 -0700 Subject: [PATCH 027/124] Make sure we always sent content-length header --- homeassistant/components/camera/__init__.py | 69 ++++++------------- homeassistant/components/frontend/__init__.py | 8 +-- homeassistant/components/http.py | 41 ++++------- tests/components/test_api.py | 2 - 4 files changed, 38 insertions(+), 82 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 41860a62cb7..d48563d471f 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -14,10 +14,7 @@ import requests from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.components import bloomsky -from homeassistant.const import ( - HTTP_NOT_FOUND, - ATTR_ENTITY_ID, - ) +from homeassistant.const import HTTP_OK, HTTP_NOT_FOUND, ATTR_ENTITY_ID DOMAIN = 'camera' @@ -36,7 +33,7 @@ STATE_IDLE = 'idle' ENTITY_IMAGE_URL = '/api/camera_proxy/{0}' -MULTIPART_BOUNDARY = '--jpegboundary' +MULTIPART_BOUNDARY = '--jpgboundary' MJPEG_START_HEADER = 'Content-type: {0}\r\n\r\n' @@ -49,17 +46,6 @@ def setup(hass, config): component.setup(config) - # ------------------------------------------------------------------------- - # CAMERA COMPONENT ENDPOINTS - # ------------------------------------------------------------------------- - # The following defines the endpoints for serving images from the camera - # via the HA http server. This is means that you can access images from - # your camera outside of your LAN without the need for port forwards etc. - - # Because the authentication header can't be added in image requests these - # endpoints are secured with session based security. - - # pylint: disable=unused-argument def _proxy_camera_image(handler, path_match, data): """Serve the camera image via the HA server.""" entity_id = path_match.group(ATTR_ENTITY_ID) @@ -77,22 +63,16 @@ def setup(hass, config): handler.end_headers() return - handler.wfile.write(response) + handler.send_response(HTTP_OK) + handler.write_content(response) hass.http.register_path( 'GET', re.compile(r'/api/camera_proxy/(?P[a-zA-Z\._0-9]+)'), _proxy_camera_image) - # pylint: disable=unused-argument def _proxy_camera_mjpeg_stream(handler, path_match, data): - """ - Proxy the camera image as an mjpeg stream via the HA server. - - This function takes still images from the IP camera and turns them - into an MJPEG stream. This means that HA can return a live video - stream even with only a still image URL available. - """ + """Proxy the camera image as an mjpeg stream via the HA server.""" entity_id = path_match.group(ATTR_ENTITY_ID) camera = component.entities.get(entity_id) @@ -112,8 +92,7 @@ def setup(hass, config): hass.http.register_path( 'GET', - re.compile( - r'/api/camera_proxy_stream/(?P[a-zA-Z\._0-9]+)'), + re.compile(r'/api/camera_proxy_stream/(?P[a-zA-Z\._0-9]+)'), _proxy_camera_mjpeg_stream) return True @@ -137,19 +116,16 @@ class Camera(Entity): return ENTITY_IMAGE_URL.format(self.entity_id) @property - # pylint: disable=no-self-use def is_recording(self): """Return true if the device is recording.""" return False @property - # pylint: disable=no-self-use def brand(self): """Camera brand.""" return None @property - # pylint: disable=no-self-use def model(self): """Camera model.""" return None @@ -160,29 +136,28 @@ class Camera(Entity): def mjpeg_stream(self, handler): """Generate an HTTP MJPEG stream from camera images.""" - handler.request.sendall(bytes('HTTP/1.1 200 OK\r\n', 'utf-8')) - handler.request.sendall(bytes( - 'Content-type: multipart/x-mixed-replace; \ - boundary=--jpgboundary\r\n\r\n', 'utf-8')) - handler.request.sendall(bytes('--jpgboundary\r\n', 'utf-8')) + def write_string(text): + """Helper method to write a string to the stream.""" + handler.request.sendall(bytes(text + '\r\n', 'utf-8')) + + write_string('HTTP/1.1 200 OK') + write_string('Content-type: multipart/x-mixed-replace; ' + 'boundary={}'.format(MULTIPART_BOUNDARY)) + write_string('') + write_string(MULTIPART_BOUNDARY) - # MJPEG_START_HEADER.format() while True: img_bytes = self.camera_image() + if img_bytes is None: continue - headers_str = '\r\n'.join(( - 'Content-length: {}'.format(len(img_bytes)), - 'Content-type: image/jpeg', - )) + '\r\n\r\n' - handler.request.sendall( - bytes(headers_str, 'utf-8') + - img_bytes + - bytes('\r\n', 'utf-8')) - - handler.request.sendall( - bytes('--jpgboundary\r\n', 'utf-8')) + write_string('Content-length: {}'.format(len(img_bytes))) + write_string('Content-type: image/jpeg') + write_string('') + handler.request.sendall(img_bytes) + write_string('') + write_string(MULTIPART_BOUNDARY) time.sleep(0.5) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 8ba2a06130b..c1025fd1657 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -66,10 +66,6 @@ def _handle_get_api_bootstrap(handler, path_match, data): def _handle_get_root(handler, path_match, data): """Render the frontend.""" - handler.send_response(HTTP_OK) - handler.send_header('Content-type', 'text/html; charset=utf-8') - handler.end_headers() - if handler.server.development: app_url = "home-assistant-polymer/src/home-assistant.html" else: @@ -86,7 +82,9 @@ def _handle_get_root(handler, path_match, data): template_html = template_html.replace('{{ auth }}', auth) template_html = template_html.replace('{{ icons }}', mdi_version.VERSION) - handler.wfile.write(template_html.encode("UTF-8")) + handler.send_response(HTTP_OK) + handler.write_content(template_html.encode("UTF-8"), + 'text/html; charset=utf-8') def _handle_get_service_worker(handler, path_match, data): diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index 3453d815044..7da4bc320f3 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -7,7 +7,6 @@ https://home-assistant.io/developers/api/ import gzip import json import logging -import os import ssl import threading import time @@ -283,31 +282,21 @@ class RequestHandler(SimpleHTTPRequestHandler): json_data = json.dumps(data, indent=4, sort_keys=True, cls=rem.JSONEncoder).encode('UTF-8') self.send_response(status_code) - self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON) - self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(json_data))) if location: self.send_header('Location', location) self.set_session_cookie_header() - self.end_headers() - - if data is not None: - self.wfile.write(json_data) + self.write_content(json_data, CONTENT_TYPE_JSON) def write_text(self, message, status_code=HTTP_OK): """Helper method to return a text message to the caller.""" msg_data = message.encode('UTF-8') self.send_response(status_code) - self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN) - self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(msg_data))) - self.set_session_cookie_header() - self.end_headers() - - self.wfile.write(msg_data) + self.write_content(msg_data, CONTENT_TYPE_TEXT_PLAIN) def write_file(self, path, cache_headers=True): """Return a file to the user.""" @@ -323,36 +312,32 @@ class RequestHandler(SimpleHTTPRequestHandler): def write_file_pointer(self, content_type, inp, cache_headers=True): """Helper function to write a file pointer to the user.""" - do_gzip = 'gzip' in self.headers.get(HTTP_HEADER_ACCEPT_ENCODING, '') - self.send_response(HTTP_OK) - self.send_header(HTTP_HEADER_CONTENT_TYPE, content_type) if cache_headers: self.set_cache_header() self.set_session_cookie_header() - if do_gzip: - gzip_data = gzip.compress(inp.read()) + self.write_content(inp.read(), content_type) + + def write_content(self, content, content_type=None): + """Helper method to write content bytes to output stream.""" + if content_type is not None: + self.send_header(HTTP_HEADER_CONTENT_TYPE, content_type) + + if 'gzip' in self.headers.get(HTTP_HEADER_ACCEPT_ENCODING, ''): + content = gzip.compress(content) self.send_header(HTTP_HEADER_CONTENT_ENCODING, "gzip") self.send_header(HTTP_HEADER_VARY, HTTP_HEADER_ACCEPT_ENCODING) - self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(gzip_data))) - - else: - fst = os.fstat(inp.fileno()) - self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(fst[6])) + self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(content))) self.end_headers() if self.command == 'HEAD': return - elif do_gzip: - self.wfile.write(gzip_data) - - else: - self.copyfile(inp, self.wfile) + self.wfile.write(content) def set_cache_header(self): """Add cache headers if not in development.""" diff --git a/tests/components/test_api.py b/tests/components/test_api.py index 6acb1c2a569..fb571fe5811 100644 --- a/tests/components/test_api.py +++ b/tests/components/test_api.py @@ -104,8 +104,6 @@ class TestAPI(unittest.TestCase): _url(const.URL_API_STATES_ENTITY.format("test.test")), headers=HA_HEADERS) - self.assertEqual(req.headers['content-length'], str(len(req.content))) - data = ha.State.from_dict(req.json()) state = hass.states.get("test.test") From fe73cbbcb6ad9c2b9e239520e6e237bdcba92e2b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 27 Mar 2016 22:06:27 -0700 Subject: [PATCH 028/124] Revert pyicloud upgrade --- homeassistant/components/device_tracker/icloud.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/icloud.py b/homeassistant/components/device_tracker/icloud.py index bcdc88a6f28..051cd962945 100644 --- a/homeassistant/components/device_tracker/icloud.py +++ b/homeassistant/components/device_tracker/icloud.py @@ -12,7 +12,7 @@ from homeassistant.helpers.event import track_utc_time_change _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pyicloud==0.8.1'] +REQUIREMENTS = ['pyicloud==0.7.2'] CONF_INTERVAL = 'interval' DEFAULT_INTERVAL = 8 diff --git a/requirements_all.txt b/requirements_all.txt index f7085806dc3..1bdc3e53725 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -176,7 +176,7 @@ pydispatcher==2.0.5 pyfttt==0.3 # homeassistant.components.device_tracker.icloud -pyicloud==0.8.1 +pyicloud==0.7.2 # homeassistant.components.device_tracker.netgear pynetgear==0.3.2 From 69fd927656ed0b32aebef70eabaa3baff31cda51 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 28 Mar 2016 00:15:26 -0700 Subject: [PATCH 029/124] Update frontend --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 1029 +++++++---------- .../www_static/home-assistant-polymer | 2 +- 3 files changed, 406 insertions(+), 627 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 3103cf36027..454ca05b502 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """DO NOT MODIFY. Auto-generated by build_frontend script.""" -VERSION = "49974cb3bb443751f7548e4e3b353304" +VERSION = "5a38035adb1406c5dbd3c67ff9d766ba" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 76db8ee085d..f5580492d7a 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -1,585 +1,362 @@