From e836674a30c4f6f6a9fd928314414e9409538205 Mon Sep 17 00:00:00 2001 From: David Broadfoot Date: Sat, 7 Apr 2018 13:48:53 +1000 Subject: [PATCH 01/41] Fix Gogogate2 'available' attribute (#13728) * Fixed bug - unable to set base readaonly property * PR fixes * Added line --- homeassistant/components/cover/gogogate2.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/cover/gogogate2.py b/homeassistant/components/cover/gogogate2.py index c2bdc9c5472..99da248b094 100644 --- a/homeassistant/components/cover/gogogate2.py +++ b/homeassistant/components/cover/gogogate2.py @@ -11,7 +11,7 @@ import voluptuous as vol from homeassistant.components.cover import ( CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE) from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, STATE_CLOSED, STATE_UNKNOWN, + CONF_USERNAME, CONF_PASSWORD, STATE_CLOSED, CONF_IP_ADDRESS, CONF_NAME) import homeassistant.helpers.config_validation as cv @@ -50,7 +50,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(MyGogogate2Device( mygogogate2, door, name) for door in devices) - return except (TypeError, KeyError, NameError, ValueError) as ex: _LOGGER.error("%s", ex) @@ -60,7 +59,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): ''.format(ex), title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID) - return class MyGogogate2Device(CoverDevice): @@ -72,7 +70,7 @@ class MyGogogate2Device(CoverDevice): self.device_id = device['door'] self._name = name or device['name'] self._status = device['status'] - self.available = None + self._available = None @property def name(self): @@ -97,24 +95,22 @@ class MyGogogate2Device(CoverDevice): @property def available(self): """Could the device be accessed during the last update call.""" - return self.available + return self._available def close_cover(self, **kwargs): """Issue close command to cover.""" self.mygogogate2.close_device(self.device_id) - self.schedule_update_ha_state(True) def open_cover(self, **kwargs): """Issue open command to cover.""" self.mygogogate2.open_device(self.device_id) - self.schedule_update_ha_state(True) def update(self): """Update status of cover.""" try: self._status = self.mygogogate2.get_status(self.device_id) - self.available = True + self._available = True except (TypeError, KeyError, NameError, ValueError) as ex: _LOGGER.error("%s", ex) - self._status = STATE_UNKNOWN - self.available = False + self._status = None + self._available = False From 0adb240fd66ef356abc1708acf7a2a45f307b545 Mon Sep 17 00:00:00 2001 From: Kane610 Date: Sat, 7 Apr 2018 23:18:49 +0200 Subject: [PATCH 02/41] Fix so it is possible to ignore discovered config entry handlers (#13741) * Fix so it is possible to ignore discovered config entry handlers * Improve efficiency --- homeassistant/components/discovery.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index b2aa5b890a8..01ef36b778b 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -84,7 +84,8 @@ CONF_IGNORE = 'ignore' CONFIG_SCHEMA = vol.Schema({ vol.Required(DOMAIN): vol.Schema({ vol.Optional(CONF_IGNORE, default=[]): - vol.All(cv.ensure_list, [vol.In(SERVICE_HANDLERS)]) + vol.All(cv.ensure_list, [ + vol.In(list(CONFIG_ENTRY_HANDLERS) + list(SERVICE_HANDLERS))]) }), }, extra=vol.ALLOW_EXTRA) From 26c76e3399dead4f014bebae2b1f38ec897011bb Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 14 Apr 2018 04:31:03 -0400 Subject: [PATCH 03/41] Prevent vesync doing I/O in event loop (#13862) --- homeassistant/components/switch/vesync.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/switch/vesync.py b/homeassistant/components/switch/vesync.py index fbc73545e19..d8579a508e2 100644 --- a/homeassistant/components/switch/vesync.py +++ b/homeassistant/components/switch/vesync.py @@ -60,6 +60,8 @@ class VeSyncSwitchHA(SwitchDevice): def __init__(self, plug): """Initialize the VeSync switch device.""" self.smartplug = plug + self._current_power_w = None + self._today_energy_kwh = None @property def unique_id(self): @@ -74,12 +76,12 @@ class VeSyncSwitchHA(SwitchDevice): @property def current_power_w(self): """Return the current power usage in W.""" - return self.smartplug.get_power() + return self._current_power_w @property def today_energy_kwh(self): """Return the today total energy usage in kWh.""" - return self.smartplug.get_kwh_today() + return self._today_energy_kwh @property def available(self) -> bool: @@ -102,3 +104,5 @@ class VeSyncSwitchHA(SwitchDevice): def update(self): """Handle data changes for node values.""" self.smartplug.update() + self._current_power_w = self.smartplug.get_power() + self._today_energy_kwh = self.smartplug.get_kwh_today() From 727ab956cf6e311397fca6573020a2d6de0527c4 Mon Sep 17 00:00:00 2001 From: Kyle Niewiada Date: Sun, 15 Apr 2018 07:59:10 -0400 Subject: [PATCH 04/41] Fix #13846 Double underscore in bluetooth address (#13884) --- homeassistant/components/device_tracker/bluetooth_tracker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/bluetooth_tracker.py b/homeassistant/components/device_tracker/bluetooth_tracker.py index 807f6c0d0a4..2ca519d225c 100644 --- a/homeassistant/components/device_tracker/bluetooth_tracker.py +++ b/homeassistant/components/device_tracker/bluetooth_tracker.py @@ -40,7 +40,7 @@ def setup_scanner(hass, config, see, discovery_info=None): attributes = {} if rssi is not None: attributes['rssi'] = rssi - see(mac="{}_{}".format(BT_PREFIX, mac), host_name=name, + see(mac="{}{}".format(BT_PREFIX, mac), host_name=name, attributes=attributes, source_type=SOURCE_TYPE_BLUETOOTH) def discover_devices(): From 663aeb11dccbf0e0c79c201ea2e85b47db72dbb0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 14 Apr 2018 17:58:45 -0400 Subject: [PATCH 05/41] Fix race condition for component loaded before listening (#13887) * Fix race condition for component loaded before listening * async/await syntax --- homeassistant/components/config/__init__.py | 49 +++++++++------------ 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py index 4d0295c382a..5a8800d9583 100644 --- a/homeassistant/components/config/__init__.py +++ b/homeassistant/components/config/__init__.py @@ -18,37 +18,26 @@ SECTIONS = ('core', 'customize', 'group', 'hassbian', 'automation', 'script', ON_DEMAND = ('zwave',) -@asyncio.coroutine -def async_setup(hass, config): +async def async_setup(hass, config): """Set up the config component.""" - yield from hass.components.frontend.async_register_built_in_panel( + await hass.components.frontend.async_register_built_in_panel( 'config', 'config', 'mdi:settings') - @asyncio.coroutine - def setup_panel(panel_name): + async def setup_panel(panel_name): """Set up a panel.""" - panel = yield from async_prepare_setup_platform( + panel = await async_prepare_setup_platform( hass, config, DOMAIN, panel_name) if not panel: return - success = yield from panel.async_setup(hass) + success = await panel.async_setup(hass) if success: key = '{}.{}'.format(DOMAIN, panel_name) hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: key}) hass.config.components.add(key) - tasks = [setup_panel(panel_name) for panel_name in SECTIONS] - - for panel_name in ON_DEMAND: - if panel_name in hass.config.components: - tasks.append(setup_panel(panel_name)) - - if tasks: - yield from asyncio.wait(tasks, loop=hass.loop) - @callback def component_loaded(event): """Respond to components being loaded.""" @@ -58,6 +47,15 @@ def async_setup(hass, config): hass.bus.async_listen(EVENT_COMPONENT_LOADED, component_loaded) + tasks = [setup_panel(panel_name) for panel_name in SECTIONS] + + for panel_name in ON_DEMAND: + if panel_name in hass.config.components: + tasks.append(setup_panel(panel_name)) + + if tasks: + await asyncio.wait(tasks, loop=hass.loop) + return True @@ -86,11 +84,10 @@ class BaseEditConfigView(HomeAssistantView): """Set value.""" raise NotImplementedError - @asyncio.coroutine - def get(self, request, config_key): + async def get(self, request, config_key): """Fetch device specific config.""" hass = request.app['hass'] - current = yield from self.read_config(hass) + current = await self.read_config(hass) value = self._get_value(hass, current, config_key) if value is None: @@ -98,11 +95,10 @@ class BaseEditConfigView(HomeAssistantView): return self.json(value) - @asyncio.coroutine - def post(self, request, config_key): + async def post(self, request, config_key): """Validate config and return results.""" try: - data = yield from request.json() + data = await request.json() except ValueError: return self.json_message('Invalid JSON specified', 400) @@ -121,10 +117,10 @@ class BaseEditConfigView(HomeAssistantView): hass = request.app['hass'] path = hass.config.path(self.path) - current = yield from self.read_config(hass) + current = await self.read_config(hass) self._write_value(hass, current, config_key, data) - yield from hass.async_add_job(_write, path, current) + await hass.async_add_job(_write, path, current) if self.post_write_hook is not None: hass.async_add_job(self.post_write_hook(hass)) @@ -133,10 +129,9 @@ class BaseEditConfigView(HomeAssistantView): 'result': 'ok', }) - @asyncio.coroutine - def read_config(self, hass): + async def read_config(self, hass): """Read the config.""" - current = yield from hass.async_add_job( + current = await hass.async_add_job( _read, hass.config.path(self.path)) if not current: current = self._empty_config() From bcd8a69dfc77778b6349d624d4052a9e2e50f6e0 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Sat, 14 Apr 2018 23:53:35 +0200 Subject: [PATCH 06/41] Missing property decorator added (#13889) --- homeassistant/components/fan/xiaomi_miio.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/fan/xiaomi_miio.py b/homeassistant/components/fan/xiaomi_miio.py index 8dc6bb54bd1..16affc08467 100644 --- a/homeassistant/components/fan/xiaomi_miio.py +++ b/homeassistant/components/fan/xiaomi_miio.py @@ -748,6 +748,7 @@ class XiaomiAirHumidifier(XiaomiGenericDevice): self._available = False _LOGGER.error("Got exception while fetching the state: %s", ex) + @property def speed_list(self) -> list: """Get the list of available speeds.""" return self._speed_list From 652063537bfc853b77f982b6397075079970e400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Tue, 17 Apr 2018 17:40:52 +0200 Subject: [PATCH 07/41] Fix call to parent broadlink switch (#13906) * Broadlink switch, fixes issue #13799 * slugify --- homeassistant/components/switch/broadlink.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/switch/broadlink.py b/homeassistant/components/switch/broadlink.py index 3e620a6a25b..50c334b1f09 100644 --- a/homeassistant/components/switch/broadlink.py +++ b/homeassistant/components/switch/broadlink.py @@ -19,7 +19,7 @@ from homeassistant.const import ( CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_FRIENDLY_NAME, CONF_HOST, CONF_MAC, CONF_SWITCHES, CONF_TIMEOUT, CONF_TYPE) import homeassistant.helpers.config_validation as cv -from homeassistant.util import Throttle +from homeassistant.util import Throttle, slugify from homeassistant.util.dt import utcnow REQUIREMENTS = ['broadlink==0.8.0'] @@ -187,7 +187,7 @@ class BroadlinkRMSwitch(SwitchDevice): def __init__(self, name, friendly_name, device, command_on, command_off): """Initialize the switch.""" - self.entity_id = ENTITY_ID_FORMAT.format(name) + self.entity_id = ENTITY_ID_FORMAT.format(slugify(name)) self._name = friendly_name self._state = False self._command_on = b64decode(command_on) if command_on else None @@ -257,7 +257,7 @@ class BroadlinkSP1Switch(BroadlinkRMSwitch): def __init__(self, friendly_name, device): """Initialize the switch.""" - super().__init__(friendly_name, device, None, None) + super().__init__(friendly_name, friendly_name, device, None, None) self._command_on = 1 self._command_off = 0 @@ -313,7 +313,7 @@ class BroadlinkMP1Slot(BroadlinkRMSwitch): def __init__(self, friendly_name, device, slot, parent_device): """Initialize the slot of switch.""" - super().__init__(friendly_name, device, None, None) + super().__init__(friendly_name, friendly_name, device, None, None) self._command_on = 1 self._command_off = 0 self._slot = slot From fadff1855ff6b697c7ba69f663a94618d276b11d Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Sun, 15 Apr 2018 15:19:28 +0200 Subject: [PATCH 08/41] Import operation modes from air humidifier (#13908) --- homeassistant/components/fan/xiaomi_miio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/fan/xiaomi_miio.py b/homeassistant/components/fan/xiaomi_miio.py index 16affc08467..2acc3895f3e 100644 --- a/homeassistant/components/fan/xiaomi_miio.py +++ b/homeassistant/components/fan/xiaomi_miio.py @@ -708,7 +708,7 @@ class XiaomiAirHumidifier(XiaomiGenericDevice): def __init__(self, name, device, model, unique_id): """Initialize the plug switch.""" - from miio.airpurifier import OperationMode + from miio.airhumidifier import OperationMode super().__init__(name, device, model, unique_id) From 6fa60c464bb012c73f03b9b3ea69d82539d7c8f5 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Sun, 15 Apr 2018 22:19:15 +0200 Subject: [PATCH 09/41] Upgrade pyqwikswitch to 0.71 (#13920) --- homeassistant/components/qwikswitch.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/qwikswitch.py b/homeassistant/components/qwikswitch.py index 708eff7cf11..4ecdad10a88 100644 --- a/homeassistant/components/qwikswitch.py +++ b/homeassistant/components/qwikswitch.py @@ -18,7 +18,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.components.light import ATTR_BRIGHTNESS import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyqwikswitch==0.6'] +REQUIREMENTS = ['pyqwikswitch==0.71'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 8fe9c7e1c13..ec705e69e52 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -885,7 +885,7 @@ pyowm==2.8.0 pypollencom==1.1.1 # homeassistant.components.qwikswitch -pyqwikswitch==0.6 +pyqwikswitch==0.71 # homeassistant.components.rainbird pyrainbird==0.1.3 From 53506821d4d0b359a4dde64d91de8833b1d28504 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 16 Apr 2018 23:24:20 -0400 Subject: [PATCH 10/41] Upgrade somecomfort to 0.5.2 (#13940) --- homeassistant/components/climate/honeywell.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/climate/honeywell.py b/homeassistant/components/climate/honeywell.py index 20d93e3116a..11a507aded2 100644 --- a/homeassistant/components/climate/honeywell.py +++ b/homeassistant/components/climate/honeywell.py @@ -20,7 +20,7 @@ from homeassistant.const import ( CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE, CONF_REGION) -REQUIREMENTS = ['evohomeclient==0.2.5', 'somecomfort==0.5.0'] +REQUIREMENTS = ['evohomeclient==0.2.5', 'somecomfort==0.5.2'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index ec705e69e52..89a58f4fcc9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1185,7 +1185,7 @@ smappy==0.2.15 snapcast==2.0.8 # homeassistant.components.climate.honeywell -somecomfort==0.5.0 +somecomfort==0.5.2 # homeassistant.components.sensor.speedtest speedtest-cli==2.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7c5467f7608..e44b0dc85d5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -174,7 +174,7 @@ rxv==0.5.1 sleepyq==0.6 # homeassistant.components.climate.honeywell -somecomfort==0.5.0 +somecomfort==0.5.2 # homeassistant.components.recorder # homeassistant.scripts.db_migrator From e9b997de3e086d6ffa860b23608792e6c70d3a4b Mon Sep 17 00:00:00 2001 From: Thibault Cohen Date: Mon, 16 Apr 2018 20:52:56 -0400 Subject: [PATCH 11/41] Update pyhydroquebec to 2.2.2 (#13946) --- homeassistant/components/sensor/hydroquebec.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/hydroquebec.py b/homeassistant/components/sensor/hydroquebec.py index 9129ee17d80..2195153ab1e 100644 --- a/homeassistant/components/sensor/hydroquebec.py +++ b/homeassistant/components/sensor/hydroquebec.py @@ -21,7 +21,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyhydroquebec==2.2.1'] +REQUIREMENTS = ['pyhydroquebec==2.2.2'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 89a58f4fcc9..093fec0a150 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -780,7 +780,7 @@ pyhiveapi==0.2.11 pyhomematic==0.1.40 # homeassistant.components.sensor.hydroquebec -pyhydroquebec==2.2.1 +pyhydroquebec==2.2.2 # homeassistant.components.alarm_control_panel.ialarm pyialarm==0.2 From 6c456ade6a858c76766b35cc3ed7baaaa6b41791 Mon Sep 17 00:00:00 2001 From: Thibault Cohen Date: Mon, 16 Apr 2018 22:16:28 -0400 Subject: [PATCH 12/41] Update pyfido to 2.1.1 (#13947) --- homeassistant/components/sensor/fido.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/fido.py b/homeassistant/components/sensor/fido.py index 25a104bf259..a2ee18b3659 100644 --- a/homeassistant/components/sensor/fido.py +++ b/homeassistant/components/sensor/fido.py @@ -21,7 +21,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyfido==2.1.0'] +REQUIREMENTS = ['pyfido==2.1.1'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 093fec0a150..44d90c4582d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -756,7 +756,7 @@ pyenvisalink==2.2 pyephember==0.1.1 # homeassistant.components.sensor.fido -pyfido==2.1.0 +pyfido==2.1.1 # homeassistant.components.climate.flexit pyflexit==0.3 From 24ec8c545b317aaf3dfc51fd2beb4483a036cb12 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 17 Apr 2018 12:03:22 -0600 Subject: [PATCH 13/41] Bumped pypollencom to 1.1.2 (#13959) * Bumped pypollencom to 1.1.2 * Updated requirements_all.txt --- homeassistant/components/sensor/pollen.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/pollen.py b/homeassistant/components/sensor/pollen.py index 640e13e437a..b55c60f6e7c 100644 --- a/homeassistant/components/sensor/pollen.py +++ b/homeassistant/components/sensor/pollen.py @@ -18,7 +18,7 @@ from homeassistant.const import ( from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle, slugify -REQUIREMENTS = ['pypollencom==1.1.1'] +REQUIREMENTS = ['pypollencom==1.1.2'] _LOGGER = logging.getLogger(__name__) ATTR_ALLERGEN_GENUS = 'primary_allergen_genus' diff --git a/requirements_all.txt b/requirements_all.txt index 44d90c4582d..01c6d395778 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -882,7 +882,7 @@ pyotp==2.2.6 pyowm==2.8.0 # homeassistant.components.sensor.pollen -pypollencom==1.1.1 +pypollencom==1.1.2 # homeassistant.components.qwikswitch pyqwikswitch==0.71 From e7aea5c5715c6cefe757860c506fd16b7bb56a10 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 17 Apr 2018 22:37:40 -0400 Subject: [PATCH 14/41] Version bump to 0.67.1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 5364fe6951e..4b8d7bcd3bc 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 67 -PATCH_VERSION = '0' +PATCH_VERSION = '1' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From c076dbe7e4f3d1efbd79b11a1d8972201f4d7cbc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 17 Apr 2018 22:59:36 -0400 Subject: [PATCH 15/41] Revert "Upgrade pyqwikswitch to 0.71 (#13920)" This reverts commit 6fa60c464bb012c73f03b9b3ea69d82539d7c8f5. --- homeassistant/components/qwikswitch.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/qwikswitch.py b/homeassistant/components/qwikswitch.py index 4ecdad10a88..708eff7cf11 100644 --- a/homeassistant/components/qwikswitch.py +++ b/homeassistant/components/qwikswitch.py @@ -18,7 +18,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.components.light import ATTR_BRIGHTNESS import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyqwikswitch==0.71'] +REQUIREMENTS = ['pyqwikswitch==0.6'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 01c6d395778..36a8f30502f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -885,7 +885,7 @@ pyowm==2.8.0 pypollencom==1.1.2 # homeassistant.components.qwikswitch -pyqwikswitch==0.71 +pyqwikswitch==0.6 # homeassistant.components.rainbird pyrainbird==0.1.3 From 2b537297084cbef78af8f5452871bae27b68e90d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 20 Apr 2018 10:58:43 -0400 Subject: [PATCH 16/41] Version bump to 0.68.0b0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 43380d00a2d..56e37e5e039 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 68 -PATCH_VERSION = '0.dev0' +PATCH_VERSION = '0b0' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From 8cb1e17ad85f26e861e5e63f2c7067d34f551fc4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 24 Apr 2018 23:18:28 -0400 Subject: [PATCH 17/41] Bump frontend to 20180425.0 --- homeassistant/components/frontend/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 87ca8bd2a28..ba487a935a2 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,7 +24,7 @@ from homeassistant.core import callback from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass -REQUIREMENTS = ['home-assistant-frontend==20180420.0'] +REQUIREMENTS = ['home-assistant-frontend==20180425.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log'] diff --git a/requirements_all.txt b/requirements_all.txt index aeb5b84811e..57197443779 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -386,7 +386,7 @@ hipnotify==1.0.8 holidays==0.9.4 # homeassistant.components.frontend -home-assistant-frontend==20180420.0 +home-assistant-frontend==20180425.0 # homeassistant.components.homekit_controller # homekit==0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0d371996e36..23ffab2fc78 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -81,7 +81,7 @@ hbmqtt==0.9.1 holidays==0.9.4 # homeassistant.components.frontend -home-assistant-frontend==20180420.0 +home-assistant-frontend==20180425.0 # homeassistant.components.influxdb # homeassistant.components.sensor.influxdb From 44be80145b55deda65b2e655fe0bd2cedc2eb8ad Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Sat, 21 Apr 2018 08:34:42 +0200 Subject: [PATCH 18/41] Qwikswitch binary sensors (#14008) --- .../components/binary_sensor/qwikswitch.py | 70 +++++++++++++++++++ homeassistant/components/qwikswitch.py | 44 ++++++++---- homeassistant/components/sensor/qwikswitch.py | 12 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../{sensor => }/test_qwikswitch.py | 70 +++++++++++++------ 6 files changed, 160 insertions(+), 40 deletions(-) create mode 100644 homeassistant/components/binary_sensor/qwikswitch.py rename tests/components/{sensor => }/test_qwikswitch.py (55%) diff --git a/homeassistant/components/binary_sensor/qwikswitch.py b/homeassistant/components/binary_sensor/qwikswitch.py new file mode 100644 index 00000000000..067021b0c7a --- /dev/null +++ b/homeassistant/components/binary_sensor/qwikswitch.py @@ -0,0 +1,70 @@ +""" +Support for Qwikswitch Binary Sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.qwikswitch/ +""" +import logging + +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.qwikswitch import QSEntity, DOMAIN as QWIKSWITCH +from homeassistant.core import callback + +DEPENDENCIES = [QWIKSWITCH] + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_platform(hass, _, add_devices, discovery_info=None): + """Add binary sensor from the main Qwikswitch component.""" + if discovery_info is None: + return + + qsusb = hass.data[QWIKSWITCH] + _LOGGER.debug("Setup qwikswitch.binary_sensor %s, %s", + qsusb, discovery_info) + devs = [QSBinarySensor(sensor) for sensor in discovery_info[QWIKSWITCH]] + add_devices(devs) + + +class QSBinarySensor(QSEntity, BinarySensorDevice): + """Sensor based on a Qwikswitch relay/dimmer module.""" + + _val = False + + def __init__(self, sensor): + """Initialize the sensor.""" + from pyqwikswitch import SENSORS + + super().__init__(sensor['id'], sensor['name']) + self.channel = sensor['channel'] + sensor_type = sensor['type'] + + self._decode, _ = SENSORS[sensor_type] + self._invert = not sensor.get('invert', False) + self._class = sensor.get('class', 'door') + + @callback + def update_packet(self, packet): + """Receive update packet from QSUSB.""" + val = self._decode(packet, channel=self.channel) + _LOGGER.debug("Update %s (%s:%s) decoded as %s: %s", + self.entity_id, self.qsid, self.channel, val, packet) + if val is not None: + self._val = bool(val) + self.async_schedule_update_ha_state() + + @property + def is_on(self): + """Check if device is on (non-zero).""" + return self._val == self._invert + + @property + def unique_id(self): + """Return a unique identifier for this sensor.""" + return "qs{}:{}".format(self.qsid, self.channel) + + @property + def device_class(self): + """Return the class of this sensor.""" + return self._class diff --git a/homeassistant/components/qwikswitch.py b/homeassistant/components/qwikswitch.py index 3dc16f513dc..f26318fa7a9 100644 --- a/homeassistant/components/qwikswitch.py +++ b/homeassistant/components/qwikswitch.py @@ -8,17 +8,18 @@ import logging import voluptuous as vol +from homeassistant.components.binary_sensor import DEVICE_CLASSES_SCHEMA +from homeassistant.components.light import ATTR_BRIGHTNESS from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, CONF_URL, - CONF_SENSORS, CONF_SWITCHES) + CONF_SENSORS, CONF_SWITCHES, CONF_URL, EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP) from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform from homeassistant.helpers.entity import Entity -from homeassistant.components.light import ATTR_BRIGHTNESS -import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyqwikswitch==0.71'] +REQUIREMENTS = ['pyqwikswitch==0.8'] _LOGGER = logging.getLogger(__name__) @@ -28,6 +29,7 @@ CONF_DIMMER_ADJUST = 'dimmer_adjust' CONF_BUTTON_EVENTS = 'button_events' CV_DIM_VALUE = vol.All(vol.Coerce(float), vol.Range(min=1, max=3)) + CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_URL, default='http://127.0.0.1:2020'): @@ -40,6 +42,8 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional('channel', default=1): int, vol.Required('name'): str, vol.Required('type'): str, + vol.Optional('class'): DEVICE_CLASSES_SCHEMA, + vol.Optional('invert'): bool })]), vol.Optional(CONF_SWITCHES, default=[]): vol.All( cv.ensure_list, [str]) @@ -115,7 +119,7 @@ class QSToggleEntity(QSEntity): async def async_setup(hass, config): """Qwiskswitch component setup.""" from pyqwikswitch.async_ import QSUsb - from pyqwikswitch import CMD_BUTTONS, QS_CMD, QS_ID, QSType + from pyqwikswitch import CMD_BUTTONS, QS_CMD, QS_ID, QSType, SENSORS # Add cmd's to in /&listen packets will fire events # By default only buttons of type [TOGGLE,SCENE EXE,LEVEL] @@ -143,22 +147,39 @@ async def async_setup(hass, config): hass.data[DOMAIN] = qsusb - _new = {'switch': [], 'light': [], 'sensor': sensors} + comps = {'switch': [], 'light': [], 'sensor': [], 'binary_sensor': []} + + try: + for sens in sensors: + _, _type = SENSORS[sens['type']] + if _type is bool: + comps['binary_sensor'].append(sens) + continue + comps['sensor'].append(sens) + for _key in ('invert', 'class'): + if _key in sens: + _LOGGER.warning( + "%s should only be used for binary_sensors: %s", + _key, sens) + + except KeyError: + _LOGGER.warning("Sensor validation failed") + for qsid, dev in qsusb.devices.items(): if qsid in switches: if dev.qstype != QSType.relay: _LOGGER.warning( "You specified a switch that is not a relay %s", qsid) continue - _new['switch'].append(qsid) + comps['switch'].append(qsid) elif dev.qstype in (QSType.relay, QSType.dimmer): - _new['light'].append(qsid) + comps['light'].append(qsid) else: _LOGGER.warning("Ignored unknown QSUSB device: %s", dev) continue # Load platforms - for comp_name, comp_conf in _new.items(): + for comp_name, comp_conf in comps.items(): if comp_conf: load_platform(hass, comp_name, DOMAIN, {DOMAIN: comp_conf}, config) @@ -190,9 +211,8 @@ async def async_setup(hass, config): @callback def async_stop(_): - """Stop the listener queue and clean up.""" + """Stop the listener.""" hass.data[DOMAIN].stop() - _LOGGER.info("Waiting for long poll to QSUSB to time out (max 30sec)") hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, async_stop) diff --git a/homeassistant/components/sensor/qwikswitch.py b/homeassistant/components/sensor/qwikswitch.py index ebd5f5254d4..1497b4ad5cc 100644 --- a/homeassistant/components/sensor/qwikswitch.py +++ b/homeassistant/components/sensor/qwikswitch.py @@ -36,18 +36,18 @@ class QSSensor(QSEntity): super().__init__(sensor['id'], sensor['name']) self.channel = sensor['channel'] - self.sensor_type = sensor['type'] + sensor_type = sensor['type'] - self._decode, self.unit = SENSORS[self.sensor_type] + self._decode, self.unit = SENSORS[sensor_type] if isinstance(self.unit, type): - self.unit = "{}:{}".format(self.sensor_type, self.channel) + self.unit = "{}:{}".format(sensor_type, self.channel) @callback def update_packet(self, packet): """Receive update packet from QSUSB.""" - val = self._decode(packet.get('data'), channel=self.channel) - _LOGGER.debug("Update %s (%s) decoded as %s: %s: %s", - self.entity_id, self.qsid, val, self.channel, packet) + val = self._decode(packet, channel=self.channel) + _LOGGER.debug("Update %s (%s:%s) decoded as %s: %s", + self.entity_id, self.qsid, self.channel, val, packet) if val is not None: self._val = val self.async_schedule_update_ha_state() diff --git a/requirements_all.txt b/requirements_all.txt index 57197443779..579d8914d66 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -898,7 +898,7 @@ pyowm==2.8.0 pypollencom==1.1.2 # homeassistant.components.qwikswitch -pyqwikswitch==0.71 +pyqwikswitch==0.8 # homeassistant.components.rainbird pyrainbird==0.1.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 23ffab2fc78..779b304f490 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -149,7 +149,7 @@ pymonoprice==0.3 pynx584==0.4 # homeassistant.components.qwikswitch -pyqwikswitch==0.71 +pyqwikswitch==0.8 # homeassistant.components.sensor.darksky # homeassistant.components.weather.darksky diff --git a/tests/components/sensor/test_qwikswitch.py b/tests/components/test_qwikswitch.py similarity index 55% rename from tests/components/sensor/test_qwikswitch.py rename to tests/components/test_qwikswitch.py index d9dfe072fc0..76655f32816 100644 --- a/tests/components/sensor/test_qwikswitch.py +++ b/tests/components/test_qwikswitch.py @@ -13,17 +13,19 @@ _LOGGER = logging.getLogger(__name__) class AiohttpClientMockResponseList(list): - """List that fires an event on empty pop, for aiohttp Mocker.""" + """Return multiple values for aiohttp Mocker. + + aoihttp mocker uses decode to fetch the next value. + """ def decode(self, _): """Return next item from list.""" try: - res = list.pop(self) + res = list.pop(self, 0) _LOGGER.debug("MockResponseList popped %s: %s", res, self) return res except IndexError: - _LOGGER.debug("MockResponseList empty") - return "" + raise AssertionError("MockResponseList empty") async def wait_till_empty(self, hass): """Wait until empty.""" @@ -52,8 +54,8 @@ def aioclient_mock(): yield mock_session -async def test_sensor_device(hass, aioclient_mock): - """Test a sensor device.""" +async def test_binary_sensor_device(hass, aioclient_mock): + """Test a binary sensor device.""" config = { 'qwikswitch': { 'sensors': { @@ -67,21 +69,49 @@ async def test_sensor_device(hass, aioclient_mock): await async_setup_component(hass, QWIKSWITCH, config) await hass.async_block_till_done() - state_obj = hass.states.get('sensor.s1') - assert state_obj - assert state_obj.state == 'None' + state_obj = hass.states.get('binary_sensor.s1') + assert state_obj.state == 'off' + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) - LISTEN.append( # Close - """{"id":"@a00001","cmd":"","data":"4e0e1601","rssi":"61%"}""") + LISTEN.append('{"id":"@a00001","cmd":"","data":"4e0e1601","rssi":"61%"}') + LISTEN.append('') # Will cause a sleep await hass.async_block_till_done() - state_obj = hass.states.get('sensor.s1') - assert state_obj.state == 'True' + state_obj = hass.states.get('binary_sensor.s1') + assert state_obj.state == 'on' - # Causes a 30second delay: can be uncommented when upstream library - # allows cancellation of asyncio.sleep(30) on failed packet ("") - # LISTEN.append( # Open - # """{"id":"@a00001","cmd":"","data":"4e0e1701","rssi":"61%"}""") - # await LISTEN.wait_till_empty(hass) - # state_obj = hass.states.get('sensor.s1') - # assert state_obj.state == 'False' + LISTEN.append('{"id":"@a00001","cmd":"","data":"4e0e1701","rssi":"61%"}') + hass.data[QWIKSWITCH]._sleep_task.cancel() + await LISTEN.wait_till_empty(hass) + state_obj = hass.states.get('binary_sensor.s1') + assert state_obj.state == 'off' + + +async def test_sensor_device(hass, aioclient_mock): + """Test a sensor device.""" + config = { + 'qwikswitch': { + 'sensors': { + 'name': 'ss1', + 'id': '@a00001', + 'channel': 1, + 'type': 'qwikcord', + } + } + } + await async_setup_component(hass, QWIKSWITCH, config) + await hass.async_block_till_done() + + state_obj = hass.states.get('sensor.ss1') + assert state_obj.state == 'None' + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + + LISTEN.append( + '{"id":"@a00001","name":"ss1","type":"rel",' + '"val":"4733800001a00000"}') + LISTEN.append('') # Will cause a sleep + await LISTEN.wait_till_empty(hass) # await hass.async_block_till_done() + + state_obj = hass.states.get('sensor.ss1') + assert state_obj.state == 'None' From 2bc87bfcf0ec2dc1ea45c05325431153afae0517 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 23 Apr 2018 13:47:06 -0400 Subject: [PATCH 19/41] Order the output of the automation editor (#14019) * Order the output of the automation editor * Lint --- homeassistant/components/config/automation.py | 34 +++++++- tests/components/config/test_automation.py | 83 +++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 tests/components/config/test_automation.py diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index 6ede91e9b66..1e260854687 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -1,6 +1,8 @@ """Provide configuration end points for Automations.""" import asyncio +from collections import OrderedDict +from homeassistant.const import CONF_ID from homeassistant.components.config import EditIdBasedConfigView from homeassistant.components.automation import ( PLATFORM_SCHEMA, DOMAIN, async_reload) @@ -13,8 +15,38 @@ CONFIG_PATH = 'automations.yaml' @asyncio.coroutine def async_setup(hass): """Set up the Automation config API.""" - hass.http.register_view(EditIdBasedConfigView( + hass.http.register_view(EditAutomationConfigView( DOMAIN, 'config', CONFIG_PATH, cv.string, PLATFORM_SCHEMA, post_write_hook=async_reload )) return True + + +class EditAutomationConfigView(EditIdBasedConfigView): + """Edit automation config.""" + + def _write_value(self, hass, data, config_key, new_value): + """Set value.""" + index = None + for index, cur_value in enumerate(data): + if cur_value[CONF_ID] == config_key: + break + else: + cur_value = OrderedDict() + cur_value[CONF_ID] = config_key + index = len(data) + data.append(cur_value) + + # Iterate through some keys that we want to have ordered in the output + updated_value = OrderedDict() + for key in ('id', 'alias', 'trigger', 'condition', 'action'): + if key in cur_value: + updated_value[key] = cur_value[key] + if key in new_value: + updated_value[key] = new_value[key] + + # We cover all current fields above, but just in case we start + # supporting more fields in the future. + updated_value.update(cur_value) + updated_value.update(new_value) + data[index] = updated_value diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py new file mode 100644 index 00000000000..327283e74aa --- /dev/null +++ b/tests/components/config/test_automation.py @@ -0,0 +1,83 @@ +"""Test Automation config panel.""" +import json +from unittest.mock import patch + +from homeassistant.bootstrap import async_setup_component +from homeassistant.components import config + + +async def test_get_device_config(hass, aiohttp_client): + """Test getting device config.""" + with patch.object(config, 'SECTIONS', ['automation']): + await async_setup_component(hass, 'config', {}) + + client = await aiohttp_client(hass.http.app) + + def mock_read(path): + """Mock reading data.""" + return [ + { + 'id': 'sun', + }, + { + 'id': 'moon', + } + ] + + with patch('homeassistant.components.config._read', mock_read): + resp = await client.get( + '/api/config/automation/config/moon') + + assert resp.status == 200 + result = await resp.json() + + assert result == {'id': 'moon'} + + +async def test_update_device_config(hass, aiohttp_client): + """Test updating device config.""" + with patch.object(config, 'SECTIONS', ['automation']): + await async_setup_component(hass, 'config', {}) + + client = await aiohttp_client(hass.http.app) + + orig_data = [ + { + 'id': 'sun', + }, + { + 'id': 'moon', + } + ] + + def mock_read(path): + """Mock reading data.""" + return orig_data + + written = [] + + def mock_write(path, data): + """Mock writing data.""" + written.append(data) + + with patch('homeassistant.components.config._read', mock_read), \ + patch('homeassistant.components.config._write', mock_write): + resp = await client.post( + '/api/config/automation/config/moon', data=json.dumps({ + 'trigger': [], + 'action': [], + 'condition': [], + })) + + assert resp.status == 200 + result = await resp.json() + assert result == {'result': 'ok'} + + assert list(orig_data[1]) == ['id', 'trigger', 'condition', 'action'] + assert orig_data[1] == { + 'id': 'moon', + 'trigger': [], + 'condition': [], + 'action': [], + } + assert written[0] == orig_data From cb839eff0fc8a36d8bd869c1830d424f567a4aeb Mon Sep 17 00:00:00 2001 From: Matt Schmitt Date: Sat, 21 Apr 2018 10:16:46 -0400 Subject: [PATCH 20/41] HomeKit Alarm Control Panel Code Exception Fix (#14025) * Catch exception for KeyError * Use get and added test --- .../components/homekit/type_security_systems.py | 2 +- .../components/homekit/test_type_security_systems.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit/type_security_systems.py b/homeassistant/components/homekit/type_security_systems.py index 6b8457a3aa5..0762e0f25f9 100644 --- a/homeassistant/components/homekit/type_security_systems.py +++ b/homeassistant/components/homekit/type_security_systems.py @@ -30,7 +30,7 @@ class SecuritySystem(HomeAccessory): def __init__(self, *args, config): """Initialize a SecuritySystem accessory object.""" super().__init__(*args, category=CATEGORY_ALARM_SYSTEM) - self._alarm_code = config[ATTR_CODE] + self._alarm_code = config.get(ATTR_CODE) self.flag_target_state = False serv_alarm = add_preload_service(self, SERV_SECURITY_SYSTEM) diff --git a/tests/components/homekit/test_type_security_systems.py b/tests/components/homekit/test_type_security_systems.py index ec538ce4b50..9c1ff0faf1a 100644 --- a/tests/components/homekit/test_type_security_systems.py +++ b/tests/components/homekit/test_type_security_systems.py @@ -109,8 +109,16 @@ class TestHomekitSecuritySystems(unittest.TestCase): acc = SecuritySystem(self.hass, 'SecuritySystem', acp, 2, config={ATTR_CODE: None}) - acc.run() - + # Set from HomeKit + acc.char_target_state.client_update_value(0) + self.hass.block_till_done() + self.assertEqual( + self.events[0].data[ATTR_SERVICE], 'alarm_arm_home') + self.assertNotIn(ATTR_CODE, self.events[0].data[ATTR_SERVICE_DATA]) + self.assertEqual(acc.char_target_state.value, 0) + + acc = SecuritySystem(self.hass, 'SecuritySystem', acp, + 2, config={}) # Set from HomeKit acc.char_target_state.client_update_value(0) self.hass.block_till_done() From fc1f6ee0f0a0fd70c2f35dc0282b9316fe8c25d6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 22 Apr 2018 22:32:15 +0200 Subject: [PATCH 21/41] Revert cast platform polling mode (#14027) --- homeassistant/components/media_player/cast.py | 64 +++----------- tests/components/media_player/test_cast.py | 85 +------------------ 2 files changed, 13 insertions(+), 136 deletions(-) diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index 30d4bd166d0..632ab4214b8 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -288,8 +288,7 @@ class CastDevice(MediaPlayerDevice): self._chromecast = None # type: Optional[pychromecast.Chromecast] self.cast_status = None self.media_status = None - self.media_status_position = None - self.media_status_position_received = None + self.media_status_received = None self._available = False # type: bool self._status_listener = None # type: Optional[CastStatusListener] @@ -362,26 +361,10 @@ class CastDevice(MediaPlayerDevice): self._chromecast = None self.cast_status = None self.media_status = None - self.media_status_position = None - self.media_status_position_received = None + self.media_status_received = None self._status_listener.invalidate() self._status_listener = None - def update(self): - """Periodically update the properties. - - Even though we receive callbacks for most state changes, some 3rd party - apps don't always send them. Better poll every now and then if the - chromecast is active (i.e. an app is running). - """ - if not self._available: - # Not connected or not available. - return - - if self._chromecast.media_controller.is_active: - # We can only update status if the media namespace is active - self._chromecast.media_controller.update_status() - # ========== Callbacks ========== def new_cast_status(self, cast_status): """Handle updates of the cast status.""" @@ -390,36 +373,8 @@ class CastDevice(MediaPlayerDevice): def new_media_status(self, media_status): """Handle updates of the media status.""" - # Only use media position for playing/paused, - # and for normal playback rate - if (media_status is None or - abs(media_status.playback_rate - 1) > 0.01 or - not (media_status.player_is_playing or - media_status.player_is_paused)): - self.media_status_position = None - self.media_status_position_received = None - else: - # Avoid unnecessary state attribute updates if player_state and - # calculated position stay the same - now = dt_util.utcnow() - do_update = \ - (self.media_status is None or - self.media_status_position is None or - self.media_status.player_state != media_status.player_state) - if not do_update: - if media_status.player_is_playing: - elapsed = now - self.media_status_position_received - do_update = abs(media_status.current_time - - (self.media_status_position + - elapsed.total_seconds())) > 1 - else: - do_update = \ - self.media_status_position != media_status.current_time - if do_update: - self.media_status_position = media_status.current_time - self.media_status_position_received = now - self.media_status = media_status + self.media_status_received = dt_util.utcnow() self.schedule_update_ha_state() def new_connection_status(self, connection_status): @@ -496,8 +451,8 @@ class CastDevice(MediaPlayerDevice): # ========== Properties ========== @property def should_poll(self): - """Polling needed for cast integration, see async_update.""" - return True + """No polling needed.""" + return False @property def name(self): @@ -625,7 +580,12 @@ class CastDevice(MediaPlayerDevice): @property def media_position(self): """Position of current playing media in seconds.""" - return self.media_status_position + if self.media_status is None or \ + not (self.media_status.player_is_playing or + self.media_status.player_is_paused or + self.media_status.player_is_idle): + return None + return self.media_status.current_time @property def media_position_updated_at(self): @@ -633,7 +593,7 @@ class CastDevice(MediaPlayerDevice): Returns value from homeassistant.util.dt.utcnow(). """ - return self.media_status_position_received + return self.media_status_received @property def unique_id(self) -> Optional[str]: diff --git a/tests/components/media_player/test_cast.py b/tests/components/media_player/test_cast.py index 0c0f3906dc2..ee69ec1c85d 100644 --- a/tests/components/media_player/test_cast.py +++ b/tests/components/media_player/test_cast.py @@ -1,7 +1,6 @@ """The tests for the Cast Media player platform.""" # pylint: disable=protected-access import asyncio -import datetime as dt from typing import Optional from unittest.mock import patch, MagicMock, Mock from uuid import UUID @@ -15,8 +14,7 @@ from homeassistant.components.media_player.cast import ChromecastInfo from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.helpers.dispatcher import async_dispatcher_connect, \ async_dispatcher_send -from homeassistant.components.media_player import cast, \ - ATTR_MEDIA_POSITION, ATTR_MEDIA_POSITION_UPDATED_AT +from homeassistant.components.media_player import cast from homeassistant.setup import async_setup_component @@ -288,8 +286,6 @@ async def test_entity_media_states(hass: HomeAssistantType): assert entity.unique_id == full_info.uuid media_status = MagicMock(images=None) - media_status.current_time = 0 - media_status.playback_rate = 1 media_status.player_is_playing = True entity.new_media_status(media_status) await hass.async_block_till_done() @@ -324,85 +320,6 @@ async def test_entity_media_states(hass: HomeAssistantType): assert state.state == 'unknown' -async def test_entity_media_position(hass: HomeAssistantType): - """Test various entity media states.""" - info = get_fake_chromecast_info() - full_info = attr.evolve(info, model_name='google home', - friendly_name='Speaker', uuid=FakeUUID) - - with patch('pychromecast.dial.get_device_status', - return_value=full_info): - chromecast, entity = await async_setup_media_player_cast(hass, info) - - media_status = MagicMock(images=None) - media_status.current_time = 10 - media_status.playback_rate = 1 - media_status.player_is_playing = True - media_status.player_is_paused = False - media_status.player_is_idle = False - now = dt.datetime.now(dt.timezone.utc) - with patch('homeassistant.util.dt.utcnow', return_value=now): - entity.new_media_status(media_status) - await hass.async_block_till_done() - state = hass.states.get('media_player.speaker') - assert state.attributes[ATTR_MEDIA_POSITION] == 10 - assert state.attributes[ATTR_MEDIA_POSITION_UPDATED_AT] == now - - media_status.current_time = 15 - now_plus_5 = now + dt.timedelta(seconds=5) - with patch('homeassistant.util.dt.utcnow', return_value=now_plus_5): - entity.new_media_status(media_status) - await hass.async_block_till_done() - state = hass.states.get('media_player.speaker') - assert state.attributes[ATTR_MEDIA_POSITION] == 10 - assert state.attributes[ATTR_MEDIA_POSITION_UPDATED_AT] == now - - media_status.current_time = 20 - with patch('homeassistant.util.dt.utcnow', return_value=now_plus_5): - entity.new_media_status(media_status) - await hass.async_block_till_done() - state = hass.states.get('media_player.speaker') - assert state.attributes[ATTR_MEDIA_POSITION] == 20 - assert state.attributes[ATTR_MEDIA_POSITION_UPDATED_AT] == now_plus_5 - - media_status.current_time = 25 - now_plus_10 = now + dt.timedelta(seconds=10) - media_status.player_is_playing = False - media_status.player_is_paused = True - with patch('homeassistant.util.dt.utcnow', return_value=now_plus_10): - entity.new_media_status(media_status) - await hass.async_block_till_done() - state = hass.states.get('media_player.speaker') - assert state.attributes[ATTR_MEDIA_POSITION] == 25 - assert state.attributes[ATTR_MEDIA_POSITION_UPDATED_AT] == now_plus_10 - - now_plus_15 = now + dt.timedelta(seconds=15) - with patch('homeassistant.util.dt.utcnow', return_value=now_plus_15): - entity.new_media_status(media_status) - await hass.async_block_till_done() - state = hass.states.get('media_player.speaker') - assert state.attributes[ATTR_MEDIA_POSITION] == 25 - assert state.attributes[ATTR_MEDIA_POSITION_UPDATED_AT] == now_plus_10 - - media_status.current_time = 30 - now_plus_20 = now + dt.timedelta(seconds=20) - with patch('homeassistant.util.dt.utcnow', return_value=now_plus_20): - entity.new_media_status(media_status) - await hass.async_block_till_done() - state = hass.states.get('media_player.speaker') - assert state.attributes[ATTR_MEDIA_POSITION] == 30 - assert state.attributes[ATTR_MEDIA_POSITION_UPDATED_AT] == now_plus_20 - - media_status.player_is_paused = False - media_status.player_is_idle = True - with patch('homeassistant.util.dt.utcnow', return_value=now_plus_20): - entity.new_media_status(media_status) - await hass.async_block_till_done() - state = hass.states.get('media_player.speaker') - assert ATTR_MEDIA_POSITION not in state.attributes - assert ATTR_MEDIA_POSITION_UPDATED_AT not in state.attributes - - async def test_switched_host(hass: HomeAssistantType): """Test cast device listens for changed hosts and disconnects old cast.""" info = get_fake_chromecast_info() From 7566bb5aed499449446ecb0a6de45dd70d700b76 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Sun, 22 Apr 2018 13:38:01 -0700 Subject: [PATCH 22/41] Handle HomeKit configuration failure more cleanly (#14041) * Handle HomeKit configuration failure more cleanly Add support for handling cases where HomeKit configuration fails, and give the user more information about what to do. * Don't consume the exception for a homekit.UnknownError If we get an UnknownError then we should alert the user but also still generate the backtrace so there's actually something for them to file in a bug report. --- .../components/homekit_controller/__init__.py | 27 ++++++++++++++++--- requirements_all.txt | 2 +- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index c33edd07918..164e7d50e4d 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -14,7 +14,7 @@ from homeassistant.components.discovery import SERVICE_HOMEKIT from homeassistant.helpers import discovery from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['homekit==0.5'] +REQUIREMENTS = ['homekit==0.6'] DOMAIN = 'homekit_controller' HOMEKIT_DIR = '.homekit' @@ -133,10 +133,31 @@ class HKDevice(): import homekit pairing_id = str(uuid.uuid4()) code = callback_data.get('code').strip() - self.pairing_data = homekit.perform_pair_setup( - self.conn, code, pairing_id) + try: + self.pairing_data = homekit.perform_pair_setup(self.conn, code, + pairing_id) + except homekit.exception.UnavailableError: + error_msg = "This accessory is already paired to another device. \ + Please reset the accessory and try again." + _configurator = self.hass.data[DOMAIN+self.hkid] + self.configurator.notify_errors(_configurator, error_msg) + return + except homekit.exception.AuthenticationError: + error_msg = "Incorrect HomeKit code for {}. Please check it and \ + try again.".format(self.model) + _configurator = self.hass.data[DOMAIN+self.hkid] + self.configurator.notify_errors(_configurator, error_msg) + return + except homekit.exception.UnknownError: + error_msg = "Received an unknown error. Please file a bug." + _configurator = self.hass.data[DOMAIN+self.hkid] + self.configurator.notify_errors(_configurator, error_msg) + raise + if self.pairing_data is not None: homekit.save_pairing(self.pairing_file, self.pairing_data) + _configurator = self.hass.data[DOMAIN+self.hkid] + self.configurator.request_done(_configurator) self.accessory_setup() else: error_msg = "Unable to pair, please try again" diff --git a/requirements_all.txt b/requirements_all.txt index 579d8914d66..5070f904b43 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -389,7 +389,7 @@ holidays==0.9.4 home-assistant-frontend==20180425.0 # homeassistant.components.homekit_controller -# homekit==0.5 +# homekit==0.6 # homeassistant.components.homematicip_cloud homematicip==0.8 From 2e3a27e418e9f149de99ed55cefe0cead7d4267f Mon Sep 17 00:00:00 2001 From: Mark Coombes Date: Mon, 23 Apr 2018 07:52:39 -0400 Subject: [PATCH 23/41] Update device classes for contact sensor HomeKit (#14051) --- homeassistant/components/homekit/const.py | 3 +++ homeassistant/components/homekit/type_sensors.py | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 1c498b4b3b9..59444c75421 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -102,6 +102,8 @@ PROP_CELSIUS = {'minValue': -273, 'maxValue': 999} # #### Device Class #### DEVICE_CLASS_CO2 = 'co2' +DEVICE_CLASS_DOOR = 'door' +DEVICE_CLASS_GARAGE_DOOR = 'garage_door' DEVICE_CLASS_GAS = 'gas' DEVICE_CLASS_HUMIDITY = 'humidity' DEVICE_CLASS_LIGHT = 'light' @@ -112,3 +114,4 @@ DEVICE_CLASS_OPENING = 'opening' DEVICE_CLASS_PM25 = 'pm25' DEVICE_CLASS_SMOKE = 'smoke' DEVICE_CLASS_TEMPERATURE = 'temperature' +DEVICE_CLASS_WINDOW = 'window' diff --git a/homeassistant/components/homekit/type_sensors.py b/homeassistant/components/homekit/type_sensors.py index 6aa8d92c0af..7d7bbc5edd6 100644 --- a/homeassistant/components/homekit/type_sensors.py +++ b/homeassistant/components/homekit/type_sensors.py @@ -20,6 +20,7 @@ from .const import ( DEVICE_CLASS_MOTION, SERV_MOTION_SENSOR, CHAR_MOTION_DETECTED, DEVICE_CLASS_OCCUPANCY, SERV_OCCUPANCY_SENSOR, CHAR_OCCUPANCY_DETECTED, DEVICE_CLASS_OPENING, SERV_CONTACT_SENSOR, CHAR_CONTACT_SENSOR_STATE, + DEVICE_CLASS_DOOR, DEVICE_CLASS_GARAGE_DOOR, DEVICE_CLASS_WINDOW, DEVICE_CLASS_SMOKE, SERV_SMOKE_SENSOR, CHAR_SMOKE_DETECTED) from .util import ( convert_to_float, temperature_to_homekit, density_to_air_quality) @@ -29,13 +30,16 @@ _LOGGER = logging.getLogger(__name__) BINARY_SENSOR_SERVICE_MAP = { DEVICE_CLASS_CO2: (SERV_CARBON_DIOXIDE_SENSOR, CHAR_CARBON_DIOXIDE_DETECTED), + DEVICE_CLASS_DOOR: (SERV_CONTACT_SENSOR, CHAR_CONTACT_SENSOR_STATE), + DEVICE_CLASS_GARAGE_DOOR: (SERV_CONTACT_SENSOR, CHAR_CONTACT_SENSOR_STATE), DEVICE_CLASS_GAS: (SERV_CARBON_MONOXIDE_SENSOR, CHAR_CARBON_MONOXIDE_DETECTED), DEVICE_CLASS_MOISTURE: (SERV_LEAK_SENSOR, CHAR_LEAK_DETECTED), DEVICE_CLASS_MOTION: (SERV_MOTION_SENSOR, CHAR_MOTION_DETECTED), DEVICE_CLASS_OCCUPANCY: (SERV_OCCUPANCY_SENSOR, CHAR_OCCUPANCY_DETECTED), DEVICE_CLASS_OPENING: (SERV_CONTACT_SENSOR, CHAR_CONTACT_SENSOR_STATE), - DEVICE_CLASS_SMOKE: (SERV_SMOKE_SENSOR, CHAR_SMOKE_DETECTED)} + DEVICE_CLASS_SMOKE: (SERV_SMOKE_SENSOR, CHAR_SMOKE_DETECTED), + DEVICE_CLASS_WINDOW: (SERV_CONTACT_SENSOR, CHAR_CONTACT_SENSOR_STATE)} @TYPES.register('TemperatureSensor') From c49751542fff78711727dd0a0beb524a4e4670b3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 24 Apr 2018 23:19:33 -0400 Subject: [PATCH 24/41] Version bump to 0.68.0b1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 56e37e5e039..eed6664bd0a 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 68 -PATCH_VERSION = '0b0' +PATCH_VERSION = '0b1' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From 1b71ce32e440075aa527d9b777a44befc86c01d2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 26 Apr 2018 16:39:14 -0400 Subject: [PATCH 25/41] Bump frontend to 20180426 --- homeassistant/components/frontend/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index ba487a935a2..4a181c00c02 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,7 +24,7 @@ from homeassistant.core import callback from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass -REQUIREMENTS = ['home-assistant-frontend==20180425.0'] +REQUIREMENTS = ['home-assistant-frontend==20180426.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log'] diff --git a/requirements_all.txt b/requirements_all.txt index 5070f904b43..e48ee1ec292 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -386,7 +386,7 @@ hipnotify==1.0.8 holidays==0.9.4 # homeassistant.components.frontend -home-assistant-frontend==20180425.0 +home-assistant-frontend==20180426.0 # homeassistant.components.homekit_controller # homekit==0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 779b304f490..876aba4574d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -81,7 +81,7 @@ hbmqtt==0.9.1 holidays==0.9.4 # homeassistant.components.frontend -home-assistant-frontend==20180425.0 +home-assistant-frontend==20180426.0 # homeassistant.components.influxdb # homeassistant.components.sensor.influxdb From 833508fbbb4cdf3ce1382095293b09acfa7b3bee Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 26 Apr 2018 16:39:42 -0400 Subject: [PATCH 26/41] Version bump to 0.68.0b2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index eed6664bd0a..bc32c20f142 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 68 -PATCH_VERSION = '0b1' +PATCH_VERSION = '0b2' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From 403a546bdc4566b8672532a70837afb0ab08c2c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Wed, 25 Apr 2018 04:45:16 +0200 Subject: [PATCH 27/41] Upgrade broadlink lib (#14074) --- homeassistant/components/sensor/broadlink.py | 2 +- homeassistant/components/switch/broadlink.py | 2 +- requirements_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/broadlink.py b/homeassistant/components/sensor/broadlink.py index 5182ba4530e..9376687cf13 100644 --- a/homeassistant/components/sensor/broadlink.py +++ b/homeassistant/components/sensor/broadlink.py @@ -19,7 +19,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['broadlink==0.8.0'] +REQUIREMENTS = ['broadlink==0.9.0'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/switch/broadlink.py b/homeassistant/components/switch/broadlink.py index 50c334b1f09..46002112177 100644 --- a/homeassistant/components/switch/broadlink.py +++ b/homeassistant/components/switch/broadlink.py @@ -22,7 +22,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle, slugify from homeassistant.util.dt import utcnow -REQUIREMENTS = ['broadlink==0.8.0'] +REQUIREMENTS = ['broadlink==0.9.0'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index e48ee1ec292..7cc644129b3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -180,7 +180,7 @@ botocore==1.7.34 # homeassistant.components.sensor.broadlink # homeassistant.components.switch.broadlink -broadlink==0.8.0 +broadlink==0.9.0 # homeassistant.components.device_tracker.bluetooth_tracker bt_proximity==0.1.2 From 9d0251cfeb8c9131e6a703e7a337bb658f576b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Thu, 26 Apr 2018 09:49:35 +0200 Subject: [PATCH 28/41] Fix timezone issue when calculating min/max values in tibber #14009 (#14080) * fix timezone issue in tibber #14009 * remove debug print --- homeassistant/components/sensor/tibber.py | 30 +++++++++++------------ 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/sensor/tibber.py b/homeassistant/components/sensor/tibber.py index 4fb378ac227..42568a6b9ad 100644 --- a/homeassistant/components/sensor/tibber.py +++ b/homeassistant/components/sensor/tibber.py @@ -60,7 +60,7 @@ class TibberSensor(Entity): """Initialize the sensor.""" self._tibber_home = tibber_home self._last_updated = None - self._newest_data_timestamp = None + self._last_data_timestamp = None self._state = None self._is_available = False self._device_state_attributes = {} @@ -70,13 +70,13 @@ class TibberSensor(Entity): async def async_update(self): """Get the latest data and updates the states.""" - now = dt_util.utcnow() + now = dt_util.now() if self._tibber_home.current_price_total and self._last_updated and \ - self._last_updated.hour == now.hour and self._newest_data_timestamp: + self._last_updated.hour == now.hour and self._last_data_timestamp: return - if (not self._newest_data_timestamp or - (self._newest_data_timestamp - now).total_seconds()/3600 < 12 + if (not self._last_data_timestamp or + (self._last_data_timestamp - now).total_seconds()/3600 < 12 or not self._is_available): _LOGGER.debug("Asking for new data.") await self._fetch_data() @@ -135,24 +135,22 @@ class TibberSensor(Entity): def _update_current_price(self): state = None - max_price = None - min_price = None - now = dt_util.utcnow() + max_price = 0 + min_price = 10000 + now = dt_util.now() for key, price_total in self._tibber_home.price_total.items(): - price_time = dt_util.as_utc(dt_util.parse_datetime(key)) + price_time = dt_util.as_local(dt_util.parse_datetime(key)) price_total = round(price_total, 3) time_diff = (now - price_time).total_seconds()/60 - if (not self._newest_data_timestamp or - price_time > self._newest_data_timestamp): - self._newest_data_timestamp = price_time + if (not self._last_data_timestamp or + price_time > self._last_data_timestamp): + self._last_data_timestamp = price_time if 0 <= time_diff < 60: state = price_total self._last_updated = price_time if now.date() == price_time.date(): - if max_price is None or price_total > max_price: - max_price = price_total - if min_price is None or price_total < min_price: - min_price = price_total + max_price = max(max_price, price_total) + min_price = min(min_price, price_total) self._state = state self._device_state_attributes['max_price'] = max_price self._device_state_attributes['min_price'] = min_price From c42c668815383168565c85db575b087aa755e8e9 Mon Sep 17 00:00:00 2001 From: GotoCode Date: Thu, 26 Apr 2018 20:35:29 +0300 Subject: [PATCH 29/41] Updated list of AWS regions for Amazon Polly (#14097) Fixes #14052 --- homeassistant/components/tts/amazon_polly.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/tts/amazon_polly.py b/homeassistant/components/tts/amazon_polly.py index d7cf0f1f2d1..46c1a24caa0 100644 --- a/homeassistant/components/tts/amazon_polly.py +++ b/homeassistant/components/tts/amazon_polly.py @@ -20,7 +20,11 @@ CONF_PROFILE_NAME = 'profile_name' ATTR_CREDENTIALS = 'credentials' DEFAULT_REGION = 'us-east-1' -SUPPORTED_REGIONS = ['us-east-1', 'us-east-2', 'us-west-2', 'eu-west-1'] +SUPPORTED_REGIONS = ['us-east-1', 'us-east-2', 'us-west-1', 'us-west-2', + 'ca-central-1', 'eu-west-1', 'eu-central-1', 'eu-west-2', + 'eu-west-3', 'ap-southeast-1', 'ap-southeast-2', + 'ap-northeast-2', 'ap-northeast-1', 'ap-south-1', + 'sa-east-1'] CONF_VOICE = 'voice' CONF_OUTPUT_FORMAT = 'output_format' From 9fb2bf72f9c969defbafcfb92b3207ff807a2a91 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 27 Apr 2018 15:35:20 -0400 Subject: [PATCH 30/41] Version bump to 0.68.0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index bc32c20f142..0a69f166b43 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 68 -PATCH_VERSION = '0b2' +PATCH_VERSION = '0' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From 7da1d757073c0dd49e7b6e17102b725388e5ac85 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Fri, 27 Apr 2018 13:39:07 -0700 Subject: [PATCH 31/41] Change Eufy brightness handling (#14111) Eufy device state isn't reported if the bulb is off, so avoid stamping on the previous values if the bulb isn't going to give us useful information. In addition, improve handling of bulb turn on if we aren't provided with a brightness - this should avoid the bulb tending to end up with a brightness of 1 after power cycling. --- homeassistant/components/light/eufy.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/light/eufy.py b/homeassistant/components/light/eufy.py index a66e219c1a8..6f0a8816eea 100644 --- a/homeassistant/components/light/eufy.py +++ b/homeassistant/components/light/eufy.py @@ -61,13 +61,14 @@ class EufyLight(Light): def update(self): """Synchronise state from the bulb.""" self._bulb.update() - self._brightness = self._bulb.brightness - self._temp = self._bulb.temperature - if self._bulb.colors: - self._colormode = True - self._hs = color_util.color_RGB_to_hs(*self._bulb.colors) - else: - self._colormode = False + if self._bulb.power: + self._brightness = self._bulb.brightness + self._temp = self._bulb.temperature + if self._bulb.colors: + self._colormode = True + self._hs = color_util.color_RGB_to_hs(*self._bulb.colors) + else: + self._colormode = False self._state = self._bulb.power @property @@ -130,7 +131,9 @@ class EufyLight(Light): if brightness is not None: brightness = int(brightness * 100 / 255) else: - brightness = max(1, self._brightness) + if self._brightness is None: + self._brightness = 100 + brightness = self._brightness if colortemp is not None: self._colormode = False From a06f61034cb3a8debbc4da9817a6f7d1f87ef582 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Sat, 28 Apr 2018 23:12:11 +0200 Subject: [PATCH 32/41] Fix color setting of tplink lights (#14108) --- homeassistant/components/light/tplink.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/light/tplink.py b/homeassistant/components/light/tplink.py index 0bbec010282..4101eab2150 100644 --- a/homeassistant/components/light/tplink.py +++ b/homeassistant/components/light/tplink.py @@ -11,8 +11,8 @@ import voluptuous as vol from homeassistant.const import (CONF_HOST, CONF_NAME) from homeassistant.components.light import ( - Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_KELVIN, ATTR_HS_COLOR, - SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_COLOR, PLATFORM_SCHEMA) + Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, + SUPPORT_COLOR_TEMP, SUPPORT_COLOR, PLATFORM_SCHEMA) import homeassistant.helpers.config_validation as cv from homeassistant.util.color import \ color_temperature_mired_to_kelvin as mired_to_kelvin @@ -90,15 +90,15 @@ class TPLinkSmartBulb(Light): if ATTR_COLOR_TEMP in kwargs: self.smartbulb.color_temp = \ mired_to_kelvin(kwargs[ATTR_COLOR_TEMP]) - if ATTR_KELVIN in kwargs: - self.smartbulb.color_temp = kwargs[ATTR_KELVIN] - if ATTR_BRIGHTNESS in kwargs: - brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness or 255) - self.smartbulb.brightness = brightness_to_percentage(brightness) + + brightness = brightness_to_percentage( + kwargs.get(ATTR_BRIGHTNESS, self.brightness or 255)) if ATTR_HS_COLOR in kwargs: hue, sat = kwargs.get(ATTR_HS_COLOR) - hsv = (hue, sat, 100) + hsv = (int(hue), int(sat), brightness) self.smartbulb.hsv = hsv + elif ATTR_BRIGHTNESS in kwargs: + self.smartbulb.brightness = brightness def turn_off(self, **kwargs): """Turn the light off.""" From 52a48b3ac9cb3584f0e3b97854381b0d8356fe50 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Fri, 27 Apr 2018 13:18:58 +0200 Subject: [PATCH 33/41] Improve precision of Hue color state (#14113) --- homeassistant/components/light/hue.py | 20 +++++--------------- tests/components/light/test_hue.py | 17 ++--------------- 2 files changed, 7 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py index 6eb8de99c99..6b4908b02d4 100644 --- a/homeassistant/components/light/hue.py +++ b/homeassistant/components/light/hue.py @@ -242,26 +242,16 @@ class HueLight(Light): @property def hs_color(self): """Return the hs color value.""" - # pylint: disable=redefined-outer-name mode = self._color_mode - - if mode not in ('hs', 'xy'): - return - source = self.light.action if self.is_group else self.light.state - hue = source.get('hue') - sat = source.get('sat') + if mode == 'xy' and 'xy' in source: + return color.color_xy_to_hs(*source['xy']) - # Sometimes the state will not include valid hue/sat values. - # Reported as issue 13434 - if hue is not None and sat is not None: - return hue / 65535 * 360, sat / 255 * 100 + if mode == 'hs' and 'hue' in source and 'sat' in source: + return source['hue'] / 65535 * 360, source['sat'] / 255 * 100 - if 'xy' not in source: - return None - - return color.color_xy_to_hs(*source['xy']) + return None @property def color_temp(self): diff --git a/tests/components/light/test_hue.py b/tests/components/light/test_hue.py index 712cd17a7c7..d36548e1e91 100644 --- a/tests/components/light/test_hue.py +++ b/tests/components/light/test_hue.py @@ -237,7 +237,7 @@ async def test_lights(hass, mock_bridge): assert lamp_1 is not None assert lamp_1.state == 'on' assert lamp_1.attributes['brightness'] == 144 - assert lamp_1.attributes['hs_color'] == (71.896, 83.137) + assert lamp_1.attributes['hs_color'] == (36.067, 69.804) lamp_2 = hass.states.get('light.hue_lamp_2') assert lamp_2 is not None @@ -253,7 +253,7 @@ async def test_lights_color_mode(hass, mock_bridge): assert lamp_1 is not None assert lamp_1.state == 'on' assert lamp_1.attributes['brightness'] == 144 - assert lamp_1.attributes['hs_color'] == (71.896, 83.137) + assert lamp_1.attributes['hs_color'] == (36.067, 69.804) assert 'color_temp' not in lamp_1.attributes new_light1_on = LIGHT_1_ON.copy() @@ -668,19 +668,6 @@ def test_hs_color(): 'colormode': 'xy', 'hue': 1234, 'sat': 123, - }), - request_bridge_update=None, - bridge=Mock(), - is_group=False, - ) - - assert light.hs_color == (1234 / 65535 * 360, 123 / 255 * 100) - - light = hue_light.HueLight( - light=Mock(state={ - 'colormode': 'xy', - 'hue': None, - 'sat': 123, 'xy': [0.4, 0.5] }), request_bridge_update=None, From b5bae17c6640d201c407defa341cf3431fe9ba31 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Mon, 30 Apr 2018 00:49:19 +0200 Subject: [PATCH 34/41] Revert Hue color state to be xy-based (#14154) --- homeassistant/components/light/hue.py | 5 +---- tests/components/light/test_hue.py | 13 ------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py index 6b4908b02d4..9f662718514 100644 --- a/homeassistant/components/light/hue.py +++ b/homeassistant/components/light/hue.py @@ -245,12 +245,9 @@ class HueLight(Light): mode = self._color_mode source = self.light.action if self.is_group else self.light.state - if mode == 'xy' and 'xy' in source: + if mode in ('xy', 'hs'): return color.color_xy_to_hs(*source['xy']) - if mode == 'hs' and 'hue' in source and 'sat' in source: - return source['hue'] / 65535 * 360, source['sat'] / 255 * 100 - return None @property diff --git a/tests/components/light/test_hue.py b/tests/components/light/test_hue.py index d36548e1e91..8f5b52ea6de 100644 --- a/tests/components/light/test_hue.py +++ b/tests/components/light/test_hue.py @@ -650,19 +650,6 @@ def test_hs_color(): assert light.hs_color is None - light = hue_light.HueLight( - light=Mock(state={ - 'colormode': 'hs', - 'hue': 1234, - 'sat': 123, - }), - request_bridge_update=None, - bridge=Mock(), - is_group=False, - ) - - assert light.hs_color == (1234 / 65535 * 360, 123 / 255 * 100) - light = hue_light.HueLight( light=Mock(state={ 'colormode': 'xy', From f2a17a5462c7173f9002dad32444cf334ba4b678 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Sun, 29 Apr 2018 00:46:36 -0700 Subject: [PATCH 35/41] Fix Python 3.6 compatibility for HomeKit controller (#14160) Python 3.6's http client passes an additional argument to _send_output, so add that to the function definition. --- homeassistant/components/homekit_controller/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index 164e7d50e4d..e36e7439e09 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -31,7 +31,7 @@ KNOWN_DEVICES = "{}-devices".format(DOMAIN) _LOGGER = logging.getLogger(__name__) -def homekit_http_send(self, message_body=None): +def homekit_http_send(self, message_body=None, encode_chunked=False): r"""Send the currently buffered request and clear the buffer. Appends an extra \r\n to the buffer. From 03c34804bc04e98f3e5884b675cd3854429af34c Mon Sep 17 00:00:00 2001 From: cdce8p <30130371+cdce8p@users.noreply.github.com> Date: Mon, 30 Apr 2018 14:58:17 +0200 Subject: [PATCH 36/41] Added CONF_IP_ADDRESS to HomeKit (#14163) --- homeassistant/components/homekit/__init__.py | 16 +++++++---- tests/components/homekit/test_homekit.py | 28 +++++++++++++------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 24c6dfa8a76..6af470e80be 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -3,6 +3,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/homekit/ """ +import ipaddress import logging from zlib import adler32 @@ -12,8 +13,8 @@ from homeassistant.components.cover import ( SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION) from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, - ATTR_DEVICE_CLASS, CONF_PORT, TEMP_CELSIUS, TEMP_FAHRENHEIT, - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) + ATTR_DEVICE_CLASS, CONF_IP_ADDRESS, CONF_PORT, TEMP_CELSIUS, + TEMP_FAHRENHEIT, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import FILTER_SCHEMA from homeassistant.util import get_local_ip @@ -35,6 +36,8 @@ REQUIREMENTS = ['HAP-python==1.1.9'] CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.All({ vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_IP_ADDRESS): + vol.All(ipaddress.ip_address, cv.string), vol.Optional(CONF_AUTO_START, default=DEFAULT_AUTO_START): cv.boolean, vol.Optional(CONF_FILTER, default={}): FILTER_SCHEMA, vol.Optional(CONF_ENTITY_CONFIG, default={}): validate_entity_config, @@ -48,11 +51,12 @@ async def async_setup(hass, config): conf = config[DOMAIN] port = conf[CONF_PORT] + ip_address = conf.get(CONF_IP_ADDRESS) auto_start = conf[CONF_AUTO_START] entity_filter = conf[CONF_FILTER] entity_config = conf[CONF_ENTITY_CONFIG] - homekit = HomeKit(hass, port, entity_filter, entity_config) + homekit = HomeKit(hass, port, ip_address, entity_filter, entity_config) homekit.setup() if auto_start: @@ -151,10 +155,11 @@ def generate_aid(entity_id): class HomeKit(): """Class to handle all actions between HomeKit and Home Assistant.""" - def __init__(self, hass, port, entity_filter, entity_config): + def __init__(self, hass, port, ip_address, entity_filter, entity_config): """Initialize a HomeKit object.""" self.hass = hass self._port = port + self._ip_address = ip_address self._filter = entity_filter self._config = entity_config self.started = False @@ -169,9 +174,10 @@ class HomeKit(): self.hass.bus.async_listen_once( EVENT_HOMEASSISTANT_STOP, self.stop) + ip_addr = self._ip_address or get_local_ip() path = self.hass.config.path(HOMEKIT_FILE) self.bridge = HomeBridge(self.hass) - self.driver = HomeDriver(self.bridge, self._port, get_local_ip(), path) + self.driver = HomeDriver(self.bridge, self._port, ip_addr, path) def add_bridge_accessory(self, state): """Try adding accessory to bridge if configured beforehand.""" diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index d1ad232d279..7ae37becbd5 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -11,7 +11,8 @@ from homeassistant.components.homekit.const import ( DEFAULT_PORT, SERVICE_HOMEKIT_START) from homeassistant.helpers.entityfilter import generate_filter from homeassistant.const import ( - CONF_PORT, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) + CONF_IP_ADDRESS, CONF_PORT, + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) from tests.common import get_test_home_assistant from tests.components.homekit.test_accessories import patch_debounce @@ -59,7 +60,7 @@ class TestHomeKit(unittest.TestCase): self.hass, DOMAIN, {DOMAIN: {}})) self.assertEqual(mock_homekit.mock_calls, [ - call(self.hass, DEFAULT_PORT, ANY, {}), + call(self.hass, DEFAULT_PORT, None, ANY, {}), call().setup()]) # Test auto start enabled @@ -74,7 +75,8 @@ class TestHomeKit(unittest.TestCase): """Test async_setup with auto start disabled and test service calls.""" mock_homekit.return_value = homekit = Mock() - config = {DOMAIN: {CONF_AUTO_START: False, CONF_PORT: 11111}} + config = {DOMAIN: {CONF_AUTO_START: False, CONF_PORT: 11111, + CONF_IP_ADDRESS: '172.0.0.0'}} self.assertTrue(setup.setup_component( self.hass, DOMAIN, config)) @@ -82,7 +84,7 @@ class TestHomeKit(unittest.TestCase): self.hass.block_till_done() self.assertEqual(mock_homekit.mock_calls, [ - call(self.hass, 11111, ANY, {}), + call(self.hass, 11111, '172.0.0.0', ANY, {}), call().setup()]) # Test start call with driver stopped. @@ -101,7 +103,7 @@ class TestHomeKit(unittest.TestCase): def test_homekit_setup(self): """Test setup of bridge and driver.""" - homekit = HomeKit(self.hass, DEFAULT_PORT, {}, {}) + homekit = HomeKit(self.hass, DEFAULT_PORT, None, {}, {}) with patch(PATH_HOMEKIT + '.accessories.HomeDriver') as mock_driver, \ patch('homeassistant.util.get_local_ip') as mock_ip: @@ -117,9 +119,17 @@ class TestHomeKit(unittest.TestCase): self.assertEqual( self.hass.bus.listeners.get(EVENT_HOMEASSISTANT_STOP), 1) + def test_homekit_setup_ip_address(self): + """Test setup with given IP address.""" + homekit = HomeKit(self.hass, DEFAULT_PORT, '172.0.0.0', {}, {}) + + with patch(PATH_HOMEKIT + '.accessories.HomeDriver') as mock_driver: + homekit.setup() + mock_driver.assert_called_with(ANY, DEFAULT_PORT, '172.0.0.0', ANY) + def test_homekit_add_accessory(self): """Add accessory if config exists and get_acc returns an accessory.""" - homekit = HomeKit(self.hass, None, lambda entity_id: True, {}) + homekit = HomeKit(self.hass, None, None, lambda entity_id: True, {}) homekit.bridge = HomeBridge(self.hass) with patch(PATH_HOMEKIT + '.accessories.HomeBridge.add_accessory') \ @@ -142,7 +152,7 @@ class TestHomeKit(unittest.TestCase): def test_homekit_entity_filter(self): """Test the entity filter.""" entity_filter = generate_filter(['cover'], ['demo.test'], [], []) - homekit = HomeKit(self.hass, None, entity_filter, {}) + homekit = HomeKit(self.hass, None, None, entity_filter, {}) with patch(PATH_HOMEKIT + '.get_accessory') as mock_get_acc: mock_get_acc.return_value = None @@ -162,7 +172,7 @@ class TestHomeKit(unittest.TestCase): @patch(PATH_HOMEKIT + '.HomeKit.add_bridge_accessory') def test_homekit_start(self, mock_add_bridge_acc, mock_show_setup_msg): """Test HomeKit start method.""" - homekit = HomeKit(self.hass, None, {}, {'cover.demo': {}}) + homekit = HomeKit(self.hass, None, None, {}, {'cover.demo': {}}) homekit.bridge = HomeBridge(self.hass) homekit.driver = Mock() @@ -184,7 +194,7 @@ class TestHomeKit(unittest.TestCase): def test_homekit_stop(self): """Test HomeKit stop method.""" - homekit = HomeKit(None, None, None, None) + homekit = HomeKit(None, None, None, None, None) homekit.driver = Mock() # Test if started = False From aba143ac9ff874452ffcb5e4c82dd45a15454623 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 30 Apr 2018 09:18:18 -0400 Subject: [PATCH 37/41] Do not sync entities with an empty name (#14181) --- .../components/google_assistant/smart_home.py | 11 +++++--- .../google_assistant/test_smart_home.py | 26 +++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py index 7e746d48bed..27d993aee76 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -102,18 +102,23 @@ class _GoogleEntity: if state.state == STATE_UNAVAILABLE: return None + entity_config = self.config.entity_config.get(state.entity_id, {}) + name = (entity_config.get(CONF_NAME) or state.name).strip() + + # If an empty string + if not name: + return None + traits = self.traits() # Found no supported traits for this entity if not traits: return None - entity_config = self.config.entity_config.get(state.entity_id, {}) - device = { 'id': state.entity_id, 'name': { - 'name': entity_config.get(CONF_NAME) or state.name + 'name': name }, 'attributes': {}, 'traits': [trait.name for trait in traits], diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index e284b026ad8..cdaf4200c97 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -286,3 +286,29 @@ async def test_unavailable_state_doesnt_sync(hass): 'devices': [] } } + + +async def test_empty_name_doesnt_sync(hass): + """Test that an entity with empty name does not sync over.""" + light = DemoLight( + None, ' ', + state=False, + ) + light.hass = hass + light.entity_id = 'light.demo_light' + await light.async_update_ha_state() + + result = await sh.async_handle_message(hass, BASIC_CONFIG, { + "requestId": REQ_ID, + "inputs": [{ + "intent": "action.devices.SYNC" + }] + }) + + assert result == { + 'requestId': REQ_ID, + 'payload': { + 'agentUserId': 'test-agent', + 'devices': [] + } + } From 7f1b591fbb79c9adc2bfe4fa8904071e624d84c5 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 30 Apr 2018 14:46:44 +0200 Subject: [PATCH 38/41] Improve chromecast disconnection logic (#14190) * Attempt Cast Fix * Cleanup --- homeassistant/components/media_player/cast.py | 26 +++++++++++++------ tests/components/media_player/test_cast.py | 16 +++++++++--- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index 632ab4214b8..a9bea9e4c1d 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -306,13 +306,18 @@ class CastDevice(MediaPlayerDevice): _LOGGER.debug("Discovered chromecast with same UUID: %s", discover) self.hass.async_add_job(self.async_set_cast_info(discover)) + async def async_stop(event): + """Disconnect socket on Home Assistant stop.""" + await self._async_disconnect() + async_dispatcher_connect(self.hass, SIGNAL_CAST_DISCOVERED, async_cast_discovered) + self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_stop) self.hass.async_add_job(self.async_set_cast_info(self._cast_info)) async def async_will_remove_from_hass(self) -> None: """Disconnect Chromecast object when removed.""" - self._async_disconnect() + await self._async_disconnect() if self._cast_info.uuid is not None: # Remove the entity from the added casts so that it can dynamically # be re-added again. @@ -328,7 +333,7 @@ class CastDevice(MediaPlayerDevice): if old_cast_info.host_port == cast_info.host_port: # Nothing connection-related updated return - self._async_disconnect() + await self._async_disconnect() # Failed connection will unfortunately never raise an exception, it # will instead just try connecting indefinitely. @@ -348,22 +353,27 @@ class CastDevice(MediaPlayerDevice): _LOGGER.debug("Connection successful!") self.async_schedule_update_ha_state() - @callback - def _async_disconnect(self): + async def _async_disconnect(self): """Disconnect Chromecast object if it is set.""" if self._chromecast is None: # Can't disconnect if not connected. return - _LOGGER.debug("Disconnecting from previous chromecast socket.") + _LOGGER.debug("Disconnecting from chromecast socket.") self._available = False - self._chromecast.disconnect(blocking=False) + self.async_schedule_update_ha_state() + + await self.hass.async_add_job(self._chromecast.disconnect) + # Invalidate some attributes self._chromecast = None self.cast_status = None self.media_status = None self.media_status_received = None - self._status_listener.invalidate() - self._status_listener = None + if self._status_listener is not None: + self._status_listener.invalidate() + self._status_listener = None + + self.async_schedule_update_ha_state() # ========== Callbacks ========== def new_cast_status(self, cast_status): diff --git a/tests/components/media_player/test_cast.py b/tests/components/media_player/test_cast.py index ee69ec1c85d..41cf6749b71 100644 --- a/tests/components/media_player/test_cast.py +++ b/tests/components/media_player/test_cast.py @@ -346,8 +346,16 @@ async def test_switched_host(hass: HomeAssistantType): async_dispatcher_send(hass, cast.SIGNAL_CAST_DISCOVERED, changed) await hass.async_block_till_done() assert get_chromecast.call_count == 1 - chromecast.disconnect.assert_called_once_with(blocking=False) + assert chromecast.disconnect.call_count == 1 - hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) - await hass.async_block_till_done() - chromecast.disconnect.assert_called_once_with(blocking=False) + +async def test_disconnect_on_stop(hass: HomeAssistantType): + """Test cast device disconnects socket on stop.""" + info = get_fake_chromecast_info() + + with patch('pychromecast.dial.get_device_status', return_value=info): + chromecast, _ = await async_setup_media_player_cast(hass, info) + + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + assert chromecast.disconnect.call_count == 1 From daeccfe7643efaaceb19970180275a9d7b47a721 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 30 Apr 2018 09:56:42 -0400 Subject: [PATCH 39/41] Fix poorly formatted automations (#14196) --- homeassistant/components/config/automation.py | 8 ++- tests/components/config/test_automation.py | 67 +++++++++++++++++-- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index 1e260854687..223159eb415 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -1,6 +1,7 @@ """Provide configuration end points for Automations.""" import asyncio from collections import OrderedDict +import uuid from homeassistant.const import CONF_ID from homeassistant.components.config import EditIdBasedConfigView @@ -29,7 +30,12 @@ class EditAutomationConfigView(EditIdBasedConfigView): """Set value.""" index = None for index, cur_value in enumerate(data): - if cur_value[CONF_ID] == config_key: + # When people copy paste their automations to the config file, + # they sometimes forget to add IDs. Fix it here. + if CONF_ID not in cur_value: + cur_value[CONF_ID] = uuid.uuid4().hex + + elif cur_value[CONF_ID] == config_key: break else: cur_value = OrderedDict() diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index 327283e74aa..2c888dd2dd2 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -42,13 +42,13 @@ async def test_update_device_config(hass, aiohttp_client): client = await aiohttp_client(hass.http.app) orig_data = [ - { - 'id': 'sun', - }, - { - 'id': 'moon', - } - ] + { + 'id': 'sun', + }, + { + 'id': 'moon', + } + ] def mock_read(path): """Mock reading data.""" @@ -81,3 +81,56 @@ async def test_update_device_config(hass, aiohttp_client): 'action': [], } assert written[0] == orig_data + + +async def test_bad_formatted_automations(hass, aiohttp_client): + """Test that we handle automations without ID.""" + with patch.object(config, 'SECTIONS', ['automation']): + await async_setup_component(hass, 'config', {}) + + client = await aiohttp_client(hass.http.app) + + orig_data = [ + { + # No ID + 'action': { + 'event': 'hello' + } + }, + { + 'id': 'moon', + } + ] + + def mock_read(path): + """Mock reading data.""" + return orig_data + + written = [] + + def mock_write(path, data): + """Mock writing data.""" + written.append(data) + + with patch('homeassistant.components.config._read', mock_read), \ + patch('homeassistant.components.config._write', mock_write): + resp = await client.post( + '/api/config/automation/config/moon', data=json.dumps({ + 'trigger': [], + 'action': [], + 'condition': [], + })) + + assert resp.status == 200 + result = await resp.json() + assert result == {'result': 'ok'} + + # Verify ID added to orig_data + assert 'id' in orig_data[0] + + assert orig_data[1] == { + 'id': 'moon', + 'trigger': [], + 'condition': [], + 'action': [], + } From c704ceaeb7c3d5b085721af4003ad560e79ea9fe Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 30 Apr 2018 13:37:12 -0400 Subject: [PATCH 40/41] Version bump to 0.68.1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 0a69f166b43..4014a719912 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 68 -PATCH_VERSION = '0' +PATCH_VERSION = '1' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From c23cc0e8271bab2dfa60f8c4096907780bf1362d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 28 Apr 2018 20:15:00 -0400 Subject: [PATCH 41/41] Disable eliqonline requirement (#14156) * Disable eliqonline requirement * Disable pylint import error --- homeassistant/components/sensor/eliqonline.py | 3 ++- requirements_all.txt | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sensor/eliqonline.py b/homeassistant/components/sensor/eliqonline.py index 3e736ed719f..23c397053c5 100644 --- a/homeassistant/components/sensor/eliqonline.py +++ b/homeassistant/components/sensor/eliqonline.py @@ -14,7 +14,8 @@ from homeassistant.const import (CONF_ACCESS_TOKEN, CONF_NAME, STATE_UNKNOWN) from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['eliqonline==1.0.13'] +# pylint: disable=import-error, no-member +REQUIREMENTS = [] # ['eliqonline==1.0.13'] - package disappeared _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 7cc644129b3..ff6e680051d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -276,9 +276,6 @@ dsmr_parser==0.11 # homeassistant.components.sensor.dweet dweepy==0.3.0 -# homeassistant.components.sensor.eliqonline -eliqonline==1.0.13 - # homeassistant.components.enocean enocean==0.40