From 8a3810082dd7734575ccc02c43082295dc41d9b4 Mon Sep 17 00:00:00 2001 From: nordlead2005 Date: Tue, 10 Jan 2017 15:57:41 -0500 Subject: [PATCH 01/10] Added forecast support to DarkSky modified: homeassistant/components/sensor/darksky.py modified: tests/components/sensor/test_darksky.py --- homeassistant/components/sensor/darksky.py | 28 +++++++++++++++++++--- tests/components/sensor/test_darksky.py | 5 ++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/sensor/darksky.py b/homeassistant/components/sensor/darksky.py index 173990a6a2f..5773a6a565e 100644 --- a/homeassistant/components/sensor/darksky.py +++ b/homeassistant/components/sensor/darksky.py @@ -25,6 +25,7 @@ _LOGGER = logging.getLogger(__name__) CONF_ATTRIBUTION = "Powered by Dark Sky" CONF_UNITS = 'units' CONF_UPDATE_INTERVAL = 'update_interval' +CONF_FORECAST = 'forecast' DEFAULT_NAME = 'Dark Sky' @@ -97,6 +98,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error("Latitude or longitude not set in Home Assistant config") return False + # Check to make sure that forecast is in a valid range of 1 week + forecast = config.get(CONF_FORECAST) + if forecast is not None: + for forecast_day in forecast: + if forecast_day > 7 or forecast_day < 1: + _LOGGER.error("DarkSky only supports 7 day forecast") + return False + if CONF_UNITS in config: units = config[CONF_UNITS] elif hass.config.units.is_metric: @@ -122,6 +131,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): sensors = [] for variable in config[CONF_MONITORED_CONDITIONS]: sensors.append(DarkSkySensor(forecast_data, variable, name)) + if forecast is not None and variable in ['temperature_min', + 'temperature_max', + 'apparent_temperature_min', + 'apparent_temperature_max', + 'precip_intensity_max']: + for forecast_day in forecast: + sensors.append(DarkSkySensor(forecast_data, + variable, name, forecast_day)) add_devices(sensors, True) @@ -129,19 +146,24 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class DarkSkySensor(Entity): """Implementation of a Dark Sky sensor.""" - def __init__(self, forecast_data, sensor_type, name): + def __init__(self, forecast_data, sensor_type, name, forecast_day=0): """Initialize the sensor.""" self.client_name = name self._name = SENSOR_TYPES[sensor_type][0] self.forecast_data = forecast_data self.type = sensor_type + self.forecast_day = forecast_day self._state = None self._unit_of_measurement = None @property def name(self): """Return the name of the sensor.""" - return '{} {}'.format(self.client_name, self._name) + if self.forecast_day == 0: + return '{} {}'.format(self.client_name, self._name) + else: + return '{} {} {}'.format(self.client_name, self._name, + self.forecast_day) @property def state(self): @@ -210,7 +232,7 @@ class DarkSkySensor(Entity): self._state = getattr(daily, 'summary', '') else: if hasattr(daily, 'data'): - self._state = self.get_state(daily.data[0]) + self._state = self.get_state(daily.data[self.forecast_day]) else: self._state = 0 else: diff --git a/tests/components/sensor/test_darksky.py b/tests/components/sensor/test_darksky.py index e3c83bad2a6..ede888e5c2c 100644 --- a/tests/components/sensor/test_darksky.py +++ b/tests/components/sensor/test_darksky.py @@ -32,7 +32,8 @@ class TestDarkSkySetup(unittest.TestCase): self.key = 'foo' self.config = { 'api_key': 'foo', - 'monitored_conditions': ['summary', 'icon'], + 'forecast': [1, 2], + 'monitored_conditions': ['summary', 'icon', 'temperature_max'], 'update_interval': timedelta(seconds=120), } self.lat = 37.8267 @@ -80,4 +81,4 @@ class TestDarkSkySetup(unittest.TestCase): darksky.setup_platform(self.hass, self.config, self.add_entities) self.assertTrue(mock_get_forecast.called) self.assertEqual(mock_get_forecast.call_count, 1) - self.assertEqual(len(self.entities), 2) + self.assertEqual(len(self.entities), 5) From 445c1cc8992240e1bbe0d5c978e54ba943d7719e Mon Sep 17 00:00:00 2001 From: Anton Lundin Date: Tue, 10 Jan 2017 10:58:39 +0100 Subject: [PATCH 02/10] Fix async_volume_up / async_volume_down (#5249) async_volume_up / async_volume_down should be async versions of volume_up / volume_down, not a async version of the default variants of volume_up / volume_down. The previous code always called into the mediaplayers set_volume_level, and never into volume_up / volume_down. Signed-off-by: Anton Lundin --- homeassistant/components/media_player/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index f5948e1eecd..1be94976d49 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -758,7 +758,7 @@ class MediaPlayerDevice(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.async_set_volume_level(min(1, self.volume_level + .1)) + return self.hass.loop.run_in_executor(None, self.volume_up) def volume_down(self): """Turn volume down for media player.""" @@ -770,7 +770,7 @@ class MediaPlayerDevice(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.async_set_volume_level(max(0, self.volume_level - .1)) + return self.hass.loop.run_in_executor(None, self.volume_down) def media_play_pause(self): """Play or pause the media player.""" From 43b6dff77fe1d457e076b9a2b47cf37a4da407ab Mon Sep 17 00:00:00 2001 From: sander76 Date: Tue, 10 Jan 2017 13:21:15 +0100 Subject: [PATCH 03/10] adding a default icon "blind" to a PowerView blinds scene. (#5210) * adding a default icon "blind" to a PowerView blinds scene. * Adding icon property to define blind icon. Removed it from the state attributes dict. * fixing lint error --- homeassistant/components/scene/hunterdouglas_powerview.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/scene/hunterdouglas_powerview.py b/homeassistant/components/scene/hunterdouglas_powerview.py index 0ae44d878f8..c831876bf11 100644 --- a/homeassistant/components/scene/hunterdouglas_powerview.py +++ b/homeassistant/components/scene/hunterdouglas_powerview.py @@ -70,6 +70,11 @@ class PowerViewScene(Scene): """Return the state attributes.""" return {"roomName": self.scene_data["roomName"]} + @property + def icon(self): + """Icon to use in the frontend.""" + return 'mdi:blinds' + def activate(self): """Activate the scene. Tries to get entities into requested state.""" self.pv_instance.activate_scene(self.scene_data["id"]) From 922308bc1f7a2a0a769a8c29d663c90a97a0583b Mon Sep 17 00:00:00 2001 From: Valentin Alexeev Date: Tue, 10 Jan 2017 17:01:04 +0200 Subject: [PATCH 04/10] Use SHA hash to make token harder to guess (#5258) * Use SHA hash to make token harder to guess Use hashlib SHA256 to encode object id instead of using it directly. * Cache access token Instead of generating a token on the fly cache it in the constructor. * Fix lint --- homeassistant/components/camera/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 5b2aa463607..5ba68dea058 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -8,6 +8,7 @@ https://home-assistant.io/components/camera/ import asyncio from datetime import timedelta import logging +import hashlib from aiohttp import web @@ -47,11 +48,13 @@ class Camera(Entity): def __init__(self): """Initialize a camera.""" self.is_streaming = False + self._access_token = hashlib.sha256( + str.encode(str(id(self)))).hexdigest() @property def access_token(self): """Access token for this camera.""" - return str(id(self)) + return self._access_token @property def should_poll(self): From 71fddd26eb9c9ffe6cbd809298f07e17aad152a4 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 10 Jan 2017 17:19:51 +0100 Subject: [PATCH 05/10] Bugfix async device_tracker see callback (#5259) --- homeassistant/components/device_tracker/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index f08a9badb6f..21e7c7b0da1 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -158,7 +158,7 @@ def async_setup(hass: HomeAssistantType, config: ConfigType): None, platform.get_scanner, hass, {DOMAIN: p_config}) elif hasattr(platform, 'async_setup_scanner'): setup = yield from platform.async_setup_scanner( - hass, p_config, tracker.see) + hass, p_config, tracker.async_see) elif hasattr(platform, 'setup_scanner'): setup = yield from hass.loop.run_in_executor( None, platform.setup_scanner, hass, p_config, tracker.see) From b8255ad320defa79b3d3a9fb1e90deb9d97da18f Mon Sep 17 00:00:00 2001 From: nordlead2005 Date: Tue, 10 Jan 2017 15:57:41 -0500 Subject: [PATCH 06/10] Added forecast support to DarkSky modified: homeassistant/components/sensor/darksky.py modified: tests/components/sensor/test_darksky.py --- homeassistant/components/sensor/darksky.py | 28 +++++++++++++++++++--- tests/components/sensor/test_darksky.py | 5 ++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/sensor/darksky.py b/homeassistant/components/sensor/darksky.py index 173990a6a2f..5773a6a565e 100644 --- a/homeassistant/components/sensor/darksky.py +++ b/homeassistant/components/sensor/darksky.py @@ -25,6 +25,7 @@ _LOGGER = logging.getLogger(__name__) CONF_ATTRIBUTION = "Powered by Dark Sky" CONF_UNITS = 'units' CONF_UPDATE_INTERVAL = 'update_interval' +CONF_FORECAST = 'forecast' DEFAULT_NAME = 'Dark Sky' @@ -97,6 +98,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error("Latitude or longitude not set in Home Assistant config") return False + # Check to make sure that forecast is in a valid range of 1 week + forecast = config.get(CONF_FORECAST) + if forecast is not None: + for forecast_day in forecast: + if forecast_day > 7 or forecast_day < 1: + _LOGGER.error("DarkSky only supports 7 day forecast") + return False + if CONF_UNITS in config: units = config[CONF_UNITS] elif hass.config.units.is_metric: @@ -122,6 +131,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): sensors = [] for variable in config[CONF_MONITORED_CONDITIONS]: sensors.append(DarkSkySensor(forecast_data, variable, name)) + if forecast is not None and variable in ['temperature_min', + 'temperature_max', + 'apparent_temperature_min', + 'apparent_temperature_max', + 'precip_intensity_max']: + for forecast_day in forecast: + sensors.append(DarkSkySensor(forecast_data, + variable, name, forecast_day)) add_devices(sensors, True) @@ -129,19 +146,24 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class DarkSkySensor(Entity): """Implementation of a Dark Sky sensor.""" - def __init__(self, forecast_data, sensor_type, name): + def __init__(self, forecast_data, sensor_type, name, forecast_day=0): """Initialize the sensor.""" self.client_name = name self._name = SENSOR_TYPES[sensor_type][0] self.forecast_data = forecast_data self.type = sensor_type + self.forecast_day = forecast_day self._state = None self._unit_of_measurement = None @property def name(self): """Return the name of the sensor.""" - return '{} {}'.format(self.client_name, self._name) + if self.forecast_day == 0: + return '{} {}'.format(self.client_name, self._name) + else: + return '{} {} {}'.format(self.client_name, self._name, + self.forecast_day) @property def state(self): @@ -210,7 +232,7 @@ class DarkSkySensor(Entity): self._state = getattr(daily, 'summary', '') else: if hasattr(daily, 'data'): - self._state = self.get_state(daily.data[0]) + self._state = self.get_state(daily.data[self.forecast_day]) else: self._state = 0 else: diff --git a/tests/components/sensor/test_darksky.py b/tests/components/sensor/test_darksky.py index e3c83bad2a6..ede888e5c2c 100644 --- a/tests/components/sensor/test_darksky.py +++ b/tests/components/sensor/test_darksky.py @@ -32,7 +32,8 @@ class TestDarkSkySetup(unittest.TestCase): self.key = 'foo' self.config = { 'api_key': 'foo', - 'monitored_conditions': ['summary', 'icon'], + 'forecast': [1, 2], + 'monitored_conditions': ['summary', 'icon', 'temperature_max'], 'update_interval': timedelta(seconds=120), } self.lat = 37.8267 @@ -80,4 +81,4 @@ class TestDarkSkySetup(unittest.TestCase): darksky.setup_platform(self.hass, self.config, self.add_entities) self.assertTrue(mock_get_forecast.called) self.assertEqual(mock_get_forecast.call_count, 1) - self.assertEqual(len(self.entities), 2) + self.assertEqual(len(self.entities), 5) From 3b59e169f1bc11b3887bc98b2f8425f6c70a0df2 Mon Sep 17 00:00:00 2001 From: joopert Date: Tue, 10 Jan 2017 22:32:43 +0100 Subject: [PATCH 07/10] Add support for NAD receivers (#5191) * Add support for NAD receivers * remove self.update() in various methods * remove setting attributes in various methods * Change import to hass style --- .coveragerc | 1 + homeassistant/components/media_player/nad.py | 182 +++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 186 insertions(+) create mode 100644 homeassistant/components/media_player/nad.py diff --git a/.coveragerc b/.coveragerc index 328746735ea..8b308b097a7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -210,6 +210,7 @@ omit = homeassistant/components/media_player/lg_netcast.py homeassistant/components/media_player/mpchc.py homeassistant/components/media_player/mpd.py + homeassistant/components/media_player/nad.py homeassistant/components/media_player/onkyo.py homeassistant/components/media_player/panasonic_viera.py homeassistant/components/media_player/pandora.py diff --git a/homeassistant/components/media_player/nad.py b/homeassistant/components/media_player/nad.py new file mode 100644 index 00000000000..0b8efda0e44 --- /dev/null +++ b/homeassistant/components/media_player/nad.py @@ -0,0 +1,182 @@ +""" +Support for interfacing with NAD receivers through RS-232. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/media_player.nad/ +""" +import logging + +import voluptuous as vol + +from homeassistant.components.media_player import ( + SUPPORT_VOLUME_SET, + SUPPORT_VOLUME_MUTE, SUPPORT_TURN_ON, SUPPORT_TURN_OFF, + SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, MediaPlayerDevice, + PLATFORM_SCHEMA) +from homeassistant.const import ( + CONF_NAME, STATE_OFF, STATE_ON) +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['https://github.com/joopert/nad_receiver/archive/' + '0.0.2.zip#nad_receiver==0.0.2'] + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_NAME = 'NAD Receiver' +DEFAULT_MIN_VOLUME = -92 +DEFAULT_MAX_VOLUME = -20 + +SUPPORT_NAD = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ + SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_VOLUME_STEP | \ + SUPPORT_SELECT_SOURCE + +CONF_SERIAL_PORT = 'serial_port' +CONF_MIN_VOLUME = 'min_volume' +CONF_MAX_VOLUME = 'max_volume' +CONF_SOURCE_DICT = 'sources' + +SOURCE_DICT_SCHEMA = vol.Schema({ + vol.Range(min=1, max=10): cv.string +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_SERIAL_PORT): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_MIN_VOLUME, default=DEFAULT_MIN_VOLUME): int, + vol.Optional(CONF_MAX_VOLUME, default=DEFAULT_MAX_VOLUME): int, + vol.Optional(CONF_SOURCE_DICT, default={}): SOURCE_DICT_SCHEMA, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the NAD platform.""" + from nad_receiver import NADReceiver + add_devices([NAD( + config.get(CONF_NAME), + NADReceiver(config.get(CONF_SERIAL_PORT)), + config.get(CONF_MIN_VOLUME), + config.get(CONF_MAX_VOLUME), + config.get(CONF_SOURCE_DICT) + )]) + + +class NAD(MediaPlayerDevice): + """Representation of a NAD Receiver.""" + + def __init__(self, name, nad_receiver, min_volume, max_volume, + source_dict): + """Initialize the NAD Receiver device.""" + self._name = name + self._nad_receiver = nad_receiver + self._min_volume = min_volume + self._max_volume = max_volume + self._source_dict = source_dict + self._reverse_mapping = {value: key for key, value in + self._source_dict.items()} + + self._volume = None + self._state = None + self._mute = None + self._source = None + + self.update() + + def calc_volume(self, decibel): + """ + Calculate the volume given the decibel. + + Return the volume (0..1). + """ + return abs(self._min_volume - decibel) / abs( + self._min_volume - self._max_volume) + + def calc_db(self, volume): + """ + Calculate the decibel given the volume. + + Return the dB. + """ + return self._min_volume + round( + abs(self._min_volume - self._max_volume) * volume) + + @property + def name(self): + """Return the name of the device.""" + return self._name + + @property + def state(self): + """Return the state of the device.""" + return self._state + + def update(self): + """Retrieve latest state.""" + if self._nad_receiver.main_power('?') == 'Off': + self._state = STATE_OFF + else: + self._state = STATE_ON + + if self._nad_receiver.main_mute('?') == 'Off': + self._mute = False + else: + self._mute = True + + self._volume = self.calc_volume(self._nad_receiver.main_volume('?')) + self._source = self._source_dict.get( + self._nad_receiver.main_source('?')) + + @property + def volume_level(self): + """Volume level of the media player (0..1).""" + return self._volume + + @property + def is_volume_muted(self): + """Boolean if volume is currently muted.""" + return self._mute + + @property + def supported_media_commands(self): + """Flag of media commands that are supported.""" + return SUPPORT_NAD + + def turn_off(self): + """Turn the media player off.""" + self._nad_receiver.main_power('=', 'Off') + + def turn_on(self): + """Turn the media player on.""" + self._nad_receiver.main_power('=', 'On') + + def volume_up(self): + """Volume up the media player.""" + self._nad_receiver.main_volume('+') + + def volume_down(self): + """Volume down the media player.""" + self._nad_receiver.main_volume('-') + + def set_volume_level(self, volume): + """Set volume level, range 0..1.""" + self._nad_receiver.main_volume('=', self.calc_db(volume)) + + def select_source(self, source): + """Select input source.""" + self._nad_receiver.main_source('=', self._reverse_mapping.get(source)) + + @property + def source(self): + """Name of the current input source.""" + return self._source + + @property + def source_list(self): + """List of available input sources.""" + return sorted(list(self._reverse_mapping.keys())) + + def mute_volume(self, mute): + """Mute (true) or unmute (false) media player.""" + if mute: + self._nad_receiver.main_mute('=', 'On') + else: + self._nad_receiver.main_mute('=', 'Off') diff --git a/requirements_all.txt b/requirements_all.txt index 6a59fffae2f..d13edd9d56a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -224,6 +224,9 @@ https://github.com/jabesq/pybotvac/archive/v0.0.1.zip#pybotvac==0.0.1 # homeassistant.components.sensor.sabnzbd https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1 +# homeassistant.components.media_player.nad +https://github.com/joopert/nad_receiver/archive/0.0.2.zip#nad_receiver==0.0.2 + # homeassistant.components.media_player.russound_rnet https://github.com/laf/russound/archive/0.1.6.zip#russound==0.1.6 From 34ab84f5a18d6d6e9e06d3bbd296aa7db0ddc540 Mon Sep 17 00:00:00 2001 From: nordlead2005 Date: Fri, 13 Jan 2017 15:22:43 -0500 Subject: [PATCH 08/10] Updated Config Validation, extended daily forecast to all supported types --- homeassistant/components/sensor/darksky.py | 114 +++++++++++---------- 1 file changed, 59 insertions(+), 55 deletions(-) diff --git a/homeassistant/components/sensor/darksky.py b/homeassistant/components/sensor/darksky.py index 5773a6a565e..7eb1449720a 100644 --- a/homeassistant/components/sensor/darksky.py +++ b/homeassistant/components/sensor/darksky.py @@ -32,52 +32,64 @@ DEFAULT_NAME = 'Dark Sky' # Sensor types are defined like so: # Name, si unit, us unit, ca unit, uk unit, uk2 unit SENSOR_TYPES = { - 'summary': ['Summary', None, None, None, None, None, None], + 'summary': ['Summary', None, None, None, None, None, None, []], 'minutely_summary': ['Minutely Summary', - None, None, None, None, None, None], - 'hourly_summary': ['Hourly Summary', None, None, None, None, None, None], - 'daily_summary': ['Daily Summary', None, None, None, None, None, None], - 'icon': ['Icon', None, None, None, None, None, None], + None, None, None, None, None, None, []], + 'hourly_summary': ['Hourly Summary', None, None, None, None, None, None, []], + 'daily_summary': ['Daily Summary', None, None, None, None, None, None, []], + 'icon': ['Icon', None, None, None, None, None, None, ['currently', 'hourly', 'daily']], 'nearest_storm_distance': ['Nearest Storm Distance', 'km', 'm', 'km', 'km', 'm', - 'mdi:weather-lightning'], + 'mdi:weather-lightning', ['currently']], 'nearest_storm_bearing': ['Nearest Storm Bearing', '°', '°', '°', '°', '°', - 'mdi:weather-lightning'], + 'mdi:weather-lightning', ['currently']], 'precip_type': ['Precip', None, None, None, None, None, - 'mdi:weather-pouring'], + 'mdi:weather-pouring', ['currently', 'minutely', 'hourly', 'daily']], 'precip_intensity': ['Precip Intensity', - 'mm', 'in', 'mm', 'mm', 'mm', 'mdi:weather-rainy'], + 'mm', 'in', 'mm', 'mm', 'mm', 'mdi:weather-rainy', + ['currently', 'minutely', 'hourly', 'daily']], 'precip_probability': ['Precip Probability', - '%', '%', '%', '%', '%', 'mdi:water-percent'], + '%', '%', '%', '%', '%', 'mdi:water-percent', + ['currently', 'minutely', 'hourly', 'daily']], 'temperature': ['Temperature', - '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer'], + '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer', + ['currently', 'hourly', 'daily']], 'apparent_temperature': ['Apparent Temperature', - '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer'], + '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer', + ['currently', 'hourly']], 'dew_point': ['Dew point', '°C', '°F', '°C', '°C', '°C', - 'mdi:thermometer'], + 'mdi:thermometer', ['currently', 'hourly', 'daily']], 'wind_speed': ['Wind Speed', 'm/s', 'mph', 'km/h', 'mph', 'mph', - 'mdi:weather-windy'], - 'wind_bearing': ['Wind Bearing', '°', '°', '°', '°', '°', 'mdi:compass'], + 'mdi:weather-windy', ['currently', 'hourly', 'daily']], + 'wind_bearing': ['Wind Bearing', '°', '°', '°', '°', '°', 'mdi:compass', + ['currently', 'hourly', 'daily']], 'cloud_cover': ['Cloud Coverage', '%', '%', '%', '%', '%', - 'mdi:weather-partlycloudy'], - 'humidity': ['Humidity', '%', '%', '%', '%', '%', 'mdi:water-percent'], + 'mdi:weather-partlycloudy', ['currently', 'hourly', 'daily']], + 'humidity': ['Humidity', '%', '%', '%', '%', '%', 'mdi:water-percent', + ['currently', 'hourly', 'daily']], 'pressure': ['Pressure', 'mbar', 'mbar', 'mbar', 'mbar', 'mbar', - 'mdi:gauge'], - 'visibility': ['Visibility', 'km', 'm', 'km', 'km', 'm', 'mdi:eye'], - 'ozone': ['Ozone', 'DU', 'DU', 'DU', 'DU', 'DU', 'mdi:eye'], + 'mdi:gauge', ['currently', 'hourly', 'daily']], + 'visibility': ['Visibility', 'km', 'm', 'km', 'km', 'm', 'mdi:eye', + ['currently', 'hourly', 'daily']], + 'ozone': ['Ozone', 'DU', 'DU', 'DU', 'DU', 'DU', 'mdi:eye', + ['currently', 'hourly', 'daily']], 'apparent_temperature_max': ['Daily High Apparent Temperature', '°C', '°F', '°C', '°C', '°C', - 'mdi:thermometer'], + 'mdi:thermometer', + ['currently', 'hourly', 'daily']], 'apparent_temperature_min': ['Daily Low Apparent Temperature', '°C', '°F', '°C', '°C', '°C', - 'mdi:thermometer'], + 'mdi:thermometer', ['currently', 'hourly', 'daily']], 'temperature_max': ['Daily High Temperature', - '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer'], + '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer', + ['currently', 'hourly', 'daily']], 'temperature_min': ['Daily Low Temperature', - '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer'], + '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer', + ['currently', 'hourly', 'daily']], 'precip_intensity_max': ['Daily Max Precip Intensity', - 'mm', 'in', 'mm', 'mm', 'mm', 'mdi:thermometer'], + 'mm', 'in', 'mm', 'mm', 'mm', 'mdi:thermometer', + ['currently', 'hourly', 'daily']], } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -88,6 +100,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_UNITS): vol.In(['auto', 'si', 'us', 'ca', 'uk', 'uk2']), vol.Optional(CONF_UPDATE_INTERVAL, default=timedelta(seconds=120)): ( vol.All(cv.time_period, cv.positive_timedelta)), + vol.Optional(CONF_FORECAST): vol.All(cv.ensure_list, [vol.Range(min=1, max=7)]), }) @@ -98,13 +111,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error("Latitude or longitude not set in Home Assistant config") return False - # Check to make sure that forecast is in a valid range of 1 week - forecast = config.get(CONF_FORECAST) - if forecast is not None: - for forecast_day in forecast: - if forecast_day > 7 or forecast_day < 1: - _LOGGER.error("DarkSky only supports 7 day forecast") - return False if CONF_UNITS in config: units = config[CONF_UNITS] @@ -128,14 +134,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): name = config.get(CONF_NAME) + forecast = config.get(CONF_FORECAST) sensors = [] for variable in config[CONF_MONITORED_CONDITIONS]: sensors.append(DarkSkySensor(forecast_data, variable, name)) - if forecast is not None and variable in ['temperature_min', - 'temperature_max', - 'apparent_temperature_min', - 'apparent_temperature_max', - 'precip_intensity_max']: + if forecast is not None and 'daily' in SENSOR_TYPES[variable][7]: for forecast_day in forecast: sensors.append(DarkSkySensor(forecast_data, variable, name, forecast_day)) @@ -220,25 +223,26 @@ class DarkSkySensor(Entity): self.forecast_data.update_hourly() hourly = self.forecast_data.data_hourly self._state = getattr(hourly, 'summary', '') - elif self.type in ['daily_summary', - 'temperature_min', - 'temperature_max', - 'apparent_temperature_min', - 'apparent_temperature_max', - 'precip_intensity_max']: - self.forecast_data.update_daily() - daily = self.forecast_data.data_daily - if self.type == 'daily_summary': - self._state = getattr(daily, 'summary', '') - else: - if hasattr(daily, 'data'): - self._state = self.get_state(daily.data[self.forecast_day]) - else: - self._state = 0 else: - self.forecast_data.update_currently() - currently = self.forecast_data.data_currently - self._state = self.get_state(currently) + if self.forecast_day > 0 or self.type in ['daily_summary', + 'temperature_min', + 'temperature_max', + 'apparent_temperature_min', + 'apparent_temperature_max', + 'precip_intensity_max']: + self.forecast_data.update_daily() + daily = self.forecast_data.data_daily + if self.type == 'daily_summary': + self._state = getattr(daily, 'summary', '') + else: + if hasattr(daily, 'data'): + self._state = self.get_state(daily.data[self.forecast_day]) + else: + self._state = 0 + else: + self.forecast_data.update_currently() + currently = self.forecast_data.data_currently + self._state = self.get_state(currently) def get_state(self, data): """ From c64eb92d688374e4d756e830a2a966827d2a2b20 Mon Sep 17 00:00:00 2001 From: nordlead2005 Date: Fri, 13 Jan 2017 16:03:02 -0500 Subject: [PATCH 09/10] Fix style errors from previous commit, fix test since adding daily for all supported types --- homeassistant/components/sensor/darksky.py | 73 +++++++--------------- tests/components/sensor/test_darksky.py | 2 +- 2 files changed, 23 insertions(+), 52 deletions(-) diff --git a/homeassistant/components/sensor/darksky.py b/homeassistant/components/sensor/darksky.py index e61c5fdd790..adbfecb8300 100644 --- a/homeassistant/components/sensor/darksky.py +++ b/homeassistant/components/sensor/darksky.py @@ -35,9 +35,11 @@ SENSOR_TYPES = { 'summary': ['Summary', None, None, None, None, None, None, []], 'minutely_summary': ['Minutely Summary', None, None, None, None, None, None, []], - 'hourly_summary': ['Hourly Summary', None, None, None, None, None, None, []], + 'hourly_summary': ['Hourly Summary', None, None, None, None, None, None, + []], 'daily_summary': ['Daily Summary', None, None, None, None, None, None, []], - 'icon': ['Icon', None, None, None, None, None, None, ['currently', 'hourly', 'daily']], + 'icon': ['Icon', None, None, None, None, None, None, + ['currently', 'hourly', 'daily']], 'nearest_storm_distance': ['Nearest Storm Distance', 'km', 'm', 'km', 'km', 'm', 'mdi:weather-lightning', ['currently']], @@ -45,13 +47,14 @@ SENSOR_TYPES = { '°', '°', '°', '°', '°', 'mdi:weather-lightning', ['currently']], 'precip_type': ['Precip', None, None, None, None, None, - 'mdi:weather-pouring', ['currently', 'minutely', 'hourly', 'daily']], + 'mdi:weather-pouring', + ['currently', 'minutely', 'hourly', 'daily']], 'precip_intensity': ['Precip Intensity', 'mm', 'in', 'mm', 'mm', 'mm', 'mdi:weather-rainy', ['currently', 'minutely', 'hourly', 'daily']], 'precip_probability': ['Precip Probability', '%', '%', '%', '%', '%', 'mdi:water-percent', - ['currently', 'minutely', 'hourly', 'daily']], + ['currently', 'minutely', 'hourly', 'daily']], 'temperature': ['Temperature', '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer', ['currently', 'hourly', 'daily']], @@ -65,7 +68,8 @@ SENSOR_TYPES = { 'wind_bearing': ['Wind Bearing', '°', '°', '°', '°', '°', 'mdi:compass', ['currently', 'hourly', 'daily']], 'cloud_cover': ['Cloud Coverage', '%', '%', '%', '%', '%', - 'mdi:weather-partlycloudy', ['currently', 'hourly', 'daily']], + 'mdi:weather-partlycloudy', + ['currently', 'hourly', 'daily']], 'humidity': ['Humidity', '%', '%', '%', '%', '%', 'mdi:water-percent', ['currently', 'hourly', 'daily']], 'pressure': ['Pressure', 'mbar', 'mbar', 'mbar', 'mbar', 'mbar', @@ -80,7 +84,8 @@ SENSOR_TYPES = { ['currently', 'hourly', 'daily']], 'apparent_temperature_min': ['Daily Low Apparent Temperature', '°C', '°F', '°C', '°C', '°C', - 'mdi:thermometer', ['currently', 'hourly', 'daily']], + 'mdi:thermometer', + ['currently', 'hourly', 'daily']], 'temperature_max': ['Daily High Temperature', '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer', ['currently', 'hourly', 'daily']], @@ -100,7 +105,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_UNITS): vol.In(['auto', 'si', 'us', 'ca', 'uk', 'uk2']), vol.Optional(CONF_UPDATE_INTERVAL, default=timedelta(seconds=120)): ( vol.All(cv.time_period, cv.positive_timedelta)), - vol.Optional(CONF_FORECAST): vol.All(cv.ensure_list, [vol.Range(min=1, max=7)]), + vol.Optional(CONF_FORECAST): + vol.All(cv.ensure_list, [vol.Range(min=1, max=7)]), }) @@ -111,17 +117,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error("Latitude or longitude not set in Home Assistant config") return False -<<<<<<< HEAD -======= - # Check to make sure that forecast is in a valid range of 1 week - forecast = config.get(CONF_FORECAST) - if forecast is not None: - for forecast_day in forecast: - if forecast_day > 7 or forecast_day < 1: - _LOGGER.error("DarkSky only supports 7 day forecast") - return False ->>>>>>> 8a3810082dd7734575ccc02c43082295dc41d9b4 - if CONF_UNITS in config: units = config[CONF_UNITS] elif hass.config.units.is_metric: @@ -148,15 +143,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): sensors = [] for variable in config[CONF_MONITORED_CONDITIONS]: sensors.append(DarkSkySensor(forecast_data, variable, name)) -<<<<<<< HEAD if forecast is not None and 'daily' in SENSOR_TYPES[variable][7]: -======= - if forecast is not None and variable in ['temperature_min', - 'temperature_max', - 'apparent_temperature_min', - 'apparent_temperature_max', - 'precip_intensity_max']: ->>>>>>> 8a3810082dd7734575ccc02c43082295dc41d9b4 for forecast_day in forecast: sensors.append(DarkSkySensor(forecast_data, variable, name, forecast_day)) @@ -241,38 +228,22 @@ class DarkSkySensor(Entity): self.forecast_data.update_hourly() hourly = self.forecast_data.data_hourly self._state = getattr(hourly, 'summary', '') -<<<<<<< HEAD -======= - elif self.type in ['daily_summary', - 'temperature_min', - 'temperature_max', - 'apparent_temperature_min', - 'apparent_temperature_max', - 'precip_intensity_max']: - self.forecast_data.update_daily() - daily = self.forecast_data.data_daily - if self.type == 'daily_summary': - self._state = getattr(daily, 'summary', '') - else: - if hasattr(daily, 'data'): - self._state = self.get_state(daily.data[self.forecast_day]) - else: - self._state = 0 ->>>>>>> 8a3810082dd7734575ccc02c43082295dc41d9b4 else: - if self.forecast_day > 0 or self.type in ['daily_summary', - 'temperature_min', - 'temperature_max', - 'apparent_temperature_min', - 'apparent_temperature_max', - 'precip_intensity_max']: + if self.forecast_day > 0 or ( + self.type in ['daily_summary', + 'temperature_min', + 'temperature_max', + 'apparent_temperature_min', + 'apparent_temperature_max', + 'precip_intensity_max']): self.forecast_data.update_daily() daily = self.forecast_data.data_daily if self.type == 'daily_summary': self._state = getattr(daily, 'summary', '') else: if hasattr(daily, 'data'): - self._state = self.get_state(daily.data[self.forecast_day]) + self._state = self.get_state( + daily.data[self.forecast_day]) else: self._state = 0 else: diff --git a/tests/components/sensor/test_darksky.py b/tests/components/sensor/test_darksky.py index ede888e5c2c..effa7b3dbd8 100644 --- a/tests/components/sensor/test_darksky.py +++ b/tests/components/sensor/test_darksky.py @@ -81,4 +81,4 @@ class TestDarkSkySetup(unittest.TestCase): darksky.setup_platform(self.hass, self.config, self.add_entities) self.assertTrue(mock_get_forecast.called) self.assertEqual(mock_get_forecast.call_count, 1) - self.assertEqual(len(self.entities), 5) + self.assertEqual(len(self.entities), 7) From e380dcce2c20ecc20f912c169425d26ca190f5dc Mon Sep 17 00:00:00 2001 From: nordlead2005 Date: Fri, 13 Jan 2017 16:13:53 -0500 Subject: [PATCH 10/10] Removed temperature from daily as it isn't supported --- homeassistant/components/sensor/darksky.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/darksky.py b/homeassistant/components/sensor/darksky.py index adbfecb8300..5d276cff856 100644 --- a/homeassistant/components/sensor/darksky.py +++ b/homeassistant/components/sensor/darksky.py @@ -57,7 +57,7 @@ SENSOR_TYPES = { ['currently', 'minutely', 'hourly', 'daily']], 'temperature': ['Temperature', '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer', - ['currently', 'hourly', 'daily']], + ['currently', 'hourly']], 'apparent_temperature': ['Apparent Temperature', '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer', ['currently', 'hourly']],