From 7151c4bcd736d216fd8615cacd28d05cffa72a69 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 30 Jan 2019 17:38:17 -0800 Subject: [PATCH 001/242] Bumped version to 0.88.0.dev0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 8701c682920..ba9d32e0daf 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ # coding: utf-8 """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 -MINOR_VERSION = 87 +MINOR_VERSION = 88 PATCH_VERSION = '0.dev0' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) From 511e35e8fdcba7f6f6665242e47c1b3339a2fc73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Herv=C3=A9?= Date: Thu, 31 Jan 2019 10:52:42 +0100 Subject: [PATCH 002/242] Fix typo in config entries doc (#20619) This fixes a typo in the docstring of config_entries which was propagated in the point component. --- homeassistant/components/point/config_flow.py | 2 +- homeassistant/config_entries.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/point/config_flow.py b/homeassistant/components/point/config_flow.py index 8cda30c7171..64583a5ab38 100644 --- a/homeassistant/components/point/config_flow.py +++ b/homeassistant/components/point/config_flow.py @@ -43,7 +43,7 @@ class PointFlowHandler(config_entries.ConfigFlow): """Handle a config flow.""" VERSION = 1 - CONNETION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL def __init__(self): """Initialize flow.""" diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 9c4c127f52e..c72f0f22827 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -30,7 +30,7 @@ At a minimum, each config flow will have to define a version number and the class ExampleConfigFlow(config_entries.ConfigFlow): VERSION = 1 - CONNETION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH async def async_step_user(self, user_input=None): … From c9971673dabfb014c6a4f40110265eae246a5a19 Mon Sep 17 00:00:00 2001 From: Julien Brochet Date: Thu, 31 Jan 2019 15:26:42 +0100 Subject: [PATCH 003/242] Update synology-srm dependency to 0.0.4 (#20625) --- homeassistant/components/device_tracker/synology_srm.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/synology_srm.py b/homeassistant/components/device_tracker/synology_srm.py index cc931b797d4..5c7ac9a5d00 100644 --- a/homeassistant/components/device_tracker/synology_srm.py +++ b/homeassistant/components/device_tracker/synology_srm.py @@ -13,7 +13,7 @@ from homeassistant.const import ( CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_VERIFY_SSL) -REQUIREMENTS = ['synology-srm==0.0.3'] +REQUIREMENTS = ['synology-srm==0.0.4'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index de8ad8c7914..2031eeaec19 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1610,7 +1610,7 @@ suds-py3==1.3.3.0 swisshydrodata==0.0.3 # homeassistant.components.device_tracker.synology_srm -synology-srm==0.0.3 +synology-srm==0.0.4 # homeassistant.components.tahoma tahoma-api==0.0.14 From e20c2aa11381fc7b658ecd586900046da555995f Mon Sep 17 00:00:00 2001 From: Sander Zumbrink Date: Thu, 31 Jan 2019 16:46:54 +0100 Subject: [PATCH 004/242] Add precision parameter to dsmr sensor (#19873) * Added precision parameter to dsmr sensor * Added precision parameter to dsmr sensor, added whitespace after comma * Added precision parameter to dsmr sensor * Added precision parameter to dsmr sensor, fixed test * Changed try except as requested --- homeassistant/components/sensor/dsmr.py | 18 ++++++++++++++---- tests/components/sensor/test_dsmr.py | 4 +++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/sensor/dsmr.py b/homeassistant/components/sensor/dsmr.py index ed3b409c49d..8b7d78aa038 100644 --- a/homeassistant/components/sensor/dsmr.py +++ b/homeassistant/components/sensor/dsmr.py @@ -24,9 +24,12 @@ REQUIREMENTS = ['dsmr_parser==0.12'] CONF_DSMR_VERSION = 'dsmr_version' CONF_RECONNECT_INTERVAL = 'reconnect_interval' +CONF_PRECISION = 'precision' DEFAULT_DSMR_VERSION = '2.2' DEFAULT_PORT = '/dev/ttyUSB0' +DEFAULT_PRECISION = 3 + DOMAIN = 'dsmr' ICON_GAS = 'mdi:fire' @@ -45,6 +48,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_DSMR_VERSION, default=DEFAULT_DSMR_VERSION): vol.All( cv.string, vol.In(['5', '4', '2.2'])), vol.Optional(CONF_RECONNECT_INTERVAL, default=30): int, + vol.Optional(CONF_PRECISION, default=DEFAULT_PRECISION): vol.Coerce(int), }) @@ -146,7 +150,7 @@ async def async_setup_platform(hass, config, async_add_entities, ] # Generate device entities - devices = [DSMREntity(name, obis) for name, obis in obis_mapping] + devices = [DSMREntity(name, obis, config) for name, obis in obis_mapping] # Protocol version specific obis if dsmr_version in ('4', '5'): @@ -156,8 +160,8 @@ async def async_setup_platform(hass, config, async_add_entities, # Add gas meter reading and derivative for usage devices += [ - DSMREntity('Gas Consumption', gas_obis), - DerivativeDSMREntity('Hourly Gas Consumption', gas_obis), + DSMREntity('Gas Consumption', gas_obis, config), + DerivativeDSMREntity('Hourly Gas Consumption', gas_obis, config), ] async_add_entities(devices) @@ -224,10 +228,11 @@ async def async_setup_platform(hass, config, async_add_entities, class DSMREntity(Entity): """Entity reading values from DSMR telegram.""" - def __init__(self, name, obis): + def __init__(self, name, obis, config): """Initialize entity.""" self._name = name self._obis = obis + self._config = config self.telegram = {} def get_dsmr_object_attr(self, attribute): @@ -267,6 +272,11 @@ class DSMREntity(Entity): if self._obis == obis.ELECTRICITY_ACTIVE_TARIFF: return self.translate_tariff(value) + try: + value = round(float(value), self._config[CONF_PRECISION]) + except TypeError: + pass + if value is not None: return value diff --git a/tests/components/sensor/test_dsmr.py b/tests/components/sensor/test_dsmr.py index 673cadd6208..8d9df11116a 100644 --- a/tests/components/sensor/test_dsmr.py +++ b/tests/components/sensor/test_dsmr.py @@ -95,7 +95,9 @@ def test_derivative(): """Test calculation of derivative value.""" from dsmr_parser.objects import MBusObject - entity = DerivativeDSMREntity('test', '1.0.0') + config = {'platform': 'dsmr'} + + entity = DerivativeDSMREntity('test', '1.0.0', config) yield from entity.async_update() assert entity.state is None, 'initial state not unknown' From 632b2042e4e21bfefd1bcdfc092367ab866ea64c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 31 Jan 2019 21:16:31 +0100 Subject: [PATCH 005/242] Split googlehome to a component with device tracker platform (#19971) * Add component for googlehome * Add missing name in CODEOWNERS * Linting issues * googledevices version bump * Use NAME from component instead of DOMAIN * Cleaner handling of accepted devices in for loop * Fixes one linting issue * Validate device_types * Fixes one linting issue * Fixes linting issue * Revert 0abb642 and import DOMAIN as GOOGLEHOME_DOMAIN * Return false if discovery_info is None * Combine if's in for loop * Use async_load_platfrom * Fix line length * Add error message to user * Shorter log message * error -> warning, remove period * Update .coveragerc * Move to correct place --- .coveragerc | 4 +- CODEOWNERS | 5 +- .../components/device_tracker/googlehome.py | 135 ++++++++---------- homeassistant/components/googlehome.py | 86 +++++++++++ requirements_all.txt | 6 +- 5 files changed, 159 insertions(+), 77 deletions(-) create mode 100644 homeassistant/components/googlehome.py diff --git a/.coveragerc b/.coveragerc index f1ff7715580..722f74a0b6a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -151,6 +151,9 @@ omit = homeassistant/components/google.py homeassistant/components/*/google.py + homeassistant/components/googlehome.py + homeassistant/components/*/googlehome.py + homeassistant/components/greeneye_monitor.py homeassistant/components/sensor/greeneye_monitor.py @@ -553,7 +556,6 @@ omit = homeassistant/components/device_tracker/ddwrt.py homeassistant/components/device_tracker/fritz.py homeassistant/components/device_tracker/google_maps.py - homeassistant/components/device_tracker/googlehome.py homeassistant/components/device_tracker/hitron_coda.py homeassistant/components/device_tracker/huawei_router.py homeassistant/components/device_tracker/icloud.py diff --git a/CODEOWNERS b/CODEOWNERS index 98eaca90076..a0d67c6191d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -62,7 +62,6 @@ homeassistant/components/cover/group.py @cdce8p homeassistant/components/cover/template.py @PhracturedBlue homeassistant/components/device_tracker/asuswrt.py @kennedyshead homeassistant/components/device_tracker/automatic.py @armills -homeassistant/components/device_tracker/googlehome.py @ludeeus homeassistant/components/device_tracker/huawei_router.py @abmantis homeassistant/components/device_tracker/quantum_gateway.py @cisasteelersfan homeassistant/components/device_tracker/tile.py @bachya @@ -188,6 +187,10 @@ homeassistant/components/eight_sleep.py @mezz64 homeassistant/components/*/eight_sleep.py @mezz64 homeassistant/components/esphome/*.py @OttoWinter +# G +homeassistant/components/googlehome.py @ludeeus +homeassistant/components/*/googlehome.py @ludeeus + # H homeassistant/components/hive.py @Rendili @KJonline homeassistant/components/*/hive.py @Rendili @KJonline diff --git a/homeassistant/components/device_tracker/googlehome.py b/homeassistant/components/device_tracker/googlehome.py index daa36d1d2c7..ba6d708295a 100644 --- a/homeassistant/components/device_tracker/googlehome.py +++ b/homeassistant/components/device_tracker/googlehome.py @@ -5,91 +5,82 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.googlehome/ """ import logging +from datetime import timedelta -import voluptuous as vol +from homeassistant.components.device_tracker import DeviceScanner +from homeassistant.components.googlehome import ( + CLIENT, DOMAIN as GOOGLEHOME_DOMAIN, NAME) +from homeassistant.helpers.event import async_track_time_interval +from homeassistant.util import slugify -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) -from homeassistant.const import CONF_HOST +DEPENDENCIES = ['googlehome'] -REQUIREMENTS = ['ghlocalapi==0.3.5'] +DEFAULT_SCAN_INTERVAL = timedelta(seconds=10) _LOGGER = logging.getLogger(__name__) -CONF_RSSI_THRESHOLD = 'rssi_threshold' -PLATFORM_SCHEMA = vol.All( - PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_RSSI_THRESHOLD, default=-70): vol.Coerce(int), - })) - - -async def async_get_scanner(hass, config): - """Validate the configuration and return an Google Home scanner.""" - scanner = GoogleHomeDeviceScanner(hass, config[DOMAIN]) - await scanner.async_connect() - return scanner if scanner.success_init else None +async def async_setup_scanner(hass, config, async_see, discovery_info=None): + """Validate the configuration and return a Google Home scanner.""" + if discovery_info is None: + _LOGGER.warning( + "To use this you need to configure the 'googlehome' component") + return False + scanner = GoogleHomeDeviceScanner(hass, hass.data[CLIENT], + discovery_info, async_see) + return await scanner.async_init() class GoogleHomeDeviceScanner(DeviceScanner): """This class queries a Google Home unit.""" - def __init__(self, hass, config): + def __init__(self, hass, client, config, async_see): """Initialize the scanner.""" - from ghlocalapi.device_info import DeviceInfo - from ghlocalapi.bluetooth import Bluetooth + self.async_see = async_see + self.hass = hass + self.rssi = config['rssi_threshold'] + self.device_types = config['device_types'] + self.host = config['host'] + self.client = client - self.last_results = {} + async def async_init(self): + """Further initialize connection to Google Home.""" + await self.client.update_data(self.host) + data = self.hass.data[GOOGLEHOME_DOMAIN][self.host] + info = data.get('info', {}) + connected = bool(info) + if connected: + await self.async_update() + async_track_time_interval(self.hass, + self.async_update, + DEFAULT_SCAN_INTERVAL) + return connected - self.success_init = False - self._host = config[CONF_HOST] - self.rssi_threshold = config[CONF_RSSI_THRESHOLD] - - session = async_get_clientsession(hass) - self.deviceinfo = DeviceInfo(hass.loop, session, self._host) - self.scanner = Bluetooth(hass.loop, session, self._host) - - async def async_connect(self): - """Initialize connection to Google Home.""" - await self.deviceinfo.get_device_info() - data = self.deviceinfo.device_info - self.success_init = data is not None - - async def async_scan_devices(self): - """Scan for new devices and return a list with found device IDs.""" - await self.async_update_info() - return list(self.last_results.keys()) - - async def async_get_device_name(self, device): - """Return the name of the given device or None if we don't know.""" - if device not in self.last_results: - return None - return '{}_{}'.format(self._host, - self.last_results[device]['btle_mac_address']) - - async def get_extra_attributes(self, device): - """Return the extra attributes of the device.""" - return self.last_results[device] - - async def async_update_info(self): + async def async_update(self, now=None): """Ensure the information from Google Home is up to date.""" - _LOGGER.debug('Checking Devices...') - await self.scanner.scan_for_devices() - await self.scanner.get_scan_result() - ghname = self.deviceinfo.device_info['name'] - devices = {} - for device in self.scanner.devices: - if device['rssi'] > self.rssi_threshold: - uuid = '{}_{}'.format(self._host, device['mac_address']) - devices[uuid] = {} - devices[uuid]['rssi'] = device['rssi'] - devices[uuid]['btle_mac_address'] = device['mac_address'] - devices[uuid]['ghname'] = ghname - devices[uuid]['source_type'] = 'bluetooth' - if device['name']: - devices[uuid]['btle_name'] = device['name'] - await self.scanner.clear_scan_result() - self.last_results = devices + _LOGGER.debug('Checking Devices on %s', self.host) + await self.client.update_data(self.host) + data = self.hass.data[GOOGLEHOME_DOMAIN][self.host] + info = data.get('info') + bluetooth = data.get('bluetooth') + if info is None or bluetooth is None: + return + google_home_name = info.get('name', NAME) + + for device in bluetooth: + if (device['device_type'] not in + self.device_types or device['rssi'] < self.rssi): + continue + + name = "{} {}".format(self.host, device['mac_address']) + + attributes = {} + attributes['btle_mac_address'] = device['mac_address'] + attributes['ghname'] = google_home_name + attributes['rssi'] = device['rssi'] + attributes['source_type'] = 'bluetooth' + if device['name']: + attributes['name'] = device['name'] + + await self.async_see(dev_id=slugify(name), + attributes=attributes) diff --git a/homeassistant/components/googlehome.py b/homeassistant/components/googlehome.py new file mode 100644 index 00000000000..78bd2d7df3f --- /dev/null +++ b/homeassistant/components/googlehome.py @@ -0,0 +1,86 @@ +""" +Support Google Home units. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/googlehome/ +""" +import logging + +import asyncio +import voluptuous as vol +from homeassistant.const import CONF_DEVICES, CONF_HOST +from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +_LOGGER = logging.getLogger(__name__) + +REQUIREMENTS = ['googledevices==1.0.2'] + +DOMAIN = 'googlehome' +CLIENT = 'googlehome_client' + +NAME = 'GoogleHome' + +CONF_DEVICE_TYPES = 'device_types' +CONF_RSSI_THRESHOLD = 'rssi_threshold' + +DEVICE_TYPES = [1, 2, 3] +DEFAULT_RSSI_THRESHOLD = -70 + +DEVICE_CONFIG = vol.Schema({ + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_DEVICE_TYPES, + default=DEVICE_TYPES): vol.All(cv.ensure_list, + [vol.In(DEVICE_TYPES)]), + vol.Optional(CONF_RSSI_THRESHOLD, + default=DEFAULT_RSSI_THRESHOLD): vol.Coerce(int), +}) + + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_DEVICES): vol.All(cv.ensure_list, [DEVICE_CONFIG]), + }), +}, extra=vol.ALLOW_EXTRA) + + +async def async_setup(hass, config): + """Set up the Google Home component.""" + hass.data[DOMAIN] = {} + hass.data[CLIENT] = GoogleHomeClient(hass) + + for device in config[DOMAIN][CONF_DEVICES]: + hass.data[DOMAIN][device['host']] = {} + hass.async_create_task( + discovery.async_load_platform( + hass, 'device_tracker', DOMAIN, device, config)) + + return True + + +class GoogleHomeClient: + """Handle all communication with the Google Home unit.""" + + def __init__(self, hass): + """Initialize the Google Home Client.""" + self.hass = hass + self._connected = None + + async def update_data(self, host): + """Update data from Google Home.""" + from googledevices.api.connect import Cast + _LOGGER.debug("Updating Google Home data for %s", host) + session = async_get_clientsession(self.hass) + + device_info = await Cast(host, self.hass.loop, session).info() + device_info_data = await device_info.get_device_info() + self._connected = bool(device_info_data) + + bluetooth = await Cast(host, self.hass.loop, session).bluetooth() + await bluetooth.scan_for_devices() + await asyncio.sleep(5) + bluetooth_data = await bluetooth.get_scan_result() + + self.hass.data[DOMAIN][host]['info'] = device_info_data + self.hass.data[DOMAIN][host]['bluetooth'] = bluetooth_data diff --git a/requirements_all.txt b/requirements_all.txt index 2031eeaec19..5faf6f2c6d0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -456,9 +456,6 @@ geojson_client==0.3 # homeassistant.components.sensor.geo_rss_events georss_client==0.5 -# homeassistant.components.device_tracker.googlehome -ghlocalapi==0.3.5 - # homeassistant.components.sensor.gitter gitterpy==0.1.7 @@ -471,6 +468,9 @@ gntp==1.0.3 # homeassistant.components.google google-api-python-client==1.6.4 +# homeassistant.components.googlehome +googledevices==1.0.2 + # homeassistant.components.sensor.google_travel_time googlemaps==2.5.1 From d7b61f7ff6213030a8de91ef1344826dbacdec0d Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Thu, 31 Jan 2019 12:22:29 -0800 Subject: [PATCH 006/242] Move mqtt_mock to tests/components/mqtt/conftest.py (#20621) * Move mqtt_mock to tests/components/mqtt/conftest.py * Move mqtt room presence sensor test to tests/components/mqtt * Revert "Move mqtt room presence sensor test to tests/components/mqtt" This reverts commit e08bc143 * Decouple mqtt room presence sensor test and mqtt_mock --- tests/components/mqtt/conftest.py | 12 ++++ tests/components/sensor/test_mqtt_room.py | 6 +- tests/components/test_snips.py | 72 ++++++++++++++++------- tests/conftest.py | 10 +--- 4 files changed, 67 insertions(+), 33 deletions(-) create mode 100644 tests/components/mqtt/conftest.py diff --git a/tests/components/mqtt/conftest.py b/tests/components/mqtt/conftest.py new file mode 100644 index 00000000000..290682549f5 --- /dev/null +++ b/tests/components/mqtt/conftest.py @@ -0,0 +1,12 @@ +"""Test fixtures for mqtt component.""" +import pytest + +from tests.common import async_mock_mqtt_component + + +@pytest.fixture +def mqtt_mock(loop, hass): + """Fixture to mock MQTT.""" + client = loop.run_until_complete(async_mock_mqtt_component(hass)) + client.reset_mock() + return client diff --git a/tests/components/sensor/test_mqtt_room.py b/tests/components/sensor/test_mqtt_room.py index 980655bac78..fe1fa318016 100644 --- a/tests/components/sensor/test_mqtt_room.py +++ b/tests/components/sensor/test_mqtt_room.py @@ -10,7 +10,7 @@ from homeassistant.components.mqtt import (CONF_STATE_TOPIC, CONF_QOS, from homeassistant.const import (CONF_NAME, CONF_PLATFORM) from homeassistant.util import dt -from tests.common import async_fire_mqtt_message +from tests.common import async_fire_mqtt_message, async_mock_mqtt_component DEVICE_ID = '123TESTMAC' NAME = 'test_device' @@ -64,8 +64,10 @@ async def assert_distance(hass, distance): assert state.attributes.get('distance') == distance -async def test_room_update(hass, mqtt_mock): +async def test_room_update(hass): """Test the updating between rooms.""" + await async_mock_mqtt_component(hass) + assert await async_setup_component(hass, sensor.DOMAIN, { sensor.DOMAIN: { CONF_PLATFORM: 'mqtt_room', diff --git a/tests/components/test_snips.py b/tests/components/test_snips.py index 977cd966981..7bcfb11ff5c 100644 --- a/tests/components/test_snips.py +++ b/tests/components/test_snips.py @@ -10,11 +10,13 @@ from homeassistant.components.mqtt import MQTT_PUBLISH_SCHEMA import homeassistant.components.snips as snips from homeassistant.helpers.intent import (ServiceIntentHandler, async_register) from tests.common import (async_fire_mqtt_message, async_mock_intent, - async_mock_service) + async_mock_service, async_mock_mqtt_component) -async def test_snips_config(hass, mqtt_mock): +async def test_snips_config(hass): """Test Snips Config.""" + await async_mock_mqtt_component(hass) + result = await async_setup_component(hass, "snips", { "snips": { "feedback_sounds": True, @@ -25,8 +27,10 @@ async def test_snips_config(hass, mqtt_mock): assert result -async def test_snips_bad_config(hass, mqtt_mock): +async def test_snips_bad_config(hass): """Test Snips bad config.""" + await async_mock_mqtt_component(hass) + result = await async_setup_component(hass, "snips", { "snips": { "feedback_sounds": "on", @@ -37,8 +41,10 @@ async def test_snips_bad_config(hass, mqtt_mock): assert not result -async def test_snips_config_feedback_on(hass, mqtt_mock): +async def test_snips_config_feedback_on(hass): """Test Snips Config.""" + await async_mock_mqtt_component(hass) + calls = async_mock_service(hass, 'mqtt', 'publish', MQTT_PUBLISH_SCHEMA) result = await async_setup_component(hass, "snips", { "snips": { @@ -57,8 +63,10 @@ async def test_snips_config_feedback_on(hass, mqtt_mock): assert calls[1].data['retain'] -async def test_snips_config_feedback_off(hass, mqtt_mock): +async def test_snips_config_feedback_off(hass): """Test Snips Config.""" + await async_mock_mqtt_component(hass) + calls = async_mock_service(hass, 'mqtt', 'publish', MQTT_PUBLISH_SCHEMA) result = await async_setup_component(hass, "snips", { "snips": { @@ -77,8 +85,10 @@ async def test_snips_config_feedback_off(hass, mqtt_mock): assert not calls[1].data['retain'] -async def test_snips_config_no_feedback(hass, mqtt_mock): +async def test_snips_config_no_feedback(hass): """Test Snips Config.""" + await async_mock_mqtt_component(hass) + calls = async_mock_service(hass, 'snips', 'say') result = await async_setup_component(hass, "snips", { "snips": {}, @@ -88,8 +98,10 @@ async def test_snips_config_no_feedback(hass, mqtt_mock): assert len(calls) == 0 -async def test_snips_intent(hass, mqtt_mock): +async def test_snips_intent(hass): """Test intent via Snips.""" + await async_mock_mqtt_component(hass) + result = await async_setup_component(hass, "snips", { "snips": {}, }) @@ -134,8 +146,10 @@ async def test_snips_intent(hass, mqtt_mock): assert intent.text_input == 'turn the lights green' -async def test_snips_service_intent(hass, mqtt_mock): +async def test_snips_service_intent(hass): """Test ServiceIntentHandler via Snips.""" + await async_mock_mqtt_component(hass) + hass.states.async_set('light.kitchen', 'off') calls = async_mock_service(hass, 'light', 'turn_on') result = await async_setup_component(hass, "snips", { @@ -178,8 +192,10 @@ async def test_snips_service_intent(hass, mqtt_mock): assert 'site_id' not in calls[0].data -async def test_snips_intent_with_duration(hass, mqtt_mock): +async def test_snips_intent_with_duration(hass): """Test intent with Snips duration.""" + await async_mock_mqtt_component(hass) + result = await async_setup_component(hass, "snips", { "snips": {}, }) @@ -232,8 +248,10 @@ async def test_snips_intent_with_duration(hass, mqtt_mock): 'timer_duration_raw': {'value': 'five minutes'}} -async def test_intent_speech_response(hass, mqtt_mock): +async def test_intent_speech_response(hass): """Test intent speech response via Snips.""" + await async_mock_mqtt_component(hass) + calls = async_mock_service(hass, 'mqtt', 'publish', MQTT_PUBLISH_SCHEMA) result = await async_setup_component(hass, "snips", { "snips": {}, @@ -273,8 +291,10 @@ async def test_intent_speech_response(hass, mqtt_mock): assert topic == 'hermes/dialogueManager/endSession' -async def test_unknown_intent(hass, mqtt_mock, caplog): +async def test_unknown_intent(hass, caplog): """Test unknown intent.""" + await async_mock_mqtt_component(hass) + caplog.set_level(logging.WARNING) result = await async_setup_component(hass, "snips", { "snips": {}, @@ -297,8 +317,10 @@ async def test_unknown_intent(hass, mqtt_mock, caplog): assert 'Received unknown intent unknownIntent' in caplog.text -async def test_snips_intent_user(hass, mqtt_mock): +async def test_snips_intent_user(hass): """Test intentName format user_XXX__intentName.""" + await async_mock_mqtt_component(hass) + result = await async_setup_component(hass, "snips", { "snips": {}, }) @@ -324,8 +346,10 @@ async def test_snips_intent_user(hass, mqtt_mock): assert intent.intent_type == 'Lights' -async def test_snips_intent_username(hass, mqtt_mock): +async def test_snips_intent_username(hass): """Test intentName format username:intentName.""" + await async_mock_mqtt_component(hass) + result = await async_setup_component(hass, "snips", { "snips": {}, }) @@ -351,8 +375,10 @@ async def test_snips_intent_username(hass, mqtt_mock): assert intent.intent_type == 'Lights' -async def test_snips_low_probability(hass, mqtt_mock, caplog): +async def test_snips_low_probability(hass, caplog): """Test intent via Snips.""" + await async_mock_mqtt_component(hass) + caplog.set_level(logging.WARNING) result = await async_setup_component(hass, "snips", { "snips": { @@ -378,8 +404,10 @@ async def test_snips_low_probability(hass, mqtt_mock, caplog): assert 'Intent below probaility threshold 0.49 < 0.5' in caplog.text -async def test_intent_special_slots(hass, mqtt_mock): +async def test_intent_special_slots(hass): """Test intent special slot values via Snips.""" + await async_mock_mqtt_component(hass) + calls = async_mock_service(hass, 'light', 'turn_on') result = await async_setup_component(hass, "snips", { "snips": {}, @@ -420,7 +448,7 @@ async def test_intent_special_slots(hass, mqtt_mock): assert calls[0].data['site_id'] == 'default' -async def test_snips_say(hass, caplog): +async def test_snips_say(hass): """Test snips say with invalid config.""" calls = async_mock_service(hass, 'snips', 'say', snips.SERVICE_SCHEMA_SAY) data = {'text': 'Hello'} @@ -433,7 +461,7 @@ async def test_snips_say(hass, caplog): assert calls[0].data['text'] == 'Hello' -async def test_snips_say_action(hass, caplog): +async def test_snips_say_action(hass): """Test snips say_action with invalid config.""" calls = async_mock_service(hass, 'snips', 'say_action', snips.SERVICE_SCHEMA_SAY_ACTION) @@ -449,7 +477,7 @@ async def test_snips_say_action(hass, caplog): assert calls[0].data['intent_filter'] == ['myIntent'] -async def test_snips_say_invalid_config(hass, caplog): +async def test_snips_say_invalid_config(hass): """Test snips say with invalid config.""" calls = async_mock_service(hass, 'snips', 'say', snips.SERVICE_SCHEMA_SAY) @@ -462,7 +490,7 @@ async def test_snips_say_invalid_config(hass, caplog): assert len(calls) == 0 -async def test_snips_say_action_invalid(hass, caplog): +async def test_snips_say_action_invalid(hass): """Test snips say_action with invalid config.""" calls = async_mock_service(hass, 'snips', 'say_action', snips.SERVICE_SCHEMA_SAY_ACTION) @@ -476,7 +504,7 @@ async def test_snips_say_action_invalid(hass, caplog): assert len(calls) == 0 -async def test_snips_feedback_on(hass, caplog): +async def test_snips_feedback_on(hass): """Test snips say with invalid config.""" calls = async_mock_service(hass, 'snips', 'feedback_on', snips.SERVICE_SCHEMA_FEEDBACK) @@ -491,7 +519,7 @@ async def test_snips_feedback_on(hass, caplog): assert calls[0].data['site_id'] == 'remote' -async def test_snips_feedback_off(hass, caplog): +async def test_snips_feedback_off(hass): """Test snips say with invalid config.""" calls = async_mock_service(hass, 'snips', 'feedback_off', snips.SERVICE_SCHEMA_FEEDBACK) @@ -506,7 +534,7 @@ async def test_snips_feedback_off(hass, caplog): assert calls[0].data['site_id'] == 'remote' -async def test_snips_feedback_config(hass, caplog): +async def test_snips_feedback_config(hass): """Test snips say with invalid config.""" calls = async_mock_service(hass, 'snips', 'feedback_on', snips.SERVICE_SCHEMA_FEEDBACK) diff --git a/tests/conftest.py b/tests/conftest.py index 528ad195195..c2e8eb1eb28 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,7 +14,7 @@ from homeassistant.auth.const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY from homeassistant.auth.providers import legacy_api_password, homeassistant from tests.common import ( - async_test_home_assistant, INSTANCES, async_mock_mqtt_component, mock_coro, + async_test_home_assistant, INSTANCES, mock_coro, mock_storage as mock_storage, MockUser, CLIENT_ID) from tests.test_util.aiohttp import mock_aiohttp_client from tests.mock.zwave import MockNetwork, MockOption @@ -92,14 +92,6 @@ def aioclient_mock(): yield mock_session -@pytest.fixture -def mqtt_mock(loop, hass): - """Fixture to mock MQTT.""" - client = loop.run_until_complete(async_mock_mqtt_component(hass)) - client.reset_mock() - return client - - @pytest.fixture def mock_openzwave(): """Mock out Open Z-Wave.""" From c63a37d0ad5aec644bd95ffd7cafc723e586fe6e Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Thu, 31 Jan 2019 15:24:52 -0500 Subject: [PATCH 007/242] Revert #20611: code in Abode alarm panel (#20629) --- homeassistant/components/alarm_control_panel/abode.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/abode.py b/homeassistant/components/alarm_control_panel/abode.py index 6d4e28243ea..947e2916300 100644 --- a/homeassistant/components/alarm_control_panel/abode.py +++ b/homeassistant/components/alarm_control_panel/abode.py @@ -57,11 +57,6 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanel): state = None return state - @property - def code_format(self): - """Return one or more digits/characters.""" - return alarm.FORMAT_NUMBER - def alarm_disarm(self, code=None): """Send disarm command.""" self._device.set_standby() From 74794102c8c0ebae853986313d32b8d30b0db665 Mon Sep 17 00:00:00 2001 From: Julius Mittenzwei Date: Fri, 1 Feb 2019 02:13:47 +0100 Subject: [PATCH 008/242] Support for new velux api, added cover.velux (#18738) * Support for new velux api, added cover.velux * More steps on new velux covers. * correct position handling of velux windows * Following suggestion from hound. * bumped version * added cover stop * bumped version of pyvlx * Bumped version to 0.2.8 * removed log_frames parameter --- homeassistant/components/cover/velux.py | 96 +++++++++++++++++++++++++ homeassistant/components/velux.py | 5 +- requirements_all.txt | 2 +- 3 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/cover/velux.py diff --git a/homeassistant/components/cover/velux.py b/homeassistant/components/cover/velux.py new file mode 100644 index 00000000000..b78d981c695 --- /dev/null +++ b/homeassistant/components/cover/velux.py @@ -0,0 +1,96 @@ +""" +Support for Velux covers. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/cover.velux/ +""" + +from homeassistant.components.cover import ( + ATTR_POSITION, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, + SUPPORT_STOP, CoverDevice) +from homeassistant.components.velux import DATA_VELUX +from homeassistant.core import callback + +DEPENDENCIES = ['velux'] + + +async def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): + """Set up cover(s) for Velux platform.""" + entities = [] + for node in hass.data[DATA_VELUX].pyvlx.nodes: + from pyvlx import OpeningDevice + if isinstance(node, OpeningDevice): + entities.append(VeluxCover(node)) + async_add_entities(entities) + + +class VeluxCover(CoverDevice): + """Representation of a Velux cover.""" + + def __init__(self, node): + """Initialize the cover.""" + self.node = node + + @callback + def async_register_callbacks(self): + """Register callbacks to update hass after device was changed.""" + async def after_update_callback(device): + """Call after device was updated.""" + await self.async_update_ha_state() + self.node.register_device_updated_cb(after_update_callback) + + async def async_added_to_hass(self): + """Store register state change callback.""" + self.async_register_callbacks() + + @property + def name(self): + """Return the name of the Velux device.""" + return self.node.name + + @property + def should_poll(self): + """No polling needed within Velux.""" + return False + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORT_OPEN | SUPPORT_CLOSE | \ + SUPPORT_SET_POSITION | SUPPORT_STOP + + @property + def current_cover_position(self): + """Return the current position of the cover.""" + return 100 - self.node.position.position_percent + + @property + def device_class(self): + """Define this cover as a window.""" + return 'window' + + @property + def is_closed(self): + """Return if the cover is closed.""" + return self.node.position.closed + + async def async_close_cover(self, **kwargs): + """Close the cover.""" + await self.node.close() + + async def async_open_cover(self, **kwargs): + """Open the cover.""" + await self.node.open() + + async def async_set_cover_position(self, **kwargs): + """Move the cover to a specific position.""" + if ATTR_POSITION in kwargs: + position_percent = 100 - kwargs[ATTR_POSITION] + from pyvlx import Position + await self.node.set_position( + Position(position_percent=position_percent)) + + async def async_stop_cover(self, **kwargs): + """Stop the cover.""" + await self.node.stop() diff --git a/homeassistant/components/velux.py b/homeassistant/components/velux.py index c3c6c1e2114..010612acc7d 100644 --- a/homeassistant/components/velux.py +++ b/homeassistant/components/velux.py @@ -14,10 +14,10 @@ from homeassistant.const import (CONF_HOST, CONF_PASSWORD) DOMAIN = "velux" DATA_VELUX = "data_velux" -SUPPORTED_DOMAINS = ['scene'] +SUPPORTED_DOMAINS = ['cover', 'scene'] _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pyvlx==0.1.3'] +REQUIREMENTS = ['pyvlx==0.2.8'] CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -59,3 +59,4 @@ class VeluxModule: async def async_start(self): """Start velux component.""" await self.pyvlx.load_scenes() + await self.pyvlx.load_nodes() diff --git a/requirements_all.txt b/requirements_all.txt index 5faf6f2c6d0..8908e251afc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1411,7 +1411,7 @@ pyvesync==0.1.1 pyvizio==0.0.4 # homeassistant.components.velux -pyvlx==0.1.3 +pyvlx==0.2.8 # homeassistant.components.notify.html5 pywebpush==1.6.0 From 5f930debd4d3733a1441c1a3cc4988eaa15f3076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Herv=C3=A9?= Date: Fri, 1 Feb 2019 06:59:31 +0100 Subject: [PATCH 009/242] Fix xiaomi default gateway in services (#20623) When xiaomi_aqara services with one gateway, the default should be set to the sid of the gateway, not the python object itself, otherwise the call fails. --- homeassistant/components/xiaomi_aqara.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/xiaomi_aqara.py b/homeassistant/components/xiaomi_aqara.py index 25e7a72db90..ca2d8c1a169 100644 --- a/homeassistant/components/xiaomi_aqara.py +++ b/homeassistant/components/xiaomi_aqara.py @@ -324,7 +324,7 @@ def _add_gateway_to_schema(xiaomi, schema): # If the user has only 1 gateway, make it the default for services. if len(gateways) == 1: - kwargs['default'] = gateways[0] + kwargs['default'] = gateways[0].sid return schema.extend({ vol.Required(ATTR_GW_MAC, **kwargs): gateway From 9e7d7354ed86826b51aef842f9bfd59d5c9c3fd0 Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Thu, 31 Jan 2019 23:58:29 -0800 Subject: [PATCH 010/242] Fix sensor.cpuspeed inside docker container (#20614) (#20656) --- homeassistant/components/sensor/cpuspeed.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/cpuspeed.py b/homeassistant/components/sensor/cpuspeed.py index e97972dae3b..f69d0b285ba 100644 --- a/homeassistant/components/sensor/cpuspeed.py +++ b/homeassistant/components/sensor/cpuspeed.py @@ -21,6 +21,9 @@ ATTR_BRAND = 'Brand' ATTR_HZ = 'GHz Advertised' ATTR_ARCH = 'arch' +HZ_ACTUAL_RAW = 'hz_actual_raw' +HZ_ADVERTISED_RAW = 'hz_advertised_raw' + DEFAULT_NAME = 'CPU speed' ICON = 'mdi:pulse' @@ -66,12 +69,17 @@ class CpuSpeedSensor(Entity): def device_state_attributes(self): """Return the state attributes.""" if self.info is not None: - return { + attrs = { ATTR_ARCH: self.info['arch'], ATTR_BRAND: self.info['brand'], - ATTR_HZ: round(self.info['hz_advertised_raw'][0]/10**9, 2) } + if HZ_ADVERTISED_RAW in self.info: + attrs[ATTR_HZ] = round( + self.info[HZ_ADVERTISED_RAW][0] / 10 ** 9, 2 + ) + return attrs + @property def icon(self): """Return the icon to use in the frontend, if any.""" @@ -82,4 +90,9 @@ class CpuSpeedSensor(Entity): from cpuinfo import cpuinfo self.info = cpuinfo.get_cpu_info() - self._state = round(float(self.info['hz_actual_raw'][0])/10**9, 2) + if HZ_ACTUAL_RAW in self.info: + self._state = round( + float(self.info[HZ_ACTUAL_RAW][0]) / 10 ** 9, 2 + ) + else: + self._state = None From 3e2dae62c0815134060105f281e2ccad26a6d8c2 Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Fri, 1 Feb 2019 00:40:27 -0800 Subject: [PATCH 011/242] Fix allow extra in locative webhook schema validation (#20657) * Allow extra in locative webhook schema validation (fixes #20566) * Remove extra attribute --- homeassistant/components/locative/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/locative/__init__.py b/homeassistant/components/locative/__init__.py index 195eacf17c2..1f7f9c3a686 100644 --- a/homeassistant/components/locative/__init__.py +++ b/homeassistant/components/locative/__init__.py @@ -48,8 +48,8 @@ WEBHOOK_SCHEMA = vol.All( vol.Required(ATTR_LONGITUDE): cv.longitude, vol.Required(ATTR_DEVICE_ID): cv.string, vol.Required(ATTR_TRIGGER): cv.string, - vol.Optional(ATTR_ID): vol.All(cv.string, _id) - }), + vol.Optional(ATTR_ID): vol.All(cv.string, _id), + }, extra=vol.ALLOW_EXTRA), _validate_test_mode ) From 25e16390508ae2b5cbc994e419fc1d310fedce28 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Fri, 1 Feb 2019 00:52:30 -0800 Subject: [PATCH 012/242] Upgrade blinkpy to re-enable motion detection (#20651) --- homeassistant/components/blink/__init__.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/blink/__init__.py b/homeassistant/components/blink/__init__.py index 57500fcc8a6..82815d11a6e 100644 --- a/homeassistant/components/blink/__init__.py +++ b/homeassistant/components/blink/__init__.py @@ -15,7 +15,7 @@ from homeassistant.const import ( CONF_BINARY_SENSORS, CONF_SENSORS, CONF_FILENAME, CONF_MONITORED_CONDITIONS, TEMP_FAHRENHEIT) -REQUIREMENTS = ['blinkpy==0.11.2'] +REQUIREMENTS = ['blinkpy==0.12.1'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 8908e251afc..f9e4d5f9352 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -199,7 +199,7 @@ bellows==0.7.0 bimmer_connected==0.5.3 # homeassistant.components.blink -blinkpy==0.11.2 +blinkpy==0.12.1 # homeassistant.components.light.blinksticklight blinkstick==1.1.8 From 7429b9d87e4fba37aee6aae7eb45701522d73aaa Mon Sep 17 00:00:00 2001 From: zewelor Date: Fri, 1 Feb 2019 10:59:05 +0100 Subject: [PATCH 013/242] Fix parsing yeelight custom effects, when not present in config (#20658) --- homeassistant/components/light/yeelight.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/light/yeelight.py b/homeassistant/components/light/yeelight.py index 249f542325f..b678fcd2799 100644 --- a/homeassistant/components/light/yeelight.py +++ b/homeassistant/components/light/yeelight.py @@ -193,8 +193,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): name = device_config[CONF_NAME] _LOGGER.debug("Adding configured %s", name) - custom_effects = _parse_custom_effects(config[CONF_CUSTOM_EFFECTS]) device = {'name': name, 'ipaddr': ipaddr} + + if CONF_CUSTOM_EFFECTS in config: + custom_effects = \ + _parse_custom_effects(config[CONF_CUSTOM_EFFECTS]) + else: + custom_effects = None + light = YeelightLight(device, device_config, custom_effects=custom_effects) lights.append(light) From 47d24759f25a183f26f96fe595fcbd3750ef0149 Mon Sep 17 00:00:00 2001 From: emkay82 <37954256+emkay82@users.noreply.github.com> Date: Fri, 1 Feb 2019 14:53:40 +0100 Subject: [PATCH 014/242] Fix pjlink issue (#20510) * Fix issue #16606 Some projectors do not respond to pjlink requests during a short period after the status changes or when its in standby, resulting in pypjlink2 throwing an error. This fix catches these errors. Furthermore, only the status 'on' and 'warm-up' is interpreted as switched on, because 'cooling' is actually a switched off status. * Update pjlink.py Improved error handling * Update pjlink.py Improved error handling * Update pjlink.py Clean up --- .../components/media_player/pjlink.py | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/media_player/pjlink.py b/homeassistant/components/media_player/pjlink.py index 168cde4a792..0609b75f98d 100644 --- a/homeassistant/components/media_player/pjlink.py +++ b/homeassistant/components/media_player/pjlink.py @@ -93,15 +93,31 @@ class PjLinkDevice(MediaPlayerDevice): def update(self): """Get the latest state from the device.""" + from pypjlink.projector import ProjectorError with self.projector() as projector: - pwstate = projector.get_power() - if pwstate == 'off': - self._pwstate = STATE_OFF - else: - self._pwstate = STATE_ON - self._muted = projector.get_mute()[1] - self._current_source = \ - format_input_source(*projector.get_input()) + try: + pwstate = projector.get_power() + if pwstate in ('on', 'warm-up'): + self._pwstate = STATE_ON + else: + self._pwstate = STATE_OFF + self._muted = projector.get_mute()[1] + self._current_source = \ + format_input_source(*projector.get_input()) + except KeyError as err: + if str(err) == "'OK'": + self._pwstate = STATE_OFF + self._muted = False + self._current_source = None + else: + raise + except ProjectorError as err: + if str(err) == 'unavailable time': + self._pwstate = STATE_OFF + self._muted = False + self._current_source = None + else: + raise @property def name(self): From 44c2a8310517e9e20cadcfe06cf798a1ad072b1c Mon Sep 17 00:00:00 2001 From: emontnemery Date: Fri, 1 Feb 2019 17:14:02 +0100 Subject: [PATCH 015/242] Add PLATFORM_SCHEMA_BASE support to check_config.py (#20663) --- homeassistant/scripts/check_config.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index 50463c28bd1..67bc97da992 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -345,14 +345,21 @@ def check_ha_config_file(hass): _comp_error(ex, domain, config) continue - if not hasattr(component, 'PLATFORM_SCHEMA'): + if (not hasattr(component, 'PLATFORM_SCHEMA') and + not hasattr(component, 'PLATFORM_SCHEMA_BASE')): continue platforms = [] for p_name, p_config in config_per_platform(config, domain): # Validate component specific platform schema try: - p_validated = component.PLATFORM_SCHEMA(p_config) + if hasattr(component, 'PLATFORM_SCHEMA_BASE'): + p_validated = \ + component.PLATFORM_SCHEMA_BASE( # type: ignore + p_config) + else: + p_validated = component.PLATFORM_SCHEMA( # type: ignore + p_config) except vol.Invalid as ex: _comp_error(ex, domain, config) continue From f19bbaec0863a38691afcfe3a0d05ce5f85e2cf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ak=C4=B1n=20=C3=96mero=C4=9Flu?= Date: Fri, 1 Feb 2019 20:28:30 +0300 Subject: [PATCH 016/242] Update deconz integration text for PWA (#20634) Text should reflect the GUI it describes --- homeassistant/components/deconz/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/deconz/strings.json b/homeassistant/components/deconz/strings.json index 9ab7b56c0ca..1bf7235713a 100644 --- a/homeassistant/components/deconz/strings.json +++ b/homeassistant/components/deconz/strings.json @@ -11,7 +11,7 @@ }, "link": { "title": "Link with deCONZ", - "description": "Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ system settings\n2. Press \"Unlock Gateway\" button" + "description": "Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ Settings -> Gateway -> Advanced\n2. Press \"Authenticate app\" button" }, "options": { "title": "Extra configuration options for deCONZ", From aec8ad21888d1893c4a52661ac00f2243b45993a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20V=C3=B6lker?= Date: Fri, 1 Feb 2019 18:35:49 +0100 Subject: [PATCH 017/242] InfluxDB - change connection test method (#20666) --- homeassistant/components/sensor/influxdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/influxdb.py b/homeassistant/components/sensor/influxdb.py index 9f34580344c..35229c2a805 100644 --- a/homeassistant/components/sensor/influxdb.py +++ b/homeassistant/components/sensor/influxdb.py @@ -111,7 +111,7 @@ class InfluxSensor(Entity): database=database, ssl=influx_conf['ssl'], verify_ssl=influx_conf['verify_ssl']) try: - influx.query("SHOW DIAGNOSTICS;") + influx.query("SHOW SERIES LIMIT 1;") self.connected = True self.data = InfluxSensorData( influx, query.get(CONF_GROUP_FUNCTION), query.get(CONF_FIELD), From 3f997aefc1579664b76f8395d3682a579ab7adf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Fri, 1 Feb 2019 19:42:45 +0200 Subject: [PATCH 018/242] Add huawei_lte notify component (#19544) * Add huawei_lte notify component * Use CONF_RECIPIENT instead of ATTR_TARGET in config --- homeassistant/components/notify/huawei_lte.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 homeassistant/components/notify/huawei_lte.py diff --git a/homeassistant/components/notify/huawei_lte.py b/homeassistant/components/notify/huawei_lte.py new file mode 100644 index 00000000000..a406a7ec2d8 --- /dev/null +++ b/homeassistant/components/notify/huawei_lte.py @@ -0,0 +1,60 @@ +"""Huawei LTE router platform for notify component. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/notify.huawei_lte/ +""" + +import logging + +import voluptuous as vol +import attr + +from homeassistant.components.notify import ( + BaseNotificationService, ATTR_TARGET, PLATFORM_SCHEMA) +from homeassistant.const import CONF_RECIPIENT, CONF_URL +import homeassistant.helpers.config_validation as cv + +from ..huawei_lte import DATA_KEY + + +DEPENDENCIES = ['huawei_lte'] + +_LOGGER = logging.getLogger(__name__) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_URL): cv.url, + vol.Required(CONF_RECIPIENT): vol.All(cv.ensure_list, [cv.string]), +}) + + +async def async_get_service(hass, config, discovery_info=None): + """Get the notification service.""" + return HuaweiLteSmsNotificationService(hass, config) + + +@attr.s +class HuaweiLteSmsNotificationService(BaseNotificationService): + """Huawei LTE router SMS notification service.""" + + hass = attr.ib() + config = attr.ib() + + def send_message(self, message="", **kwargs): + """Send message to target numbers.""" + from huawei_lte_api.exceptions import ResponseErrorException + + targets = kwargs.get(ATTR_TARGET, self.config.get(CONF_RECIPIENT)) + if not targets or not message: + return + + data = self.hass.data[DATA_KEY].get_data(self.config) + if not data: + _LOGGER.error("Router not available") + return + + try: + resp = data.client.sms.send_sms( + phone_numbers=targets, message=message) + _LOGGER.debug("Sent to %s: %s", targets, resp) + except ResponseErrorException as ex: + _LOGGER.error("Could not send to %s: %s", targets, ex) From 198dc2b7a9542b994c1792feb968e97b46f8a49a Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Fri, 1 Feb 2019 21:37:00 +0100 Subject: [PATCH 019/242] Upgrade rxv to 0.6.0 (#20669) --- homeassistant/components/media_player/yamaha.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/media_player/yamaha.py b/homeassistant/components/media_player/yamaha.py index 0bb34aee7e1..67c55dad0d1 100644 --- a/homeassistant/components/media_player/yamaha.py +++ b/homeassistant/components/media_player/yamaha.py @@ -21,7 +21,7 @@ from homeassistant.const import ( STATE_PLAYING) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['rxv==0.5.1'] +REQUIREMENTS = ['rxv==0.6.0'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index f9e4d5f9352..b6027a4b478 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1489,7 +1489,7 @@ russound==0.1.9 russound_rio==0.1.4 # homeassistant.components.media_player.yamaha -rxv==0.5.1 +rxv==0.6.0 # homeassistant.components.media_player.samsungtv samsungctl[websocket]==0.7.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a56626734eb..e95d1ec35f5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -257,7 +257,7 @@ rflink==0.0.37 ring_doorbell==0.2.2 # homeassistant.components.media_player.yamaha -rxv==0.5.1 +rxv==0.6.0 # homeassistant.components.simplisafe simplisafe-python==3.1.14 From da807b20a05cd8085413e2be56f0d867ec6e5619 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 1 Feb 2019 12:50:48 -0800 Subject: [PATCH 020/242] Updated frontend to 20190201.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 5613bdff3ff..8f3afef16cd 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==20190130.1'] +REQUIREMENTS = ['home-assistant-frontend==20190201.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index b6027a4b478..5fa560e9258 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -526,7 +526,7 @@ hole==0.3.0 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190130.1 +home-assistant-frontend==20190201.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e95d1ec35f5..50a61ee2acc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -113,7 +113,7 @@ hdate==0.8.7 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190130.1 +home-assistant-frontend==20190201.0 # homeassistant.components.homekit_controller homekit==0.12.2 From 495524ecc03f0610e247fe23ed52c74f03c5279f Mon Sep 17 00:00:00 2001 From: Till Date: Fri, 1 Feb 2019 23:08:03 +0100 Subject: [PATCH 021/242] Update miflora.py to have relevant sensor icons (#20650) * Update miflora.py to have relevant sensor icons Adds relevant default icons for the sensors of the Mi Flora plant sensor component. * Clean up code --- homeassistant/components/sensor/miflora.py | 24 ++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/sensor/miflora.py b/homeassistant/components/sensor/miflora.py index 412b339caf3..91f873f5a2d 100644 --- a/homeassistant/components/sensor/miflora.py +++ b/homeassistant/components/sensor/miflora.py @@ -30,13 +30,13 @@ DEFAULT_NAME = 'Mi Flora' SCAN_INTERVAL = timedelta(seconds=1200) -# Sensor types are defined like: Name, units +# Sensor types are defined like: Name, units, icon SENSOR_TYPES = { - 'temperature': ['Temperature', '°C'], - 'light': ['Light intensity', 'lx'], - 'moisture': ['Moisture', '%'], - 'conductivity': ['Conductivity', 'µS/cm'], - 'battery': ['Battery', '%'], + 'temperature': ['Temperature', '°C', 'mdi:thermometer'], + 'light': ['Light intensity', 'lx', 'mdi:white-balance-sunny'], + 'moisture': ['Moisture', '%', 'mdi:water-percent'], + 'conductivity': ['Conductivity', 'µS/cm', 'mdi:flash-circle'], + 'battery': ['Battery', '%', 'mdi:battery-charging'], } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -75,13 +75,14 @@ async def async_setup_platform(hass, config, async_add_entities, for parameter in config[CONF_MONITORED_CONDITIONS]: name = SENSOR_TYPES[parameter][0] unit = SENSOR_TYPES[parameter][1] + icon = SENSOR_TYPES[parameter][2] prefix = config.get(CONF_NAME) if prefix: name = "{} {}".format(prefix, name) devs.append(MiFloraSensor( - poller, parameter, name, unit, force_update, median)) + poller, parameter, name, unit, icon, force_update, median)) async_add_entities(devs) @@ -89,11 +90,13 @@ async def async_setup_platform(hass, config, async_add_entities, class MiFloraSensor(Entity): """Implementing the MiFlora sensor.""" - def __init__(self, poller, parameter, name, unit, force_update, median): + def __init__( + self, poller, parameter, name, unit, icon, force_update, median): """Initialize the sensor.""" self.poller = poller self.parameter = parameter self._unit = unit + self._icon = icon self._name = name self._state = None self.data = [] @@ -126,6 +129,11 @@ class MiFloraSensor(Entity): """Return the units of measurement.""" return self._unit + @property + def icon(self): + """Return the icon of the sensor.""" + return self._icon + @property def force_update(self): """Force update.""" From 57ef8c271ed9f2a00fe2fa3dbe52ae6edf6cb2c4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 1 Feb 2019 14:09:23 -0800 Subject: [PATCH 022/242] Fix geofency requiring a configuration.yaml entry (#20631) --- homeassistant/components/geofency/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/geofency/__init__.py b/homeassistant/components/geofency/__init__.py index 239af14add8..f58580b83c7 100644 --- a/homeassistant/components/geofency/__init__.py +++ b/homeassistant/components/geofency/__init__.py @@ -68,8 +68,8 @@ WEBHOOK_SCHEMA = vol.Schema({ async def async_setup(hass, hass_config): """Set up the Geofency component.""" - config = hass_config[DOMAIN] - mobile_beacons = config[CONF_MOBILE_BEACONS] + config = hass_config.get(DOMAIN, {}) + mobile_beacons = config.get(CONF_MOBILE_BEACONS, []) hass.data[DOMAIN] = [slugify(beacon) for beacon in mobile_beacons] return True From ec57db78b505562666415210a989ecb039368106 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 1 Feb 2019 15:45:44 -0800 Subject: [PATCH 023/242] Consolidate config flow components (#20635) * Consolidate config flow components * Fix tests * Fix tests * Put unifi back * Fix reqs * Update coveragerc --- .coveragerc | 54 +++++++------------ .../cast.py => cast/media_player.py} | 0 .../{climate/daikin.py => daikin/climate.py} | 0 .../{sensor/daikin.py => daikin/sensor.py} | 0 .../hangouts.py => hangouts/notify.py} | 0 .../alarm_control_panel.py} | 0 .../binary_sensor.py} | 0 .../climate.py} | 0 .../cover.py} | 0 .../light.py} | 0 .../sensor.py} | 0 .../switch.py} | 0 .../ifttt.py => ifttt/alarm_control_panel.py} | 0 .../{notify/ios.py => ios/notify.py} | 0 .../{sensor/ios.py => ios/sensor.py} | 0 .../{light/lifx.py => lifx/light.py} | 0 .../components/light/lutron_caseta.py | 2 +- .../luftdaten.py => luftdaten/sensor.py} | 0 .../{lutron.py => lutron/__init__.py} | 0 .../{cover/lutron.py => lutron/cover.py} | 0 .../{light/lutron.py => lutron/light.py} | 0 .../{scene/lutron.py => lutron/scene.py} | 0 .../{switch/lutron.py => lutron/switch.py} | 0 .../mqtt.py => mqtt/device_tracker.py} | 0 .../nest.py => nest/binary_sensor.py} | 0 .../{camera/nest.py => nest/camera.py} | 0 .../{climate/nest.py => nest/climate.py} | 0 .../{sensor/nest.py => nest/sensor.py} | 0 .../device_tracker.py} | 0 .../point.py => point/binary_sensor.py} | 0 .../{sensor/point.py => point/sensor.py} | 0 .../{weather/smhi.py => smhi/weather.py} | 0 .../sonos.py => sonos/media_player.py} | 0 .../binary_sensor.py} | 0 .../tellduslive.py => tellduslive/cover.py} | 0 .../tellduslive.py => tellduslive/light.py} | 0 .../tellduslive.py => tellduslive/sensor.py} | 0 .../tellduslive.py => tellduslive/switch.py} | 0 .../{light/tradfri.py => tradfri/light.py} | 0 .../{sensor/tradfri.py => tradfri/sensor.py} | 0 .../{switch/tradfri.py => tradfri/switch.py} | 0 .../{switch/unifi.py => unifi/switch.py} | 0 .../{sensor/upnp.py => upnp/sensor.py} | 0 requirements_all.txt | 2 +- tests/components/cast/test_init.py | 2 +- .../test_media_player.py} | 14 ++--- tests/components/device_tracker/test_mqtt.py | 2 +- .../device_tracker/test_owntracks.py | 20 +++---- tests/components/device_tracker/test_unifi.py | 4 +- tests/components/ios/test_init.py | 2 +- .../test_smhi.py => smhi/test_weather.py} | 4 +- tests/components/sonos/test_init.py | 2 +- .../test_media_player.py} | 5 +- 53 files changed, 50 insertions(+), 63 deletions(-) rename homeassistant/components/{media_player/cast.py => cast/media_player.py} (100%) rename homeassistant/components/{climate/daikin.py => daikin/climate.py} (100%) rename homeassistant/components/{sensor/daikin.py => daikin/sensor.py} (100%) rename homeassistant/components/{notify/hangouts.py => hangouts/notify.py} (100%) rename homeassistant/components/{alarm_control_panel/homematicip_cloud.py => homematicip_cloud/alarm_control_panel.py} (100%) rename homeassistant/components/{binary_sensor/homematicip_cloud.py => homematicip_cloud/binary_sensor.py} (100%) rename homeassistant/components/{climate/homematicip_cloud.py => homematicip_cloud/climate.py} (100%) rename homeassistant/components/{cover/homematicip_cloud.py => homematicip_cloud/cover.py} (100%) rename homeassistant/components/{light/homematicip_cloud.py => homematicip_cloud/light.py} (100%) rename homeassistant/components/{sensor/homematicip_cloud.py => homematicip_cloud/sensor.py} (100%) rename homeassistant/components/{switch/homematicip_cloud.py => homematicip_cloud/switch.py} (100%) rename homeassistant/components/{alarm_control_panel/ifttt.py => ifttt/alarm_control_panel.py} (100%) rename homeassistant/components/{notify/ios.py => ios/notify.py} (100%) rename homeassistant/components/{sensor/ios.py => ios/sensor.py} (100%) rename homeassistant/components/{light/lifx.py => lifx/light.py} (100%) rename homeassistant/components/{sensor/luftdaten.py => luftdaten/sensor.py} (100%) rename homeassistant/components/{lutron.py => lutron/__init__.py} (100%) rename homeassistant/components/{cover/lutron.py => lutron/cover.py} (100%) rename homeassistant/components/{light/lutron.py => lutron/light.py} (100%) rename homeassistant/components/{scene/lutron.py => lutron/scene.py} (100%) rename homeassistant/components/{switch/lutron.py => lutron/switch.py} (100%) rename homeassistant/components/{device_tracker/mqtt.py => mqtt/device_tracker.py} (100%) rename homeassistant/components/{binary_sensor/nest.py => nest/binary_sensor.py} (100%) rename homeassistant/components/{camera/nest.py => nest/camera.py} (100%) rename homeassistant/components/{climate/nest.py => nest/climate.py} (100%) rename homeassistant/components/{sensor/nest.py => nest/sensor.py} (100%) rename homeassistant/components/{device_tracker/owntracks.py => owntracks/device_tracker.py} (100%) rename homeassistant/components/{binary_sensor/point.py => point/binary_sensor.py} (100%) rename homeassistant/components/{sensor/point.py => point/sensor.py} (100%) rename homeassistant/components/{weather/smhi.py => smhi/weather.py} (100%) rename homeassistant/components/{media_player/sonos.py => sonos/media_player.py} (100%) rename homeassistant/components/{binary_sensor/tellduslive.py => tellduslive/binary_sensor.py} (100%) rename homeassistant/components/{cover/tellduslive.py => tellduslive/cover.py} (100%) rename homeassistant/components/{light/tellduslive.py => tellduslive/light.py} (100%) rename homeassistant/components/{sensor/tellduslive.py => tellduslive/sensor.py} (100%) rename homeassistant/components/{switch/tellduslive.py => tellduslive/switch.py} (100%) rename homeassistant/components/{light/tradfri.py => tradfri/light.py} (100%) rename homeassistant/components/{sensor/tradfri.py => tradfri/sensor.py} (100%) rename homeassistant/components/{switch/tradfri.py => tradfri/switch.py} (100%) rename homeassistant/components/{switch/unifi.py => unifi/switch.py} (100%) rename homeassistant/components/{sensor/upnp.py => upnp/sensor.py} (100%) rename tests/components/{media_player/test_cast.py => cast/test_media_player.py} (97%) rename tests/components/{weather/test_smhi.py => smhi/test_weather.py} (98%) rename tests/components/{media_player/test_sonos.py => sonos/test_media_player.py} (98%) diff --git a/.coveragerc b/.coveragerc index 722f74a0b6a..448adfcee3f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -19,8 +19,7 @@ omit = homeassistant/components/alarmdecoder.py homeassistant/components/*/alarmdecoder.py - homeassistant/components/ambient_station/__init__.py - homeassistant/components/ambient_station/sensor.py + homeassistant/components/ambient_station/* homeassistant/components/amcrest.py homeassistant/components/*/amcrest.py @@ -69,16 +68,13 @@ omit = homeassistant/components/sensor/coinbase.py homeassistant/components/cast/* - homeassistant/components/*/cast.py homeassistant/components/cloudflare.py homeassistant/components/comfoconnect.py homeassistant/components/*/comfoconnect.py - homeassistant/components/daikin/__init__.py - homeassistant/components/daikin/const.py - homeassistant/components/*/daikin.py + homeassistant/components/daikin/* homeassistant/components/digital_ocean.py homeassistant/components/*/digital_ocean.py @@ -181,9 +177,7 @@ omit = homeassistant/components/homematic/__init__.py homeassistant/components/*/homematic.py - homeassistant/components/homematicip_cloud/hap.py - homeassistant/components/homematicip_cloud/device.py - homeassistant/components/*/homematicip_cloud.py + homeassistant/components/homematicip_cloud/* homeassistant/components/homeworks.py homeassistant/components/*/homeworks.py @@ -194,6 +188,8 @@ omit = homeassistant/components/hydrawise.py homeassistant/components/*/hydrawise.py + homeassistant/components/ifttt/* + homeassistant/components/ihc/* homeassistant/components/*/ihc.py @@ -204,8 +200,7 @@ omit = homeassistant/components/insteon_plm.py - homeassistant/components/ios.py - homeassistant/components/*/ios.py + homeassistant/components/ios/* homeassistant/components/iota.py homeassistant/components/*/iota.py @@ -234,15 +229,19 @@ omit = homeassistant/components/lcn.py homeassistant/components/*/lcn.py - homeassistant/components/linode.py - homeassistant/components/*/linode.py + homeassistant/components/lifx/* homeassistant/components/lightwave.py homeassistant/components/*/lightwave.py + homeassistant/components/linode.py + homeassistant/components/*/linode.py + homeassistant/components/logi_circle.py homeassistant/components/*/logi_circle.py + homeassistant/components/luftdaten/* + homeassistant/components/lupusec.py homeassistant/components/*/lupusec.py @@ -275,8 +274,7 @@ omit = homeassistant/components/neato.py homeassistant/components/*/neato.py - homeassistant/components/nest/__init__.py - homeassistant/components/*/nest.py + homeassistant/components/nest/* homeassistant/components/netatmo.py homeassistant/components/*/netatmo.py @@ -303,9 +301,7 @@ omit = homeassistant/components/pilight.py homeassistant/components/*/pilight.py - homeassistant/components/point/__init__.py - homeassistant/components/point/const.py - homeassistant/components/*/point.py + homeassistant/components/point/* homeassistant/components/switch/qwikswitch.py homeassistant/components/light/qwikswitch.py @@ -362,8 +358,7 @@ omit = homeassistant/components/smappee.py homeassistant/components/*/smappee.py - homeassistant/components/sonos/__init__.py - homeassistant/components/*/sonos.py + homeassistant/components/sonos/* homeassistant/components/tado.py homeassistant/components/*/tado.py @@ -371,9 +366,7 @@ omit = homeassistant/components/tahoma.py homeassistant/components/*/tahoma.py - homeassistant/components/tellduslive/__init__.py - homeassistant/components/tellduslive/entry.py - homeassistant/components/*/tellduslive.py + homeassistant/components/tellduslive/* homeassistant/components/tellstick.py homeassistant/components/*/tellstick.py @@ -395,15 +388,16 @@ omit = homeassistant/components/tplink_lte.py homeassistant/components/*/tplink_lte.py - homeassistant/components/tradfri.py - homeassistant/components/*/tradfri.py - + homeassistant/components/tradfri/* + homeassistant/components/transmission.py homeassistant/components/*/transmission.py homeassistant/components/notify/twilio_sms.py homeassistant/components/notify/twilio_call.py + homeassistant/components/upnp.py + homeassistant/components/upcloud.py homeassistant/components/*/upcloud.py @@ -466,7 +460,6 @@ omit = homeassistant/components/zha/core/device.py homeassistant/components/zha/core/listeners.py homeassistant/components/zha/core/gateway.py - homeassistant/components/*/zha.py homeassistant/components/zigbee.py homeassistant/components/*/zigbee.py @@ -485,7 +478,6 @@ omit = homeassistant/components/alarm_control_panel/canary.py homeassistant/components/alarm_control_panel/concord232.py homeassistant/components/alarm_control_panel/ialarm.py - homeassistant/components/alarm_control_panel/ifttt.py homeassistant/components/alarm_control_panel/manual_mqtt.py homeassistant/components/alarm_control_panel/nx584.py homeassistant/components/alarm_control_panel/totalconnect.py @@ -584,13 +576,11 @@ omit = homeassistant/components/downloader.py homeassistant/components/emoncms_history.py homeassistant/components/emulated_hue/upnp.py - homeassistant/components/fan/mqtt.py homeassistant/components/fan/wemo.py homeassistant/components/folder_watcher.py homeassistant/components/foursquare.py homeassistant/components/goalfeed.py homeassistant/components/idteck_prox.py - homeassistant/components/ifttt.py homeassistant/components/image_processing/dlib_face_detect.py homeassistant/components/image_processing/dlib_face_identify.py homeassistant/components/image_processing/seven_segments.py @@ -611,7 +601,6 @@ omit = homeassistant/components/light/hyperion.py homeassistant/components/light/iglo.py homeassistant/components/light/lifx_legacy.py - homeassistant/components/light/lifx.py homeassistant/components/light/limitlessled.py homeassistant/components/light/lw12wifi.py homeassistant/components/light/mystrom.py @@ -801,7 +790,6 @@ omit = homeassistant/components/sensor/haveibeenpwned.py homeassistant/components/sensor/hp_ilo.py homeassistant/components/sensor/htu21d.py - homeassistant/components/sensor/upnp.py homeassistant/components/sensor/iliad_italy.py homeassistant/components/sensor/imap_email_content.py homeassistant/components/sensor/imap.py @@ -816,7 +804,6 @@ omit = homeassistant/components/sensor/linux_battery.py homeassistant/components/sensor/london_underground.py homeassistant/components/sensor/loopenergy.py - homeassistant/components/sensor/luftdaten.py homeassistant/components/sensor/lyft.py homeassistant/components/sensor/magicseaweed.py homeassistant/components/sensor/meteo_france.py @@ -949,7 +936,6 @@ omit = homeassistant/components/tts/baidu.py homeassistant/components/tts/microsoft.py homeassistant/components/tts/picotts.py - homeassistant/components/vacuum/mqtt.py homeassistant/components/vacuum/roomba.py homeassistant/components/water_heater/econet.py homeassistant/components/watson_iot.py diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/cast/media_player.py similarity index 100% rename from homeassistant/components/media_player/cast.py rename to homeassistant/components/cast/media_player.py diff --git a/homeassistant/components/climate/daikin.py b/homeassistant/components/daikin/climate.py similarity index 100% rename from homeassistant/components/climate/daikin.py rename to homeassistant/components/daikin/climate.py diff --git a/homeassistant/components/sensor/daikin.py b/homeassistant/components/daikin/sensor.py similarity index 100% rename from homeassistant/components/sensor/daikin.py rename to homeassistant/components/daikin/sensor.py diff --git a/homeassistant/components/notify/hangouts.py b/homeassistant/components/hangouts/notify.py similarity index 100% rename from homeassistant/components/notify/hangouts.py rename to homeassistant/components/hangouts/notify.py diff --git a/homeassistant/components/alarm_control_panel/homematicip_cloud.py b/homeassistant/components/homematicip_cloud/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/homematicip_cloud.py rename to homeassistant/components/homematicip_cloud/alarm_control_panel.py diff --git a/homeassistant/components/binary_sensor/homematicip_cloud.py b/homeassistant/components/homematicip_cloud/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/homematicip_cloud.py rename to homeassistant/components/homematicip_cloud/binary_sensor.py diff --git a/homeassistant/components/climate/homematicip_cloud.py b/homeassistant/components/homematicip_cloud/climate.py similarity index 100% rename from homeassistant/components/climate/homematicip_cloud.py rename to homeassistant/components/homematicip_cloud/climate.py diff --git a/homeassistant/components/cover/homematicip_cloud.py b/homeassistant/components/homematicip_cloud/cover.py similarity index 100% rename from homeassistant/components/cover/homematicip_cloud.py rename to homeassistant/components/homematicip_cloud/cover.py diff --git a/homeassistant/components/light/homematicip_cloud.py b/homeassistant/components/homematicip_cloud/light.py similarity index 100% rename from homeassistant/components/light/homematicip_cloud.py rename to homeassistant/components/homematicip_cloud/light.py diff --git a/homeassistant/components/sensor/homematicip_cloud.py b/homeassistant/components/homematicip_cloud/sensor.py similarity index 100% rename from homeassistant/components/sensor/homematicip_cloud.py rename to homeassistant/components/homematicip_cloud/sensor.py diff --git a/homeassistant/components/switch/homematicip_cloud.py b/homeassistant/components/homematicip_cloud/switch.py similarity index 100% rename from homeassistant/components/switch/homematicip_cloud.py rename to homeassistant/components/homematicip_cloud/switch.py diff --git a/homeassistant/components/alarm_control_panel/ifttt.py b/homeassistant/components/ifttt/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/ifttt.py rename to homeassistant/components/ifttt/alarm_control_panel.py diff --git a/homeassistant/components/notify/ios.py b/homeassistant/components/ios/notify.py similarity index 100% rename from homeassistant/components/notify/ios.py rename to homeassistant/components/ios/notify.py diff --git a/homeassistant/components/sensor/ios.py b/homeassistant/components/ios/sensor.py similarity index 100% rename from homeassistant/components/sensor/ios.py rename to homeassistant/components/ios/sensor.py diff --git a/homeassistant/components/light/lifx.py b/homeassistant/components/lifx/light.py similarity index 100% rename from homeassistant/components/light/lifx.py rename to homeassistant/components/lifx/light.py diff --git a/homeassistant/components/light/lutron_caseta.py b/homeassistant/components/light/lutron_caseta.py index 21360e71c42..d454fe3c75e 100644 --- a/homeassistant/components/light/lutron_caseta.py +++ b/homeassistant/components/light/lutron_caseta.py @@ -8,7 +8,7 @@ import logging from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light, DOMAIN) -from homeassistant.components.light.lutron import ( +from homeassistant.components.lutron.light import ( to_hass_level, to_lutron_level) from homeassistant.components.lutron_caseta import ( LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice) diff --git a/homeassistant/components/sensor/luftdaten.py b/homeassistant/components/luftdaten/sensor.py similarity index 100% rename from homeassistant/components/sensor/luftdaten.py rename to homeassistant/components/luftdaten/sensor.py diff --git a/homeassistant/components/lutron.py b/homeassistant/components/lutron/__init__.py similarity index 100% rename from homeassistant/components/lutron.py rename to homeassistant/components/lutron/__init__.py diff --git a/homeassistant/components/cover/lutron.py b/homeassistant/components/lutron/cover.py similarity index 100% rename from homeassistant/components/cover/lutron.py rename to homeassistant/components/lutron/cover.py diff --git a/homeassistant/components/light/lutron.py b/homeassistant/components/lutron/light.py similarity index 100% rename from homeassistant/components/light/lutron.py rename to homeassistant/components/lutron/light.py diff --git a/homeassistant/components/scene/lutron.py b/homeassistant/components/lutron/scene.py similarity index 100% rename from homeassistant/components/scene/lutron.py rename to homeassistant/components/lutron/scene.py diff --git a/homeassistant/components/switch/lutron.py b/homeassistant/components/lutron/switch.py similarity index 100% rename from homeassistant/components/switch/lutron.py rename to homeassistant/components/lutron/switch.py diff --git a/homeassistant/components/device_tracker/mqtt.py b/homeassistant/components/mqtt/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/mqtt.py rename to homeassistant/components/mqtt/device_tracker.py diff --git a/homeassistant/components/binary_sensor/nest.py b/homeassistant/components/nest/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/nest.py rename to homeassistant/components/nest/binary_sensor.py diff --git a/homeassistant/components/camera/nest.py b/homeassistant/components/nest/camera.py similarity index 100% rename from homeassistant/components/camera/nest.py rename to homeassistant/components/nest/camera.py diff --git a/homeassistant/components/climate/nest.py b/homeassistant/components/nest/climate.py similarity index 100% rename from homeassistant/components/climate/nest.py rename to homeassistant/components/nest/climate.py diff --git a/homeassistant/components/sensor/nest.py b/homeassistant/components/nest/sensor.py similarity index 100% rename from homeassistant/components/sensor/nest.py rename to homeassistant/components/nest/sensor.py diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/owntracks/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/owntracks.py rename to homeassistant/components/owntracks/device_tracker.py diff --git a/homeassistant/components/binary_sensor/point.py b/homeassistant/components/point/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/point.py rename to homeassistant/components/point/binary_sensor.py diff --git a/homeassistant/components/sensor/point.py b/homeassistant/components/point/sensor.py similarity index 100% rename from homeassistant/components/sensor/point.py rename to homeassistant/components/point/sensor.py diff --git a/homeassistant/components/weather/smhi.py b/homeassistant/components/smhi/weather.py similarity index 100% rename from homeassistant/components/weather/smhi.py rename to homeassistant/components/smhi/weather.py diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/sonos/media_player.py similarity index 100% rename from homeassistant/components/media_player/sonos.py rename to homeassistant/components/sonos/media_player.py diff --git a/homeassistant/components/binary_sensor/tellduslive.py b/homeassistant/components/tellduslive/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/tellduslive.py rename to homeassistant/components/tellduslive/binary_sensor.py diff --git a/homeassistant/components/cover/tellduslive.py b/homeassistant/components/tellduslive/cover.py similarity index 100% rename from homeassistant/components/cover/tellduslive.py rename to homeassistant/components/tellduslive/cover.py diff --git a/homeassistant/components/light/tellduslive.py b/homeassistant/components/tellduslive/light.py similarity index 100% rename from homeassistant/components/light/tellduslive.py rename to homeassistant/components/tellduslive/light.py diff --git a/homeassistant/components/sensor/tellduslive.py b/homeassistant/components/tellduslive/sensor.py similarity index 100% rename from homeassistant/components/sensor/tellduslive.py rename to homeassistant/components/tellduslive/sensor.py diff --git a/homeassistant/components/switch/tellduslive.py b/homeassistant/components/tellduslive/switch.py similarity index 100% rename from homeassistant/components/switch/tellduslive.py rename to homeassistant/components/tellduslive/switch.py diff --git a/homeassistant/components/light/tradfri.py b/homeassistant/components/tradfri/light.py similarity index 100% rename from homeassistant/components/light/tradfri.py rename to homeassistant/components/tradfri/light.py diff --git a/homeassistant/components/sensor/tradfri.py b/homeassistant/components/tradfri/sensor.py similarity index 100% rename from homeassistant/components/sensor/tradfri.py rename to homeassistant/components/tradfri/sensor.py diff --git a/homeassistant/components/switch/tradfri.py b/homeassistant/components/tradfri/switch.py similarity index 100% rename from homeassistant/components/switch/tradfri.py rename to homeassistant/components/tradfri/switch.py diff --git a/homeassistant/components/switch/unifi.py b/homeassistant/components/unifi/switch.py similarity index 100% rename from homeassistant/components/switch/unifi.py rename to homeassistant/components/unifi/switch.py diff --git a/homeassistant/components/sensor/upnp.py b/homeassistant/components/upnp/sensor.py similarity index 100% rename from homeassistant/components/sensor/upnp.py rename to homeassistant/components/upnp/sensor.py diff --git a/requirements_all.txt b/requirements_all.txt index 5fa560e9258..2d74da98a6c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -126,7 +126,7 @@ aioimaplib==0.7.13 # homeassistant.components.lifx aiolifx==0.6.7 -# homeassistant.components.light.lifx +# homeassistant.components.lifx.light aiolifx_effects==0.2.1 # homeassistant.components.scene.hunterdouglas_powerview diff --git a/tests/components/cast/test_init.py b/tests/components/cast/test_init.py index 0121cd1c794..9f8e07809cb 100644 --- a/tests/components/cast/test_init.py +++ b/tests/components/cast/test_init.py @@ -10,7 +10,7 @@ from tests.common import MockDependency, mock_coro async def test_creating_entry_sets_up_media_player(hass): """Test setting up Cast loads the media player.""" - with patch('homeassistant.components.media_player.cast.async_setup_entry', + with patch('homeassistant.components.cast.media_player.async_setup_entry', return_value=mock_coro(True)) as mock_setup, \ MockDependency('pychromecast', 'discovery'), \ patch('pychromecast.discovery.discover_chromecasts', diff --git a/tests/components/media_player/test_cast.py b/tests/components/cast/test_media_player.py similarity index 97% rename from tests/components/media_player/test_cast.py rename to tests/components/cast/test_media_player.py index 8fd1ae18841..2e0fe9d1529 100644 --- a/tests/components/media_player/test_cast.py +++ b/tests/components/cast/test_media_player.py @@ -10,11 +10,11 @@ import pytest from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.typing import HomeAssistantType -from homeassistant.components.media_player.cast import ChromecastInfo +from homeassistant.components.cast.media_player 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 +from homeassistant.components.cast import media_player as cast from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry, mock_coro @@ -212,7 +212,7 @@ async def test_create_cast_device_with_uuid(hass): async def test_normal_chromecast_not_starting_discovery(hass): """Test cast platform not starting discovery when not required.""" # pylint: disable=no-member - with patch('homeassistant.components.media_player.cast.' + with patch('homeassistant.components.cast.media_player.' '_setup_internal_discovery') as setup_discovery: # normal (non-group) chromecast shouldn't start discovery. add_entities = await async_setup_cast(hass, {'host': 'host1'}) @@ -369,7 +369,7 @@ async def test_entry_setup_no_config(hass: HomeAssistantType): await async_setup_component(hass, 'cast', {}) with patch( - 'homeassistant.components.media_player.cast._async_setup_platform', + 'homeassistant.components.cast.media_player._async_setup_platform', return_value=mock_coro()) as mock_setup: await cast.async_setup_entry(hass, MockConfigEntry(), None) @@ -388,7 +388,7 @@ async def test_entry_setup_single_config(hass: HomeAssistantType): }) with patch( - 'homeassistant.components.media_player.cast._async_setup_platform', + 'homeassistant.components.cast.media_player._async_setup_platform', return_value=mock_coro()) as mock_setup: await cast.async_setup_entry(hass, MockConfigEntry(), None) @@ -408,7 +408,7 @@ async def test_entry_setup_list_config(hass: HomeAssistantType): }) with patch( - 'homeassistant.components.media_player.cast._async_setup_platform', + 'homeassistant.components.cast.media_player._async_setup_platform', return_value=mock_coro()) as mock_setup: await cast.async_setup_entry(hass, MockConfigEntry(), None) @@ -428,7 +428,7 @@ async def test_entry_setup_platform_not_ready(hass: HomeAssistantType): }) with patch( - 'homeassistant.components.media_player.cast._async_setup_platform', + 'homeassistant.components.cast.media_player._async_setup_platform', return_value=mock_coro(exception=Exception)) as mock_setup: with pytest.raises(PlatformNotReady): await cast.async_setup_entry(hass, MockConfigEntry(), None) diff --git a/tests/components/device_tracker/test_mqtt.py b/tests/components/device_tracker/test_mqtt.py index abfa32ca06b..3e584aa541c 100644 --- a/tests/components/device_tracker/test_mqtt.py +++ b/tests/components/device_tracker/test_mqtt.py @@ -30,7 +30,7 @@ async def test_ensure_device_tracker_platform_validation(hass): """Check that Qos was added by validation.""" assert 'qos' in config - with patch('homeassistant.components.device_tracker.mqtt.' + with patch('homeassistant.components.mqtt.device_tracker.' 'async_setup_scanner', autospec=True, side_effect=mock_setup_scanner) as mock_sp: diff --git a/tests/components/device_tracker/test_owntracks.py b/tests/components/device_tracker/test_owntracks.py index 79d0bdb6f73..5e65e0a75c7 100644 --- a/tests/components/device_tracker/test_owntracks.py +++ b/tests/components/device_tracker/test_owntracks.py @@ -1273,8 +1273,8 @@ async def test_single_waypoint_import(hass, context): async def test_not_implemented_message(hass, context): """Handle not implemented message type.""" - patch_handler = patch('homeassistant.components.device_tracker.' - 'owntracks.async_handle_not_impl_msg', + patch_handler = patch('homeassistant.components.owntracks.' + 'device_tracker.async_handle_not_impl_msg', return_value=mock_coro(False)) patch_handler.start() assert not await send_message(hass, LWT_TOPIC, LWT_MESSAGE) @@ -1283,8 +1283,8 @@ async def test_not_implemented_message(hass, context): async def test_unsupported_message(hass, context): """Handle not implemented message type.""" - patch_handler = patch('homeassistant.components.device_tracker.' - 'owntracks.async_handle_unsupported_msg', + patch_handler = patch('homeassistant.components.owntracks.' + 'device_tracker.async_handle_unsupported_msg', return_value=mock_coro(False)) patch_handler.start() assert not await send_message(hass, BAD_TOPIC, BAD_MESSAGE) @@ -1366,7 +1366,7 @@ def config_context(hass, setup_comp): patch_save.stop() -@patch('homeassistant.components.device_tracker.owntracks.get_cipher', +@patch('homeassistant.components.owntracks.device_tracker.get_cipher', mock_cipher) async def test_encrypted_payload(hass, config_context): """Test encrypted payload.""" @@ -1377,7 +1377,7 @@ async def test_encrypted_payload(hass, config_context): assert_location_latitude(hass, LOCATION_MESSAGE['lat']) -@patch('homeassistant.components.device_tracker.owntracks.get_cipher', +@patch('homeassistant.components.owntracks.device_tracker.get_cipher', mock_cipher) async def test_encrypted_payload_topic_key(hass, config_context): """Test encrypted payload with a topic key.""" @@ -1390,7 +1390,7 @@ async def test_encrypted_payload_topic_key(hass, config_context): assert_location_latitude(hass, LOCATION_MESSAGE['lat']) -@patch('homeassistant.components.device_tracker.owntracks.get_cipher', +@patch('homeassistant.components.owntracks.device_tracker.get_cipher', mock_cipher) async def test_encrypted_payload_no_key(hass, config_context): """Test encrypted payload with no key, .""" @@ -1403,7 +1403,7 @@ async def test_encrypted_payload_no_key(hass, config_context): assert hass.states.get(DEVICE_TRACKER_STATE) is None -@patch('homeassistant.components.device_tracker.owntracks.get_cipher', +@patch('homeassistant.components.owntracks.device_tracker.get_cipher', mock_cipher) async def test_encrypted_payload_wrong_key(hass, config_context): """Test encrypted payload with wrong key.""" @@ -1414,7 +1414,7 @@ async def test_encrypted_payload_wrong_key(hass, config_context): assert hass.states.get(DEVICE_TRACKER_STATE) is None -@patch('homeassistant.components.device_tracker.owntracks.get_cipher', +@patch('homeassistant.components.owntracks.device_tracker.get_cipher', mock_cipher) async def test_encrypted_payload_wrong_topic_key(hass, config_context): """Test encrypted payload with wrong topic key.""" @@ -1427,7 +1427,7 @@ async def test_encrypted_payload_wrong_topic_key(hass, config_context): assert hass.states.get(DEVICE_TRACKER_STATE) is None -@patch('homeassistant.components.device_tracker.owntracks.get_cipher', +@patch('homeassistant.components.owntracks.device_tracker.get_cipher', mock_cipher) async def test_encrypted_payload_no_topic_key(hass, config_context): """Test encrypted payload with no topic key.""" diff --git a/tests/components/device_tracker/test_unifi.py b/tests/components/device_tracker/test_unifi.py index 33adff9adf8..b3ce1e93e4c 100644 --- a/tests/components/device_tracker/test_unifi.py +++ b/tests/components/device_tracker/test_unifi.py @@ -25,8 +25,8 @@ def mock_ctrl(): @pytest.fixture def mock_scanner(): """Mock UnifyScanner.""" - with mock.patch('homeassistant.components.device_tracker' - '.unifi.UnifiScanner') as scanner: + with mock.patch('homeassistant.components.device_tracker.unifi' + '.UnifiScanner') as scanner: yield scanner diff --git a/tests/components/ios/test_init.py b/tests/components/ios/test_init.py index ad1ab328325..7141c8c9d3a 100644 --- a/tests/components/ios/test_init.py +++ b/tests/components/ios/test_init.py @@ -26,7 +26,7 @@ def mock_dependencies(hass): async def test_creating_entry_sets_up_sensor(hass): """Test setting up iOS loads the sensor component.""" - with patch('homeassistant.components.sensor.ios.async_setup_entry', + with patch('homeassistant.components.ios.sensor.async_setup_entry', return_value=mock_coro(True)) as mock_setup: result = await hass.config_entries.flow.async_init( ios.DOMAIN, context={'source': config_entries.SOURCE_USER}) diff --git a/tests/components/weather/test_smhi.py b/tests/components/smhi/test_weather.py similarity index 98% rename from tests/components/weather/test_smhi.py rename to tests/components/smhi/test_weather.py index 11a5028842b..aaf22ffce65 100644 --- a/tests/components/weather/test_smhi.py +++ b/tests/components/smhi/test_weather.py @@ -9,8 +9,8 @@ from homeassistant.components.weather import ( ATTR_WEATHER_TEMPERATURE, ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_PRESSURE, ATTR_FORECAST_TEMP_LOW, ATTR_WEATHER_VISIBILITY, ATTR_WEATHER_ATTRIBUTION, ATTR_WEATHER_WIND_BEARING, ATTR_WEATHER_WIND_SPEED, - ATTR_FORECAST_PRECIPITATION, smhi as weather_smhi, - DOMAIN as WEATHER_DOMAIN) + ATTR_FORECAST_PRECIPITATION, DOMAIN as WEATHER_DOMAIN) +from homeassistant.components.smhi import weather as weather_smhi from homeassistant.const import TEMP_CELSIUS from homeassistant.core import HomeAssistant diff --git a/tests/components/sonos/test_init.py b/tests/components/sonos/test_init.py index 8d46f4d57a3..a09fa7d2615 100644 --- a/tests/components/sonos/test_init.py +++ b/tests/components/sonos/test_init.py @@ -10,7 +10,7 @@ from tests.common import mock_coro async def test_creating_entry_sets_up_media_player(hass): """Test setting up Sonos loads the media player.""" - with patch('homeassistant.components.media_player.sonos.async_setup_entry', + with patch('homeassistant.components.sonos.media_player.async_setup_entry', return_value=mock_coro(True)) as mock_setup, \ patch('pysonos.discover', return_value=True): result = await hass.config_entries.flow.async_init( diff --git a/tests/components/media_player/test_sonos.py b/tests/components/sonos/test_media_player.py similarity index 98% rename from tests/components/media_player/test_sonos.py rename to tests/components/sonos/test_media_player.py index bf81aee5982..79eab1c1699 100644 --- a/tests/components/media_player/test_sonos.py +++ b/tests/components/sonos/test_media_player.py @@ -8,8 +8,9 @@ import pysonos from pysonos import alarms from homeassistant.setup import setup_component -from homeassistant.components.media_player import sonos, DOMAIN -from homeassistant.components.media_player.sonos import CONF_INTERFACE_ADDR +from homeassistant.components.sonos import media_player as sonos +from homeassistant.components.media_player import DOMAIN +from homeassistant.components.sonos.media_player import CONF_INTERFACE_ADDR from homeassistant.const import CONF_HOSTS, CONF_PLATFORM from tests.common import get_test_home_assistant From c9671f8205e5c677189c88014c33ad52d3120e33 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 1 Feb 2019 16:31:53 -0800 Subject: [PATCH 024/242] Test is broken --- tests/components/sensor/test_history_stats.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/components/sensor/test_history_stats.py b/tests/components/sensor/test_history_stats.py index 67cacb29880..a739325847f 100644 --- a/tests/components/sensor/test_history_stats.py +++ b/tests/components/sensor/test_history_stats.py @@ -70,9 +70,9 @@ class TestHistoryStatsSensor(unittest.TestCase): assert sensor1_start.second == 0 # End = 02:01:00 - assert sensor1_end.hour == 2 - assert sensor1_end.minute == 1 - assert sensor1_end.second == 0 + # assert sensor1_end.hour == 2 + # assert sensor1_end.minute == 1 + # assert sensor1_end.second == 0 # Start = 21:59:00 assert sensor2_start.hour == 21 From fcccf133ba210b2f4758c1f21cabecbee56f5fcd Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Fri, 1 Feb 2019 21:50:22 -0800 Subject: [PATCH 025/242] Split out fastdotcom into a component and a sensor platform (#20341) * Split out fastdotcom into a component and a sensor platform * Update .coveragerc * Switching to async and using a Throttle * Add the async_track_time_interval call * Remove the throttle * Reorder sensor methods and add should_poll property --- .coveragerc | 3 +- .../components/fastdotcom/__init__.py | 76 ++++++++++++ homeassistant/components/fastdotcom/sensor.py | 85 +++++++++++++ .../components/fastdotcom/services.yaml | 2 + homeassistant/components/sensor/fastdotcom.py | 115 ------------------ requirements_all.txt | 2 +- 6 files changed, 166 insertions(+), 117 deletions(-) create mode 100644 homeassistant/components/fastdotcom/__init__.py create mode 100644 homeassistant/components/fastdotcom/sensor.py create mode 100644 homeassistant/components/fastdotcom/services.yaml delete mode 100644 homeassistant/components/sensor/fastdotcom.py diff --git a/.coveragerc b/.coveragerc index 448adfcee3f..d9632103f67 100644 --- a/.coveragerc +++ b/.coveragerc @@ -138,6 +138,8 @@ omit = homeassistant/components/eufy.py homeassistant/components/*/eufy.py + homeassistant/components/fastdotcom/* + homeassistant/components/fibaro/__init__.py homeassistant/components/*/fibaro.py @@ -767,7 +769,6 @@ omit = homeassistant/components/sensor/enphase_envoy.py homeassistant/components/sensor/envirophat.py homeassistant/components/sensor/etherscan.py - homeassistant/components/sensor/fastdotcom.py homeassistant/components/sensor/fedex.py homeassistant/components/sensor/filesize.py homeassistant/components/sensor/fints.py diff --git a/homeassistant/components/fastdotcom/__init__.py b/homeassistant/components/fastdotcom/__init__.py new file mode 100644 index 00000000000..b5df45216e5 --- /dev/null +++ b/homeassistant/components/fastdotcom/__init__.py @@ -0,0 +1,76 @@ +""" +Support for testing internet speed via Fast.com. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/fastdotcom/ +""" + +import logging +from datetime import timedelta + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.const import CONF_UPDATE_INTERVAL +from homeassistant.helpers.discovery import async_load_platform +from homeassistant.helpers.dispatcher import dispatcher_send +from homeassistant.helpers.event import async_track_time_interval + +REQUIREMENTS = ['fastdotcom==0.0.3'] + +DOMAIN = 'fastdotcom' +DATA_UPDATED = '{}_data_updated'.format(DOMAIN) + +_LOGGER = logging.getLogger(__name__) + +CONF_MANUAL = 'manual' + +DEFAULT_INTERVAL = timedelta(hours=1) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Optional(CONF_UPDATE_INTERVAL, default=DEFAULT_INTERVAL): + vol.All( + cv.time_period, cv.positive_timedelta + ), + vol.Optional(CONF_MANUAL, default=False): cv.boolean, + }) +}, extra=vol.ALLOW_EXTRA) + + +async def async_setup(hass, config): + """Set up the Fast.com component.""" + conf = config[DOMAIN] + data = hass.data[DOMAIN] = SpeedtestData( + hass, conf[CONF_UPDATE_INTERVAL], conf[CONF_MANUAL] + ) + + def update(call=None): + """Service call to manually update the data.""" + data.update() + + hass.services.async_register(DOMAIN, 'speedtest', update) + + hass.async_create_task( + async_load_platform(hass, 'sensor', DOMAIN, {}, config) + ) + + return True + + +class SpeedtestData: + """Get the latest data from fast.com.""" + + def __init__(self, hass, interval, manual): + """Initialize the data object.""" + self.data = None + self._hass = hass + if not manual: + async_track_time_interval(self._hass, self.update, interval) + + def update(self): + """Get the latest data from fast.com.""" + from fastdotcom import fast_com + _LOGGER.debug("Executing fast.com speedtest") + self.data = {'download': fast_com()} + dispatcher_send(self._hass, DATA_UPDATED) diff --git a/homeassistant/components/fastdotcom/sensor.py b/homeassistant/components/fastdotcom/sensor.py new file mode 100644 index 00000000000..4d105cebf44 --- /dev/null +++ b/homeassistant/components/fastdotcom/sensor.py @@ -0,0 +1,85 @@ +""" +Support for Fast.com internet speed testing sensor. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.fastdotcom/ +""" +import logging + +from homeassistant.components.fastdotcom import DOMAIN as FASTDOTCOM_DOMAIN, \ + DATA_UPDATED +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.restore_state import RestoreEntity + +DEPENDENCIES = ['fastdotcom'] + +_LOGGER = logging.getLogger(__name__) + +ICON = 'mdi:speedometer' + +UNIT_OF_MEASUREMENT = 'Mbit/s' + + +async def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): + """Set up the Fast.com sensor.""" + async_add_entities([SpeedtestSensor(hass.data[FASTDOTCOM_DOMAIN])]) + + +class SpeedtestSensor(RestoreEntity): + """Implementation of a FAst.com sensor.""" + + def __init__(self, speedtest_data): + """Initialize the sensor.""" + self._name = 'Fast.com Download' + self.speedtest_client = speedtest_data + self._state = None + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the device.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return UNIT_OF_MEASUREMENT + + @property + def icon(self): + """Return icon.""" + return ICON + + @property + def should_poll(self): + """Return the polling requirement for this sensor.""" + return False + + async def async_added_to_hass(self): + """Handle entity which will be added.""" + await super().async_added_to_hass() + state = await self.async_get_last_state() + if not state: + return + self._state = state.state + + async_dispatcher_connect( + self.hass, DATA_UPDATED, self._schedule_immediate_update + ) + + def update(self): + """Get the latest data and update the states.""" + data = self.speedtest_client.data + if data is None: + return + self._state = data['download'] + + @callback + def _schedule_immediate_update(self): + self.async_schedule_update_ha_state(True) diff --git a/homeassistant/components/fastdotcom/services.yaml b/homeassistant/components/fastdotcom/services.yaml new file mode 100644 index 00000000000..fe6cb1ac12d --- /dev/null +++ b/homeassistant/components/fastdotcom/services.yaml @@ -0,0 +1,2 @@ +speedtest: + description: Immediately take a speedest with Fast.com \ No newline at end of file diff --git a/homeassistant/components/sensor/fastdotcom.py b/homeassistant/components/sensor/fastdotcom.py deleted file mode 100644 index 8e975c48574..00000000000 --- a/homeassistant/components/sensor/fastdotcom.py +++ /dev/null @@ -1,115 +0,0 @@ -""" -Support for Fast.com internet speed testing sensor. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.fastdotcom/ -""" -import logging - -import voluptuous as vol - -from homeassistant.components.sensor import DOMAIN, PLATFORM_SCHEMA -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.event import track_time_change -from homeassistant.helpers.restore_state import RestoreEntity -import homeassistant.util.dt as dt_util - -REQUIREMENTS = ['fastdotcom==0.0.3'] - -_LOGGER = logging.getLogger(__name__) - -CONF_SECOND = 'second' -CONF_MINUTE = 'minute' -CONF_HOUR = 'hour' -CONF_MANUAL = 'manual' - -ICON = 'mdi:speedometer' - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_SECOND, default=[0]): - vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(0, 59))]), - vol.Optional(CONF_MINUTE, default=[0]): - vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(0, 59))]), - vol.Optional(CONF_HOUR): - vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(0, 23))]), - vol.Optional(CONF_MANUAL, default=False): cv.boolean, -}) - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Fast.com sensor.""" - data = SpeedtestData(hass, config) - sensor = SpeedtestSensor(data) - add_entities([sensor]) - - def update(call=None): - """Update service for manual updates.""" - data.update(dt_util.now()) - sensor.update() - - hass.services.register(DOMAIN, 'update_fastdotcom', update) - - -class SpeedtestSensor(RestoreEntity): - """Implementation of a FAst.com sensor.""" - - def __init__(self, speedtest_data): - """Initialize the sensor.""" - self._name = 'Fast.com Download' - self.speedtest_client = speedtest_data - self._state = None - self._unit_of_measurement = 'Mbit/s' - - @property - def name(self): - """Return the name of the sensor.""" - return self._name - - @property - def state(self): - """Return the state of the device.""" - return self._state - - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity, if any.""" - return self._unit_of_measurement - - def update(self): - """Get the latest data and update the states.""" - data = self.speedtest_client.data - if data is None: - return - - self._state = data['download'] - - async def async_added_to_hass(self): - """Handle entity which will be added.""" - await super().async_added_to_hass() - state = await self.async_get_last_state() - if not state: - return - self._state = state.state - - @property - def icon(self): - """Return icon.""" - return ICON - - -class SpeedtestData: - """Get the latest data from fast.com.""" - - def __init__(self, hass, config): - """Initialize the data object.""" - self.data = None - if not config.get(CONF_MANUAL): - track_time_change( - hass, self.update, second=config.get(CONF_SECOND), - minute=config.get(CONF_MINUTE), hour=config.get(CONF_HOUR)) - - def update(self, now): - """Get the latest data from fast.com.""" - from fastdotcom import fast_com - _LOGGER.info("Executing fast.com speedtest") - self.data = {'download': fast_com()} diff --git a/requirements_all.txt b/requirements_all.txt index 2d74da98a6c..b0bf06292e8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -398,7 +398,7 @@ evohomeclient==0.2.8 # homeassistant.components.image_processing.dlib_face_identify # face_recognition==1.0.0 -# homeassistant.components.sensor.fastdotcom +# homeassistant.components.fastdotcom fastdotcom==0.0.3 # homeassistant.components.sensor.fedex From 384a9625c9d30c1b3be811a25e044267a679720a Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sat, 2 Feb 2019 06:11:50 +0000 Subject: [PATCH 026/242] fix test commented in #20678 (#20680) --- tests/components/sensor/test_history_stats.py | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/tests/components/sensor/test_history_stats.py b/tests/components/sensor/test_history_stats.py index a739325847f..28d01de4b34 100644 --- a/tests/components/sensor/test_history_stats.py +++ b/tests/components/sensor/test_history_stats.py @@ -1,8 +1,11 @@ """The test for the History Statistics sensor platform.""" # pylint: disable=protected-access -from datetime import timedelta +from datetime import datetime, timedelta import unittest from unittest.mock import patch +import pytest +import pytz +from homeassistant.helpers import template from homeassistant.const import STATE_UNKNOWN from homeassistant.setup import setup_component @@ -12,7 +15,6 @@ from homeassistant.helpers.template import Template import homeassistant.util.dt as dt_util from tests.common import init_recorder_component, get_test_home_assistant -import pytest class TestHistoryStatsSensor(unittest.TestCase): @@ -50,19 +52,22 @@ class TestHistoryStatsSensor(unittest.TestCase): def test_period_parsing(self): """Test the conversion from templates to period.""" - today = Template('{{ now().replace(hour=0).replace(minute=0)' - '.replace(second=0) }}', self.hass) - duration = timedelta(hours=2, minutes=1) + now = datetime(2019, 1, 1, 23, 30, 0, tzinfo=pytz.utc) + with patch.dict(template.ENV.globals, {'now': lambda: now}): + print(dt_util.now()) + today = Template('{{ now().replace(hour=0).replace(minute=0)' + '.replace(second=0) }}', self.hass) + duration = timedelta(hours=2, minutes=1) - sensor1 = HistoryStatsSensor( - self.hass, 'test', 'on', today, None, duration, 'time', 'test') - sensor2 = HistoryStatsSensor( - self.hass, 'test', 'on', None, today, duration, 'time', 'test') + sensor1 = HistoryStatsSensor( + self.hass, 'test', 'on', today, None, duration, 'time', 'test') + sensor2 = HistoryStatsSensor( + self.hass, 'test', 'on', None, today, duration, 'time', 'test') - sensor1.update_period() - sensor1_start, sensor1_end = sensor1._period - sensor2.update_period() - sensor2_start, sensor2_end = sensor2._period + sensor1.update_period() + sensor1_start, sensor1_end = sensor1._period + sensor2.update_period() + sensor2_start, sensor2_end = sensor2._period # Start = 00:00:00 assert sensor1_start.hour == 0 @@ -70,9 +75,9 @@ class TestHistoryStatsSensor(unittest.TestCase): assert sensor1_start.second == 0 # End = 02:01:00 - # assert sensor1_end.hour == 2 - # assert sensor1_end.minute == 1 - # assert sensor1_end.second == 0 + assert sensor1_end.hour == 2 + assert sensor1_end.minute == 1 + assert sensor1_end.second == 0 # Start = 21:59:00 assert sensor2_start.hour == 21 From 47f60e6cf2a019b7f53f6f4aec6b6f95544a0a82 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 2 Feb 2019 02:52:34 -0800 Subject: [PATCH 027/242] Remove fingerprint middleware (#20682) * Remove fingerprint middleware * Lint --- homeassistant/components/http/__init__.py | 6 ++---- homeassistant/components/http/static.py | 23 +---------------------- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index d43ba989f28..02b9affefd4 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -25,8 +25,7 @@ from .auth import setup_auth from .ban import setup_bans from .cors import setup_cors from .real_ip import setup_real_ip -from .static import ( - CachingFileResponse, CachingStaticResource, staticresource_middleware) +from .static import CachingFileResponse, CachingStaticResource # Import as alias from .const import KEY_AUTHENTICATED, KEY_REAL_IP # noqa @@ -192,8 +191,7 @@ class HomeAssistantHTTP: use_x_forwarded_for, trusted_proxies, trusted_networks, login_threshold, is_ban_enabled, ssl_profile): """Initialize the HTTP Home Assistant server.""" - app = self.app = web.Application( - middlewares=[staticresource_middleware]) + app = self.app = web.Application(middlewares=[]) # This order matters setup_real_ip(app, use_x_forwarded_for, trusted_proxies) diff --git a/homeassistant/components/http/static.py b/homeassistant/components/http/static.py index 8b28a7cf288..54e72c88ff3 100644 --- a/homeassistant/components/http/static.py +++ b/homeassistant/components/http/static.py @@ -1,15 +1,10 @@ """Static file handling for HTTP component.""" - -import re - from aiohttp import hdrs -from aiohttp.web import FileResponse, middleware +from aiohttp.web import FileResponse from aiohttp.web_exceptions import HTTPNotFound from aiohttp.web_urldispatcher import StaticResource from yarl import URL -_FINGERPRINT = re.compile(r'^(.+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE) - class CachingStaticResource(StaticResource): """Static Resource handler that will add cache headers.""" @@ -56,19 +51,3 @@ class CachingFileResponse(FileResponse): # Overwriting like this because __init__ can change implementation. self._sendfile = sendfile - - -@middleware -async def staticresource_middleware(request, handler): - """Middleware to strip out fingerprint from fingerprinted assets.""" - path = request.path - if not path.startswith('/static/') and not path.startswith('/frontend'): - return await handler(request) - - fingerprinted = _FINGERPRINT.match(request.match_info['filename']) - - if fingerprinted: - request.match_info['filename'] = \ - '{}.{}'.format(*fingerprinted.groups()) - - return await handler(request) From ca143f8a354c4d05c86c3a52adcbe956c211d0b8 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sat, 2 Feb 2019 13:54:46 +0000 Subject: [PATCH 028/242] print() left behind (#20689) --- tests/components/sensor/test_history_stats.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/components/sensor/test_history_stats.py b/tests/components/sensor/test_history_stats.py index 28d01de4b34..eda0034c922 100644 --- a/tests/components/sensor/test_history_stats.py +++ b/tests/components/sensor/test_history_stats.py @@ -54,7 +54,6 @@ class TestHistoryStatsSensor(unittest.TestCase): """Test the conversion from templates to period.""" now = datetime(2019, 1, 1, 23, 30, 0, tzinfo=pytz.utc) with patch.dict(template.ENV.globals, {'now': lambda: now}): - print(dt_util.now()) today = Template('{{ now().replace(hour=0).replace(minute=0)' '.replace(second=0) }}', self.hass) duration = timedelta(hours=2, minutes=1) From a24da611c576467c28e711c471f3a5b283f5a1c2 Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Sat, 2 Feb 2019 09:12:24 -0600 Subject: [PATCH 029/242] Add SmartThings Light platform (#20652) * Add SmartThings Light platform and tests * Cleaned a few awk comments * Updates per review feedback * Switched to super * Changes per review feedback --- homeassistant/components/smartthings/const.py | 1 + homeassistant/components/smartthings/light.py | 215 +++++++++++++ tests/components/smartthings/test_light.py | 293 ++++++++++++++++++ tests/components/smartthings/test_switch.py | 17 +- 4 files changed, 518 insertions(+), 8 deletions(-) create mode 100644 homeassistant/components/smartthings/light.py create mode 100644 tests/components/smartthings/test_light.py diff --git a/homeassistant/components/smartthings/const.py b/homeassistant/components/smartthings/const.py index 9a6d96bfab9..a9f47fc7c72 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -18,6 +18,7 @@ SETTINGS_INSTANCE_ID = "hassInstanceId" STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 SUPPORTED_PLATFORMS = [ + 'light', 'switch' ] SUPPORTED_CAPABILITIES = [ diff --git a/homeassistant/components/smartthings/light.py b/homeassistant/components/smartthings/light.py new file mode 100644 index 00000000000..8495be62a73 --- /dev/null +++ b/homeassistant/components/smartthings/light.py @@ -0,0 +1,215 @@ +""" +Support for lights through the SmartThings cloud API. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/smartthings.light/ +""" +import asyncio + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_TRANSITION, + SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_TRANSITION, + Light) +import homeassistant.util.color as color_util + +from . import SmartThingsEntity +from .const import DATA_BROKERS, DOMAIN + +DEPENDENCIES = ['smartthings'] + + +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): + """Platform uses config entry setup.""" + pass + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Add lights for a config entry.""" + broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] + async_add_entities( + [SmartThingsLight(device) for device in broker.devices.values() + if is_light(device)], True) + + +def is_light(device): + """Determine if the device should be represented as a light.""" + from pysmartthings import Capability + + # Must be able to be turned on/off. + if Capability.switch not in device.capabilities: + return False + # Not a fan (which might also have switch_level) + if Capability.fan_speed in device.capabilities: + return False + # Must have one of these + light_capabilities = [ + Capability.color_control, + Capability.color_temperature, + Capability.switch_level + ] + if any(capability in device.capabilities + for capability in light_capabilities): + return True + return False + + +def convert_scale(value, value_scale, target_scale, round_digits=4): + """Convert a value to a different scale.""" + return round(value * target_scale / value_scale, round_digits) + + +class SmartThingsLight(SmartThingsEntity, Light): + """Define a SmartThings Light.""" + + def __init__(self, device): + """Initialize a SmartThingsLight.""" + super().__init__(device) + self._brightness = None + self._color_temp = None + self._hs_color = None + self._supported_features = self._determine_features() + + def _determine_features(self): + """Get features supported by the device.""" + from pysmartthings.device import Capability + + features = 0 + # Brightness and transition + if Capability.switch_level in self._device.capabilities: + features |= \ + SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + # Color Temperature + if Capability.color_temperature in self._device.capabilities: + features |= SUPPORT_COLOR_TEMP + # Color + if Capability.color_control in self._device.capabilities: + features |= SUPPORT_COLOR + + return features + + async def async_turn_on(self, **kwargs) -> None: + """Turn the light on.""" + tasks = [] + # Color temperature + if self._supported_features & SUPPORT_COLOR_TEMP \ + and ATTR_COLOR_TEMP in kwargs: + tasks.append(self.async_set_color_temp( + kwargs[ATTR_COLOR_TEMP])) + # Color + if self._supported_features & SUPPORT_COLOR \ + and ATTR_HS_COLOR in kwargs: + tasks.append(self.async_set_color( + kwargs[ATTR_HS_COLOR])) + if tasks: + # Set temp/color first + await asyncio.gather(*tasks) + + # Switch/brightness/transition + if self._supported_features & SUPPORT_BRIGHTNESS \ + and ATTR_BRIGHTNESS in kwargs: + await self.async_set_level( + kwargs[ATTR_BRIGHTNESS], + kwargs.get(ATTR_TRANSITION, 0)) + else: + await self._device.switch_on(set_status=True) + + # State is set optimistically in the commands above, therefore update + # the entity state ahead of receiving the confirming push updates + self.async_schedule_update_ha_state(True) + + async def async_turn_off(self, **kwargs) -> None: + """Turn the light off.""" + # Switch/transition + if self._supported_features & SUPPORT_TRANSITION \ + and ATTR_TRANSITION in kwargs: + await self.async_set_level(0, int(kwargs[ATTR_TRANSITION])) + else: + await self._device.switch_off(set_status=True) + + # State is set optimistically in the commands above, therefore update + # the entity state ahead of receiving the confirming push updates + self.async_schedule_update_ha_state(True) + + async def async_update(self): + """Update entity attributes when the device status has changed.""" + # Brightness and transition + if self._supported_features & SUPPORT_BRIGHTNESS: + self._brightness = convert_scale( + self._device.status.level, 100, 255) + # Color Temperature + if self._supported_features & SUPPORT_COLOR_TEMP: + self._color_temp = color_util.color_temperature_kelvin_to_mired( + self._device.status.color_temperature) + # Color + if self._supported_features & SUPPORT_COLOR: + self._hs_color = ( + convert_scale(self._device.status.hue, 100, 360), + self._device.status.saturation + ) + + async def async_set_color(self, hs_color): + """Set the color of the device.""" + hue = convert_scale(float(hs_color[0]), 360, 100) + hue = max(min(hue, 100.0), 0.0) + saturation = max(min(float(hs_color[1]), 100.0), 0.0) + await self._device.set_color( + hue, saturation, set_status=True) + + async def async_set_color_temp(self, value: float): + """Set the color temperature of the device.""" + kelvin = color_util.color_temperature_mired_to_kelvin(value) + kelvin = max(min(kelvin, 30000.0), 1.0) + await self._device.set_color_temperature( + kelvin, set_status=True) + + async def async_set_level(self, brightness: int, transition: int): + """Set the brightness of the light over transition.""" + level = int(convert_scale(brightness, 255, 100, 0)) + # Due to rounding, set level to 1 (one) so we don't inadvertently + # turn off the light when a low brightness is set. + level = 1 if level == 0 and brightness > 0 else level + level = max(min(level, 100), 0) + duration = int(transition) + await self._device.set_level(level, duration, set_status=True) + + @property + def brightness(self): + """Return the brightness of this light between 0..255.""" + return self._brightness + + @property + def color_temp(self): + """Return the CT color value in mireds.""" + return self._color_temp + + @property + def hs_color(self): + """Return the hue and saturation color value [float, float].""" + return self._hs_color + + @property + def is_on(self) -> bool: + """Return true if light is on.""" + return self._device.status.switch + + @property + def max_mireds(self): + """Return the warmest color_temp that this light supports.""" + # SmartThings does not expose this attribute, instead it's + # implemented within each device-type handler. This value is the + # lowest kelvin found supported across 20+ handlers. + return 500 # 2000K + + @property + def min_mireds(self): + """Return the coldest color_temp that this light supports.""" + # SmartThings does not expose this attribute, instead it's + # implemented within each device-type handler. This value is the + # highest kelvin found supported across 20+ handlers. + return 111 # 9000K + + @property + def supported_features(self) -> int: + """Flag supported features.""" + return self._supported_features diff --git a/tests/components/smartthings/test_light.py b/tests/components/smartthings/test_light.py new file mode 100644 index 00000000000..a4f1103f270 --- /dev/null +++ b/tests/components/smartthings/test_light.py @@ -0,0 +1,293 @@ +""" +Test for the SmartThings light platform. + +The only mocking required is of the underlying SmartThings API object so +real HTTP calls are not initiated during testing. +""" +from pysmartthings import Attribute, Capability +import pytest + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_TRANSITION, + SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_TRANSITION) +from homeassistant.components.smartthings import DeviceBroker, light +from homeassistant.components.smartthings.const import ( + DATA_BROKERS, DOMAIN, SIGNAL_SMARTTHINGS_UPDATE) +from homeassistant.config_entries import ( + CONN_CLASS_CLOUD_PUSH, SOURCE_USER, ConfigEntry) +from homeassistant.const import ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES +from homeassistant.helpers.dispatcher import async_dispatcher_send + + +@pytest.fixture(name="light_devices") +def light_devices_fixture(device_factory): + """Fixture returns a set of mock light devices.""" + return [ + device_factory( + "Dimmer 1", + capabilities=[Capability.switch, Capability.switch_level], + status={Attribute.switch: 'on', Attribute.level: 100}), + device_factory( + "Color Dimmer 1", + capabilities=[Capability.switch, Capability.switch_level, + Capability.color_control], + status={Attribute.switch: 'off', Attribute.level: 0, + Attribute.hue: 76.0, Attribute.saturation: 55.0}), + device_factory( + "Color Dimmer 2", + capabilities=[Capability.switch, Capability.switch_level, + Capability.color_control, + Capability.color_temperature], + status={Attribute.switch: 'on', Attribute.level: 100, + Attribute.hue: 76.0, Attribute.saturation: 55.0, + Attribute.color_temperature: 4500}) + ] + + +async def _setup_platform(hass, *devices): + """Set up the SmartThings light platform and prerequisites.""" + hass.config.components.add(DOMAIN) + broker = DeviceBroker(hass, devices, '') + config_entry = ConfigEntry("1", DOMAIN, "Test", {}, + SOURCE_USER, CONN_CLASS_CLOUD_PUSH) + hass.data[DOMAIN] = { + DATA_BROKERS: { + config_entry.entry_id: broker + } + } + await hass.config_entries.async_forward_entry_setup(config_entry, 'light') + await hass.async_block_till_done() + return config_entry + + +async def test_async_setup_platform(): + """Test setup platform does nothing (it uses config entries).""" + await light.async_setup_platform(None, None, None) + + +def test_is_light(device_factory, light_devices): + """Test lights are correctly identified.""" + non_lights = [ + device_factory('Unknown', ['Unknown']), + device_factory("Fan 1", + [Capability.switch, Capability.switch_level, + Capability.fan_speed]), + device_factory("Switch 1", [Capability.switch]), + device_factory("Can't be turned off", + [Capability.switch_level, Capability.color_control, + Capability.color_temperature]) + ] + + for device in light_devices: + assert light.is_light(device), device.name + for device in non_lights: + assert not light.is_light(device), device.name + + +async def test_entity_state(hass, light_devices): + """Tests the state attributes properly match the light types.""" + await _setup_platform(hass, *light_devices) + + # Dimmer 1 + state = hass.states.get('light.dimmer_1') + assert state.state == 'on' + assert state.attributes[ATTR_SUPPORTED_FEATURES] == \ + SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + assert state.attributes[ATTR_BRIGHTNESS] == 255 + + # Color Dimmer 1 + state = hass.states.get('light.color_dimmer_1') + assert state.state == 'off' + assert state.attributes[ATTR_SUPPORTED_FEATURES] == \ + SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION | SUPPORT_COLOR + + # Color Dimmer 2 + state = hass.states.get('light.color_dimmer_2') + assert state.state == 'on' + assert state.attributes[ATTR_SUPPORTED_FEATURES] == \ + SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION | SUPPORT_COLOR | \ + SUPPORT_COLOR_TEMP + assert state.attributes[ATTR_BRIGHTNESS] == 255 + assert state.attributes[ATTR_HS_COLOR] == (273.6, 55.0) + assert state.attributes[ATTR_COLOR_TEMP] == 222 + + +async def test_entity_and_device_attributes(hass, device_factory): + """Test the attributes of the entity are correct.""" + # Arrange + device = device_factory( + "Light 1", [Capability.switch, Capability.switch_level]) + entity_registry = await hass.helpers.entity_registry.async_get_registry() + device_registry = await hass.helpers.device_registry.async_get_registry() + # Act + await _setup_platform(hass, device) + # Assert + entry = entity_registry.async_get("light.light_1") + assert entry + assert entry.unique_id == device.device_id + + entry = device_registry.async_get_device( + {(DOMAIN, device.device_id)}, []) + assert entry + assert entry.name == device.label + assert entry.model == device.device_type_name + assert entry.manufacturer == 'Unavailable' + + +async def test_turn_off(hass, light_devices): + """Test the light turns of successfully.""" + # Arrange + await _setup_platform(hass, *light_devices) + # Act + await hass.services.async_call( + 'light', 'turn_off', {'entity_id': 'light.color_dimmer_2'}, + blocking=True) + # Assert + state = hass.states.get('light.color_dimmer_2') + assert state is not None + assert state.state == 'off' + + +async def test_turn_off_with_transition(hass, light_devices): + """Test the light turns of successfully with transition.""" + # Arrange + await _setup_platform(hass, *light_devices) + # Act + await hass.services.async_call( + 'light', 'turn_off', + {ATTR_ENTITY_ID: "light.color_dimmer_2", ATTR_TRANSITION: 2}, + blocking=True) + # Assert + state = hass.states.get("light.color_dimmer_2") + assert state is not None + assert state.state == 'off' + + +async def test_turn_on(hass, light_devices): + """Test the light turns of successfully.""" + # Arrange + await _setup_platform(hass, *light_devices) + # Act + await hass.services.async_call( + 'light', 'turn_on', {ATTR_ENTITY_ID: "light.color_dimmer_1"}, + blocking=True) + # Assert + state = hass.states.get("light.color_dimmer_1") + assert state is not None + assert state.state == 'on' + + +async def test_turn_on_with_brightness(hass, light_devices): + """Test the light turns on to the specified brightness.""" + # Arrange + await _setup_platform(hass, *light_devices) + # Act + await hass.services.async_call( + 'light', 'turn_on', + {ATTR_ENTITY_ID: "light.color_dimmer_1", + ATTR_BRIGHTNESS: 75, ATTR_TRANSITION: 2}, + blocking=True) + # Assert + state = hass.states.get("light.color_dimmer_1") + assert state is not None + assert state.state == 'on' + # round-trip rounding error (expected) + assert state.attributes[ATTR_BRIGHTNESS] == 73.95 + + +async def test_turn_on_with_minimal_brightness(hass, light_devices): + """ + Test lights set to lowest brightness when converted scale would be zero. + + SmartThings light brightness is a percentage (0-100), but HASS uses a + 0-255 scale. This tests if a really low value (1-2) is passed, we don't + set the level to zero, which turns off the lights in SmartThings. + """ + # Arrange + await _setup_platform(hass, *light_devices) + # Act + await hass.services.async_call( + 'light', 'turn_on', + {ATTR_ENTITY_ID: "light.color_dimmer_1", + ATTR_BRIGHTNESS: 2}, + blocking=True) + # Assert + state = hass.states.get("light.color_dimmer_1") + assert state is not None + assert state.state == 'on' + # round-trip rounding error (expected) + assert state.attributes[ATTR_BRIGHTNESS] == 2.55 + + +async def test_turn_on_with_color(hass, light_devices): + """Test the light turns on with color.""" + # Arrange + await _setup_platform(hass, *light_devices) + # Act + await hass.services.async_call( + 'light', 'turn_on', + {ATTR_ENTITY_ID: "light.color_dimmer_2", + ATTR_HS_COLOR: (180, 50)}, + blocking=True) + # Assert + state = hass.states.get("light.color_dimmer_2") + assert state is not None + assert state.state == 'on' + assert state.attributes[ATTR_HS_COLOR] == (180, 50) + + +async def test_turn_on_with_color_temp(hass, light_devices): + """Test the light turns on with color temp.""" + # Arrange + await _setup_platform(hass, *light_devices) + # Act + await hass.services.async_call( + 'light', 'turn_on', + {ATTR_ENTITY_ID: "light.color_dimmer_2", + ATTR_COLOR_TEMP: 300}, + blocking=True) + # Assert + state = hass.states.get("light.color_dimmer_2") + assert state is not None + assert state.state == 'on' + assert state.attributes[ATTR_COLOR_TEMP] == 300 + + +async def test_update_from_signal(hass, device_factory): + """Test the light updates when receiving a signal.""" + # Arrange + device = device_factory( + "Color Dimmer 2", + capabilities=[Capability.switch, Capability.switch_level, + Capability.color_control, Capability.color_temperature], + status={Attribute.switch: 'off', Attribute.level: 100, + Attribute.hue: 76.0, Attribute.saturation: 55.0, + Attribute.color_temperature: 4500}) + await _setup_platform(hass, device) + await device.switch_on(True) + # Act + async_dispatcher_send(hass, SIGNAL_SMARTTHINGS_UPDATE, + [device.device_id]) + # Assert + await hass.async_block_till_done() + state = hass.states.get('light.color_dimmer_2') + assert state is not None + assert state.state == 'on' + + +async def test_unload_config_entry(hass, device_factory): + """Test the light is removed when the config entry is unloaded.""" + # Arrange + device = device_factory( + "Color Dimmer 2", + capabilities=[Capability.switch, Capability.switch_level, + Capability.color_control, Capability.color_temperature], + status={Attribute.switch: 'off', Attribute.level: 100, + Attribute.hue: 76.0, Attribute.saturation: 55.0, + Attribute.color_temperature: 4500}) + config_entry = await _setup_platform(hass, device) + # Act + await hass.config_entries.async_forward_entry_unload( + config_entry, 'light') + # Assert + assert not hass.states.get('light.color_dimmer_2') diff --git a/tests/components/smartthings/test_switch.py b/tests/components/smartthings/test_switch.py index 7bf8b15af51..a8013105291 100644 --- a/tests/components/smartthings/test_switch.py +++ b/tests/components/smartthings/test_switch.py @@ -62,15 +62,16 @@ async def test_entity_and_device_attributes(hass, device_factory): # Act await _setup_platform(hass, device) # Assert - entity = entity_registry.async_get('switch.switch_1') - assert entity - assert entity.unique_id == device.device_id - device_entry = device_registry.async_get_device( + entry = entity_registry.async_get('switch.switch_1') + assert entry + assert entry.unique_id == device.device_id + + entry = device_registry.async_get_device( {(DOMAIN, device.device_id)}, []) - assert device_entry - assert device_entry.name == device.label - assert device_entry.model == device.device_type_name - assert device_entry.manufacturer == 'Unavailable' + assert entry + assert entry.name == device.label + assert entry.model == device.device_type_name + assert entry.manufacturer == 'Unavailable' async def test_turn_off(hass, device_factory): From e2d3c27e8515d6bce546382a79c717891bcb5905 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 2 Feb 2019 07:13:16 -0800 Subject: [PATCH 030/242] Embed all platforms into components (#20677) * Consolidate all components with platforms * Organize tests * Fix more tests * Fix Verisure tests * one final test fix * Add change * Fix coverage --- .coveragerc | 666 ++++++------------ .../{abode.py => abode/__init__.py} | 0 .../abode.py => abode/alarm_control_panel.py} | 0 .../abode.py => abode/binary_sensor.py} | 0 .../{camera/abode.py => abode/camera.py} | 0 .../{cover/abode.py => abode/cover.py} | 0 .../{light/abode.py => abode/light.py} | 0 .../{lock/abode.py => abode/lock.py} | 0 .../{sensor/abode.py => abode/sensor.py} | 0 .../{switch/abode.py => abode/switch.py} | 0 .../ads.py => ads/binary_sensor.py} | 0 .../components/{light/ads.py => ads/light.py} | 0 .../{sensor/ads.py => ads/sensor.py} | 0 .../{switch/ads.py => ads/switch.py} | 0 .../__init__.py} | 0 .../alarm_control_panel.py} | 0 .../binary_sensor.py} | 0 .../sensor.py} | 0 .../{amcrest.py => amcrest/__init__.py} | 0 .../{camera/amcrest.py => amcrest/camera.py} | 0 .../{sensor/amcrest.py => amcrest/sensor.py} | 0 .../{switch/amcrest.py => amcrest/switch.py} | 0 .../__init__.py} | 0 .../binary_sensor.py} | 0 .../sensor.py} | 0 .../switch.py} | 0 .../{apcupsd.py => apcupsd/__init__.py} | 0 .../apcupsd.py => apcupsd/binary_sensor.py} | 0 .../{sensor/apcupsd.py => apcupsd/sensor.py} | 0 .../{apple_tv.py => apple_tv/__init__.py} | 0 .../apple_tv.py => apple_tv/media_player.py} | 0 .../apple_tv.py => apple_tv/remote.py} | 0 .../{aqualogic.py => aqualogic/__init__.py} | 0 .../aqualogic.py => aqualogic/sensor.py} | 0 .../aqualogic.py => aqualogic/switch.py} | 0 .../{arduino.py => arduino/__init__.py} | 0 .../{sensor/arduino.py => arduino/sensor.py} | 0 .../{switch/arduino.py => arduino/switch.py} | 0 .../components/{arlo.py => arlo/__init__.py} | 0 .../arlo.py => arlo/alarm_control_panel.py} | 0 .../{camera/arlo.py => arlo/camera.py} | 0 .../{sensor/arlo.py => arlo/sensor.py} | 0 .../__init__.py} | 0 .../mailbox.py} | 0 .../{august.py => august/__init__.py} | 0 .../august.py => august/binary_sensor.py} | 0 .../{camera/august.py => august/camera.py} | 0 .../{lock/august.py => august/lock.py} | 0 .../axis.py => axis/binary_sensor.py} | 0 .../{camera/axis.py => axis/camera.py} | 0 .../{bbb_gpio.py => bbb_gpio/__init__.py} | 0 .../bbb_gpio.py => bbb_gpio/binary_sensor.py} | 0 .../bbb_gpio.py => bbb_gpio/switch.py} | 0 .../blink.py => blink/alarm_control_panel.py} | 0 .../blink.py => blink/binary_sensor.py} | 0 .../{camera/blink.py => blink/camera.py} | 0 .../{sensor/blink.py => blink/sensor.py} | 0 .../{bloomsky.py => bloomsky/__init__.py} | 0 .../bloomsky.py => bloomsky/binary_sensor.py} | 0 .../bloomsky.py => bloomsky/camera.py} | 0 .../bloomsky.py => bloomsky/sensor.py} | 0 .../binary_sensor.py} | 0 .../device_tracker.py} | 0 .../lock.py} | 0 .../sensor.py} | 0 .../__init__.py} | 0 .../comfoconnect.py => comfoconnect/fan.py} | 0 .../sensor.py} | 0 .../__init__.py} | 0 .../binary_sensor.py} | 0 .../switch.py} | 0 .../{doorbird.py => doorbird/__init__.py} | 0 .../doorbird.py => doorbird/camera.py} | 0 .../doorbird.py => doorbird/switch.py} | 0 .../{dweet.py => dweet/__init__.py} | 0 .../{sensor/dweet.py => dweet/sensor.py} | 0 .../__init__.py} | 0 .../sensor.py} | 0 .../switch.py} | 0 .../{ecobee.py => ecobee/__init__.py} | 0 .../ecobee.py => ecobee/binary_sensor.py} | 0 .../{climate/ecobee.py => ecobee/climate.py} | 0 .../{notify/ecobee.py => ecobee/notify.py} | 0 .../{sensor/ecobee.py => ecobee/sensor.py} | 0 .../{weather/ecobee.py => ecobee/weather.py} | 0 .../{ecovacs.py => ecovacs/__init__.py} | 0 .../{vacuum/ecovacs.py => ecovacs/vacuum.py} | 0 .../{edp_redy.py => edp_redy/__init__.py} | 0 .../edp_redy.py => edp_redy/sensor.py} | 0 .../edp_redy.py => edp_redy/switch.py} | 0 .../{egardia.py => egardia/__init__.py} | 0 .../alarm_control_panel.py} | 0 .../egardia.py => egardia/binary_sensor.py} | 0 .../__init__.py} | 0 .../binary_sensor.py} | 0 .../eight_sleep.py => eight_sleep/sensor.py} | 0 .../elkm1.py => elkm1/alarm_control_panel.py} | 0 .../{climate/elkm1.py => elkm1/climate.py} | 0 .../{light/elkm1.py => elkm1/light.py} | 0 .../{scene/elkm1.py => elkm1/scene.py} | 0 .../{sensor/elkm1.py => elkm1/sensor.py} | 0 .../{switch/elkm1.py => elkm1/switch.py} | 0 .../{enocean.py => enocean/__init__.py} | 0 .../enocean.py => enocean/binary_sensor.py} | 0 .../{light/enocean.py => enocean/light.py} | 0 .../{sensor/enocean.py => enocean/sensor.py} | 0 .../{switch/enocean.py => enocean/switch.py} | 0 .../alarm_control_panel.py} | 0 .../binary_sensor.py} | 0 .../envisalink.py => envisalink/sensor.py} | 0 .../components/{eufy.py => eufy/__init__.py} | 0 .../{light/eufy.py => eufy/light.py} | 0 .../{switch/eufy.py => eufy/switch.py} | 0 .../{evohome.py => evohome/__init__.py} | 0 .../evohome.py => evohome/climate.py} | 0 .../fibaro.py => fibaro/binary_sensor.py} | 0 .../{cover/fibaro.py => fibaro/cover.py} | 0 .../{light/fibaro.py => fibaro/light.py} | 0 .../{scene/fibaro.py => fibaro/scene.py} | 0 .../{sensor/fibaro.py => fibaro/sensor.py} | 0 .../{switch/fibaro.py => fibaro/switch.py} | 0 .../{freebox.py => freebox/__init__.py} | 0 .../freebox.py => freebox/device_tracker.py} | 0 .../{sensor/freebox.py => freebox/sensor.py} | 0 .../{fritzbox.py => fritzbox/__init__.py} | 0 .../fritzbox.py => fritzbox/binary_sensor.py} | 0 .../fritzbox.py => fritzbox/climate.py} | 0 .../fritzbox.py => fritzbox/sensor.py} | 0 .../fritzbox.py => fritzbox/switch.py} | 0 .../{gc100.py => gc100/__init__.py} | 0 .../gc100.py => gc100/binary_sensor.py} | 0 .../{switch/gc100.py => gc100/switch.py} | 0 .../{google.py => google/__init__.py} | 0 .../google.py => google/calendar.py} | 0 .../{tts/google.py => google/tts.py} | 0 .../{googlehome.py => googlehome/__init__.py} | 0 .../device_tracker.py} | 0 .../habitica.py => habitica/sensor.py} | 0 .../{hdmi_cec.py => hdmi_cec/__init__.py} | 0 .../hdmi_cec.py => hdmi_cec/media_player.py} | 0 .../hdmi_cec.py => hdmi_cec/switch.py} | 0 .../components/{hive.py => hive/__init__.py} | 0 .../hive.py => hive/binary_sensor.py} | 0 .../{climate/hive.py => hive/climate.py} | 0 .../{light/hive.py => hive/light.py} | 0 .../{sensor/hive.py => hive/sensor.py} | 0 .../{switch/hive.py => hive/switch.py} | 0 .../{hlk_sw16.py => hlk_sw16/__init__.py} | 0 .../hlk_sw16.py => hlk_sw16/switch.py} | 0 .../binary_sensor.py} | 0 .../homematic.py => homematic/climate.py} | 0 .../homematic.py => homematic/cover.py} | 0 .../homematic.py => homematic/light.py} | 0 .../{lock/homematic.py => homematic/lock.py} | 0 .../homematic.py => homematic/notify.py} | 0 .../homematic.py => homematic/sensor.py} | 0 .../homematic.py => homematic/switch.py} | 0 .../{homeworks.py => homeworks/__init__.py} | 0 .../homeworks.py => homeworks/light.py} | 0 .../{huawei_lte.py => huawei_lte/__init__.py} | 0 .../device_tracker.py} | 0 .../huawei_lte.py => huawei_lte/notify.py} | 0 .../huawei_lte.py => huawei_lte/sensor.py} | 0 .../{hydrawise.py => hydrawise/__init__.py} | 0 .../binary_sensor.py} | 0 .../hydrawise.py => hydrawise/sensor.py} | 0 .../hydrawise.py => hydrawise/switch.py} | 0 .../ihc.py => ihc/binary_sensor.py} | 0 .../components/{light/ihc.py => ihc/light.py} | 0 .../{sensor/ihc.py => ihc/sensor.py} | 0 .../{switch/ihc.py => ihc/switch.py} | 0 .../insteon.py => insteon/binary_sensor.py} | 0 .../{cover/insteon.py => insteon/cover.py} | 0 .../{fan/insteon.py => insteon/fan.py} | 0 .../{light/insteon.py => insteon/light.py} | 0 .../{sensor/insteon.py => insteon/sensor.py} | 0 .../{switch/insteon.py => insteon/switch.py} | 0 .../components/{iota.py => iota/__init__.py} | 0 .../{sensor/iota.py => iota/sensor.py} | 0 .../{isy994.py => isy994/__init__.py} | 0 .../isy994.py => isy994/binary_sensor.py} | 0 .../{cover/isy994.py => isy994/cover.py} | 0 .../{fan/isy994.py => isy994/fan.py} | 0 .../{light/isy994.py => isy994/light.py} | 0 .../{lock/isy994.py => isy994/lock.py} | 0 .../{sensor/isy994.py => isy994/sensor.py} | 0 .../{switch/isy994.py => isy994/switch.py} | 0 .../__init__.py} | 0 .../notify.py} | 0 .../{juicenet.py => juicenet/__init__.py} | 0 .../juicenet.py => juicenet/sensor.py} | 0 .../components/{kira.py => kira/__init__.py} | 0 .../{remote/kira.py => kira/remote.py} | 0 .../{sensor/kira.py => kira/sensor.py} | 0 .../components/{knx.py => knx/__init__.py} | 0 .../knx.py => knx/binary_sensor.py} | 0 .../{climate/knx.py => knx/climate.py} | 0 .../components/{cover/knx.py => knx/cover.py} | 0 .../components/{light/knx.py => knx/light.py} | 0 .../{notify/knx.py => knx/notify.py} | 0 .../components/{scene/knx.py => knx/scene.py} | 0 .../{sensor/knx.py => knx/sensor.py} | 0 .../{switch/knx.py => knx/switch.py} | 0 .../{konnected.py => konnected/__init__.py} | 0 .../binary_sensor.py} | 0 .../konnected.py => konnected/switch.py} | 0 .../{lametric.py => lametric/__init__.py} | 0 .../lametric.py => lametric/notify.py} | 0 .../components/{lcn.py => lcn/__init__.py} | 0 .../components/{light/lcn.py => lcn/light.py} | 0 .../{switch/lcn.py => lcn/switch.py} | 0 .../{lightwave.py => lightwave/__init__.py} | 0 .../lightwave.py => lightwave/light.py} | 0 .../lightwave.py => lightwave/switch.py} | 0 .../{linode.py => linode/__init__.py} | 0 .../linode.py => linode/binary_sensor.py} | 0 .../{switch/linode.py => linode/switch.py} | 0 .../__init__.py} | 0 .../logi_circle.py => logi_circle/camera.py} | 0 .../logi_circle.py => logi_circle/sensor.py} | 0 .../{lupusec.py => lupusec/__init__.py} | 0 .../alarm_control_panel.py} | 0 .../lupusec.py => lupusec/binary_sensor.py} | 0 .../{switch/lupusec.py => lupusec/switch.py} | 0 .../__init__.py} | 0 .../cover.py} | 0 .../light.py} | 0 .../scene.py} | 0 .../switch.py} | 0 .../{matrix.py => matrix/__init__.py} | 0 .../{notify/matrix.py => matrix/notify.py} | 0 .../{maxcube.py => maxcube/__init__.py} | 0 .../maxcube.py => maxcube/binary_sensor.py} | 0 .../maxcube.py => maxcube/climate.py} | 0 .../{mochad.py => mochad/__init__.py} | 0 .../{light/mochad.py => mochad/light.py} | 0 .../{switch/mochad.py => mochad/switch.py} | 0 .../{modbus.py => modbus/__init__.py} | 0 .../modbus.py => modbus/binary_sensor.py} | 0 .../{climate/modbus.py => modbus/climate.py} | 0 .../{sensor/modbus.py => modbus/sensor.py} | 0 .../{switch/modbus.py => modbus/switch.py} | 0 .../{mychevy.py => mychevy/__init__.py} | 0 .../mychevy.py => mychevy/binary_sensor.py} | 0 .../{sensor/mychevy.py => mychevy/sensor.py} | 0 .../binary_sensor.py} | 0 .../mysensors.py => mysensors/climate.py} | 0 .../mysensors.py => mysensors/cover.py} | 0 .../device_tracker.py} | 0 .../mysensors.py => mysensors/light.py} | 0 .../mysensors.py => mysensors/notify.py} | 0 .../mysensors.py => mysensors/sensor.py} | 0 .../mysensors.py => mysensors/switch.py} | 0 .../{neato.py => neato/__init__.py} | 0 .../{camera/neato.py => neato/camera.py} | 0 .../{switch/neato.py => neato/switch.py} | 0 .../{vacuum/neato.py => neato/vacuum.py} | 0 .../{netatmo.py => netatmo/__init__.py} | 0 .../netatmo.py => netatmo/binary_sensor.py} | 0 .../{camera/netatmo.py => netatmo/camera.py} | 0 .../netatmo.py => netatmo/climate.py} | 0 .../{sensor/netatmo.py => netatmo/sensor.py} | 0 .../__init__.py} | 0 .../netgear_lte.py => netgear_lte/notify.py} | 0 .../netgear_lte.py => netgear_lte/sensor.py} | 0 .../{octoprint.py => octoprint/__init__.py} | 0 .../binary_sensor.py} | 0 .../octoprint.py => octoprint/sensor.py} | 0 .../binary_sensor.py} | 0 .../climate.py} | 0 .../sensor.py} | 0 .../{pilight.py => pilight/__init__.py} | 0 .../pilight.py => pilight/binary_sensor.py} | 0 .../{sensor/pilight.py => pilight/sensor.py} | 0 .../{switch/pilight.py => pilight/switch.py} | 0 .../__init__.py} | 0 .../light.py} | 0 .../{qwikswitch.py => qwikswitch/__init__.py} | 0 .../binary_sensor.py} | 0 .../qwikswitch.py => qwikswitch/light.py} | 0 .../qwikswitch.py => qwikswitch/sensor.py} | 0 .../qwikswitch.py => qwikswitch/switch.py} | 0 .../{rachio.py => rachio/__init__.py} | 0 .../rachio.py => rachio/binary_sensor.py} | 0 .../{switch/rachio.py => rachio/switch.py} | 0 .../{raincloud.py => raincloud/__init__.py} | 0 .../binary_sensor.py} | 0 .../raincloud.py => raincloud/sensor.py} | 0 .../raincloud.py => raincloud/switch.py} | 0 .../{raspihats.py => raspihats/__init__.py} | 0 .../binary_sensor.py} | 0 .../raspihats.py => raspihats/switch.py} | 0 .../{rfxtrx.py => rfxtrx/__init__.py} | 0 .../rfxtrx.py => rfxtrx/binary_sensor.py} | 0 .../{cover/rfxtrx.py => rfxtrx/cover.py} | 0 .../{light/rfxtrx.py => rfxtrx/light.py} | 0 .../{sensor/rfxtrx.py => rfxtrx/sensor.py} | 0 .../{switch/rfxtrx.py => rfxtrx/switch.py} | 0 .../components/{roku.py => roku/__init__.py} | 0 .../roku.py => roku/media_player.py} | 0 .../{remote/roku.py => roku/remote.py} | 0 .../{rpi_gpio.py => rpi_gpio/__init__.py} | 0 .../rpi_gpio.py => rpi_gpio/binary_sensor.py} | 0 .../{cover/rpi_gpio.py => rpi_gpio/cover.py} | 0 .../rpi_gpio.py => rpi_gpio/switch.py} | 0 .../{rpi_pfio.py => rpi_pfio/__init__.py} | 0 .../rpi_pfio.py => rpi_pfio/binary_sensor.py} | 0 .../rpi_pfio.py => rpi_pfio/switch.py} | 0 .../{sabnzbd.py => sabnzbd/__init__.py} | 0 .../{sensor/sabnzbd.py => sabnzbd/sensor.py} | 0 .../__init__.py} | 0 .../alarm_control_panel.py} | 0 .../binary_sensor.py} | 0 .../{scsgate.py => scsgate/__init__.py} | 0 .../{cover/scsgate.py => scsgate/cover.py} | 0 .../{light/scsgate.py => scsgate/light.py} | 0 .../{switch/scsgate.py => scsgate/switch.py} | 0 .../{sense.py => sense/__init__.py} | 0 .../sense.py => sense/binary_sensor.py} | 0 .../{sensor/sense.py => sense/sensor.py} | 0 .../{sisyphus.py => sisyphus/__init__.py} | 0 .../{light/sisyphus.py => sisyphus/light.py} | 0 .../sisyphus.py => sisyphus/media_player.py} | 0 .../{skybell.py => skybell/__init__.py} | 0 .../skybell.py => skybell/binary_sensor.py} | 0 .../{camera/skybell.py => skybell/camera.py} | 0 .../{light/skybell.py => skybell/light.py} | 0 .../{sensor/skybell.py => skybell/sensor.py} | 0 .../{switch/skybell.py => skybell/switch.py} | 0 .../{smappee.py => smappee/__init__.py} | 0 .../{sensor/smappee.py => smappee/sensor.py} | 0 .../{switch/smappee.py => smappee/switch.py} | 0 .../{spider.py => spider/__init__.py} | 0 .../{climate/spider.py => spider/climate.py} | 0 .../{switch/spider.py => spider/switch.py} | 0 .../components/{tado.py => tado/__init__.py} | 0 .../{climate/tado.py => tado/climate.py} | 0 .../tado.py => tado/device_tracker.py} | 0 .../{sensor/tado.py => tado/sensor.py} | 0 .../{tahoma.py => tahoma/__init__.py} | 0 .../tahoma.py => tahoma/binary_sensor.py} | 0 .../{cover/tahoma.py => tahoma/cover.py} | 0 .../{scene/tahoma.py => tahoma/scene.py} | 0 .../{sensor/tahoma.py => tahoma/sensor.py} | 0 .../{switch/tahoma.py => tahoma/switch.py} | 0 .../{tellstick.py => tellstick/__init__.py} | 0 .../tellstick.py => tellstick/cover.py} | 0 .../tellstick.py => tellstick/light.py} | 0 .../tellstick.py => tellstick/sensor.py} | 0 .../tellstick.py => tellstick/switch.py} | 0 .../{tesla.py => tesla/__init__.py} | 0 .../tesla.py => tesla/binary_sensor.py} | 0 .../{climate/tesla.py => tesla/climate.py} | 0 .../tesla.py => tesla/device_tracker.py} | 0 .../{lock/tesla.py => tesla/lock.py} | 0 .../{sensor/tesla.py => tesla/sensor.py} | 0 .../{switch/tesla.py => tesla/switch.py} | 0 .../__init__.py} | 0 .../sensor.py} | 0 .../components/thinkingcleaner/__init__.py | 1 + .../sensor.py} | 0 .../switch.py} | 0 .../{notify/tibber.py => tibber/notify.py} | 0 .../{sensor/tibber.py => tibber/sensor.py} | 0 .../components/{toon.py => toon/__init__.py} | 0 .../{climate/toon.py => toon/climate.py} | 0 .../{sensor/toon.py => toon/sensor.py} | 0 .../{switch/toon.py => toon/switch.py} | 0 .../{tplink_lte.py => tplink_lte/__init__.py} | 0 .../tplink_lte.py => tplink_lte/notify.py} | 0 .../__init__.py} | 0 .../sensor.py} | 0 .../switch.py} | 0 .../components/{tuya.py => tuya/__init__.py} | 0 .../{climate/tuya.py => tuya/climate.py} | 0 .../{cover/tuya.py => tuya/cover.py} | 0 .../components/{fan/tuya.py => tuya/fan.py} | 0 .../{light/tuya.py => tuya/light.py} | 0 .../{scene/tuya.py => tuya/scene.py} | 0 .../{switch/tuya.py => tuya/switch.py} | 0 .../{upcloud.py => upcloud/__init__.py} | 0 .../upcloud.py => upcloud/binary_sensor.py} | 0 .../{switch/upcloud.py => upcloud/switch.py} | 0 .../components/{usps.py => usps/__init__.py} | 0 .../{camera/usps.py => usps/camera.py} | 0 .../{sensor/usps.py => usps/sensor.py} | 0 .../{velbus.py => velbus/__init__.py} | 0 .../velbus.py => velbus/binary_sensor.py} | 0 .../{climate/velbus.py => velbus/climate.py} | 0 .../{cover/velbus.py => velbus/cover.py} | 0 .../{sensor/velbus.py => velbus/sensor.py} | 0 .../{switch/velbus.py => velbus/switch.py} | 0 .../{velux.py => velux/__init__.py} | 0 .../{cover/velux.py => velux/cover.py} | 0 .../{scene/velux.py => velux/scene.py} | 0 .../components/{vera.py => vera/__init__.py} | 0 .../vera.py => vera/binary_sensor.py} | 0 .../{climate/vera.py => vera/climate.py} | 0 .../{cover/vera.py => vera/cover.py} | 0 .../{light/vera.py => vera/light.py} | 0 .../components/{lock/vera.py => vera/lock.py} | 0 .../{scene/vera.py => vera/scene.py} | 0 .../{sensor/vera.py => vera/sensor.py} | 0 .../{switch/vera.py => vera/switch.py} | 0 .../{verisure.py => verisure/__init__.py} | 0 .../alarm_control_panel.py} | 0 .../verisure.py => verisure/binary_sensor.py} | 0 .../verisure.py => verisure/camera.py} | 0 .../{lock/verisure.py => verisure/lock.py} | 0 .../verisure.py => verisure/sensor.py} | 0 .../verisure.py => verisure/switch.py} | 0 .../__init__.py} | 0 .../binary_sensor.py} | 0 .../device_tracker.py} | 0 .../volvooncall.py => volvooncall/lock.py} | 0 .../volvooncall.py => volvooncall/sensor.py} | 0 .../volvooncall.py => volvooncall/switch.py} | 0 .../{w800rf32.py => w800rf32/__init__.py} | 0 .../w800rf32.py => w800rf32/binary_sensor.py} | 0 .../__init__.py} | 0 .../sensor.py} | 0 homeassistant/components/webostv/__init__.py | 1 + .../webostv.py => webostv/media_player.py} | 0 .../{notify/webostv.py => webostv/notify.py} | 0 .../components/{wemo.py => wemo/__init__.py} | 0 .../wemo.py => wemo/binary_sensor.py} | 0 .../components/{fan/wemo.py => wemo/fan.py} | 0 .../{light/wemo.py => wemo/light.py} | 0 .../{switch/wemo.py => wemo/switch.py} | 0 .../wink.py => wink/alarm_control_panel.py} | 0 .../wink.py => wink/binary_sensor.py} | 0 .../{climate/wink.py => wink/climate.py} | 0 .../{cover/wink.py => wink/cover.py} | 0 .../components/{fan/wink.py => wink/fan.py} | 0 .../{light/wink.py => wink/light.py} | 0 .../components/{lock/wink.py => wink/lock.py} | 0 .../{scene/wink.py => wink/scene.py} | 0 .../{sensor/wink.py => wink/sensor.py} | 0 .../{switch/wink.py => wink/switch.py} | 0 .../wink.py => wink/water_heater.py} | 0 .../__init__.py} | 0 .../binary_sensor.py} | 0 .../wirelesstag.py => wirelesstag/sensor.py} | 0 .../wirelesstag.py => wirelesstag/switch.py} | 0 .../__init__.py} | 0 .../binary_sensor.py} | 0 .../xiaomi_aqara.py => xiaomi_aqara/cover.py} | 0 .../xiaomi_aqara.py => xiaomi_aqara/light.py} | 0 .../xiaomi_aqara.py => xiaomi_aqara/lock.py} | 0 .../sensor.py} | 0 .../switch.py} | 0 .../components/xiaomi_miio/__init__.py | 1 + .../device_tracker.py} | 0 .../xiaomi_miio.py => xiaomi_miio/fan.py} | 0 .../xiaomi_miio.py => xiaomi_miio/light.py} | 0 .../xiaomi_miio.py => xiaomi_miio/remote.py} | 0 .../xiaomi_miio.py => xiaomi_miio/sensor.py} | 0 .../xiaomi_miio.py => xiaomi_miio/switch.py} | 0 .../xiaomi_miio.py => xiaomi_miio/vacuum.py} | 0 .../{zabbix.py => zabbix/__init__.py} | 0 .../{sensor/zabbix.py => zabbix/sensor.py} | 0 .../{zigbee.py => zigbee/__init__.py} | 0 .../zigbee.py => zigbee/binary_sensor.py} | 0 .../{light/zigbee.py => zigbee/light.py} | 0 .../{sensor/zigbee.py => zigbee/sensor.py} | 0 .../{switch/zigbee.py => zigbee/switch.py} | 0 homeassistant/helpers/state.py | 4 +- requirements_all.txt | 46 +- requirements_test_all.txt | 2 +- tests/components/arlo/__init__.py | 1 + .../test_arlo.py => arlo/test_sensor.py} | 4 +- tests/components/ecobee/__init__.py | 1 + .../test_ecobee.py => ecobee/test_climate.py} | 2 +- tests/components/fritzbox/__init__.py | 1 + .../test_climate.py} | 2 +- tests/components/google/__init__.py | 1 + .../test_calendar.py} | 14 +- .../test_google.py => google/test_tts.py} | 2 +- tests/components/kira/__init__.py | 1 + .../test_kira.py => kira/test_remote.py} | 2 +- .../test_kira.py => kira/test_sensor.py} | 2 +- tests/components/mochad/__init__.py | 1 + .../test_mochad.py => mochad/test_light.py} | 4 +- .../test_mochad.py => mochad/test_switch.py} | 4 +- tests/components/verisure/__init__.py | 1 + .../test_lock.py} | 2 +- tests/components/webostv/__init__.py | 1 + .../test_media_player.py} | 2 +- tests/components/xiaomi_miio/__init__.py | 1 + .../test_vacuum.py} | 2 +- 490 files changed, 255 insertions(+), 517 deletions(-) rename homeassistant/components/{abode.py => abode/__init__.py} (100%) rename homeassistant/components/{alarm_control_panel/abode.py => abode/alarm_control_panel.py} (100%) rename homeassistant/components/{binary_sensor/abode.py => abode/binary_sensor.py} (100%) rename homeassistant/components/{camera/abode.py => abode/camera.py} (100%) rename homeassistant/components/{cover/abode.py => abode/cover.py} (100%) rename homeassistant/components/{light/abode.py => abode/light.py} (100%) rename homeassistant/components/{lock/abode.py => abode/lock.py} (100%) rename homeassistant/components/{sensor/abode.py => abode/sensor.py} (100%) rename homeassistant/components/{switch/abode.py => abode/switch.py} (100%) rename homeassistant/components/{binary_sensor/ads.py => ads/binary_sensor.py} (100%) rename homeassistant/components/{light/ads.py => ads/light.py} (100%) rename homeassistant/components/{sensor/ads.py => ads/sensor.py} (100%) rename homeassistant/components/{switch/ads.py => ads/switch.py} (100%) rename homeassistant/components/{alarmdecoder.py => alarmdecoder/__init__.py} (100%) rename homeassistant/components/{alarm_control_panel/alarmdecoder.py => alarmdecoder/alarm_control_panel.py} (100%) rename homeassistant/components/{binary_sensor/alarmdecoder.py => alarmdecoder/binary_sensor.py} (100%) rename homeassistant/components/{sensor/alarmdecoder.py => alarmdecoder/sensor.py} (100%) rename homeassistant/components/{amcrest.py => amcrest/__init__.py} (100%) rename homeassistant/components/{camera/amcrest.py => amcrest/camera.py} (100%) rename homeassistant/components/{sensor/amcrest.py => amcrest/sensor.py} (100%) rename homeassistant/components/{switch/amcrest.py => amcrest/switch.py} (100%) rename homeassistant/components/{android_ip_webcam.py => android_ip_webcam/__init__.py} (100%) rename homeassistant/components/{binary_sensor/android_ip_webcam.py => android_ip_webcam/binary_sensor.py} (100%) rename homeassistant/components/{sensor/android_ip_webcam.py => android_ip_webcam/sensor.py} (100%) rename homeassistant/components/{switch/android_ip_webcam.py => android_ip_webcam/switch.py} (100%) rename homeassistant/components/{apcupsd.py => apcupsd/__init__.py} (100%) rename homeassistant/components/{binary_sensor/apcupsd.py => apcupsd/binary_sensor.py} (100%) rename homeassistant/components/{sensor/apcupsd.py => apcupsd/sensor.py} (100%) rename homeassistant/components/{apple_tv.py => apple_tv/__init__.py} (100%) rename homeassistant/components/{media_player/apple_tv.py => apple_tv/media_player.py} (100%) rename homeassistant/components/{remote/apple_tv.py => apple_tv/remote.py} (100%) rename homeassistant/components/{aqualogic.py => aqualogic/__init__.py} (100%) rename homeassistant/components/{sensor/aqualogic.py => aqualogic/sensor.py} (100%) rename homeassistant/components/{switch/aqualogic.py => aqualogic/switch.py} (100%) rename homeassistant/components/{arduino.py => arduino/__init__.py} (100%) rename homeassistant/components/{sensor/arduino.py => arduino/sensor.py} (100%) rename homeassistant/components/{switch/arduino.py => arduino/switch.py} (100%) rename homeassistant/components/{arlo.py => arlo/__init__.py} (100%) rename homeassistant/components/{alarm_control_panel/arlo.py => arlo/alarm_control_panel.py} (100%) rename homeassistant/components/{camera/arlo.py => arlo/camera.py} (100%) rename homeassistant/components/{sensor/arlo.py => arlo/sensor.py} (100%) rename homeassistant/components/{asterisk_mbox.py => asterisk_mbox/__init__.py} (100%) rename homeassistant/components/{mailbox/asterisk_mbox.py => asterisk_mbox/mailbox.py} (100%) rename homeassistant/components/{august.py => august/__init__.py} (100%) rename homeassistant/components/{binary_sensor/august.py => august/binary_sensor.py} (100%) rename homeassistant/components/{camera/august.py => august/camera.py} (100%) rename homeassistant/components/{lock/august.py => august/lock.py} (100%) rename homeassistant/components/{binary_sensor/axis.py => axis/binary_sensor.py} (100%) rename homeassistant/components/{camera/axis.py => axis/camera.py} (100%) rename homeassistant/components/{bbb_gpio.py => bbb_gpio/__init__.py} (100%) rename homeassistant/components/{binary_sensor/bbb_gpio.py => bbb_gpio/binary_sensor.py} (100%) rename homeassistant/components/{switch/bbb_gpio.py => bbb_gpio/switch.py} (100%) rename homeassistant/components/{alarm_control_panel/blink.py => blink/alarm_control_panel.py} (100%) rename homeassistant/components/{binary_sensor/blink.py => blink/binary_sensor.py} (100%) rename homeassistant/components/{camera/blink.py => blink/camera.py} (100%) rename homeassistant/components/{sensor/blink.py => blink/sensor.py} (100%) rename homeassistant/components/{bloomsky.py => bloomsky/__init__.py} (100%) rename homeassistant/components/{binary_sensor/bloomsky.py => bloomsky/binary_sensor.py} (100%) rename homeassistant/components/{camera/bloomsky.py => bloomsky/camera.py} (100%) rename homeassistant/components/{sensor/bloomsky.py => bloomsky/sensor.py} (100%) rename homeassistant/components/{binary_sensor/bmw_connected_drive.py => bmw_connected_drive/binary_sensor.py} (100%) rename homeassistant/components/{device_tracker/bmw_connected_drive.py => bmw_connected_drive/device_tracker.py} (100%) rename homeassistant/components/{lock/bmw_connected_drive.py => bmw_connected_drive/lock.py} (100%) rename homeassistant/components/{sensor/bmw_connected_drive.py => bmw_connected_drive/sensor.py} (100%) rename homeassistant/components/{comfoconnect.py => comfoconnect/__init__.py} (100%) rename homeassistant/components/{fan/comfoconnect.py => comfoconnect/fan.py} (100%) rename homeassistant/components/{sensor/comfoconnect.py => comfoconnect/sensor.py} (100%) rename homeassistant/components/{digital_ocean.py => digital_ocean/__init__.py} (100%) rename homeassistant/components/{binary_sensor/digital_ocean.py => digital_ocean/binary_sensor.py} (100%) rename homeassistant/components/{switch/digital_ocean.py => digital_ocean/switch.py} (100%) rename homeassistant/components/{doorbird.py => doorbird/__init__.py} (100%) rename homeassistant/components/{camera/doorbird.py => doorbird/camera.py} (100%) rename homeassistant/components/{switch/doorbird.py => doorbird/switch.py} (100%) rename homeassistant/components/{dweet.py => dweet/__init__.py} (100%) rename homeassistant/components/{sensor/dweet.py => dweet/sensor.py} (100%) rename homeassistant/components/{ecoal_boiler.py => ecoal_boiler/__init__.py} (100%) rename homeassistant/components/{sensor/ecoal_boiler.py => ecoal_boiler/sensor.py} (100%) rename homeassistant/components/{switch/ecoal_boiler.py => ecoal_boiler/switch.py} (100%) rename homeassistant/components/{ecobee.py => ecobee/__init__.py} (100%) rename homeassistant/components/{binary_sensor/ecobee.py => ecobee/binary_sensor.py} (100%) rename homeassistant/components/{climate/ecobee.py => ecobee/climate.py} (100%) rename homeassistant/components/{notify/ecobee.py => ecobee/notify.py} (100%) rename homeassistant/components/{sensor/ecobee.py => ecobee/sensor.py} (100%) rename homeassistant/components/{weather/ecobee.py => ecobee/weather.py} (100%) rename homeassistant/components/{ecovacs.py => ecovacs/__init__.py} (100%) rename homeassistant/components/{vacuum/ecovacs.py => ecovacs/vacuum.py} (100%) rename homeassistant/components/{edp_redy.py => edp_redy/__init__.py} (100%) rename homeassistant/components/{sensor/edp_redy.py => edp_redy/sensor.py} (100%) rename homeassistant/components/{switch/edp_redy.py => edp_redy/switch.py} (100%) rename homeassistant/components/{egardia.py => egardia/__init__.py} (100%) rename homeassistant/components/{alarm_control_panel/egardia.py => egardia/alarm_control_panel.py} (100%) rename homeassistant/components/{binary_sensor/egardia.py => egardia/binary_sensor.py} (100%) rename homeassistant/components/{eight_sleep.py => eight_sleep/__init__.py} (100%) rename homeassistant/components/{binary_sensor/eight_sleep.py => eight_sleep/binary_sensor.py} (100%) rename homeassistant/components/{sensor/eight_sleep.py => eight_sleep/sensor.py} (100%) rename homeassistant/components/{alarm_control_panel/elkm1.py => elkm1/alarm_control_panel.py} (100%) rename homeassistant/components/{climate/elkm1.py => elkm1/climate.py} (100%) rename homeassistant/components/{light/elkm1.py => elkm1/light.py} (100%) rename homeassistant/components/{scene/elkm1.py => elkm1/scene.py} (100%) rename homeassistant/components/{sensor/elkm1.py => elkm1/sensor.py} (100%) rename homeassistant/components/{switch/elkm1.py => elkm1/switch.py} (100%) rename homeassistant/components/{enocean.py => enocean/__init__.py} (100%) rename homeassistant/components/{binary_sensor/enocean.py => enocean/binary_sensor.py} (100%) rename homeassistant/components/{light/enocean.py => enocean/light.py} (100%) rename homeassistant/components/{sensor/enocean.py => enocean/sensor.py} (100%) rename homeassistant/components/{switch/enocean.py => enocean/switch.py} (100%) rename homeassistant/components/{alarm_control_panel/envisalink.py => envisalink/alarm_control_panel.py} (100%) rename homeassistant/components/{binary_sensor/envisalink.py => envisalink/binary_sensor.py} (100%) rename homeassistant/components/{sensor/envisalink.py => envisalink/sensor.py} (100%) rename homeassistant/components/{eufy.py => eufy/__init__.py} (100%) rename homeassistant/components/{light/eufy.py => eufy/light.py} (100%) rename homeassistant/components/{switch/eufy.py => eufy/switch.py} (100%) rename homeassistant/components/{evohome.py => evohome/__init__.py} (100%) rename homeassistant/components/{climate/evohome.py => evohome/climate.py} (100%) rename homeassistant/components/{binary_sensor/fibaro.py => fibaro/binary_sensor.py} (100%) rename homeassistant/components/{cover/fibaro.py => fibaro/cover.py} (100%) rename homeassistant/components/{light/fibaro.py => fibaro/light.py} (100%) rename homeassistant/components/{scene/fibaro.py => fibaro/scene.py} (100%) rename homeassistant/components/{sensor/fibaro.py => fibaro/sensor.py} (100%) rename homeassistant/components/{switch/fibaro.py => fibaro/switch.py} (100%) rename homeassistant/components/{freebox.py => freebox/__init__.py} (100%) rename homeassistant/components/{device_tracker/freebox.py => freebox/device_tracker.py} (100%) rename homeassistant/components/{sensor/freebox.py => freebox/sensor.py} (100%) rename homeassistant/components/{fritzbox.py => fritzbox/__init__.py} (100%) rename homeassistant/components/{binary_sensor/fritzbox.py => fritzbox/binary_sensor.py} (100%) rename homeassistant/components/{climate/fritzbox.py => fritzbox/climate.py} (100%) rename homeassistant/components/{sensor/fritzbox.py => fritzbox/sensor.py} (100%) rename homeassistant/components/{switch/fritzbox.py => fritzbox/switch.py} (100%) rename homeassistant/components/{gc100.py => gc100/__init__.py} (100%) rename homeassistant/components/{binary_sensor/gc100.py => gc100/binary_sensor.py} (100%) rename homeassistant/components/{switch/gc100.py => gc100/switch.py} (100%) rename homeassistant/components/{google.py => google/__init__.py} (100%) rename homeassistant/components/{calendar/google.py => google/calendar.py} (100%) rename homeassistant/components/{tts/google.py => google/tts.py} (100%) rename homeassistant/components/{googlehome.py => googlehome/__init__.py} (100%) rename homeassistant/components/{device_tracker/googlehome.py => googlehome/device_tracker.py} (100%) rename homeassistant/components/{sensor/habitica.py => habitica/sensor.py} (100%) rename homeassistant/components/{hdmi_cec.py => hdmi_cec/__init__.py} (100%) rename homeassistant/components/{media_player/hdmi_cec.py => hdmi_cec/media_player.py} (100%) rename homeassistant/components/{switch/hdmi_cec.py => hdmi_cec/switch.py} (100%) rename homeassistant/components/{hive.py => hive/__init__.py} (100%) rename homeassistant/components/{binary_sensor/hive.py => hive/binary_sensor.py} (100%) rename homeassistant/components/{climate/hive.py => hive/climate.py} (100%) rename homeassistant/components/{light/hive.py => hive/light.py} (100%) rename homeassistant/components/{sensor/hive.py => hive/sensor.py} (100%) rename homeassistant/components/{switch/hive.py => hive/switch.py} (100%) rename homeassistant/components/{hlk_sw16.py => hlk_sw16/__init__.py} (100%) rename homeassistant/components/{switch/hlk_sw16.py => hlk_sw16/switch.py} (100%) rename homeassistant/components/{binary_sensor/homematic.py => homematic/binary_sensor.py} (100%) rename homeassistant/components/{climate/homematic.py => homematic/climate.py} (100%) rename homeassistant/components/{cover/homematic.py => homematic/cover.py} (100%) rename homeassistant/components/{light/homematic.py => homematic/light.py} (100%) rename homeassistant/components/{lock/homematic.py => homematic/lock.py} (100%) rename homeassistant/components/{notify/homematic.py => homematic/notify.py} (100%) rename homeassistant/components/{sensor/homematic.py => homematic/sensor.py} (100%) rename homeassistant/components/{switch/homematic.py => homematic/switch.py} (100%) rename homeassistant/components/{homeworks.py => homeworks/__init__.py} (100%) rename homeassistant/components/{light/homeworks.py => homeworks/light.py} (100%) rename homeassistant/components/{huawei_lte.py => huawei_lte/__init__.py} (100%) rename homeassistant/components/{device_tracker/huawei_lte.py => huawei_lte/device_tracker.py} (100%) rename homeassistant/components/{notify/huawei_lte.py => huawei_lte/notify.py} (100%) rename homeassistant/components/{sensor/huawei_lte.py => huawei_lte/sensor.py} (100%) rename homeassistant/components/{hydrawise.py => hydrawise/__init__.py} (100%) rename homeassistant/components/{binary_sensor/hydrawise.py => hydrawise/binary_sensor.py} (100%) rename homeassistant/components/{sensor/hydrawise.py => hydrawise/sensor.py} (100%) rename homeassistant/components/{switch/hydrawise.py => hydrawise/switch.py} (100%) rename homeassistant/components/{binary_sensor/ihc.py => ihc/binary_sensor.py} (100%) rename homeassistant/components/{light/ihc.py => ihc/light.py} (100%) rename homeassistant/components/{sensor/ihc.py => ihc/sensor.py} (100%) rename homeassistant/components/{switch/ihc.py => ihc/switch.py} (100%) rename homeassistant/components/{binary_sensor/insteon.py => insteon/binary_sensor.py} (100%) rename homeassistant/components/{cover/insteon.py => insteon/cover.py} (100%) rename homeassistant/components/{fan/insteon.py => insteon/fan.py} (100%) rename homeassistant/components/{light/insteon.py => insteon/light.py} (100%) rename homeassistant/components/{sensor/insteon.py => insteon/sensor.py} (100%) rename homeassistant/components/{switch/insteon.py => insteon/switch.py} (100%) rename homeassistant/components/{iota.py => iota/__init__.py} (100%) rename homeassistant/components/{sensor/iota.py => iota/sensor.py} (100%) rename homeassistant/components/{isy994.py => isy994/__init__.py} (100%) rename homeassistant/components/{binary_sensor/isy994.py => isy994/binary_sensor.py} (100%) rename homeassistant/components/{cover/isy994.py => isy994/cover.py} (100%) rename homeassistant/components/{fan/isy994.py => isy994/fan.py} (100%) rename homeassistant/components/{light/isy994.py => isy994/light.py} (100%) rename homeassistant/components/{lock/isy994.py => isy994/lock.py} (100%) rename homeassistant/components/{sensor/isy994.py => isy994/sensor.py} (100%) rename homeassistant/components/{switch/isy994.py => isy994/switch.py} (100%) rename homeassistant/components/{joaoapps_join.py => joaoapps_join/__init__.py} (100%) rename homeassistant/components/{notify/joaoapps_join.py => joaoapps_join/notify.py} (100%) rename homeassistant/components/{juicenet.py => juicenet/__init__.py} (100%) rename homeassistant/components/{sensor/juicenet.py => juicenet/sensor.py} (100%) rename homeassistant/components/{kira.py => kira/__init__.py} (100%) rename homeassistant/components/{remote/kira.py => kira/remote.py} (100%) rename homeassistant/components/{sensor/kira.py => kira/sensor.py} (100%) rename homeassistant/components/{knx.py => knx/__init__.py} (100%) rename homeassistant/components/{binary_sensor/knx.py => knx/binary_sensor.py} (100%) rename homeassistant/components/{climate/knx.py => knx/climate.py} (100%) rename homeassistant/components/{cover/knx.py => knx/cover.py} (100%) rename homeassistant/components/{light/knx.py => knx/light.py} (100%) rename homeassistant/components/{notify/knx.py => knx/notify.py} (100%) rename homeassistant/components/{scene/knx.py => knx/scene.py} (100%) rename homeassistant/components/{sensor/knx.py => knx/sensor.py} (100%) rename homeassistant/components/{switch/knx.py => knx/switch.py} (100%) rename homeassistant/components/{konnected.py => konnected/__init__.py} (100%) rename homeassistant/components/{binary_sensor/konnected.py => konnected/binary_sensor.py} (100%) rename homeassistant/components/{switch/konnected.py => konnected/switch.py} (100%) rename homeassistant/components/{lametric.py => lametric/__init__.py} (100%) rename homeassistant/components/{notify/lametric.py => lametric/notify.py} (100%) rename homeassistant/components/{lcn.py => lcn/__init__.py} (100%) rename homeassistant/components/{light/lcn.py => lcn/light.py} (100%) rename homeassistant/components/{switch/lcn.py => lcn/switch.py} (100%) rename homeassistant/components/{lightwave.py => lightwave/__init__.py} (100%) rename homeassistant/components/{light/lightwave.py => lightwave/light.py} (100%) rename homeassistant/components/{switch/lightwave.py => lightwave/switch.py} (100%) rename homeassistant/components/{linode.py => linode/__init__.py} (100%) rename homeassistant/components/{binary_sensor/linode.py => linode/binary_sensor.py} (100%) rename homeassistant/components/{switch/linode.py => linode/switch.py} (100%) rename homeassistant/components/{logi_circle.py => logi_circle/__init__.py} (100%) rename homeassistant/components/{camera/logi_circle.py => logi_circle/camera.py} (100%) rename homeassistant/components/{sensor/logi_circle.py => logi_circle/sensor.py} (100%) rename homeassistant/components/{lupusec.py => lupusec/__init__.py} (100%) rename homeassistant/components/{alarm_control_panel/lupusec.py => lupusec/alarm_control_panel.py} (100%) rename homeassistant/components/{binary_sensor/lupusec.py => lupusec/binary_sensor.py} (100%) rename homeassistant/components/{switch/lupusec.py => lupusec/switch.py} (100%) rename homeassistant/components/{lutron_caseta.py => lutron_caseta/__init__.py} (100%) rename homeassistant/components/{cover/lutron_caseta.py => lutron_caseta/cover.py} (100%) rename homeassistant/components/{light/lutron_caseta.py => lutron_caseta/light.py} (100%) rename homeassistant/components/{scene/lutron_caseta.py => lutron_caseta/scene.py} (100%) rename homeassistant/components/{switch/lutron_caseta.py => lutron_caseta/switch.py} (100%) rename homeassistant/components/{matrix.py => matrix/__init__.py} (100%) rename homeassistant/components/{notify/matrix.py => matrix/notify.py} (100%) rename homeassistant/components/{maxcube.py => maxcube/__init__.py} (100%) rename homeassistant/components/{binary_sensor/maxcube.py => maxcube/binary_sensor.py} (100%) rename homeassistant/components/{climate/maxcube.py => maxcube/climate.py} (100%) rename homeassistant/components/{mochad.py => mochad/__init__.py} (100%) rename homeassistant/components/{light/mochad.py => mochad/light.py} (100%) rename homeassistant/components/{switch/mochad.py => mochad/switch.py} (100%) rename homeassistant/components/{modbus.py => modbus/__init__.py} (100%) rename homeassistant/components/{binary_sensor/modbus.py => modbus/binary_sensor.py} (100%) rename homeassistant/components/{climate/modbus.py => modbus/climate.py} (100%) rename homeassistant/components/{sensor/modbus.py => modbus/sensor.py} (100%) rename homeassistant/components/{switch/modbus.py => modbus/switch.py} (100%) rename homeassistant/components/{mychevy.py => mychevy/__init__.py} (100%) rename homeassistant/components/{binary_sensor/mychevy.py => mychevy/binary_sensor.py} (100%) rename homeassistant/components/{sensor/mychevy.py => mychevy/sensor.py} (100%) rename homeassistant/components/{binary_sensor/mysensors.py => mysensors/binary_sensor.py} (100%) rename homeassistant/components/{climate/mysensors.py => mysensors/climate.py} (100%) rename homeassistant/components/{cover/mysensors.py => mysensors/cover.py} (100%) rename homeassistant/components/{device_tracker/mysensors.py => mysensors/device_tracker.py} (100%) rename homeassistant/components/{light/mysensors.py => mysensors/light.py} (100%) rename homeassistant/components/{notify/mysensors.py => mysensors/notify.py} (100%) rename homeassistant/components/{sensor/mysensors.py => mysensors/sensor.py} (100%) rename homeassistant/components/{switch/mysensors.py => mysensors/switch.py} (100%) rename homeassistant/components/{neato.py => neato/__init__.py} (100%) rename homeassistant/components/{camera/neato.py => neato/camera.py} (100%) rename homeassistant/components/{switch/neato.py => neato/switch.py} (100%) rename homeassistant/components/{vacuum/neato.py => neato/vacuum.py} (100%) rename homeassistant/components/{netatmo.py => netatmo/__init__.py} (100%) rename homeassistant/components/{binary_sensor/netatmo.py => netatmo/binary_sensor.py} (100%) rename homeassistant/components/{camera/netatmo.py => netatmo/camera.py} (100%) rename homeassistant/components/{climate/netatmo.py => netatmo/climate.py} (100%) rename homeassistant/components/{sensor/netatmo.py => netatmo/sensor.py} (100%) rename homeassistant/components/{netgear_lte.py => netgear_lte/__init__.py} (100%) rename homeassistant/components/{notify/netgear_lte.py => netgear_lte/notify.py} (100%) rename homeassistant/components/{sensor/netgear_lte.py => netgear_lte/sensor.py} (100%) rename homeassistant/components/{octoprint.py => octoprint/__init__.py} (100%) rename homeassistant/components/{binary_sensor/octoprint.py => octoprint/binary_sensor.py} (100%) rename homeassistant/components/{sensor/octoprint.py => octoprint/sensor.py} (100%) rename homeassistant/components/{binary_sensor/opentherm_gw.py => opentherm_gw/binary_sensor.py} (100%) rename homeassistant/components/{climate/opentherm_gw.py => opentherm_gw/climate.py} (100%) rename homeassistant/components/{sensor/opentherm_gw.py => opentherm_gw/sensor.py} (100%) rename homeassistant/components/{pilight.py => pilight/__init__.py} (100%) rename homeassistant/components/{binary_sensor/pilight.py => pilight/binary_sensor.py} (100%) rename homeassistant/components/{sensor/pilight.py => pilight/sensor.py} (100%) rename homeassistant/components/{switch/pilight.py => pilight/switch.py} (100%) rename homeassistant/components/{plum_lightpad.py => plum_lightpad/__init__.py} (100%) rename homeassistant/components/{light/plum_lightpad.py => plum_lightpad/light.py} (100%) rename homeassistant/components/{qwikswitch.py => qwikswitch/__init__.py} (100%) rename homeassistant/components/{binary_sensor/qwikswitch.py => qwikswitch/binary_sensor.py} (100%) rename homeassistant/components/{light/qwikswitch.py => qwikswitch/light.py} (100%) rename homeassistant/components/{sensor/qwikswitch.py => qwikswitch/sensor.py} (100%) rename homeassistant/components/{switch/qwikswitch.py => qwikswitch/switch.py} (100%) rename homeassistant/components/{rachio.py => rachio/__init__.py} (100%) rename homeassistant/components/{binary_sensor/rachio.py => rachio/binary_sensor.py} (100%) rename homeassistant/components/{switch/rachio.py => rachio/switch.py} (100%) rename homeassistant/components/{raincloud.py => raincloud/__init__.py} (100%) rename homeassistant/components/{binary_sensor/raincloud.py => raincloud/binary_sensor.py} (100%) rename homeassistant/components/{sensor/raincloud.py => raincloud/sensor.py} (100%) rename homeassistant/components/{switch/raincloud.py => raincloud/switch.py} (100%) rename homeassistant/components/{raspihats.py => raspihats/__init__.py} (100%) rename homeassistant/components/{binary_sensor/raspihats.py => raspihats/binary_sensor.py} (100%) rename homeassistant/components/{switch/raspihats.py => raspihats/switch.py} (100%) rename homeassistant/components/{rfxtrx.py => rfxtrx/__init__.py} (100%) rename homeassistant/components/{binary_sensor/rfxtrx.py => rfxtrx/binary_sensor.py} (100%) rename homeassistant/components/{cover/rfxtrx.py => rfxtrx/cover.py} (100%) rename homeassistant/components/{light/rfxtrx.py => rfxtrx/light.py} (100%) rename homeassistant/components/{sensor/rfxtrx.py => rfxtrx/sensor.py} (100%) rename homeassistant/components/{switch/rfxtrx.py => rfxtrx/switch.py} (100%) rename homeassistant/components/{roku.py => roku/__init__.py} (100%) rename homeassistant/components/{media_player/roku.py => roku/media_player.py} (100%) rename homeassistant/components/{remote/roku.py => roku/remote.py} (100%) rename homeassistant/components/{rpi_gpio.py => rpi_gpio/__init__.py} (100%) rename homeassistant/components/{binary_sensor/rpi_gpio.py => rpi_gpio/binary_sensor.py} (100%) rename homeassistant/components/{cover/rpi_gpio.py => rpi_gpio/cover.py} (100%) rename homeassistant/components/{switch/rpi_gpio.py => rpi_gpio/switch.py} (100%) rename homeassistant/components/{rpi_pfio.py => rpi_pfio/__init__.py} (100%) rename homeassistant/components/{binary_sensor/rpi_pfio.py => rpi_pfio/binary_sensor.py} (100%) rename homeassistant/components/{switch/rpi_pfio.py => rpi_pfio/switch.py} (100%) rename homeassistant/components/{sabnzbd.py => sabnzbd/__init__.py} (100%) rename homeassistant/components/{sensor/sabnzbd.py => sabnzbd/sensor.py} (100%) rename homeassistant/components/{satel_integra.py => satel_integra/__init__.py} (100%) rename homeassistant/components/{alarm_control_panel/satel_integra.py => satel_integra/alarm_control_panel.py} (100%) rename homeassistant/components/{binary_sensor/satel_integra.py => satel_integra/binary_sensor.py} (100%) rename homeassistant/components/{scsgate.py => scsgate/__init__.py} (100%) rename homeassistant/components/{cover/scsgate.py => scsgate/cover.py} (100%) rename homeassistant/components/{light/scsgate.py => scsgate/light.py} (100%) rename homeassistant/components/{switch/scsgate.py => scsgate/switch.py} (100%) rename homeassistant/components/{sense.py => sense/__init__.py} (100%) rename homeassistant/components/{binary_sensor/sense.py => sense/binary_sensor.py} (100%) rename homeassistant/components/{sensor/sense.py => sense/sensor.py} (100%) rename homeassistant/components/{sisyphus.py => sisyphus/__init__.py} (100%) rename homeassistant/components/{light/sisyphus.py => sisyphus/light.py} (100%) rename homeassistant/components/{media_player/sisyphus.py => sisyphus/media_player.py} (100%) rename homeassistant/components/{skybell.py => skybell/__init__.py} (100%) rename homeassistant/components/{binary_sensor/skybell.py => skybell/binary_sensor.py} (100%) rename homeassistant/components/{camera/skybell.py => skybell/camera.py} (100%) rename homeassistant/components/{light/skybell.py => skybell/light.py} (100%) rename homeassistant/components/{sensor/skybell.py => skybell/sensor.py} (100%) rename homeassistant/components/{switch/skybell.py => skybell/switch.py} (100%) rename homeassistant/components/{smappee.py => smappee/__init__.py} (100%) rename homeassistant/components/{sensor/smappee.py => smappee/sensor.py} (100%) rename homeassistant/components/{switch/smappee.py => smappee/switch.py} (100%) rename homeassistant/components/{spider.py => spider/__init__.py} (100%) rename homeassistant/components/{climate/spider.py => spider/climate.py} (100%) rename homeassistant/components/{switch/spider.py => spider/switch.py} (100%) rename homeassistant/components/{tado.py => tado/__init__.py} (100%) rename homeassistant/components/{climate/tado.py => tado/climate.py} (100%) rename homeassistant/components/{device_tracker/tado.py => tado/device_tracker.py} (100%) rename homeassistant/components/{sensor/tado.py => tado/sensor.py} (100%) rename homeassistant/components/{tahoma.py => tahoma/__init__.py} (100%) rename homeassistant/components/{binary_sensor/tahoma.py => tahoma/binary_sensor.py} (100%) rename homeassistant/components/{cover/tahoma.py => tahoma/cover.py} (100%) rename homeassistant/components/{scene/tahoma.py => tahoma/scene.py} (100%) rename homeassistant/components/{sensor/tahoma.py => tahoma/sensor.py} (100%) rename homeassistant/components/{switch/tahoma.py => tahoma/switch.py} (100%) rename homeassistant/components/{tellstick.py => tellstick/__init__.py} (100%) rename homeassistant/components/{cover/tellstick.py => tellstick/cover.py} (100%) rename homeassistant/components/{light/tellstick.py => tellstick/light.py} (100%) rename homeassistant/components/{sensor/tellstick.py => tellstick/sensor.py} (100%) rename homeassistant/components/{switch/tellstick.py => tellstick/switch.py} (100%) rename homeassistant/components/{tesla.py => tesla/__init__.py} (100%) rename homeassistant/components/{binary_sensor/tesla.py => tesla/binary_sensor.py} (100%) rename homeassistant/components/{climate/tesla.py => tesla/climate.py} (100%) rename homeassistant/components/{device_tracker/tesla.py => tesla/device_tracker.py} (100%) rename homeassistant/components/{lock/tesla.py => tesla/lock.py} (100%) rename homeassistant/components/{sensor/tesla.py => tesla/sensor.py} (100%) rename homeassistant/components/{switch/tesla.py => tesla/switch.py} (100%) rename homeassistant/components/{thethingsnetwork.py => thethingsnetwork/__init__.py} (100%) rename homeassistant/components/{sensor/thethingsnetwork.py => thethingsnetwork/sensor.py} (100%) create mode 100644 homeassistant/components/thinkingcleaner/__init__.py rename homeassistant/components/{sensor/thinkingcleaner.py => thinkingcleaner/sensor.py} (100%) rename homeassistant/components/{switch/thinkingcleaner.py => thinkingcleaner/switch.py} (100%) rename homeassistant/components/{notify/tibber.py => tibber/notify.py} (100%) rename homeassistant/components/{sensor/tibber.py => tibber/sensor.py} (100%) rename homeassistant/components/{toon.py => toon/__init__.py} (100%) rename homeassistant/components/{climate/toon.py => toon/climate.py} (100%) rename homeassistant/components/{sensor/toon.py => toon/sensor.py} (100%) rename homeassistant/components/{switch/toon.py => toon/switch.py} (100%) rename homeassistant/components/{tplink_lte.py => tplink_lte/__init__.py} (100%) rename homeassistant/components/{notify/tplink_lte.py => tplink_lte/notify.py} (100%) rename homeassistant/components/{transmission.py => transmission/__init__.py} (100%) rename homeassistant/components/{sensor/transmission.py => transmission/sensor.py} (100%) rename homeassistant/components/{switch/transmission.py => transmission/switch.py} (100%) rename homeassistant/components/{tuya.py => tuya/__init__.py} (100%) rename homeassistant/components/{climate/tuya.py => tuya/climate.py} (100%) rename homeassistant/components/{cover/tuya.py => tuya/cover.py} (100%) rename homeassistant/components/{fan/tuya.py => tuya/fan.py} (100%) rename homeassistant/components/{light/tuya.py => tuya/light.py} (100%) rename homeassistant/components/{scene/tuya.py => tuya/scene.py} (100%) rename homeassistant/components/{switch/tuya.py => tuya/switch.py} (100%) rename homeassistant/components/{upcloud.py => upcloud/__init__.py} (100%) rename homeassistant/components/{binary_sensor/upcloud.py => upcloud/binary_sensor.py} (100%) rename homeassistant/components/{switch/upcloud.py => upcloud/switch.py} (100%) rename homeassistant/components/{usps.py => usps/__init__.py} (100%) rename homeassistant/components/{camera/usps.py => usps/camera.py} (100%) rename homeassistant/components/{sensor/usps.py => usps/sensor.py} (100%) rename homeassistant/components/{velbus.py => velbus/__init__.py} (100%) rename homeassistant/components/{binary_sensor/velbus.py => velbus/binary_sensor.py} (100%) rename homeassistant/components/{climate/velbus.py => velbus/climate.py} (100%) rename homeassistant/components/{cover/velbus.py => velbus/cover.py} (100%) rename homeassistant/components/{sensor/velbus.py => velbus/sensor.py} (100%) rename homeassistant/components/{switch/velbus.py => velbus/switch.py} (100%) rename homeassistant/components/{velux.py => velux/__init__.py} (100%) rename homeassistant/components/{cover/velux.py => velux/cover.py} (100%) rename homeassistant/components/{scene/velux.py => velux/scene.py} (100%) rename homeassistant/components/{vera.py => vera/__init__.py} (100%) rename homeassistant/components/{binary_sensor/vera.py => vera/binary_sensor.py} (100%) rename homeassistant/components/{climate/vera.py => vera/climate.py} (100%) rename homeassistant/components/{cover/vera.py => vera/cover.py} (100%) rename homeassistant/components/{light/vera.py => vera/light.py} (100%) rename homeassistant/components/{lock/vera.py => vera/lock.py} (100%) rename homeassistant/components/{scene/vera.py => vera/scene.py} (100%) rename homeassistant/components/{sensor/vera.py => vera/sensor.py} (100%) rename homeassistant/components/{switch/vera.py => vera/switch.py} (100%) rename homeassistant/components/{verisure.py => verisure/__init__.py} (100%) rename homeassistant/components/{alarm_control_panel/verisure.py => verisure/alarm_control_panel.py} (100%) rename homeassistant/components/{binary_sensor/verisure.py => verisure/binary_sensor.py} (100%) rename homeassistant/components/{camera/verisure.py => verisure/camera.py} (100%) rename homeassistant/components/{lock/verisure.py => verisure/lock.py} (100%) rename homeassistant/components/{sensor/verisure.py => verisure/sensor.py} (100%) rename homeassistant/components/{switch/verisure.py => verisure/switch.py} (100%) rename homeassistant/components/{volvooncall.py => volvooncall/__init__.py} (100%) rename homeassistant/components/{binary_sensor/volvooncall.py => volvooncall/binary_sensor.py} (100%) rename homeassistant/components/{device_tracker/volvooncall.py => volvooncall/device_tracker.py} (100%) rename homeassistant/components/{lock/volvooncall.py => volvooncall/lock.py} (100%) rename homeassistant/components/{sensor/volvooncall.py => volvooncall/sensor.py} (100%) rename homeassistant/components/{switch/volvooncall.py => volvooncall/switch.py} (100%) rename homeassistant/components/{w800rf32.py => w800rf32/__init__.py} (100%) rename homeassistant/components/{binary_sensor/w800rf32.py => w800rf32/binary_sensor.py} (100%) rename homeassistant/components/{waterfurnace.py => waterfurnace/__init__.py} (100%) rename homeassistant/components/{sensor/waterfurnace.py => waterfurnace/sensor.py} (100%) create mode 100644 homeassistant/components/webostv/__init__.py rename homeassistant/components/{media_player/webostv.py => webostv/media_player.py} (100%) rename homeassistant/components/{notify/webostv.py => webostv/notify.py} (100%) rename homeassistant/components/{wemo.py => wemo/__init__.py} (100%) rename homeassistant/components/{binary_sensor/wemo.py => wemo/binary_sensor.py} (100%) rename homeassistant/components/{fan/wemo.py => wemo/fan.py} (100%) rename homeassistant/components/{light/wemo.py => wemo/light.py} (100%) rename homeassistant/components/{switch/wemo.py => wemo/switch.py} (100%) rename homeassistant/components/{alarm_control_panel/wink.py => wink/alarm_control_panel.py} (100%) rename homeassistant/components/{binary_sensor/wink.py => wink/binary_sensor.py} (100%) rename homeassistant/components/{climate/wink.py => wink/climate.py} (100%) rename homeassistant/components/{cover/wink.py => wink/cover.py} (100%) rename homeassistant/components/{fan/wink.py => wink/fan.py} (100%) rename homeassistant/components/{light/wink.py => wink/light.py} (100%) rename homeassistant/components/{lock/wink.py => wink/lock.py} (100%) rename homeassistant/components/{scene/wink.py => wink/scene.py} (100%) rename homeassistant/components/{sensor/wink.py => wink/sensor.py} (100%) rename homeassistant/components/{switch/wink.py => wink/switch.py} (100%) rename homeassistant/components/{water_heater/wink.py => wink/water_heater.py} (100%) rename homeassistant/components/{wirelesstag.py => wirelesstag/__init__.py} (100%) rename homeassistant/components/{binary_sensor/wirelesstag.py => wirelesstag/binary_sensor.py} (100%) rename homeassistant/components/{sensor/wirelesstag.py => wirelesstag/sensor.py} (100%) rename homeassistant/components/{switch/wirelesstag.py => wirelesstag/switch.py} (100%) rename homeassistant/components/{xiaomi_aqara.py => xiaomi_aqara/__init__.py} (100%) rename homeassistant/components/{binary_sensor/xiaomi_aqara.py => xiaomi_aqara/binary_sensor.py} (100%) rename homeassistant/components/{cover/xiaomi_aqara.py => xiaomi_aqara/cover.py} (100%) rename homeassistant/components/{light/xiaomi_aqara.py => xiaomi_aqara/light.py} (100%) rename homeassistant/components/{lock/xiaomi_aqara.py => xiaomi_aqara/lock.py} (100%) rename homeassistant/components/{sensor/xiaomi_aqara.py => xiaomi_aqara/sensor.py} (100%) rename homeassistant/components/{switch/xiaomi_aqara.py => xiaomi_aqara/switch.py} (100%) create mode 100644 homeassistant/components/xiaomi_miio/__init__.py rename homeassistant/components/{device_tracker/xiaomi_miio.py => xiaomi_miio/device_tracker.py} (100%) rename homeassistant/components/{fan/xiaomi_miio.py => xiaomi_miio/fan.py} (100%) rename homeassistant/components/{light/xiaomi_miio.py => xiaomi_miio/light.py} (100%) rename homeassistant/components/{remote/xiaomi_miio.py => xiaomi_miio/remote.py} (100%) rename homeassistant/components/{sensor/xiaomi_miio.py => xiaomi_miio/sensor.py} (100%) rename homeassistant/components/{switch/xiaomi_miio.py => xiaomi_miio/switch.py} (100%) rename homeassistant/components/{vacuum/xiaomi_miio.py => xiaomi_miio/vacuum.py} (100%) rename homeassistant/components/{zabbix.py => zabbix/__init__.py} (100%) rename homeassistant/components/{sensor/zabbix.py => zabbix/sensor.py} (100%) rename homeassistant/components/{zigbee.py => zigbee/__init__.py} (100%) rename homeassistant/components/{binary_sensor/zigbee.py => zigbee/binary_sensor.py} (100%) rename homeassistant/components/{light/zigbee.py => zigbee/light.py} (100%) rename homeassistant/components/{sensor/zigbee.py => zigbee/sensor.py} (100%) rename homeassistant/components/{switch/zigbee.py => zigbee/switch.py} (100%) create mode 100644 tests/components/arlo/__init__.py rename tests/components/{sensor/test_arlo.py => arlo/test_sensor.py} (98%) create mode 100644 tests/components/ecobee/__init__.py rename tests/components/{climate/test_ecobee.py => ecobee/test_climate.py} (99%) create mode 100644 tests/components/fritzbox/__init__.py rename tests/components/{climate/test_fritzbox.py => fritzbox/test_climate.py} (99%) create mode 100644 tests/components/google/__init__.py rename tests/components/{calendar/test_google.py => google/test_calendar.py} (97%) rename tests/components/{tts/test_google.py => google/test_tts.py} (99%) create mode 100644 tests/components/kira/__init__.py rename tests/components/{remote/test_kira.py => kira/test_remote.py} (96%) rename tests/components/{sensor/test_kira.py => kira/test_sensor.py} (96%) create mode 100644 tests/components/mochad/__init__.py rename tests/components/{light/test_mochad.py => mochad/test_light.py} (97%) rename tests/components/{switch/test_mochad.py => mochad/test_switch.py} (94%) create mode 100644 tests/components/verisure/__init__.py rename tests/components/{lock/test_verisure.py => verisure/test_lock.py} (98%) create mode 100644 tests/components/webostv/__init__.py rename tests/components/{media_player/test_webostv.py => webostv/test_media_player.py} (96%) create mode 100644 tests/components/xiaomi_miio/__init__.py rename tests/components/{vacuum/test_xiaomi_miio.py => xiaomi_miio/test_vacuum.py} (99%) diff --git a/.coveragerc b/.coveragerc index d9632103f67..65e4656297f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -10,472 +10,13 @@ omit = homeassistant/helpers/signal.py # omit pieces of code that rely on external devices being present - homeassistant/components/abode.py - homeassistant/components/*/abode.py - - homeassistant/components/ads/__init__.py - homeassistant/components/*/ads.py - - homeassistant/components/alarmdecoder.py - homeassistant/components/*/alarmdecoder.py - - homeassistant/components/ambient_station/* - - homeassistant/components/amcrest.py - homeassistant/components/*/amcrest.py - - homeassistant/components/apcupsd.py - homeassistant/components/*/apcupsd.py - - homeassistant/components/apple_tv.py - homeassistant/components/*/apple_tv.py - - homeassistant/components/aqualogic.py - homeassistant/components/*/aqualogic.py - - homeassistant/components/arduino.py - homeassistant/components/*/arduino.py - - homeassistant/components/bmw_connected_drive/*.py - homeassistant/components/*/bmw_connected_drive.py - - homeassistant/components/android_ip_webcam.py - homeassistant/components/*/android_ip_webcam.py - - homeassistant/components/arlo.py - homeassistant/components/*/arlo.py - - homeassistant/components/asterisk_mbox.py - homeassistant/components/*/asterisk_mbox.py - homeassistant/components/*/asterisk_cdr.py - - homeassistant/components/august.py - homeassistant/components/*/august.py - - homeassistant/components/axis.py - homeassistant/components/*/axis.py - - homeassistant/components/bbb_gpio.py - homeassistant/components/*/bbb_gpio.py - - homeassistant/components/blink/* - homeassistant/components/*/blink.py - - homeassistant/components/bloomsky.py - homeassistant/components/*/bloomsky.py - - homeassistant/components/coinbase.py - homeassistant/components/sensor/coinbase.py - - homeassistant/components/cast/* - - homeassistant/components/cloudflare.py - - homeassistant/components/comfoconnect.py - homeassistant/components/*/comfoconnect.py - - homeassistant/components/daikin/* - - homeassistant/components/digital_ocean.py - homeassistant/components/*/digital_ocean.py - - homeassistant/components/danfoss_air/* - - homeassistant/components/dominos.py - - homeassistant/components/doorbird.py - homeassistant/components/*/doorbird.py - - homeassistant/components/dovado/* - - homeassistant/components/dweet.py - homeassistant/components/*/dweet.py - - homeassistant/components/eight_sleep.py - homeassistant/components/*/eight_sleep.py - - homeassistant/components/ecoal_boiler.py - homeassistant/components/*/ecoal_boiler.py - - homeassistant/components/ecobee.py - homeassistant/components/*/ecobee.py - - homeassistant/components/edp_redy.py - homeassistant/components/*/edp_redy.py - - homeassistant/components/egardia.py - homeassistant/components/*/egardia.py - - homeassistant/components/elkm1/* - homeassistant/components/*/elkm1.py - - homeassistant/components/enocean.py - homeassistant/components/*/enocean.py - - homeassistant/components/envisalink/__init__.py - homeassistant/components/*/envisalink.py - - homeassistant/components/evohome.py - homeassistant/components/*/evohome.py - - homeassistant/components/freebox.py - homeassistant/components/*/freebox.py - - homeassistant/components/fritzbox.py - homeassistant/components/*/fritzbox.py - - homeassistant/components/ecovacs.py - homeassistant/components/*/ecovacs.py - - homeassistant/components/esphome/__init__.py - homeassistant/components/esphome/binary_sensor.py - homeassistant/components/esphome/cover.py - homeassistant/components/esphome/fan.py - homeassistant/components/esphome/light.py - homeassistant/components/esphome/sensor.py - homeassistant/components/esphome/switch.py - - homeassistant/components/eufy.py - homeassistant/components/*/eufy.py - - homeassistant/components/fastdotcom/* - - homeassistant/components/fibaro/__init__.py - homeassistant/components/*/fibaro.py - - homeassistant/components/gc100.py - homeassistant/components/*/gc100.py - - homeassistant/components/google.py - homeassistant/components/*/google.py - - homeassistant/components/googlehome.py - homeassistant/components/*/googlehome.py - - homeassistant/components/greeneye_monitor.py - homeassistant/components/sensor/greeneye_monitor.py - - homeassistant/components/habitica/* - homeassistant/components/*/habitica.py - - homeassistant/components/hangouts/__init__.py - homeassistant/components/hangouts/const.py - homeassistant/components/hangouts/hangouts_bot.py - homeassistant/components/hangouts/hangups_utils.py - homeassistant/components/hangouts/intents.py - homeassistant/components/*/hangouts.py - - homeassistant/components/hdmi_cec.py - homeassistant/components/*/hdmi_cec.py - - homeassistant/components/hive.py - homeassistant/components/*/hive.py - - homeassistant/components/hlk_sw16.py - homeassistant/components/*/hlk_sw16.py - - homeassistant/components/homekit_controller/* - - homeassistant/components/homematic/__init__.py - homeassistant/components/*/homematic.py - - homeassistant/components/homematicip_cloud/* - - homeassistant/components/homeworks.py - homeassistant/components/*/homeworks.py - - homeassistant/components/huawei_lte.py - homeassistant/components/*/huawei_lte.py - - homeassistant/components/hydrawise.py - homeassistant/components/*/hydrawise.py - - homeassistant/components/ifttt/* - + homeassistant/components/ads/* homeassistant/components/ihc/* - homeassistant/components/*/ihc.py - - homeassistant/components/insteon/* - homeassistant/components/*/insteon.py - - homeassistant/components/insteon_local.py - - homeassistant/components/insteon_plm.py - - homeassistant/components/ios/* - - homeassistant/components/iota.py - homeassistant/components/*/iota.py - - homeassistant/components/isy994.py - homeassistant/components/*/isy994.py - - homeassistant/components/joaoapps_join.py - homeassistant/components/*/joaoapps_join.py - - homeassistant/components/juicenet.py - homeassistant/components/*/juicenet.py - - homeassistant/components/kira.py - homeassistant/components/*/kira.py - - homeassistant/components/knx.py - homeassistant/components/*/knx.py - - homeassistant/components/konnected.py - homeassistant/components/*/konnected.py - - homeassistant/components/lametric.py - homeassistant/components/*/lametric.py - - homeassistant/components/lcn.py - homeassistant/components/*/lcn.py - - homeassistant/components/lifx/* - - homeassistant/components/lightwave.py - homeassistant/components/*/lightwave.py - - homeassistant/components/linode.py - homeassistant/components/*/linode.py - - homeassistant/components/logi_circle.py - homeassistant/components/*/logi_circle.py - - homeassistant/components/luftdaten/* - - homeassistant/components/lupusec.py - homeassistant/components/*/lupusec.py - - homeassistant/components/lutron.py - homeassistant/components/*/lutron.py - - homeassistant/components/lutron_caseta.py - homeassistant/components/*/lutron_caseta.py - - homeassistant/components/mailgun/notify.py - - homeassistant/components/matrix.py - homeassistant/components/*/matrix.py - - homeassistant/components/maxcube.py - homeassistant/components/*/maxcube.py - - homeassistant/components/mochad.py - homeassistant/components/*/mochad.py - - homeassistant/components/modbus.py - homeassistant/components/*/modbus.py - - homeassistant/components/mychevy.py - homeassistant/components/*/mychevy.py - - homeassistant/components/mysensors/* - homeassistant/components/*/mysensors.py - - homeassistant/components/neato.py - homeassistant/components/*/neato.py - - homeassistant/components/nest/* - - homeassistant/components/netatmo.py - homeassistant/components/*/netatmo.py - - homeassistant/components/netgear_lte.py - homeassistant/components/*/netgear_lte.py - - homeassistant/components/octoprint.py - homeassistant/components/*/octoprint.py - - homeassistant/components/opencv.py - homeassistant/components/*/opencv.py - - homeassistant/components/opentherm_gw/* - homeassistant/components/*/opentherm_gw.py - - homeassistant/components/openuv/__init__.py - homeassistant/components/openuv/binary_sensor.py - homeassistant/components/openuv/sensor.py - - homeassistant/components/plum_lightpad.py - homeassistant/components/*/plum_lightpad.py - - homeassistant/components/pilight.py - homeassistant/components/*/pilight.py - - homeassistant/components/point/* - - homeassistant/components/switch/qwikswitch.py - homeassistant/components/light/qwikswitch.py - - homeassistant/components/rachio.py - homeassistant/components/*/rachio.py - - homeassistant/components/raincloud.py - homeassistant/components/*/raincloud.py - - homeassistant/components/rainmachine/__init__.py - homeassistant/components/rainmachine/binary_sensor.py - homeassistant/components/rainmachine/sensor.py - homeassistant/components/rainmachine/switch.py - - homeassistant/components/raspihats.py - homeassistant/components/*/raspihats.py - - homeassistant/components/*/raspyrfm.py - - homeassistant/components/rfxtrx.py - homeassistant/components/*/rfxtrx.py - - homeassistant/components/roku.py - homeassistant/components/*/roku.py - - homeassistant/components/rpi_gpio.py - homeassistant/components/*/rpi_gpio.py - - homeassistant/components/rpi_pfio.py - homeassistant/components/*/rpi_pfio.py - - homeassistant/components/sabnzbd.py - homeassistant/components/*/sabnzbd.py - - homeassistant/components/satel_integra.py - homeassistant/components/*/satel_integra.py - - homeassistant/components/scsgate.py - homeassistant/components/*/scsgate.py - - homeassistant/components/sense.py - homeassistant/components/*/sense.py - - homeassistant/components/simplisafe/__init__.py - homeassistant/components/simplisafe/alarm_control_panel.py - - homeassistant/components/sisyphus.py - homeassistant/components/*/sisyphus.py - - homeassistant/components/skybell.py - homeassistant/components/*/skybell.py - - homeassistant/components/smappee.py - homeassistant/components/*/smappee.py - - homeassistant/components/sonos/* - - homeassistant/components/tado.py - homeassistant/components/*/tado.py - - homeassistant/components/tahoma.py - homeassistant/components/*/tahoma.py - - homeassistant/components/tellduslive/* - - homeassistant/components/tellstick.py - homeassistant/components/*/tellstick.py - - homeassistant/components/tesla.py - homeassistant/components/*/tesla.py - - homeassistant/components/thethingsnetwork.py - homeassistant/components/*/thethingsnetwork.py - - homeassistant/components/*/thinkingcleaner.py - - homeassistant/components/tibber/* - homeassistant/components/*/tibber.py - - homeassistant/components/toon.py - homeassistant/components/*/toon.py - - homeassistant/components/tplink_lte.py - homeassistant/components/*/tplink_lte.py - - homeassistant/components/tradfri/* - - homeassistant/components/transmission.py - homeassistant/components/*/transmission.py - - homeassistant/components/notify/twilio_sms.py - homeassistant/components/notify/twilio_call.py - - homeassistant/components/upnp.py - - homeassistant/components/upcloud.py - homeassistant/components/*/upcloud.py - - homeassistant/components/usps.py - homeassistant/components/*/usps.py - - homeassistant/components/velbus.py - homeassistant/components/*/velbus.py - - homeassistant/components/velux.py - homeassistant/components/*/velux.py - - homeassistant/components/vera.py - homeassistant/components/*/vera.py - - homeassistant/components/verisure.py - homeassistant/components/*/verisure.py - - homeassistant/components/volvooncall.py - homeassistant/components/*/volvooncall.py - - homeassistant/components/waterfurnace.py - homeassistant/components/*/waterfurnace.py - - homeassistant/components/*/webostv.py - - homeassistant/components/w800rf32.py - homeassistant/components/*/w800rf32.py - - homeassistant/components/wemo.py - homeassistant/components/*/wemo.py - - homeassistant/components/wink/* - homeassistant/components/*/wink.py - - homeassistant/components/wirelesstag.py - homeassistant/components/*/wirelesstag.py - - homeassistant/components/xiaomi_aqara.py - homeassistant/components/*/xiaomi_aqara.py - - homeassistant/components/*/xiaomi_miio.py - - homeassistant/components/zabbix.py - homeassistant/components/*/zabbix.py - - homeassistant/components/zha/__init__.py - homeassistant/components/zha/binary_sensor.py - homeassistant/components/zha/const.py - homeassistant/components/zha/event.py - homeassistant/components/zha/fan.py - homeassistant/components/zha/light.py - homeassistant/components/zha/sensor.py - homeassistant/components/zha/switch.py - homeassistant/components/zha/api.py - homeassistant/components/zha/entity.py - homeassistant/components/zha/device_entity.py - homeassistant/components/zha/core/helpers.py - homeassistant/components/zha/core/const.py - homeassistant/components/zha/core/device.py - homeassistant/components/zha/core/listeners.py - homeassistant/components/zha/core/gateway.py - - homeassistant/components/zigbee.py - homeassistant/components/*/zigbee.py - - homeassistant/components/zoneminder/* - - homeassistant/components/tuya.py - homeassistant/components/*/tuya.py - - homeassistant/components/spider.py - homeassistant/components/*/spider.py - - homeassistant/components/air_quality/opensensemap.py + homeassistant/components/knx/* + homeassistant/components/lcn/* + homeassistant/components/abode/* homeassistant/components/air_quality/nilu.py + homeassistant/components/air_quality/opensensemap.py homeassistant/components/alarm_control_panel/alarmdotcom.py homeassistant/components/alarm_control_panel/canary.py homeassistant/components/alarm_control_panel/concord232.py @@ -484,7 +25,20 @@ omit = homeassistant/components/alarm_control_panel/nx584.py homeassistant/components/alarm_control_panel/totalconnect.py homeassistant/components/alarm_control_panel/yale_smart_alarm.py + homeassistant/components/alarmdecoder/* + homeassistant/components/ambient_station/* + homeassistant/components/amcrest/* + homeassistant/components/android_ip_webcam/* + homeassistant/components/apcupsd/* homeassistant/components/apiai.py + homeassistant/components/apple_tv/* + homeassistant/components/aqualogic/* + homeassistant/components/arduino/* + homeassistant/components/arlo/* + homeassistant/components/asterisk_mbox/* + homeassistant/components/august/* + homeassistant/components/axis/* + homeassistant/components/bbb_gpio/* homeassistant/components/binary_sensor/arest.py homeassistant/components/binary_sensor/concord232.py homeassistant/components/binary_sensor/flic.py @@ -495,6 +49,9 @@ omit = homeassistant/components/binary_sensor/rest.py homeassistant/components/binary_sensor/tapsaff.py homeassistant/components/binary_sensor/uptimerobot.py + homeassistant/components/blink/* + homeassistant/components/bloomsky/* + homeassistant/components/bmw_connected_drive/* homeassistant/components/browser.py homeassistant/components/calendar/caldav.py homeassistant/components/calendar/todoist.py @@ -512,6 +69,7 @@ omit = homeassistant/components/camera/xeoma.py homeassistant/components/camera/xiaomi.py homeassistant/components/camera/yi.py + homeassistant/components/cast/* homeassistant/components/climate/ephember.py homeassistant/components/climate/eq3btsmart.py homeassistant/components/climate/flexit.py @@ -527,6 +85,9 @@ omit = homeassistant/components/climate/touchline.py homeassistant/components/climate/venstar.py homeassistant/components/climate/zhong_hong.py + homeassistant/components/cloudflare.py + homeassistant/components/coinbase.py + homeassistant/components/comfoconnect/* homeassistant/components/cover/aladdin_connect.py homeassistant/components/cover/brunt.py homeassistant/components/cover/garadget.py @@ -537,6 +98,8 @@ omit = homeassistant/components/cover/opengarage.py homeassistant/components/cover/rpi_gpio.py homeassistant/components/cover/scsgate.py + homeassistant/components/daikin/* + homeassistant/components/danfoss_air/* homeassistant/components/device_tracker/actiontec.py homeassistant/components/device_tracker/aruba.py homeassistant/components/device_tracker/asuswrt.py @@ -575,21 +138,80 @@ omit = homeassistant/components/device_tracker/traccar.py homeassistant/components/device_tracker/trackr.py homeassistant/components/device_tracker/ubus.py + homeassistant/components/digital_ocean/* + homeassistant/components/dominos.py + homeassistant/components/doorbird/* + homeassistant/components/dovado/* homeassistant/components/downloader.py + homeassistant/components/dweet/* + homeassistant/components/ecoal_boiler/* + homeassistant/components/ecobee/* + homeassistant/components/ecovacs/* + homeassistant/components/edp_redy/* + homeassistant/components/egardia/* + homeassistant/components/eight_sleep/* + homeassistant/components/elkm1/* homeassistant/components/emoncms_history.py homeassistant/components/emulated_hue/upnp.py + homeassistant/components/enocean/* + homeassistant/components/envisalink/* + homeassistant/components/esphome/__init__.py + homeassistant/components/esphome/binary_sensor.py + homeassistant/components/esphome/cover.py + homeassistant/components/esphome/fan.py + homeassistant/components/esphome/light.py + homeassistant/components/esphome/sensor.py + homeassistant/components/esphome/switch.py + homeassistant/components/eufy/* + homeassistant/components/evohome/* homeassistant/components/fan/wemo.py + homeassistant/components/fastdotcom/* + homeassistant/components/fibaro/* homeassistant/components/folder_watcher.py homeassistant/components/foursquare.py + homeassistant/components/freebox/* + homeassistant/components/fritzbox/* + homeassistant/components/gc100/* homeassistant/components/goalfeed.py + homeassistant/components/google/* + homeassistant/components/googlehome/* + homeassistant/components/greeneye_monitor.py + homeassistant/components/habitica/* + homeassistant/components/hangouts/__init__.py + homeassistant/components/hangouts/* + homeassistant/components/hangouts/const.py + homeassistant/components/hangouts/hangouts_bot.py + homeassistant/components/hangouts/hangups_utils.py + homeassistant/components/hdmi_cec/* + homeassistant/components/hive/* + homeassistant/components/hlk_sw16/* + homeassistant/components/homekit_controller/* + homeassistant/components/homematic/* + homeassistant/components/homematicip_cloud/* + homeassistant/components/homeworks/* + homeassistant/components/huawei_lte/* + homeassistant/components/hydrawise/* homeassistant/components/idteck_prox.py + homeassistant/components/ifttt/* homeassistant/components/image_processing/dlib_face_detect.py homeassistant/components/image_processing/dlib_face_identify.py + homeassistant/components/image_processing/qrcode.py homeassistant/components/image_processing/seven_segments.py homeassistant/components/image_processing/tensorflow.py - homeassistant/components/image_processing/qrcode.py + homeassistant/components/insteon_local.py + homeassistant/components/insteon_plm.py + homeassistant/components/insteon/* + homeassistant/components/ios/* + homeassistant/components/iota/* + homeassistant/components/isy994/* + homeassistant/components/joaoapps_join/* + homeassistant/components/juicenet/* homeassistant/components/keyboard_remote.py homeassistant/components/keyboard.py + homeassistant/components/kira/* + homeassistant/components/konnected/* + homeassistant/components/lametric/* + homeassistant/components/lifx/* homeassistant/components/light/avion.py homeassistant/components/light/blinksticklight.py homeassistant/components/light/blinkt.py @@ -620,13 +242,24 @@ omit = homeassistant/components/light/yeelight.py homeassistant/components/light/yeelightsunflower.py homeassistant/components/light/zengge.py + homeassistant/components/lightwave/* + homeassistant/components/linode/* homeassistant/components/lirc.py homeassistant/components/lock/kiwi.py homeassistant/components/lock/lockitron.py homeassistant/components/lock/nello.py homeassistant/components/lock/nuki.py homeassistant/components/lock/sesame.py + homeassistant/components/logi_circle/* + homeassistant/components/luftdaten/* + homeassistant/components/lupusec/* + homeassistant/components/lutron_caseta/* + homeassistant/components/lutron/* + homeassistant/components/mailbox/asterisk_cdr.py + homeassistant/components/mailgun/notify.py homeassistant/components/map.py + homeassistant/components/matrix/* + homeassistant/components/maxcube/* homeassistant/components/media_extractor.py homeassistant/components/media_player/anthemav.py homeassistant/components/media_player/aquostv.py @@ -681,14 +314,22 @@ omit = homeassistant/components/media_player/yamaha_musiccast.py homeassistant/components/media_player/yamaha.py homeassistant/components/media_player/ziggo_mediabox_xl.py + homeassistant/components/mochad/* + homeassistant/components/modbus/* + homeassistant/components/mychevy/* homeassistant/components/mycroft.py + homeassistant/components/mysensors/* + homeassistant/components/neato/* + homeassistant/components/nest/* + homeassistant/components/netatmo/* + homeassistant/components/netgear_lte/* homeassistant/components/notify/aws_lambda.py homeassistant/components/notify/aws_sns.py homeassistant/components/notify/aws_sqs.py homeassistant/components/notify/ciscospark.py homeassistant/components/notify/clickatell.py - homeassistant/components/notify/clicksend.py homeassistant/components/notify/clicksend_tts.py + homeassistant/components/notify/clicksend.py homeassistant/components/notify/discord.py homeassistant/components/notify/flock.py homeassistant/components/notify/free_mobile.py @@ -719,17 +360,45 @@ omit = homeassistant/components/notify/syslog.py homeassistant/components/notify/telegram.py homeassistant/components/notify/telstra.py + homeassistant/components/notify/twilio_call.py + homeassistant/components/notify/twilio_sms.py homeassistant/components/notify/twitter.py homeassistant/components/notify/xmpp.py homeassistant/components/nuimo_controller.py + homeassistant/components/octoprint/* + homeassistant/components/opencv/* + homeassistant/components/opentherm_gw/* + homeassistant/components/openuv/__init__.py + homeassistant/components/openuv/binary_sensor.py + homeassistant/components/openuv/sensor.py + homeassistant/components/pilight/* + homeassistant/components/plum_lightpad/* + homeassistant/components/point/* homeassistant/components/prometheus.py + homeassistant/components/qwikswitch/* + homeassistant/components/rachio/* homeassistant/components/rainbird.py + homeassistant/components/raincloud/* + homeassistant/components/rainmachine/__init__.py + homeassistant/components/rainmachine/binary_sensor.py + homeassistant/components/rainmachine/sensor.py + homeassistant/components/rainmachine/switch.py + homeassistant/components/raspihats/* + homeassistant/components/raspyrfm/* homeassistant/components/remember_the_milk/__init__.py homeassistant/components/remote/harmony.py homeassistant/components/remote/itach.py + homeassistant/components/rfxtrx/* + homeassistant/components/roku/* homeassistant/components/route53.py + homeassistant/components/rpi_gpio/* + homeassistant/components/rpi_pfio/* + homeassistant/components/sabnzbd/* + homeassistant/components/satel_integra/* homeassistant/components/scene/hunterdouglas_powerview.py homeassistant/components/scene/lifx_cloud.py + homeassistant/components/scsgate/* + homeassistant/components/sense/* homeassistant/components/sensor/aftership.py homeassistant/components/sensor/airvisual.py homeassistant/components/sensor/alpha_vantage.py @@ -747,6 +416,7 @@ omit = homeassistant/components/sensor/buienradar.py homeassistant/components/sensor/cert_expiry.py homeassistant/components/sensor/citybikes.py + homeassistant/components/sensor/coinbase.py homeassistant/components/sensor/comed_hourly_pricing.py homeassistant/components/sensor/cpuspeed.py homeassistant/components/sensor/crimereports.py @@ -786,6 +456,7 @@ omit = homeassistant/components/sensor/glances.py homeassistant/components/sensor/google_travel_time.py homeassistant/components/sensor/gpsd.py + homeassistant/components/sensor/greeneye_monitor.py homeassistant/components/sensor/gtfs.py homeassistant/components/sensor/gtt.py homeassistant/components/sensor/haveibeenpwned.py @@ -817,8 +488,8 @@ omit = homeassistant/components/sensor/mvglive.py homeassistant/components/sensor/nederlandse_spoorwegen.py homeassistant/components/sensor/netatmo_public.py - homeassistant/components/sensor/netdata.py homeassistant/components/sensor/netdata_public.py + homeassistant/components/sensor/netdata.py homeassistant/components/sensor/neurio_energy.py homeassistant/components/sensor/nmbs.py homeassistant/components/sensor/noaa_tides.py @@ -855,8 +526,8 @@ omit = homeassistant/components/sensor/serial_pm.py homeassistant/components/sensor/serial.py homeassistant/components/sensor/seventeentrack.py - homeassistant/components/sensor/sht31.py homeassistant/components/sensor/shodan.py + homeassistant/components/sensor/sht31.py homeassistant/components/sensor/sigfox.py homeassistant/components/sensor/simulated.py homeassistant/components/sensor/skybeacon.py @@ -868,6 +539,7 @@ omit = homeassistant/components/sensor/sonarr.py homeassistant/components/sensor/speedtest.py homeassistant/components/sensor/spotcrime.py + homeassistant/components/sensor/srp_energy.py homeassistant/components/sensor/starlingbank.py homeassistant/components/sensor/steam_online.py homeassistant/components/sensor/supervisord.py @@ -875,7 +547,6 @@ omit = homeassistant/components/sensor/swiss_public_transport.py homeassistant/components/sensor/syncthru.py homeassistant/components/sensor/synologydsm.py - homeassistant/components/sensor/srp_energy.py homeassistant/components/sensor/systemmonitor.py homeassistant/components/sensor/sytadin.py homeassistant/components/sensor/tank_utility.py @@ -903,7 +574,14 @@ omit = homeassistant/components/sensor/zamg.py homeassistant/components/sensor/zestimate.py homeassistant/components/shiftr.py + homeassistant/components/simplisafe/__init__.py + homeassistant/components/simplisafe/alarm_control_panel.py + homeassistant/components/sisyphus/* + homeassistant/components/skybell/* + homeassistant/components/smappee/* + homeassistant/components/sonos/* homeassistant/components/spc.py + homeassistant/components/spider/* homeassistant/components/switch/acer_projector.py homeassistant/components/switch/anel_pwrctrl.py homeassistant/components/switch/arest.py @@ -922,8 +600,8 @@ omit = homeassistant/components/switch/pencom.py homeassistant/components/switch/pulseaudio_loopback.py homeassistant/components/switch/rainbird.py - homeassistant/components/switch/rest.py homeassistant/components/switch/recswitch.py + homeassistant/components/switch/rest.py homeassistant/components/switch/rpi_rf.py homeassistant/components/switch/snmp.py homeassistant/components/switch/switchbot.py @@ -931,14 +609,37 @@ omit = homeassistant/components/switch/telnet.py homeassistant/components/switch/tplink.py homeassistant/components/switch/vesync.py + homeassistant/components/tado/* + homeassistant/components/tahoma/* homeassistant/components/telegram_bot/* + homeassistant/components/tellduslive/* + homeassistant/components/tellstick/* + homeassistant/components/tesla/* + homeassistant/components/thethingsnetwork/* homeassistant/components/thingspeak.py + homeassistant/components/thinkingcleaner/* + homeassistant/components/tibber/* + homeassistant/components/toon/* + homeassistant/components/tplink_lte/* + homeassistant/components/tradfri/* + homeassistant/components/transmission/* homeassistant/components/tts/amazon_polly.py homeassistant/components/tts/baidu.py homeassistant/components/tts/microsoft.py homeassistant/components/tts/picotts.py + homeassistant/components/tuya/* + homeassistant/components/upcloud/* + homeassistant/components/upnp/* + homeassistant/components/usps/* homeassistant/components/vacuum/roomba.py + homeassistant/components/velbus/* + homeassistant/components/velux/* + homeassistant/components/vera/* + homeassistant/components/verisure/* + homeassistant/components/volvooncall/* + homeassistant/components/w800rf32/* homeassistant/components/water_heater/econet.py + homeassistant/components/waterfurnace/* homeassistant/components/watson_iot.py homeassistant/components/weather/bom.py homeassistant/components/weather/buienradar.py @@ -947,7 +648,32 @@ omit = homeassistant/components/weather/metoffice.py homeassistant/components/weather/openweathermap.py homeassistant/components/weather/zamg.py + homeassistant/components/webostv/* + homeassistant/components/wemo/* + homeassistant/components/wink/* + homeassistant/components/wirelesstag/* + homeassistant/components/xiaomi_aqara/* + homeassistant/components/xiaomi_miio/* + homeassistant/components/zabbix/* homeassistant/components/zeroconf.py + homeassistant/components/zha/__init__.py + homeassistant/components/zha/api.py + homeassistant/components/zha/binary_sensor.py + homeassistant/components/zha/const.py + homeassistant/components/zha/core/const.py + homeassistant/components/zha/core/device.py + homeassistant/components/zha/core/gateway.py + homeassistant/components/zha/core/helpers.py + homeassistant/components/zha/core/listeners.py + homeassistant/components/zha/device_entity.py + homeassistant/components/zha/entity.py + homeassistant/components/zha/event.py + homeassistant/components/zha/fan.py + homeassistant/components/zha/light.py + homeassistant/components/zha/sensor.py + homeassistant/components/zha/switch.py + homeassistant/components/zigbee/* + homeassistant/components/zoneminder/* homeassistant/components/zwave/util.py [report] diff --git a/homeassistant/components/abode.py b/homeassistant/components/abode/__init__.py similarity index 100% rename from homeassistant/components/abode.py rename to homeassistant/components/abode/__init__.py diff --git a/homeassistant/components/alarm_control_panel/abode.py b/homeassistant/components/abode/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/abode.py rename to homeassistant/components/abode/alarm_control_panel.py diff --git a/homeassistant/components/binary_sensor/abode.py b/homeassistant/components/abode/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/abode.py rename to homeassistant/components/abode/binary_sensor.py diff --git a/homeassistant/components/camera/abode.py b/homeassistant/components/abode/camera.py similarity index 100% rename from homeassistant/components/camera/abode.py rename to homeassistant/components/abode/camera.py diff --git a/homeassistant/components/cover/abode.py b/homeassistant/components/abode/cover.py similarity index 100% rename from homeassistant/components/cover/abode.py rename to homeassistant/components/abode/cover.py diff --git a/homeassistant/components/light/abode.py b/homeassistant/components/abode/light.py similarity index 100% rename from homeassistant/components/light/abode.py rename to homeassistant/components/abode/light.py diff --git a/homeassistant/components/lock/abode.py b/homeassistant/components/abode/lock.py similarity index 100% rename from homeassistant/components/lock/abode.py rename to homeassistant/components/abode/lock.py diff --git a/homeassistant/components/sensor/abode.py b/homeassistant/components/abode/sensor.py similarity index 100% rename from homeassistant/components/sensor/abode.py rename to homeassistant/components/abode/sensor.py diff --git a/homeassistant/components/switch/abode.py b/homeassistant/components/abode/switch.py similarity index 100% rename from homeassistant/components/switch/abode.py rename to homeassistant/components/abode/switch.py diff --git a/homeassistant/components/binary_sensor/ads.py b/homeassistant/components/ads/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/ads.py rename to homeassistant/components/ads/binary_sensor.py diff --git a/homeassistant/components/light/ads.py b/homeassistant/components/ads/light.py similarity index 100% rename from homeassistant/components/light/ads.py rename to homeassistant/components/ads/light.py diff --git a/homeassistant/components/sensor/ads.py b/homeassistant/components/ads/sensor.py similarity index 100% rename from homeassistant/components/sensor/ads.py rename to homeassistant/components/ads/sensor.py diff --git a/homeassistant/components/switch/ads.py b/homeassistant/components/ads/switch.py similarity index 100% rename from homeassistant/components/switch/ads.py rename to homeassistant/components/ads/switch.py diff --git a/homeassistant/components/alarmdecoder.py b/homeassistant/components/alarmdecoder/__init__.py similarity index 100% rename from homeassistant/components/alarmdecoder.py rename to homeassistant/components/alarmdecoder/__init__.py diff --git a/homeassistant/components/alarm_control_panel/alarmdecoder.py b/homeassistant/components/alarmdecoder/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/alarmdecoder.py rename to homeassistant/components/alarmdecoder/alarm_control_panel.py diff --git a/homeassistant/components/binary_sensor/alarmdecoder.py b/homeassistant/components/alarmdecoder/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/alarmdecoder.py rename to homeassistant/components/alarmdecoder/binary_sensor.py diff --git a/homeassistant/components/sensor/alarmdecoder.py b/homeassistant/components/alarmdecoder/sensor.py similarity index 100% rename from homeassistant/components/sensor/alarmdecoder.py rename to homeassistant/components/alarmdecoder/sensor.py diff --git a/homeassistant/components/amcrest.py b/homeassistant/components/amcrest/__init__.py similarity index 100% rename from homeassistant/components/amcrest.py rename to homeassistant/components/amcrest/__init__.py diff --git a/homeassistant/components/camera/amcrest.py b/homeassistant/components/amcrest/camera.py similarity index 100% rename from homeassistant/components/camera/amcrest.py rename to homeassistant/components/amcrest/camera.py diff --git a/homeassistant/components/sensor/amcrest.py b/homeassistant/components/amcrest/sensor.py similarity index 100% rename from homeassistant/components/sensor/amcrest.py rename to homeassistant/components/amcrest/sensor.py diff --git a/homeassistant/components/switch/amcrest.py b/homeassistant/components/amcrest/switch.py similarity index 100% rename from homeassistant/components/switch/amcrest.py rename to homeassistant/components/amcrest/switch.py diff --git a/homeassistant/components/android_ip_webcam.py b/homeassistant/components/android_ip_webcam/__init__.py similarity index 100% rename from homeassistant/components/android_ip_webcam.py rename to homeassistant/components/android_ip_webcam/__init__.py diff --git a/homeassistant/components/binary_sensor/android_ip_webcam.py b/homeassistant/components/android_ip_webcam/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/android_ip_webcam.py rename to homeassistant/components/android_ip_webcam/binary_sensor.py diff --git a/homeassistant/components/sensor/android_ip_webcam.py b/homeassistant/components/android_ip_webcam/sensor.py similarity index 100% rename from homeassistant/components/sensor/android_ip_webcam.py rename to homeassistant/components/android_ip_webcam/sensor.py diff --git a/homeassistant/components/switch/android_ip_webcam.py b/homeassistant/components/android_ip_webcam/switch.py similarity index 100% rename from homeassistant/components/switch/android_ip_webcam.py rename to homeassistant/components/android_ip_webcam/switch.py diff --git a/homeassistant/components/apcupsd.py b/homeassistant/components/apcupsd/__init__.py similarity index 100% rename from homeassistant/components/apcupsd.py rename to homeassistant/components/apcupsd/__init__.py diff --git a/homeassistant/components/binary_sensor/apcupsd.py b/homeassistant/components/apcupsd/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/apcupsd.py rename to homeassistant/components/apcupsd/binary_sensor.py diff --git a/homeassistant/components/sensor/apcupsd.py b/homeassistant/components/apcupsd/sensor.py similarity index 100% rename from homeassistant/components/sensor/apcupsd.py rename to homeassistant/components/apcupsd/sensor.py diff --git a/homeassistant/components/apple_tv.py b/homeassistant/components/apple_tv/__init__.py similarity index 100% rename from homeassistant/components/apple_tv.py rename to homeassistant/components/apple_tv/__init__.py diff --git a/homeassistant/components/media_player/apple_tv.py b/homeassistant/components/apple_tv/media_player.py similarity index 100% rename from homeassistant/components/media_player/apple_tv.py rename to homeassistant/components/apple_tv/media_player.py diff --git a/homeassistant/components/remote/apple_tv.py b/homeassistant/components/apple_tv/remote.py similarity index 100% rename from homeassistant/components/remote/apple_tv.py rename to homeassistant/components/apple_tv/remote.py diff --git a/homeassistant/components/aqualogic.py b/homeassistant/components/aqualogic/__init__.py similarity index 100% rename from homeassistant/components/aqualogic.py rename to homeassistant/components/aqualogic/__init__.py diff --git a/homeassistant/components/sensor/aqualogic.py b/homeassistant/components/aqualogic/sensor.py similarity index 100% rename from homeassistant/components/sensor/aqualogic.py rename to homeassistant/components/aqualogic/sensor.py diff --git a/homeassistant/components/switch/aqualogic.py b/homeassistant/components/aqualogic/switch.py similarity index 100% rename from homeassistant/components/switch/aqualogic.py rename to homeassistant/components/aqualogic/switch.py diff --git a/homeassistant/components/arduino.py b/homeassistant/components/arduino/__init__.py similarity index 100% rename from homeassistant/components/arduino.py rename to homeassistant/components/arduino/__init__.py diff --git a/homeassistant/components/sensor/arduino.py b/homeassistant/components/arduino/sensor.py similarity index 100% rename from homeassistant/components/sensor/arduino.py rename to homeassistant/components/arduino/sensor.py diff --git a/homeassistant/components/switch/arduino.py b/homeassistant/components/arduino/switch.py similarity index 100% rename from homeassistant/components/switch/arduino.py rename to homeassistant/components/arduino/switch.py diff --git a/homeassistant/components/arlo.py b/homeassistant/components/arlo/__init__.py similarity index 100% rename from homeassistant/components/arlo.py rename to homeassistant/components/arlo/__init__.py diff --git a/homeassistant/components/alarm_control_panel/arlo.py b/homeassistant/components/arlo/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/arlo.py rename to homeassistant/components/arlo/alarm_control_panel.py diff --git a/homeassistant/components/camera/arlo.py b/homeassistant/components/arlo/camera.py similarity index 100% rename from homeassistant/components/camera/arlo.py rename to homeassistant/components/arlo/camera.py diff --git a/homeassistant/components/sensor/arlo.py b/homeassistant/components/arlo/sensor.py similarity index 100% rename from homeassistant/components/sensor/arlo.py rename to homeassistant/components/arlo/sensor.py diff --git a/homeassistant/components/asterisk_mbox.py b/homeassistant/components/asterisk_mbox/__init__.py similarity index 100% rename from homeassistant/components/asterisk_mbox.py rename to homeassistant/components/asterisk_mbox/__init__.py diff --git a/homeassistant/components/mailbox/asterisk_mbox.py b/homeassistant/components/asterisk_mbox/mailbox.py similarity index 100% rename from homeassistant/components/mailbox/asterisk_mbox.py rename to homeassistant/components/asterisk_mbox/mailbox.py diff --git a/homeassistant/components/august.py b/homeassistant/components/august/__init__.py similarity index 100% rename from homeassistant/components/august.py rename to homeassistant/components/august/__init__.py diff --git a/homeassistant/components/binary_sensor/august.py b/homeassistant/components/august/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/august.py rename to homeassistant/components/august/binary_sensor.py diff --git a/homeassistant/components/camera/august.py b/homeassistant/components/august/camera.py similarity index 100% rename from homeassistant/components/camera/august.py rename to homeassistant/components/august/camera.py diff --git a/homeassistant/components/lock/august.py b/homeassistant/components/august/lock.py similarity index 100% rename from homeassistant/components/lock/august.py rename to homeassistant/components/august/lock.py diff --git a/homeassistant/components/binary_sensor/axis.py b/homeassistant/components/axis/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/axis.py rename to homeassistant/components/axis/binary_sensor.py diff --git a/homeassistant/components/camera/axis.py b/homeassistant/components/axis/camera.py similarity index 100% rename from homeassistant/components/camera/axis.py rename to homeassistant/components/axis/camera.py diff --git a/homeassistant/components/bbb_gpio.py b/homeassistant/components/bbb_gpio/__init__.py similarity index 100% rename from homeassistant/components/bbb_gpio.py rename to homeassistant/components/bbb_gpio/__init__.py diff --git a/homeassistant/components/binary_sensor/bbb_gpio.py b/homeassistant/components/bbb_gpio/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/bbb_gpio.py rename to homeassistant/components/bbb_gpio/binary_sensor.py diff --git a/homeassistant/components/switch/bbb_gpio.py b/homeassistant/components/bbb_gpio/switch.py similarity index 100% rename from homeassistant/components/switch/bbb_gpio.py rename to homeassistant/components/bbb_gpio/switch.py diff --git a/homeassistant/components/alarm_control_panel/blink.py b/homeassistant/components/blink/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/blink.py rename to homeassistant/components/blink/alarm_control_panel.py diff --git a/homeassistant/components/binary_sensor/blink.py b/homeassistant/components/blink/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/blink.py rename to homeassistant/components/blink/binary_sensor.py diff --git a/homeassistant/components/camera/blink.py b/homeassistant/components/blink/camera.py similarity index 100% rename from homeassistant/components/camera/blink.py rename to homeassistant/components/blink/camera.py diff --git a/homeassistant/components/sensor/blink.py b/homeassistant/components/blink/sensor.py similarity index 100% rename from homeassistant/components/sensor/blink.py rename to homeassistant/components/blink/sensor.py diff --git a/homeassistant/components/bloomsky.py b/homeassistant/components/bloomsky/__init__.py similarity index 100% rename from homeassistant/components/bloomsky.py rename to homeassistant/components/bloomsky/__init__.py diff --git a/homeassistant/components/binary_sensor/bloomsky.py b/homeassistant/components/bloomsky/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/bloomsky.py rename to homeassistant/components/bloomsky/binary_sensor.py diff --git a/homeassistant/components/camera/bloomsky.py b/homeassistant/components/bloomsky/camera.py similarity index 100% rename from homeassistant/components/camera/bloomsky.py rename to homeassistant/components/bloomsky/camera.py diff --git a/homeassistant/components/sensor/bloomsky.py b/homeassistant/components/bloomsky/sensor.py similarity index 100% rename from homeassistant/components/sensor/bloomsky.py rename to homeassistant/components/bloomsky/sensor.py diff --git a/homeassistant/components/binary_sensor/bmw_connected_drive.py b/homeassistant/components/bmw_connected_drive/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/bmw_connected_drive.py rename to homeassistant/components/bmw_connected_drive/binary_sensor.py diff --git a/homeassistant/components/device_tracker/bmw_connected_drive.py b/homeassistant/components/bmw_connected_drive/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/bmw_connected_drive.py rename to homeassistant/components/bmw_connected_drive/device_tracker.py diff --git a/homeassistant/components/lock/bmw_connected_drive.py b/homeassistant/components/bmw_connected_drive/lock.py similarity index 100% rename from homeassistant/components/lock/bmw_connected_drive.py rename to homeassistant/components/bmw_connected_drive/lock.py diff --git a/homeassistant/components/sensor/bmw_connected_drive.py b/homeassistant/components/bmw_connected_drive/sensor.py similarity index 100% rename from homeassistant/components/sensor/bmw_connected_drive.py rename to homeassistant/components/bmw_connected_drive/sensor.py diff --git a/homeassistant/components/comfoconnect.py b/homeassistant/components/comfoconnect/__init__.py similarity index 100% rename from homeassistant/components/comfoconnect.py rename to homeassistant/components/comfoconnect/__init__.py diff --git a/homeassistant/components/fan/comfoconnect.py b/homeassistant/components/comfoconnect/fan.py similarity index 100% rename from homeassistant/components/fan/comfoconnect.py rename to homeassistant/components/comfoconnect/fan.py diff --git a/homeassistant/components/sensor/comfoconnect.py b/homeassistant/components/comfoconnect/sensor.py similarity index 100% rename from homeassistant/components/sensor/comfoconnect.py rename to homeassistant/components/comfoconnect/sensor.py diff --git a/homeassistant/components/digital_ocean.py b/homeassistant/components/digital_ocean/__init__.py similarity index 100% rename from homeassistant/components/digital_ocean.py rename to homeassistant/components/digital_ocean/__init__.py diff --git a/homeassistant/components/binary_sensor/digital_ocean.py b/homeassistant/components/digital_ocean/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/digital_ocean.py rename to homeassistant/components/digital_ocean/binary_sensor.py diff --git a/homeassistant/components/switch/digital_ocean.py b/homeassistant/components/digital_ocean/switch.py similarity index 100% rename from homeassistant/components/switch/digital_ocean.py rename to homeassistant/components/digital_ocean/switch.py diff --git a/homeassistant/components/doorbird.py b/homeassistant/components/doorbird/__init__.py similarity index 100% rename from homeassistant/components/doorbird.py rename to homeassistant/components/doorbird/__init__.py diff --git a/homeassistant/components/camera/doorbird.py b/homeassistant/components/doorbird/camera.py similarity index 100% rename from homeassistant/components/camera/doorbird.py rename to homeassistant/components/doorbird/camera.py diff --git a/homeassistant/components/switch/doorbird.py b/homeassistant/components/doorbird/switch.py similarity index 100% rename from homeassistant/components/switch/doorbird.py rename to homeassistant/components/doorbird/switch.py diff --git a/homeassistant/components/dweet.py b/homeassistant/components/dweet/__init__.py similarity index 100% rename from homeassistant/components/dweet.py rename to homeassistant/components/dweet/__init__.py diff --git a/homeassistant/components/sensor/dweet.py b/homeassistant/components/dweet/sensor.py similarity index 100% rename from homeassistant/components/sensor/dweet.py rename to homeassistant/components/dweet/sensor.py diff --git a/homeassistant/components/ecoal_boiler.py b/homeassistant/components/ecoal_boiler/__init__.py similarity index 100% rename from homeassistant/components/ecoal_boiler.py rename to homeassistant/components/ecoal_boiler/__init__.py diff --git a/homeassistant/components/sensor/ecoal_boiler.py b/homeassistant/components/ecoal_boiler/sensor.py similarity index 100% rename from homeassistant/components/sensor/ecoal_boiler.py rename to homeassistant/components/ecoal_boiler/sensor.py diff --git a/homeassistant/components/switch/ecoal_boiler.py b/homeassistant/components/ecoal_boiler/switch.py similarity index 100% rename from homeassistant/components/switch/ecoal_boiler.py rename to homeassistant/components/ecoal_boiler/switch.py diff --git a/homeassistant/components/ecobee.py b/homeassistant/components/ecobee/__init__.py similarity index 100% rename from homeassistant/components/ecobee.py rename to homeassistant/components/ecobee/__init__.py diff --git a/homeassistant/components/binary_sensor/ecobee.py b/homeassistant/components/ecobee/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/ecobee.py rename to homeassistant/components/ecobee/binary_sensor.py diff --git a/homeassistant/components/climate/ecobee.py b/homeassistant/components/ecobee/climate.py similarity index 100% rename from homeassistant/components/climate/ecobee.py rename to homeassistant/components/ecobee/climate.py diff --git a/homeassistant/components/notify/ecobee.py b/homeassistant/components/ecobee/notify.py similarity index 100% rename from homeassistant/components/notify/ecobee.py rename to homeassistant/components/ecobee/notify.py diff --git a/homeassistant/components/sensor/ecobee.py b/homeassistant/components/ecobee/sensor.py similarity index 100% rename from homeassistant/components/sensor/ecobee.py rename to homeassistant/components/ecobee/sensor.py diff --git a/homeassistant/components/weather/ecobee.py b/homeassistant/components/ecobee/weather.py similarity index 100% rename from homeassistant/components/weather/ecobee.py rename to homeassistant/components/ecobee/weather.py diff --git a/homeassistant/components/ecovacs.py b/homeassistant/components/ecovacs/__init__.py similarity index 100% rename from homeassistant/components/ecovacs.py rename to homeassistant/components/ecovacs/__init__.py diff --git a/homeassistant/components/vacuum/ecovacs.py b/homeassistant/components/ecovacs/vacuum.py similarity index 100% rename from homeassistant/components/vacuum/ecovacs.py rename to homeassistant/components/ecovacs/vacuum.py diff --git a/homeassistant/components/edp_redy.py b/homeassistant/components/edp_redy/__init__.py similarity index 100% rename from homeassistant/components/edp_redy.py rename to homeassistant/components/edp_redy/__init__.py diff --git a/homeassistant/components/sensor/edp_redy.py b/homeassistant/components/edp_redy/sensor.py similarity index 100% rename from homeassistant/components/sensor/edp_redy.py rename to homeassistant/components/edp_redy/sensor.py diff --git a/homeassistant/components/switch/edp_redy.py b/homeassistant/components/edp_redy/switch.py similarity index 100% rename from homeassistant/components/switch/edp_redy.py rename to homeassistant/components/edp_redy/switch.py diff --git a/homeassistant/components/egardia.py b/homeassistant/components/egardia/__init__.py similarity index 100% rename from homeassistant/components/egardia.py rename to homeassistant/components/egardia/__init__.py diff --git a/homeassistant/components/alarm_control_panel/egardia.py b/homeassistant/components/egardia/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/egardia.py rename to homeassistant/components/egardia/alarm_control_panel.py diff --git a/homeassistant/components/binary_sensor/egardia.py b/homeassistant/components/egardia/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/egardia.py rename to homeassistant/components/egardia/binary_sensor.py diff --git a/homeassistant/components/eight_sleep.py b/homeassistant/components/eight_sleep/__init__.py similarity index 100% rename from homeassistant/components/eight_sleep.py rename to homeassistant/components/eight_sleep/__init__.py diff --git a/homeassistant/components/binary_sensor/eight_sleep.py b/homeassistant/components/eight_sleep/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/eight_sleep.py rename to homeassistant/components/eight_sleep/binary_sensor.py diff --git a/homeassistant/components/sensor/eight_sleep.py b/homeassistant/components/eight_sleep/sensor.py similarity index 100% rename from homeassistant/components/sensor/eight_sleep.py rename to homeassistant/components/eight_sleep/sensor.py diff --git a/homeassistant/components/alarm_control_panel/elkm1.py b/homeassistant/components/elkm1/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/elkm1.py rename to homeassistant/components/elkm1/alarm_control_panel.py diff --git a/homeassistant/components/climate/elkm1.py b/homeassistant/components/elkm1/climate.py similarity index 100% rename from homeassistant/components/climate/elkm1.py rename to homeassistant/components/elkm1/climate.py diff --git a/homeassistant/components/light/elkm1.py b/homeassistant/components/elkm1/light.py similarity index 100% rename from homeassistant/components/light/elkm1.py rename to homeassistant/components/elkm1/light.py diff --git a/homeassistant/components/scene/elkm1.py b/homeassistant/components/elkm1/scene.py similarity index 100% rename from homeassistant/components/scene/elkm1.py rename to homeassistant/components/elkm1/scene.py diff --git a/homeassistant/components/sensor/elkm1.py b/homeassistant/components/elkm1/sensor.py similarity index 100% rename from homeassistant/components/sensor/elkm1.py rename to homeassistant/components/elkm1/sensor.py diff --git a/homeassistant/components/switch/elkm1.py b/homeassistant/components/elkm1/switch.py similarity index 100% rename from homeassistant/components/switch/elkm1.py rename to homeassistant/components/elkm1/switch.py diff --git a/homeassistant/components/enocean.py b/homeassistant/components/enocean/__init__.py similarity index 100% rename from homeassistant/components/enocean.py rename to homeassistant/components/enocean/__init__.py diff --git a/homeassistant/components/binary_sensor/enocean.py b/homeassistant/components/enocean/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/enocean.py rename to homeassistant/components/enocean/binary_sensor.py diff --git a/homeassistant/components/light/enocean.py b/homeassistant/components/enocean/light.py similarity index 100% rename from homeassistant/components/light/enocean.py rename to homeassistant/components/enocean/light.py diff --git a/homeassistant/components/sensor/enocean.py b/homeassistant/components/enocean/sensor.py similarity index 100% rename from homeassistant/components/sensor/enocean.py rename to homeassistant/components/enocean/sensor.py diff --git a/homeassistant/components/switch/enocean.py b/homeassistant/components/enocean/switch.py similarity index 100% rename from homeassistant/components/switch/enocean.py rename to homeassistant/components/enocean/switch.py diff --git a/homeassistant/components/alarm_control_panel/envisalink.py b/homeassistant/components/envisalink/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/envisalink.py rename to homeassistant/components/envisalink/alarm_control_panel.py diff --git a/homeassistant/components/binary_sensor/envisalink.py b/homeassistant/components/envisalink/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/envisalink.py rename to homeassistant/components/envisalink/binary_sensor.py diff --git a/homeassistant/components/sensor/envisalink.py b/homeassistant/components/envisalink/sensor.py similarity index 100% rename from homeassistant/components/sensor/envisalink.py rename to homeassistant/components/envisalink/sensor.py diff --git a/homeassistant/components/eufy.py b/homeassistant/components/eufy/__init__.py similarity index 100% rename from homeassistant/components/eufy.py rename to homeassistant/components/eufy/__init__.py diff --git a/homeassistant/components/light/eufy.py b/homeassistant/components/eufy/light.py similarity index 100% rename from homeassistant/components/light/eufy.py rename to homeassistant/components/eufy/light.py diff --git a/homeassistant/components/switch/eufy.py b/homeassistant/components/eufy/switch.py similarity index 100% rename from homeassistant/components/switch/eufy.py rename to homeassistant/components/eufy/switch.py diff --git a/homeassistant/components/evohome.py b/homeassistant/components/evohome/__init__.py similarity index 100% rename from homeassistant/components/evohome.py rename to homeassistant/components/evohome/__init__.py diff --git a/homeassistant/components/climate/evohome.py b/homeassistant/components/evohome/climate.py similarity index 100% rename from homeassistant/components/climate/evohome.py rename to homeassistant/components/evohome/climate.py diff --git a/homeassistant/components/binary_sensor/fibaro.py b/homeassistant/components/fibaro/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/fibaro.py rename to homeassistant/components/fibaro/binary_sensor.py diff --git a/homeassistant/components/cover/fibaro.py b/homeassistant/components/fibaro/cover.py similarity index 100% rename from homeassistant/components/cover/fibaro.py rename to homeassistant/components/fibaro/cover.py diff --git a/homeassistant/components/light/fibaro.py b/homeassistant/components/fibaro/light.py similarity index 100% rename from homeassistant/components/light/fibaro.py rename to homeassistant/components/fibaro/light.py diff --git a/homeassistant/components/scene/fibaro.py b/homeassistant/components/fibaro/scene.py similarity index 100% rename from homeassistant/components/scene/fibaro.py rename to homeassistant/components/fibaro/scene.py diff --git a/homeassistant/components/sensor/fibaro.py b/homeassistant/components/fibaro/sensor.py similarity index 100% rename from homeassistant/components/sensor/fibaro.py rename to homeassistant/components/fibaro/sensor.py diff --git a/homeassistant/components/switch/fibaro.py b/homeassistant/components/fibaro/switch.py similarity index 100% rename from homeassistant/components/switch/fibaro.py rename to homeassistant/components/fibaro/switch.py diff --git a/homeassistant/components/freebox.py b/homeassistant/components/freebox/__init__.py similarity index 100% rename from homeassistant/components/freebox.py rename to homeassistant/components/freebox/__init__.py diff --git a/homeassistant/components/device_tracker/freebox.py b/homeassistant/components/freebox/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/freebox.py rename to homeassistant/components/freebox/device_tracker.py diff --git a/homeassistant/components/sensor/freebox.py b/homeassistant/components/freebox/sensor.py similarity index 100% rename from homeassistant/components/sensor/freebox.py rename to homeassistant/components/freebox/sensor.py diff --git a/homeassistant/components/fritzbox.py b/homeassistant/components/fritzbox/__init__.py similarity index 100% rename from homeassistant/components/fritzbox.py rename to homeassistant/components/fritzbox/__init__.py diff --git a/homeassistant/components/binary_sensor/fritzbox.py b/homeassistant/components/fritzbox/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/fritzbox.py rename to homeassistant/components/fritzbox/binary_sensor.py diff --git a/homeassistant/components/climate/fritzbox.py b/homeassistant/components/fritzbox/climate.py similarity index 100% rename from homeassistant/components/climate/fritzbox.py rename to homeassistant/components/fritzbox/climate.py diff --git a/homeassistant/components/sensor/fritzbox.py b/homeassistant/components/fritzbox/sensor.py similarity index 100% rename from homeassistant/components/sensor/fritzbox.py rename to homeassistant/components/fritzbox/sensor.py diff --git a/homeassistant/components/switch/fritzbox.py b/homeassistant/components/fritzbox/switch.py similarity index 100% rename from homeassistant/components/switch/fritzbox.py rename to homeassistant/components/fritzbox/switch.py diff --git a/homeassistant/components/gc100.py b/homeassistant/components/gc100/__init__.py similarity index 100% rename from homeassistant/components/gc100.py rename to homeassistant/components/gc100/__init__.py diff --git a/homeassistant/components/binary_sensor/gc100.py b/homeassistant/components/gc100/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/gc100.py rename to homeassistant/components/gc100/binary_sensor.py diff --git a/homeassistant/components/switch/gc100.py b/homeassistant/components/gc100/switch.py similarity index 100% rename from homeassistant/components/switch/gc100.py rename to homeassistant/components/gc100/switch.py diff --git a/homeassistant/components/google.py b/homeassistant/components/google/__init__.py similarity index 100% rename from homeassistant/components/google.py rename to homeassistant/components/google/__init__.py diff --git a/homeassistant/components/calendar/google.py b/homeassistant/components/google/calendar.py similarity index 100% rename from homeassistant/components/calendar/google.py rename to homeassistant/components/google/calendar.py diff --git a/homeassistant/components/tts/google.py b/homeassistant/components/google/tts.py similarity index 100% rename from homeassistant/components/tts/google.py rename to homeassistant/components/google/tts.py diff --git a/homeassistant/components/googlehome.py b/homeassistant/components/googlehome/__init__.py similarity index 100% rename from homeassistant/components/googlehome.py rename to homeassistant/components/googlehome/__init__.py diff --git a/homeassistant/components/device_tracker/googlehome.py b/homeassistant/components/googlehome/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/googlehome.py rename to homeassistant/components/googlehome/device_tracker.py diff --git a/homeassistant/components/sensor/habitica.py b/homeassistant/components/habitica/sensor.py similarity index 100% rename from homeassistant/components/sensor/habitica.py rename to homeassistant/components/habitica/sensor.py diff --git a/homeassistant/components/hdmi_cec.py b/homeassistant/components/hdmi_cec/__init__.py similarity index 100% rename from homeassistant/components/hdmi_cec.py rename to homeassistant/components/hdmi_cec/__init__.py diff --git a/homeassistant/components/media_player/hdmi_cec.py b/homeassistant/components/hdmi_cec/media_player.py similarity index 100% rename from homeassistant/components/media_player/hdmi_cec.py rename to homeassistant/components/hdmi_cec/media_player.py diff --git a/homeassistant/components/switch/hdmi_cec.py b/homeassistant/components/hdmi_cec/switch.py similarity index 100% rename from homeassistant/components/switch/hdmi_cec.py rename to homeassistant/components/hdmi_cec/switch.py diff --git a/homeassistant/components/hive.py b/homeassistant/components/hive/__init__.py similarity index 100% rename from homeassistant/components/hive.py rename to homeassistant/components/hive/__init__.py diff --git a/homeassistant/components/binary_sensor/hive.py b/homeassistant/components/hive/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/hive.py rename to homeassistant/components/hive/binary_sensor.py diff --git a/homeassistant/components/climate/hive.py b/homeassistant/components/hive/climate.py similarity index 100% rename from homeassistant/components/climate/hive.py rename to homeassistant/components/hive/climate.py diff --git a/homeassistant/components/light/hive.py b/homeassistant/components/hive/light.py similarity index 100% rename from homeassistant/components/light/hive.py rename to homeassistant/components/hive/light.py diff --git a/homeassistant/components/sensor/hive.py b/homeassistant/components/hive/sensor.py similarity index 100% rename from homeassistant/components/sensor/hive.py rename to homeassistant/components/hive/sensor.py diff --git a/homeassistant/components/switch/hive.py b/homeassistant/components/hive/switch.py similarity index 100% rename from homeassistant/components/switch/hive.py rename to homeassistant/components/hive/switch.py diff --git a/homeassistant/components/hlk_sw16.py b/homeassistant/components/hlk_sw16/__init__.py similarity index 100% rename from homeassistant/components/hlk_sw16.py rename to homeassistant/components/hlk_sw16/__init__.py diff --git a/homeassistant/components/switch/hlk_sw16.py b/homeassistant/components/hlk_sw16/switch.py similarity index 100% rename from homeassistant/components/switch/hlk_sw16.py rename to homeassistant/components/hlk_sw16/switch.py diff --git a/homeassistant/components/binary_sensor/homematic.py b/homeassistant/components/homematic/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/homematic.py rename to homeassistant/components/homematic/binary_sensor.py diff --git a/homeassistant/components/climate/homematic.py b/homeassistant/components/homematic/climate.py similarity index 100% rename from homeassistant/components/climate/homematic.py rename to homeassistant/components/homematic/climate.py diff --git a/homeassistant/components/cover/homematic.py b/homeassistant/components/homematic/cover.py similarity index 100% rename from homeassistant/components/cover/homematic.py rename to homeassistant/components/homematic/cover.py diff --git a/homeassistant/components/light/homematic.py b/homeassistant/components/homematic/light.py similarity index 100% rename from homeassistant/components/light/homematic.py rename to homeassistant/components/homematic/light.py diff --git a/homeassistant/components/lock/homematic.py b/homeassistant/components/homematic/lock.py similarity index 100% rename from homeassistant/components/lock/homematic.py rename to homeassistant/components/homematic/lock.py diff --git a/homeassistant/components/notify/homematic.py b/homeassistant/components/homematic/notify.py similarity index 100% rename from homeassistant/components/notify/homematic.py rename to homeassistant/components/homematic/notify.py diff --git a/homeassistant/components/sensor/homematic.py b/homeassistant/components/homematic/sensor.py similarity index 100% rename from homeassistant/components/sensor/homematic.py rename to homeassistant/components/homematic/sensor.py diff --git a/homeassistant/components/switch/homematic.py b/homeassistant/components/homematic/switch.py similarity index 100% rename from homeassistant/components/switch/homematic.py rename to homeassistant/components/homematic/switch.py diff --git a/homeassistant/components/homeworks.py b/homeassistant/components/homeworks/__init__.py similarity index 100% rename from homeassistant/components/homeworks.py rename to homeassistant/components/homeworks/__init__.py diff --git a/homeassistant/components/light/homeworks.py b/homeassistant/components/homeworks/light.py similarity index 100% rename from homeassistant/components/light/homeworks.py rename to homeassistant/components/homeworks/light.py diff --git a/homeassistant/components/huawei_lte.py b/homeassistant/components/huawei_lte/__init__.py similarity index 100% rename from homeassistant/components/huawei_lte.py rename to homeassistant/components/huawei_lte/__init__.py diff --git a/homeassistant/components/device_tracker/huawei_lte.py b/homeassistant/components/huawei_lte/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/huawei_lte.py rename to homeassistant/components/huawei_lte/device_tracker.py diff --git a/homeassistant/components/notify/huawei_lte.py b/homeassistant/components/huawei_lte/notify.py similarity index 100% rename from homeassistant/components/notify/huawei_lte.py rename to homeassistant/components/huawei_lte/notify.py diff --git a/homeassistant/components/sensor/huawei_lte.py b/homeassistant/components/huawei_lte/sensor.py similarity index 100% rename from homeassistant/components/sensor/huawei_lte.py rename to homeassistant/components/huawei_lte/sensor.py diff --git a/homeassistant/components/hydrawise.py b/homeassistant/components/hydrawise/__init__.py similarity index 100% rename from homeassistant/components/hydrawise.py rename to homeassistant/components/hydrawise/__init__.py diff --git a/homeassistant/components/binary_sensor/hydrawise.py b/homeassistant/components/hydrawise/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/hydrawise.py rename to homeassistant/components/hydrawise/binary_sensor.py diff --git a/homeassistant/components/sensor/hydrawise.py b/homeassistant/components/hydrawise/sensor.py similarity index 100% rename from homeassistant/components/sensor/hydrawise.py rename to homeassistant/components/hydrawise/sensor.py diff --git a/homeassistant/components/switch/hydrawise.py b/homeassistant/components/hydrawise/switch.py similarity index 100% rename from homeassistant/components/switch/hydrawise.py rename to homeassistant/components/hydrawise/switch.py diff --git a/homeassistant/components/binary_sensor/ihc.py b/homeassistant/components/ihc/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/ihc.py rename to homeassistant/components/ihc/binary_sensor.py diff --git a/homeassistant/components/light/ihc.py b/homeassistant/components/ihc/light.py similarity index 100% rename from homeassistant/components/light/ihc.py rename to homeassistant/components/ihc/light.py diff --git a/homeassistant/components/sensor/ihc.py b/homeassistant/components/ihc/sensor.py similarity index 100% rename from homeassistant/components/sensor/ihc.py rename to homeassistant/components/ihc/sensor.py diff --git a/homeassistant/components/switch/ihc.py b/homeassistant/components/ihc/switch.py similarity index 100% rename from homeassistant/components/switch/ihc.py rename to homeassistant/components/ihc/switch.py diff --git a/homeassistant/components/binary_sensor/insteon.py b/homeassistant/components/insteon/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/insteon.py rename to homeassistant/components/insteon/binary_sensor.py diff --git a/homeassistant/components/cover/insteon.py b/homeassistant/components/insteon/cover.py similarity index 100% rename from homeassistant/components/cover/insteon.py rename to homeassistant/components/insteon/cover.py diff --git a/homeassistant/components/fan/insteon.py b/homeassistant/components/insteon/fan.py similarity index 100% rename from homeassistant/components/fan/insteon.py rename to homeassistant/components/insteon/fan.py diff --git a/homeassistant/components/light/insteon.py b/homeassistant/components/insteon/light.py similarity index 100% rename from homeassistant/components/light/insteon.py rename to homeassistant/components/insteon/light.py diff --git a/homeassistant/components/sensor/insteon.py b/homeassistant/components/insteon/sensor.py similarity index 100% rename from homeassistant/components/sensor/insteon.py rename to homeassistant/components/insteon/sensor.py diff --git a/homeassistant/components/switch/insteon.py b/homeassistant/components/insteon/switch.py similarity index 100% rename from homeassistant/components/switch/insteon.py rename to homeassistant/components/insteon/switch.py diff --git a/homeassistant/components/iota.py b/homeassistant/components/iota/__init__.py similarity index 100% rename from homeassistant/components/iota.py rename to homeassistant/components/iota/__init__.py diff --git a/homeassistant/components/sensor/iota.py b/homeassistant/components/iota/sensor.py similarity index 100% rename from homeassistant/components/sensor/iota.py rename to homeassistant/components/iota/sensor.py diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994/__init__.py similarity index 100% rename from homeassistant/components/isy994.py rename to homeassistant/components/isy994/__init__.py diff --git a/homeassistant/components/binary_sensor/isy994.py b/homeassistant/components/isy994/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/isy994.py rename to homeassistant/components/isy994/binary_sensor.py diff --git a/homeassistant/components/cover/isy994.py b/homeassistant/components/isy994/cover.py similarity index 100% rename from homeassistant/components/cover/isy994.py rename to homeassistant/components/isy994/cover.py diff --git a/homeassistant/components/fan/isy994.py b/homeassistant/components/isy994/fan.py similarity index 100% rename from homeassistant/components/fan/isy994.py rename to homeassistant/components/isy994/fan.py diff --git a/homeassistant/components/light/isy994.py b/homeassistant/components/isy994/light.py similarity index 100% rename from homeassistant/components/light/isy994.py rename to homeassistant/components/isy994/light.py diff --git a/homeassistant/components/lock/isy994.py b/homeassistant/components/isy994/lock.py similarity index 100% rename from homeassistant/components/lock/isy994.py rename to homeassistant/components/isy994/lock.py diff --git a/homeassistant/components/sensor/isy994.py b/homeassistant/components/isy994/sensor.py similarity index 100% rename from homeassistant/components/sensor/isy994.py rename to homeassistant/components/isy994/sensor.py diff --git a/homeassistant/components/switch/isy994.py b/homeassistant/components/isy994/switch.py similarity index 100% rename from homeassistant/components/switch/isy994.py rename to homeassistant/components/isy994/switch.py diff --git a/homeassistant/components/joaoapps_join.py b/homeassistant/components/joaoapps_join/__init__.py similarity index 100% rename from homeassistant/components/joaoapps_join.py rename to homeassistant/components/joaoapps_join/__init__.py diff --git a/homeassistant/components/notify/joaoapps_join.py b/homeassistant/components/joaoapps_join/notify.py similarity index 100% rename from homeassistant/components/notify/joaoapps_join.py rename to homeassistant/components/joaoapps_join/notify.py diff --git a/homeassistant/components/juicenet.py b/homeassistant/components/juicenet/__init__.py similarity index 100% rename from homeassistant/components/juicenet.py rename to homeassistant/components/juicenet/__init__.py diff --git a/homeassistant/components/sensor/juicenet.py b/homeassistant/components/juicenet/sensor.py similarity index 100% rename from homeassistant/components/sensor/juicenet.py rename to homeassistant/components/juicenet/sensor.py diff --git a/homeassistant/components/kira.py b/homeassistant/components/kira/__init__.py similarity index 100% rename from homeassistant/components/kira.py rename to homeassistant/components/kira/__init__.py diff --git a/homeassistant/components/remote/kira.py b/homeassistant/components/kira/remote.py similarity index 100% rename from homeassistant/components/remote/kira.py rename to homeassistant/components/kira/remote.py diff --git a/homeassistant/components/sensor/kira.py b/homeassistant/components/kira/sensor.py similarity index 100% rename from homeassistant/components/sensor/kira.py rename to homeassistant/components/kira/sensor.py diff --git a/homeassistant/components/knx.py b/homeassistant/components/knx/__init__.py similarity index 100% rename from homeassistant/components/knx.py rename to homeassistant/components/knx/__init__.py diff --git a/homeassistant/components/binary_sensor/knx.py b/homeassistant/components/knx/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/knx.py rename to homeassistant/components/knx/binary_sensor.py diff --git a/homeassistant/components/climate/knx.py b/homeassistant/components/knx/climate.py similarity index 100% rename from homeassistant/components/climate/knx.py rename to homeassistant/components/knx/climate.py diff --git a/homeassistant/components/cover/knx.py b/homeassistant/components/knx/cover.py similarity index 100% rename from homeassistant/components/cover/knx.py rename to homeassistant/components/knx/cover.py diff --git a/homeassistant/components/light/knx.py b/homeassistant/components/knx/light.py similarity index 100% rename from homeassistant/components/light/knx.py rename to homeassistant/components/knx/light.py diff --git a/homeassistant/components/notify/knx.py b/homeassistant/components/knx/notify.py similarity index 100% rename from homeassistant/components/notify/knx.py rename to homeassistant/components/knx/notify.py diff --git a/homeassistant/components/scene/knx.py b/homeassistant/components/knx/scene.py similarity index 100% rename from homeassistant/components/scene/knx.py rename to homeassistant/components/knx/scene.py diff --git a/homeassistant/components/sensor/knx.py b/homeassistant/components/knx/sensor.py similarity index 100% rename from homeassistant/components/sensor/knx.py rename to homeassistant/components/knx/sensor.py diff --git a/homeassistant/components/switch/knx.py b/homeassistant/components/knx/switch.py similarity index 100% rename from homeassistant/components/switch/knx.py rename to homeassistant/components/knx/switch.py diff --git a/homeassistant/components/konnected.py b/homeassistant/components/konnected/__init__.py similarity index 100% rename from homeassistant/components/konnected.py rename to homeassistant/components/konnected/__init__.py diff --git a/homeassistant/components/binary_sensor/konnected.py b/homeassistant/components/konnected/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/konnected.py rename to homeassistant/components/konnected/binary_sensor.py diff --git a/homeassistant/components/switch/konnected.py b/homeassistant/components/konnected/switch.py similarity index 100% rename from homeassistant/components/switch/konnected.py rename to homeassistant/components/konnected/switch.py diff --git a/homeassistant/components/lametric.py b/homeassistant/components/lametric/__init__.py similarity index 100% rename from homeassistant/components/lametric.py rename to homeassistant/components/lametric/__init__.py diff --git a/homeassistant/components/notify/lametric.py b/homeassistant/components/lametric/notify.py similarity index 100% rename from homeassistant/components/notify/lametric.py rename to homeassistant/components/lametric/notify.py diff --git a/homeassistant/components/lcn.py b/homeassistant/components/lcn/__init__.py similarity index 100% rename from homeassistant/components/lcn.py rename to homeassistant/components/lcn/__init__.py diff --git a/homeassistant/components/light/lcn.py b/homeassistant/components/lcn/light.py similarity index 100% rename from homeassistant/components/light/lcn.py rename to homeassistant/components/lcn/light.py diff --git a/homeassistant/components/switch/lcn.py b/homeassistant/components/lcn/switch.py similarity index 100% rename from homeassistant/components/switch/lcn.py rename to homeassistant/components/lcn/switch.py diff --git a/homeassistant/components/lightwave.py b/homeassistant/components/lightwave/__init__.py similarity index 100% rename from homeassistant/components/lightwave.py rename to homeassistant/components/lightwave/__init__.py diff --git a/homeassistant/components/light/lightwave.py b/homeassistant/components/lightwave/light.py similarity index 100% rename from homeassistant/components/light/lightwave.py rename to homeassistant/components/lightwave/light.py diff --git a/homeassistant/components/switch/lightwave.py b/homeassistant/components/lightwave/switch.py similarity index 100% rename from homeassistant/components/switch/lightwave.py rename to homeassistant/components/lightwave/switch.py diff --git a/homeassistant/components/linode.py b/homeassistant/components/linode/__init__.py similarity index 100% rename from homeassistant/components/linode.py rename to homeassistant/components/linode/__init__.py diff --git a/homeassistant/components/binary_sensor/linode.py b/homeassistant/components/linode/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/linode.py rename to homeassistant/components/linode/binary_sensor.py diff --git a/homeassistant/components/switch/linode.py b/homeassistant/components/linode/switch.py similarity index 100% rename from homeassistant/components/switch/linode.py rename to homeassistant/components/linode/switch.py diff --git a/homeassistant/components/logi_circle.py b/homeassistant/components/logi_circle/__init__.py similarity index 100% rename from homeassistant/components/logi_circle.py rename to homeassistant/components/logi_circle/__init__.py diff --git a/homeassistant/components/camera/logi_circle.py b/homeassistant/components/logi_circle/camera.py similarity index 100% rename from homeassistant/components/camera/logi_circle.py rename to homeassistant/components/logi_circle/camera.py diff --git a/homeassistant/components/sensor/logi_circle.py b/homeassistant/components/logi_circle/sensor.py similarity index 100% rename from homeassistant/components/sensor/logi_circle.py rename to homeassistant/components/logi_circle/sensor.py diff --git a/homeassistant/components/lupusec.py b/homeassistant/components/lupusec/__init__.py similarity index 100% rename from homeassistant/components/lupusec.py rename to homeassistant/components/lupusec/__init__.py diff --git a/homeassistant/components/alarm_control_panel/lupusec.py b/homeassistant/components/lupusec/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/lupusec.py rename to homeassistant/components/lupusec/alarm_control_panel.py diff --git a/homeassistant/components/binary_sensor/lupusec.py b/homeassistant/components/lupusec/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/lupusec.py rename to homeassistant/components/lupusec/binary_sensor.py diff --git a/homeassistant/components/switch/lupusec.py b/homeassistant/components/lupusec/switch.py similarity index 100% rename from homeassistant/components/switch/lupusec.py rename to homeassistant/components/lupusec/switch.py diff --git a/homeassistant/components/lutron_caseta.py b/homeassistant/components/lutron_caseta/__init__.py similarity index 100% rename from homeassistant/components/lutron_caseta.py rename to homeassistant/components/lutron_caseta/__init__.py diff --git a/homeassistant/components/cover/lutron_caseta.py b/homeassistant/components/lutron_caseta/cover.py similarity index 100% rename from homeassistant/components/cover/lutron_caseta.py rename to homeassistant/components/lutron_caseta/cover.py diff --git a/homeassistant/components/light/lutron_caseta.py b/homeassistant/components/lutron_caseta/light.py similarity index 100% rename from homeassistant/components/light/lutron_caseta.py rename to homeassistant/components/lutron_caseta/light.py diff --git a/homeassistant/components/scene/lutron_caseta.py b/homeassistant/components/lutron_caseta/scene.py similarity index 100% rename from homeassistant/components/scene/lutron_caseta.py rename to homeassistant/components/lutron_caseta/scene.py diff --git a/homeassistant/components/switch/lutron_caseta.py b/homeassistant/components/lutron_caseta/switch.py similarity index 100% rename from homeassistant/components/switch/lutron_caseta.py rename to homeassistant/components/lutron_caseta/switch.py diff --git a/homeassistant/components/matrix.py b/homeassistant/components/matrix/__init__.py similarity index 100% rename from homeassistant/components/matrix.py rename to homeassistant/components/matrix/__init__.py diff --git a/homeassistant/components/notify/matrix.py b/homeassistant/components/matrix/notify.py similarity index 100% rename from homeassistant/components/notify/matrix.py rename to homeassistant/components/matrix/notify.py diff --git a/homeassistant/components/maxcube.py b/homeassistant/components/maxcube/__init__.py similarity index 100% rename from homeassistant/components/maxcube.py rename to homeassistant/components/maxcube/__init__.py diff --git a/homeassistant/components/binary_sensor/maxcube.py b/homeassistant/components/maxcube/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/maxcube.py rename to homeassistant/components/maxcube/binary_sensor.py diff --git a/homeassistant/components/climate/maxcube.py b/homeassistant/components/maxcube/climate.py similarity index 100% rename from homeassistant/components/climate/maxcube.py rename to homeassistant/components/maxcube/climate.py diff --git a/homeassistant/components/mochad.py b/homeassistant/components/mochad/__init__.py similarity index 100% rename from homeassistant/components/mochad.py rename to homeassistant/components/mochad/__init__.py diff --git a/homeassistant/components/light/mochad.py b/homeassistant/components/mochad/light.py similarity index 100% rename from homeassistant/components/light/mochad.py rename to homeassistant/components/mochad/light.py diff --git a/homeassistant/components/switch/mochad.py b/homeassistant/components/mochad/switch.py similarity index 100% rename from homeassistant/components/switch/mochad.py rename to homeassistant/components/mochad/switch.py diff --git a/homeassistant/components/modbus.py b/homeassistant/components/modbus/__init__.py similarity index 100% rename from homeassistant/components/modbus.py rename to homeassistant/components/modbus/__init__.py diff --git a/homeassistant/components/binary_sensor/modbus.py b/homeassistant/components/modbus/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/modbus.py rename to homeassistant/components/modbus/binary_sensor.py diff --git a/homeassistant/components/climate/modbus.py b/homeassistant/components/modbus/climate.py similarity index 100% rename from homeassistant/components/climate/modbus.py rename to homeassistant/components/modbus/climate.py diff --git a/homeassistant/components/sensor/modbus.py b/homeassistant/components/modbus/sensor.py similarity index 100% rename from homeassistant/components/sensor/modbus.py rename to homeassistant/components/modbus/sensor.py diff --git a/homeassistant/components/switch/modbus.py b/homeassistant/components/modbus/switch.py similarity index 100% rename from homeassistant/components/switch/modbus.py rename to homeassistant/components/modbus/switch.py diff --git a/homeassistant/components/mychevy.py b/homeassistant/components/mychevy/__init__.py similarity index 100% rename from homeassistant/components/mychevy.py rename to homeassistant/components/mychevy/__init__.py diff --git a/homeassistant/components/binary_sensor/mychevy.py b/homeassistant/components/mychevy/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/mychevy.py rename to homeassistant/components/mychevy/binary_sensor.py diff --git a/homeassistant/components/sensor/mychevy.py b/homeassistant/components/mychevy/sensor.py similarity index 100% rename from homeassistant/components/sensor/mychevy.py rename to homeassistant/components/mychevy/sensor.py diff --git a/homeassistant/components/binary_sensor/mysensors.py b/homeassistant/components/mysensors/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/mysensors.py rename to homeassistant/components/mysensors/binary_sensor.py diff --git a/homeassistant/components/climate/mysensors.py b/homeassistant/components/mysensors/climate.py similarity index 100% rename from homeassistant/components/climate/mysensors.py rename to homeassistant/components/mysensors/climate.py diff --git a/homeassistant/components/cover/mysensors.py b/homeassistant/components/mysensors/cover.py similarity index 100% rename from homeassistant/components/cover/mysensors.py rename to homeassistant/components/mysensors/cover.py diff --git a/homeassistant/components/device_tracker/mysensors.py b/homeassistant/components/mysensors/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/mysensors.py rename to homeassistant/components/mysensors/device_tracker.py diff --git a/homeassistant/components/light/mysensors.py b/homeassistant/components/mysensors/light.py similarity index 100% rename from homeassistant/components/light/mysensors.py rename to homeassistant/components/mysensors/light.py diff --git a/homeassistant/components/notify/mysensors.py b/homeassistant/components/mysensors/notify.py similarity index 100% rename from homeassistant/components/notify/mysensors.py rename to homeassistant/components/mysensors/notify.py diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/mysensors/sensor.py similarity index 100% rename from homeassistant/components/sensor/mysensors.py rename to homeassistant/components/mysensors/sensor.py diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/mysensors/switch.py similarity index 100% rename from homeassistant/components/switch/mysensors.py rename to homeassistant/components/mysensors/switch.py diff --git a/homeassistant/components/neato.py b/homeassistant/components/neato/__init__.py similarity index 100% rename from homeassistant/components/neato.py rename to homeassistant/components/neato/__init__.py diff --git a/homeassistant/components/camera/neato.py b/homeassistant/components/neato/camera.py similarity index 100% rename from homeassistant/components/camera/neato.py rename to homeassistant/components/neato/camera.py diff --git a/homeassistant/components/switch/neato.py b/homeassistant/components/neato/switch.py similarity index 100% rename from homeassistant/components/switch/neato.py rename to homeassistant/components/neato/switch.py diff --git a/homeassistant/components/vacuum/neato.py b/homeassistant/components/neato/vacuum.py similarity index 100% rename from homeassistant/components/vacuum/neato.py rename to homeassistant/components/neato/vacuum.py diff --git a/homeassistant/components/netatmo.py b/homeassistant/components/netatmo/__init__.py similarity index 100% rename from homeassistant/components/netatmo.py rename to homeassistant/components/netatmo/__init__.py diff --git a/homeassistant/components/binary_sensor/netatmo.py b/homeassistant/components/netatmo/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/netatmo.py rename to homeassistant/components/netatmo/binary_sensor.py diff --git a/homeassistant/components/camera/netatmo.py b/homeassistant/components/netatmo/camera.py similarity index 100% rename from homeassistant/components/camera/netatmo.py rename to homeassistant/components/netatmo/camera.py diff --git a/homeassistant/components/climate/netatmo.py b/homeassistant/components/netatmo/climate.py similarity index 100% rename from homeassistant/components/climate/netatmo.py rename to homeassistant/components/netatmo/climate.py diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/netatmo/sensor.py similarity index 100% rename from homeassistant/components/sensor/netatmo.py rename to homeassistant/components/netatmo/sensor.py diff --git a/homeassistant/components/netgear_lte.py b/homeassistant/components/netgear_lte/__init__.py similarity index 100% rename from homeassistant/components/netgear_lte.py rename to homeassistant/components/netgear_lte/__init__.py diff --git a/homeassistant/components/notify/netgear_lte.py b/homeassistant/components/netgear_lte/notify.py similarity index 100% rename from homeassistant/components/notify/netgear_lte.py rename to homeassistant/components/netgear_lte/notify.py diff --git a/homeassistant/components/sensor/netgear_lte.py b/homeassistant/components/netgear_lte/sensor.py similarity index 100% rename from homeassistant/components/sensor/netgear_lte.py rename to homeassistant/components/netgear_lte/sensor.py diff --git a/homeassistant/components/octoprint.py b/homeassistant/components/octoprint/__init__.py similarity index 100% rename from homeassistant/components/octoprint.py rename to homeassistant/components/octoprint/__init__.py diff --git a/homeassistant/components/binary_sensor/octoprint.py b/homeassistant/components/octoprint/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/octoprint.py rename to homeassistant/components/octoprint/binary_sensor.py diff --git a/homeassistant/components/sensor/octoprint.py b/homeassistant/components/octoprint/sensor.py similarity index 100% rename from homeassistant/components/sensor/octoprint.py rename to homeassistant/components/octoprint/sensor.py diff --git a/homeassistant/components/binary_sensor/opentherm_gw.py b/homeassistant/components/opentherm_gw/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/opentherm_gw.py rename to homeassistant/components/opentherm_gw/binary_sensor.py diff --git a/homeassistant/components/climate/opentherm_gw.py b/homeassistant/components/opentherm_gw/climate.py similarity index 100% rename from homeassistant/components/climate/opentherm_gw.py rename to homeassistant/components/opentherm_gw/climate.py diff --git a/homeassistant/components/sensor/opentherm_gw.py b/homeassistant/components/opentherm_gw/sensor.py similarity index 100% rename from homeassistant/components/sensor/opentherm_gw.py rename to homeassistant/components/opentherm_gw/sensor.py diff --git a/homeassistant/components/pilight.py b/homeassistant/components/pilight/__init__.py similarity index 100% rename from homeassistant/components/pilight.py rename to homeassistant/components/pilight/__init__.py diff --git a/homeassistant/components/binary_sensor/pilight.py b/homeassistant/components/pilight/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/pilight.py rename to homeassistant/components/pilight/binary_sensor.py diff --git a/homeassistant/components/sensor/pilight.py b/homeassistant/components/pilight/sensor.py similarity index 100% rename from homeassistant/components/sensor/pilight.py rename to homeassistant/components/pilight/sensor.py diff --git a/homeassistant/components/switch/pilight.py b/homeassistant/components/pilight/switch.py similarity index 100% rename from homeassistant/components/switch/pilight.py rename to homeassistant/components/pilight/switch.py diff --git a/homeassistant/components/plum_lightpad.py b/homeassistant/components/plum_lightpad/__init__.py similarity index 100% rename from homeassistant/components/plum_lightpad.py rename to homeassistant/components/plum_lightpad/__init__.py diff --git a/homeassistant/components/light/plum_lightpad.py b/homeassistant/components/plum_lightpad/light.py similarity index 100% rename from homeassistant/components/light/plum_lightpad.py rename to homeassistant/components/plum_lightpad/light.py diff --git a/homeassistant/components/qwikswitch.py b/homeassistant/components/qwikswitch/__init__.py similarity index 100% rename from homeassistant/components/qwikswitch.py rename to homeassistant/components/qwikswitch/__init__.py diff --git a/homeassistant/components/binary_sensor/qwikswitch.py b/homeassistant/components/qwikswitch/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/qwikswitch.py rename to homeassistant/components/qwikswitch/binary_sensor.py diff --git a/homeassistant/components/light/qwikswitch.py b/homeassistant/components/qwikswitch/light.py similarity index 100% rename from homeassistant/components/light/qwikswitch.py rename to homeassistant/components/qwikswitch/light.py diff --git a/homeassistant/components/sensor/qwikswitch.py b/homeassistant/components/qwikswitch/sensor.py similarity index 100% rename from homeassistant/components/sensor/qwikswitch.py rename to homeassistant/components/qwikswitch/sensor.py diff --git a/homeassistant/components/switch/qwikswitch.py b/homeassistant/components/qwikswitch/switch.py similarity index 100% rename from homeassistant/components/switch/qwikswitch.py rename to homeassistant/components/qwikswitch/switch.py diff --git a/homeassistant/components/rachio.py b/homeassistant/components/rachio/__init__.py similarity index 100% rename from homeassistant/components/rachio.py rename to homeassistant/components/rachio/__init__.py diff --git a/homeassistant/components/binary_sensor/rachio.py b/homeassistant/components/rachio/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/rachio.py rename to homeassistant/components/rachio/binary_sensor.py diff --git a/homeassistant/components/switch/rachio.py b/homeassistant/components/rachio/switch.py similarity index 100% rename from homeassistant/components/switch/rachio.py rename to homeassistant/components/rachio/switch.py diff --git a/homeassistant/components/raincloud.py b/homeassistant/components/raincloud/__init__.py similarity index 100% rename from homeassistant/components/raincloud.py rename to homeassistant/components/raincloud/__init__.py diff --git a/homeassistant/components/binary_sensor/raincloud.py b/homeassistant/components/raincloud/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/raincloud.py rename to homeassistant/components/raincloud/binary_sensor.py diff --git a/homeassistant/components/sensor/raincloud.py b/homeassistant/components/raincloud/sensor.py similarity index 100% rename from homeassistant/components/sensor/raincloud.py rename to homeassistant/components/raincloud/sensor.py diff --git a/homeassistant/components/switch/raincloud.py b/homeassistant/components/raincloud/switch.py similarity index 100% rename from homeassistant/components/switch/raincloud.py rename to homeassistant/components/raincloud/switch.py diff --git a/homeassistant/components/raspihats.py b/homeassistant/components/raspihats/__init__.py similarity index 100% rename from homeassistant/components/raspihats.py rename to homeassistant/components/raspihats/__init__.py diff --git a/homeassistant/components/binary_sensor/raspihats.py b/homeassistant/components/raspihats/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/raspihats.py rename to homeassistant/components/raspihats/binary_sensor.py diff --git a/homeassistant/components/switch/raspihats.py b/homeassistant/components/raspihats/switch.py similarity index 100% rename from homeassistant/components/switch/raspihats.py rename to homeassistant/components/raspihats/switch.py diff --git a/homeassistant/components/rfxtrx.py b/homeassistant/components/rfxtrx/__init__.py similarity index 100% rename from homeassistant/components/rfxtrx.py rename to homeassistant/components/rfxtrx/__init__.py diff --git a/homeassistant/components/binary_sensor/rfxtrx.py b/homeassistant/components/rfxtrx/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/rfxtrx.py rename to homeassistant/components/rfxtrx/binary_sensor.py diff --git a/homeassistant/components/cover/rfxtrx.py b/homeassistant/components/rfxtrx/cover.py similarity index 100% rename from homeassistant/components/cover/rfxtrx.py rename to homeassistant/components/rfxtrx/cover.py diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/rfxtrx/light.py similarity index 100% rename from homeassistant/components/light/rfxtrx.py rename to homeassistant/components/rfxtrx/light.py diff --git a/homeassistant/components/sensor/rfxtrx.py b/homeassistant/components/rfxtrx/sensor.py similarity index 100% rename from homeassistant/components/sensor/rfxtrx.py rename to homeassistant/components/rfxtrx/sensor.py diff --git a/homeassistant/components/switch/rfxtrx.py b/homeassistant/components/rfxtrx/switch.py similarity index 100% rename from homeassistant/components/switch/rfxtrx.py rename to homeassistant/components/rfxtrx/switch.py diff --git a/homeassistant/components/roku.py b/homeassistant/components/roku/__init__.py similarity index 100% rename from homeassistant/components/roku.py rename to homeassistant/components/roku/__init__.py diff --git a/homeassistant/components/media_player/roku.py b/homeassistant/components/roku/media_player.py similarity index 100% rename from homeassistant/components/media_player/roku.py rename to homeassistant/components/roku/media_player.py diff --git a/homeassistant/components/remote/roku.py b/homeassistant/components/roku/remote.py similarity index 100% rename from homeassistant/components/remote/roku.py rename to homeassistant/components/roku/remote.py diff --git a/homeassistant/components/rpi_gpio.py b/homeassistant/components/rpi_gpio/__init__.py similarity index 100% rename from homeassistant/components/rpi_gpio.py rename to homeassistant/components/rpi_gpio/__init__.py diff --git a/homeassistant/components/binary_sensor/rpi_gpio.py b/homeassistant/components/rpi_gpio/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/rpi_gpio.py rename to homeassistant/components/rpi_gpio/binary_sensor.py diff --git a/homeassistant/components/cover/rpi_gpio.py b/homeassistant/components/rpi_gpio/cover.py similarity index 100% rename from homeassistant/components/cover/rpi_gpio.py rename to homeassistant/components/rpi_gpio/cover.py diff --git a/homeassistant/components/switch/rpi_gpio.py b/homeassistant/components/rpi_gpio/switch.py similarity index 100% rename from homeassistant/components/switch/rpi_gpio.py rename to homeassistant/components/rpi_gpio/switch.py diff --git a/homeassistant/components/rpi_pfio.py b/homeassistant/components/rpi_pfio/__init__.py similarity index 100% rename from homeassistant/components/rpi_pfio.py rename to homeassistant/components/rpi_pfio/__init__.py diff --git a/homeassistant/components/binary_sensor/rpi_pfio.py b/homeassistant/components/rpi_pfio/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/rpi_pfio.py rename to homeassistant/components/rpi_pfio/binary_sensor.py diff --git a/homeassistant/components/switch/rpi_pfio.py b/homeassistant/components/rpi_pfio/switch.py similarity index 100% rename from homeassistant/components/switch/rpi_pfio.py rename to homeassistant/components/rpi_pfio/switch.py diff --git a/homeassistant/components/sabnzbd.py b/homeassistant/components/sabnzbd/__init__.py similarity index 100% rename from homeassistant/components/sabnzbd.py rename to homeassistant/components/sabnzbd/__init__.py diff --git a/homeassistant/components/sensor/sabnzbd.py b/homeassistant/components/sabnzbd/sensor.py similarity index 100% rename from homeassistant/components/sensor/sabnzbd.py rename to homeassistant/components/sabnzbd/sensor.py diff --git a/homeassistant/components/satel_integra.py b/homeassistant/components/satel_integra/__init__.py similarity index 100% rename from homeassistant/components/satel_integra.py rename to homeassistant/components/satel_integra/__init__.py diff --git a/homeassistant/components/alarm_control_panel/satel_integra.py b/homeassistant/components/satel_integra/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/satel_integra.py rename to homeassistant/components/satel_integra/alarm_control_panel.py diff --git a/homeassistant/components/binary_sensor/satel_integra.py b/homeassistant/components/satel_integra/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/satel_integra.py rename to homeassistant/components/satel_integra/binary_sensor.py diff --git a/homeassistant/components/scsgate.py b/homeassistant/components/scsgate/__init__.py similarity index 100% rename from homeassistant/components/scsgate.py rename to homeassistant/components/scsgate/__init__.py diff --git a/homeassistant/components/cover/scsgate.py b/homeassistant/components/scsgate/cover.py similarity index 100% rename from homeassistant/components/cover/scsgate.py rename to homeassistant/components/scsgate/cover.py diff --git a/homeassistant/components/light/scsgate.py b/homeassistant/components/scsgate/light.py similarity index 100% rename from homeassistant/components/light/scsgate.py rename to homeassistant/components/scsgate/light.py diff --git a/homeassistant/components/switch/scsgate.py b/homeassistant/components/scsgate/switch.py similarity index 100% rename from homeassistant/components/switch/scsgate.py rename to homeassistant/components/scsgate/switch.py diff --git a/homeassistant/components/sense.py b/homeassistant/components/sense/__init__.py similarity index 100% rename from homeassistant/components/sense.py rename to homeassistant/components/sense/__init__.py diff --git a/homeassistant/components/binary_sensor/sense.py b/homeassistant/components/sense/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/sense.py rename to homeassistant/components/sense/binary_sensor.py diff --git a/homeassistant/components/sensor/sense.py b/homeassistant/components/sense/sensor.py similarity index 100% rename from homeassistant/components/sensor/sense.py rename to homeassistant/components/sense/sensor.py diff --git a/homeassistant/components/sisyphus.py b/homeassistant/components/sisyphus/__init__.py similarity index 100% rename from homeassistant/components/sisyphus.py rename to homeassistant/components/sisyphus/__init__.py diff --git a/homeassistant/components/light/sisyphus.py b/homeassistant/components/sisyphus/light.py similarity index 100% rename from homeassistant/components/light/sisyphus.py rename to homeassistant/components/sisyphus/light.py diff --git a/homeassistant/components/media_player/sisyphus.py b/homeassistant/components/sisyphus/media_player.py similarity index 100% rename from homeassistant/components/media_player/sisyphus.py rename to homeassistant/components/sisyphus/media_player.py diff --git a/homeassistant/components/skybell.py b/homeassistant/components/skybell/__init__.py similarity index 100% rename from homeassistant/components/skybell.py rename to homeassistant/components/skybell/__init__.py diff --git a/homeassistant/components/binary_sensor/skybell.py b/homeassistant/components/skybell/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/skybell.py rename to homeassistant/components/skybell/binary_sensor.py diff --git a/homeassistant/components/camera/skybell.py b/homeassistant/components/skybell/camera.py similarity index 100% rename from homeassistant/components/camera/skybell.py rename to homeassistant/components/skybell/camera.py diff --git a/homeassistant/components/light/skybell.py b/homeassistant/components/skybell/light.py similarity index 100% rename from homeassistant/components/light/skybell.py rename to homeassistant/components/skybell/light.py diff --git a/homeassistant/components/sensor/skybell.py b/homeassistant/components/skybell/sensor.py similarity index 100% rename from homeassistant/components/sensor/skybell.py rename to homeassistant/components/skybell/sensor.py diff --git a/homeassistant/components/switch/skybell.py b/homeassistant/components/skybell/switch.py similarity index 100% rename from homeassistant/components/switch/skybell.py rename to homeassistant/components/skybell/switch.py diff --git a/homeassistant/components/smappee.py b/homeassistant/components/smappee/__init__.py similarity index 100% rename from homeassistant/components/smappee.py rename to homeassistant/components/smappee/__init__.py diff --git a/homeassistant/components/sensor/smappee.py b/homeassistant/components/smappee/sensor.py similarity index 100% rename from homeassistant/components/sensor/smappee.py rename to homeassistant/components/smappee/sensor.py diff --git a/homeassistant/components/switch/smappee.py b/homeassistant/components/smappee/switch.py similarity index 100% rename from homeassistant/components/switch/smappee.py rename to homeassistant/components/smappee/switch.py diff --git a/homeassistant/components/spider.py b/homeassistant/components/spider/__init__.py similarity index 100% rename from homeassistant/components/spider.py rename to homeassistant/components/spider/__init__.py diff --git a/homeassistant/components/climate/spider.py b/homeassistant/components/spider/climate.py similarity index 100% rename from homeassistant/components/climate/spider.py rename to homeassistant/components/spider/climate.py diff --git a/homeassistant/components/switch/spider.py b/homeassistant/components/spider/switch.py similarity index 100% rename from homeassistant/components/switch/spider.py rename to homeassistant/components/spider/switch.py diff --git a/homeassistant/components/tado.py b/homeassistant/components/tado/__init__.py similarity index 100% rename from homeassistant/components/tado.py rename to homeassistant/components/tado/__init__.py diff --git a/homeassistant/components/climate/tado.py b/homeassistant/components/tado/climate.py similarity index 100% rename from homeassistant/components/climate/tado.py rename to homeassistant/components/tado/climate.py diff --git a/homeassistant/components/device_tracker/tado.py b/homeassistant/components/tado/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/tado.py rename to homeassistant/components/tado/device_tracker.py diff --git a/homeassistant/components/sensor/tado.py b/homeassistant/components/tado/sensor.py similarity index 100% rename from homeassistant/components/sensor/tado.py rename to homeassistant/components/tado/sensor.py diff --git a/homeassistant/components/tahoma.py b/homeassistant/components/tahoma/__init__.py similarity index 100% rename from homeassistant/components/tahoma.py rename to homeassistant/components/tahoma/__init__.py diff --git a/homeassistant/components/binary_sensor/tahoma.py b/homeassistant/components/tahoma/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/tahoma.py rename to homeassistant/components/tahoma/binary_sensor.py diff --git a/homeassistant/components/cover/tahoma.py b/homeassistant/components/tahoma/cover.py similarity index 100% rename from homeassistant/components/cover/tahoma.py rename to homeassistant/components/tahoma/cover.py diff --git a/homeassistant/components/scene/tahoma.py b/homeassistant/components/tahoma/scene.py similarity index 100% rename from homeassistant/components/scene/tahoma.py rename to homeassistant/components/tahoma/scene.py diff --git a/homeassistant/components/sensor/tahoma.py b/homeassistant/components/tahoma/sensor.py similarity index 100% rename from homeassistant/components/sensor/tahoma.py rename to homeassistant/components/tahoma/sensor.py diff --git a/homeassistant/components/switch/tahoma.py b/homeassistant/components/tahoma/switch.py similarity index 100% rename from homeassistant/components/switch/tahoma.py rename to homeassistant/components/tahoma/switch.py diff --git a/homeassistant/components/tellstick.py b/homeassistant/components/tellstick/__init__.py similarity index 100% rename from homeassistant/components/tellstick.py rename to homeassistant/components/tellstick/__init__.py diff --git a/homeassistant/components/cover/tellstick.py b/homeassistant/components/tellstick/cover.py similarity index 100% rename from homeassistant/components/cover/tellstick.py rename to homeassistant/components/tellstick/cover.py diff --git a/homeassistant/components/light/tellstick.py b/homeassistant/components/tellstick/light.py similarity index 100% rename from homeassistant/components/light/tellstick.py rename to homeassistant/components/tellstick/light.py diff --git a/homeassistant/components/sensor/tellstick.py b/homeassistant/components/tellstick/sensor.py similarity index 100% rename from homeassistant/components/sensor/tellstick.py rename to homeassistant/components/tellstick/sensor.py diff --git a/homeassistant/components/switch/tellstick.py b/homeassistant/components/tellstick/switch.py similarity index 100% rename from homeassistant/components/switch/tellstick.py rename to homeassistant/components/tellstick/switch.py diff --git a/homeassistant/components/tesla.py b/homeassistant/components/tesla/__init__.py similarity index 100% rename from homeassistant/components/tesla.py rename to homeassistant/components/tesla/__init__.py diff --git a/homeassistant/components/binary_sensor/tesla.py b/homeassistant/components/tesla/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/tesla.py rename to homeassistant/components/tesla/binary_sensor.py diff --git a/homeassistant/components/climate/tesla.py b/homeassistant/components/tesla/climate.py similarity index 100% rename from homeassistant/components/climate/tesla.py rename to homeassistant/components/tesla/climate.py diff --git a/homeassistant/components/device_tracker/tesla.py b/homeassistant/components/tesla/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/tesla.py rename to homeassistant/components/tesla/device_tracker.py diff --git a/homeassistant/components/lock/tesla.py b/homeassistant/components/tesla/lock.py similarity index 100% rename from homeassistant/components/lock/tesla.py rename to homeassistant/components/tesla/lock.py diff --git a/homeassistant/components/sensor/tesla.py b/homeassistant/components/tesla/sensor.py similarity index 100% rename from homeassistant/components/sensor/tesla.py rename to homeassistant/components/tesla/sensor.py diff --git a/homeassistant/components/switch/tesla.py b/homeassistant/components/tesla/switch.py similarity index 100% rename from homeassistant/components/switch/tesla.py rename to homeassistant/components/tesla/switch.py diff --git a/homeassistant/components/thethingsnetwork.py b/homeassistant/components/thethingsnetwork/__init__.py similarity index 100% rename from homeassistant/components/thethingsnetwork.py rename to homeassistant/components/thethingsnetwork/__init__.py diff --git a/homeassistant/components/sensor/thethingsnetwork.py b/homeassistant/components/thethingsnetwork/sensor.py similarity index 100% rename from homeassistant/components/sensor/thethingsnetwork.py rename to homeassistant/components/thethingsnetwork/sensor.py diff --git a/homeassistant/components/thinkingcleaner/__init__.py b/homeassistant/components/thinkingcleaner/__init__.py new file mode 100644 index 00000000000..5358060ea8a --- /dev/null +++ b/homeassistant/components/thinkingcleaner/__init__.py @@ -0,0 +1 @@ +"""Thinkingcleaner integration.""" diff --git a/homeassistant/components/sensor/thinkingcleaner.py b/homeassistant/components/thinkingcleaner/sensor.py similarity index 100% rename from homeassistant/components/sensor/thinkingcleaner.py rename to homeassistant/components/thinkingcleaner/sensor.py diff --git a/homeassistant/components/switch/thinkingcleaner.py b/homeassistant/components/thinkingcleaner/switch.py similarity index 100% rename from homeassistant/components/switch/thinkingcleaner.py rename to homeassistant/components/thinkingcleaner/switch.py diff --git a/homeassistant/components/notify/tibber.py b/homeassistant/components/tibber/notify.py similarity index 100% rename from homeassistant/components/notify/tibber.py rename to homeassistant/components/tibber/notify.py diff --git a/homeassistant/components/sensor/tibber.py b/homeassistant/components/tibber/sensor.py similarity index 100% rename from homeassistant/components/sensor/tibber.py rename to homeassistant/components/tibber/sensor.py diff --git a/homeassistant/components/toon.py b/homeassistant/components/toon/__init__.py similarity index 100% rename from homeassistant/components/toon.py rename to homeassistant/components/toon/__init__.py diff --git a/homeassistant/components/climate/toon.py b/homeassistant/components/toon/climate.py similarity index 100% rename from homeassistant/components/climate/toon.py rename to homeassistant/components/toon/climate.py diff --git a/homeassistant/components/sensor/toon.py b/homeassistant/components/toon/sensor.py similarity index 100% rename from homeassistant/components/sensor/toon.py rename to homeassistant/components/toon/sensor.py diff --git a/homeassistant/components/switch/toon.py b/homeassistant/components/toon/switch.py similarity index 100% rename from homeassistant/components/switch/toon.py rename to homeassistant/components/toon/switch.py diff --git a/homeassistant/components/tplink_lte.py b/homeassistant/components/tplink_lte/__init__.py similarity index 100% rename from homeassistant/components/tplink_lte.py rename to homeassistant/components/tplink_lte/__init__.py diff --git a/homeassistant/components/notify/tplink_lte.py b/homeassistant/components/tplink_lte/notify.py similarity index 100% rename from homeassistant/components/notify/tplink_lte.py rename to homeassistant/components/tplink_lte/notify.py diff --git a/homeassistant/components/transmission.py b/homeassistant/components/transmission/__init__.py similarity index 100% rename from homeassistant/components/transmission.py rename to homeassistant/components/transmission/__init__.py diff --git a/homeassistant/components/sensor/transmission.py b/homeassistant/components/transmission/sensor.py similarity index 100% rename from homeassistant/components/sensor/transmission.py rename to homeassistant/components/transmission/sensor.py diff --git a/homeassistant/components/switch/transmission.py b/homeassistant/components/transmission/switch.py similarity index 100% rename from homeassistant/components/switch/transmission.py rename to homeassistant/components/transmission/switch.py diff --git a/homeassistant/components/tuya.py b/homeassistant/components/tuya/__init__.py similarity index 100% rename from homeassistant/components/tuya.py rename to homeassistant/components/tuya/__init__.py diff --git a/homeassistant/components/climate/tuya.py b/homeassistant/components/tuya/climate.py similarity index 100% rename from homeassistant/components/climate/tuya.py rename to homeassistant/components/tuya/climate.py diff --git a/homeassistant/components/cover/tuya.py b/homeassistant/components/tuya/cover.py similarity index 100% rename from homeassistant/components/cover/tuya.py rename to homeassistant/components/tuya/cover.py diff --git a/homeassistant/components/fan/tuya.py b/homeassistant/components/tuya/fan.py similarity index 100% rename from homeassistant/components/fan/tuya.py rename to homeassistant/components/tuya/fan.py diff --git a/homeassistant/components/light/tuya.py b/homeassistant/components/tuya/light.py similarity index 100% rename from homeassistant/components/light/tuya.py rename to homeassistant/components/tuya/light.py diff --git a/homeassistant/components/scene/tuya.py b/homeassistant/components/tuya/scene.py similarity index 100% rename from homeassistant/components/scene/tuya.py rename to homeassistant/components/tuya/scene.py diff --git a/homeassistant/components/switch/tuya.py b/homeassistant/components/tuya/switch.py similarity index 100% rename from homeassistant/components/switch/tuya.py rename to homeassistant/components/tuya/switch.py diff --git a/homeassistant/components/upcloud.py b/homeassistant/components/upcloud/__init__.py similarity index 100% rename from homeassistant/components/upcloud.py rename to homeassistant/components/upcloud/__init__.py diff --git a/homeassistant/components/binary_sensor/upcloud.py b/homeassistant/components/upcloud/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/upcloud.py rename to homeassistant/components/upcloud/binary_sensor.py diff --git a/homeassistant/components/switch/upcloud.py b/homeassistant/components/upcloud/switch.py similarity index 100% rename from homeassistant/components/switch/upcloud.py rename to homeassistant/components/upcloud/switch.py diff --git a/homeassistant/components/usps.py b/homeassistant/components/usps/__init__.py similarity index 100% rename from homeassistant/components/usps.py rename to homeassistant/components/usps/__init__.py diff --git a/homeassistant/components/camera/usps.py b/homeassistant/components/usps/camera.py similarity index 100% rename from homeassistant/components/camera/usps.py rename to homeassistant/components/usps/camera.py diff --git a/homeassistant/components/sensor/usps.py b/homeassistant/components/usps/sensor.py similarity index 100% rename from homeassistant/components/sensor/usps.py rename to homeassistant/components/usps/sensor.py diff --git a/homeassistant/components/velbus.py b/homeassistant/components/velbus/__init__.py similarity index 100% rename from homeassistant/components/velbus.py rename to homeassistant/components/velbus/__init__.py diff --git a/homeassistant/components/binary_sensor/velbus.py b/homeassistant/components/velbus/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/velbus.py rename to homeassistant/components/velbus/binary_sensor.py diff --git a/homeassistant/components/climate/velbus.py b/homeassistant/components/velbus/climate.py similarity index 100% rename from homeassistant/components/climate/velbus.py rename to homeassistant/components/velbus/climate.py diff --git a/homeassistant/components/cover/velbus.py b/homeassistant/components/velbus/cover.py similarity index 100% rename from homeassistant/components/cover/velbus.py rename to homeassistant/components/velbus/cover.py diff --git a/homeassistant/components/sensor/velbus.py b/homeassistant/components/velbus/sensor.py similarity index 100% rename from homeassistant/components/sensor/velbus.py rename to homeassistant/components/velbus/sensor.py diff --git a/homeassistant/components/switch/velbus.py b/homeassistant/components/velbus/switch.py similarity index 100% rename from homeassistant/components/switch/velbus.py rename to homeassistant/components/velbus/switch.py diff --git a/homeassistant/components/velux.py b/homeassistant/components/velux/__init__.py similarity index 100% rename from homeassistant/components/velux.py rename to homeassistant/components/velux/__init__.py diff --git a/homeassistant/components/cover/velux.py b/homeassistant/components/velux/cover.py similarity index 100% rename from homeassistant/components/cover/velux.py rename to homeassistant/components/velux/cover.py diff --git a/homeassistant/components/scene/velux.py b/homeassistant/components/velux/scene.py similarity index 100% rename from homeassistant/components/scene/velux.py rename to homeassistant/components/velux/scene.py diff --git a/homeassistant/components/vera.py b/homeassistant/components/vera/__init__.py similarity index 100% rename from homeassistant/components/vera.py rename to homeassistant/components/vera/__init__.py diff --git a/homeassistant/components/binary_sensor/vera.py b/homeassistant/components/vera/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/vera.py rename to homeassistant/components/vera/binary_sensor.py diff --git a/homeassistant/components/climate/vera.py b/homeassistant/components/vera/climate.py similarity index 100% rename from homeassistant/components/climate/vera.py rename to homeassistant/components/vera/climate.py diff --git a/homeassistant/components/cover/vera.py b/homeassistant/components/vera/cover.py similarity index 100% rename from homeassistant/components/cover/vera.py rename to homeassistant/components/vera/cover.py diff --git a/homeassistant/components/light/vera.py b/homeassistant/components/vera/light.py similarity index 100% rename from homeassistant/components/light/vera.py rename to homeassistant/components/vera/light.py diff --git a/homeassistant/components/lock/vera.py b/homeassistant/components/vera/lock.py similarity index 100% rename from homeassistant/components/lock/vera.py rename to homeassistant/components/vera/lock.py diff --git a/homeassistant/components/scene/vera.py b/homeassistant/components/vera/scene.py similarity index 100% rename from homeassistant/components/scene/vera.py rename to homeassistant/components/vera/scene.py diff --git a/homeassistant/components/sensor/vera.py b/homeassistant/components/vera/sensor.py similarity index 100% rename from homeassistant/components/sensor/vera.py rename to homeassistant/components/vera/sensor.py diff --git a/homeassistant/components/switch/vera.py b/homeassistant/components/vera/switch.py similarity index 100% rename from homeassistant/components/switch/vera.py rename to homeassistant/components/vera/switch.py diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure/__init__.py similarity index 100% rename from homeassistant/components/verisure.py rename to homeassistant/components/verisure/__init__.py diff --git a/homeassistant/components/alarm_control_panel/verisure.py b/homeassistant/components/verisure/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/verisure.py rename to homeassistant/components/verisure/alarm_control_panel.py diff --git a/homeassistant/components/binary_sensor/verisure.py b/homeassistant/components/verisure/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/verisure.py rename to homeassistant/components/verisure/binary_sensor.py diff --git a/homeassistant/components/camera/verisure.py b/homeassistant/components/verisure/camera.py similarity index 100% rename from homeassistant/components/camera/verisure.py rename to homeassistant/components/verisure/camera.py diff --git a/homeassistant/components/lock/verisure.py b/homeassistant/components/verisure/lock.py similarity index 100% rename from homeassistant/components/lock/verisure.py rename to homeassistant/components/verisure/lock.py diff --git a/homeassistant/components/sensor/verisure.py b/homeassistant/components/verisure/sensor.py similarity index 100% rename from homeassistant/components/sensor/verisure.py rename to homeassistant/components/verisure/sensor.py diff --git a/homeassistant/components/switch/verisure.py b/homeassistant/components/verisure/switch.py similarity index 100% rename from homeassistant/components/switch/verisure.py rename to homeassistant/components/verisure/switch.py diff --git a/homeassistant/components/volvooncall.py b/homeassistant/components/volvooncall/__init__.py similarity index 100% rename from homeassistant/components/volvooncall.py rename to homeassistant/components/volvooncall/__init__.py diff --git a/homeassistant/components/binary_sensor/volvooncall.py b/homeassistant/components/volvooncall/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/volvooncall.py rename to homeassistant/components/volvooncall/binary_sensor.py diff --git a/homeassistant/components/device_tracker/volvooncall.py b/homeassistant/components/volvooncall/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/volvooncall.py rename to homeassistant/components/volvooncall/device_tracker.py diff --git a/homeassistant/components/lock/volvooncall.py b/homeassistant/components/volvooncall/lock.py similarity index 100% rename from homeassistant/components/lock/volvooncall.py rename to homeassistant/components/volvooncall/lock.py diff --git a/homeassistant/components/sensor/volvooncall.py b/homeassistant/components/volvooncall/sensor.py similarity index 100% rename from homeassistant/components/sensor/volvooncall.py rename to homeassistant/components/volvooncall/sensor.py diff --git a/homeassistant/components/switch/volvooncall.py b/homeassistant/components/volvooncall/switch.py similarity index 100% rename from homeassistant/components/switch/volvooncall.py rename to homeassistant/components/volvooncall/switch.py diff --git a/homeassistant/components/w800rf32.py b/homeassistant/components/w800rf32/__init__.py similarity index 100% rename from homeassistant/components/w800rf32.py rename to homeassistant/components/w800rf32/__init__.py diff --git a/homeassistant/components/binary_sensor/w800rf32.py b/homeassistant/components/w800rf32/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/w800rf32.py rename to homeassistant/components/w800rf32/binary_sensor.py diff --git a/homeassistant/components/waterfurnace.py b/homeassistant/components/waterfurnace/__init__.py similarity index 100% rename from homeassistant/components/waterfurnace.py rename to homeassistant/components/waterfurnace/__init__.py diff --git a/homeassistant/components/sensor/waterfurnace.py b/homeassistant/components/waterfurnace/sensor.py similarity index 100% rename from homeassistant/components/sensor/waterfurnace.py rename to homeassistant/components/waterfurnace/sensor.py diff --git a/homeassistant/components/webostv/__init__.py b/homeassistant/components/webostv/__init__.py new file mode 100644 index 00000000000..de9de74054c --- /dev/null +++ b/homeassistant/components/webostv/__init__.py @@ -0,0 +1 @@ +"""WebOS TV integration.""" diff --git a/homeassistant/components/media_player/webostv.py b/homeassistant/components/webostv/media_player.py similarity index 100% rename from homeassistant/components/media_player/webostv.py rename to homeassistant/components/webostv/media_player.py diff --git a/homeassistant/components/notify/webostv.py b/homeassistant/components/webostv/notify.py similarity index 100% rename from homeassistant/components/notify/webostv.py rename to homeassistant/components/webostv/notify.py diff --git a/homeassistant/components/wemo.py b/homeassistant/components/wemo/__init__.py similarity index 100% rename from homeassistant/components/wemo.py rename to homeassistant/components/wemo/__init__.py diff --git a/homeassistant/components/binary_sensor/wemo.py b/homeassistant/components/wemo/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/wemo.py rename to homeassistant/components/wemo/binary_sensor.py diff --git a/homeassistant/components/fan/wemo.py b/homeassistant/components/wemo/fan.py similarity index 100% rename from homeassistant/components/fan/wemo.py rename to homeassistant/components/wemo/fan.py diff --git a/homeassistant/components/light/wemo.py b/homeassistant/components/wemo/light.py similarity index 100% rename from homeassistant/components/light/wemo.py rename to homeassistant/components/wemo/light.py diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/wemo/switch.py similarity index 100% rename from homeassistant/components/switch/wemo.py rename to homeassistant/components/wemo/switch.py diff --git a/homeassistant/components/alarm_control_panel/wink.py b/homeassistant/components/wink/alarm_control_panel.py similarity index 100% rename from homeassistant/components/alarm_control_panel/wink.py rename to homeassistant/components/wink/alarm_control_panel.py diff --git a/homeassistant/components/binary_sensor/wink.py b/homeassistant/components/wink/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/wink.py rename to homeassistant/components/wink/binary_sensor.py diff --git a/homeassistant/components/climate/wink.py b/homeassistant/components/wink/climate.py similarity index 100% rename from homeassistant/components/climate/wink.py rename to homeassistant/components/wink/climate.py diff --git a/homeassistant/components/cover/wink.py b/homeassistant/components/wink/cover.py similarity index 100% rename from homeassistant/components/cover/wink.py rename to homeassistant/components/wink/cover.py diff --git a/homeassistant/components/fan/wink.py b/homeassistant/components/wink/fan.py similarity index 100% rename from homeassistant/components/fan/wink.py rename to homeassistant/components/wink/fan.py diff --git a/homeassistant/components/light/wink.py b/homeassistant/components/wink/light.py similarity index 100% rename from homeassistant/components/light/wink.py rename to homeassistant/components/wink/light.py diff --git a/homeassistant/components/lock/wink.py b/homeassistant/components/wink/lock.py similarity index 100% rename from homeassistant/components/lock/wink.py rename to homeassistant/components/wink/lock.py diff --git a/homeassistant/components/scene/wink.py b/homeassistant/components/wink/scene.py similarity index 100% rename from homeassistant/components/scene/wink.py rename to homeassistant/components/wink/scene.py diff --git a/homeassistant/components/sensor/wink.py b/homeassistant/components/wink/sensor.py similarity index 100% rename from homeassistant/components/sensor/wink.py rename to homeassistant/components/wink/sensor.py diff --git a/homeassistant/components/switch/wink.py b/homeassistant/components/wink/switch.py similarity index 100% rename from homeassistant/components/switch/wink.py rename to homeassistant/components/wink/switch.py diff --git a/homeassistant/components/water_heater/wink.py b/homeassistant/components/wink/water_heater.py similarity index 100% rename from homeassistant/components/water_heater/wink.py rename to homeassistant/components/wink/water_heater.py diff --git a/homeassistant/components/wirelesstag.py b/homeassistant/components/wirelesstag/__init__.py similarity index 100% rename from homeassistant/components/wirelesstag.py rename to homeassistant/components/wirelesstag/__init__.py diff --git a/homeassistant/components/binary_sensor/wirelesstag.py b/homeassistant/components/wirelesstag/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/wirelesstag.py rename to homeassistant/components/wirelesstag/binary_sensor.py diff --git a/homeassistant/components/sensor/wirelesstag.py b/homeassistant/components/wirelesstag/sensor.py similarity index 100% rename from homeassistant/components/sensor/wirelesstag.py rename to homeassistant/components/wirelesstag/sensor.py diff --git a/homeassistant/components/switch/wirelesstag.py b/homeassistant/components/wirelesstag/switch.py similarity index 100% rename from homeassistant/components/switch/wirelesstag.py rename to homeassistant/components/wirelesstag/switch.py diff --git a/homeassistant/components/xiaomi_aqara.py b/homeassistant/components/xiaomi_aqara/__init__.py similarity index 100% rename from homeassistant/components/xiaomi_aqara.py rename to homeassistant/components/xiaomi_aqara/__init__.py diff --git a/homeassistant/components/binary_sensor/xiaomi_aqara.py b/homeassistant/components/xiaomi_aqara/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/xiaomi_aqara.py rename to homeassistant/components/xiaomi_aqara/binary_sensor.py diff --git a/homeassistant/components/cover/xiaomi_aqara.py b/homeassistant/components/xiaomi_aqara/cover.py similarity index 100% rename from homeassistant/components/cover/xiaomi_aqara.py rename to homeassistant/components/xiaomi_aqara/cover.py diff --git a/homeassistant/components/light/xiaomi_aqara.py b/homeassistant/components/xiaomi_aqara/light.py similarity index 100% rename from homeassistant/components/light/xiaomi_aqara.py rename to homeassistant/components/xiaomi_aqara/light.py diff --git a/homeassistant/components/lock/xiaomi_aqara.py b/homeassistant/components/xiaomi_aqara/lock.py similarity index 100% rename from homeassistant/components/lock/xiaomi_aqara.py rename to homeassistant/components/xiaomi_aqara/lock.py diff --git a/homeassistant/components/sensor/xiaomi_aqara.py b/homeassistant/components/xiaomi_aqara/sensor.py similarity index 100% rename from homeassistant/components/sensor/xiaomi_aqara.py rename to homeassistant/components/xiaomi_aqara/sensor.py diff --git a/homeassistant/components/switch/xiaomi_aqara.py b/homeassistant/components/xiaomi_aqara/switch.py similarity index 100% rename from homeassistant/components/switch/xiaomi_aqara.py rename to homeassistant/components/xiaomi_aqara/switch.py diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py new file mode 100644 index 00000000000..2f9bee9d702 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -0,0 +1 @@ +"""Xiaomi Miio integration.""" diff --git a/homeassistant/components/device_tracker/xiaomi_miio.py b/homeassistant/components/xiaomi_miio/device_tracker.py similarity index 100% rename from homeassistant/components/device_tracker/xiaomi_miio.py rename to homeassistant/components/xiaomi_miio/device_tracker.py diff --git a/homeassistant/components/fan/xiaomi_miio.py b/homeassistant/components/xiaomi_miio/fan.py similarity index 100% rename from homeassistant/components/fan/xiaomi_miio.py rename to homeassistant/components/xiaomi_miio/fan.py diff --git a/homeassistant/components/light/xiaomi_miio.py b/homeassistant/components/xiaomi_miio/light.py similarity index 100% rename from homeassistant/components/light/xiaomi_miio.py rename to homeassistant/components/xiaomi_miio/light.py diff --git a/homeassistant/components/remote/xiaomi_miio.py b/homeassistant/components/xiaomi_miio/remote.py similarity index 100% rename from homeassistant/components/remote/xiaomi_miio.py rename to homeassistant/components/xiaomi_miio/remote.py diff --git a/homeassistant/components/sensor/xiaomi_miio.py b/homeassistant/components/xiaomi_miio/sensor.py similarity index 100% rename from homeassistant/components/sensor/xiaomi_miio.py rename to homeassistant/components/xiaomi_miio/sensor.py diff --git a/homeassistant/components/switch/xiaomi_miio.py b/homeassistant/components/xiaomi_miio/switch.py similarity index 100% rename from homeassistant/components/switch/xiaomi_miio.py rename to homeassistant/components/xiaomi_miio/switch.py diff --git a/homeassistant/components/vacuum/xiaomi_miio.py b/homeassistant/components/xiaomi_miio/vacuum.py similarity index 100% rename from homeassistant/components/vacuum/xiaomi_miio.py rename to homeassistant/components/xiaomi_miio/vacuum.py diff --git a/homeassistant/components/zabbix.py b/homeassistant/components/zabbix/__init__.py similarity index 100% rename from homeassistant/components/zabbix.py rename to homeassistant/components/zabbix/__init__.py diff --git a/homeassistant/components/sensor/zabbix.py b/homeassistant/components/zabbix/sensor.py similarity index 100% rename from homeassistant/components/sensor/zabbix.py rename to homeassistant/components/zabbix/sensor.py diff --git a/homeassistant/components/zigbee.py b/homeassistant/components/zigbee/__init__.py similarity index 100% rename from homeassistant/components/zigbee.py rename to homeassistant/components/zigbee/__init__.py diff --git a/homeassistant/components/binary_sensor/zigbee.py b/homeassistant/components/zigbee/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/zigbee.py rename to homeassistant/components/zigbee/binary_sensor.py diff --git a/homeassistant/components/light/zigbee.py b/homeassistant/components/zigbee/light.py similarity index 100% rename from homeassistant/components/light/zigbee.py rename to homeassistant/components/zigbee/light.py diff --git a/homeassistant/components/sensor/zigbee.py b/homeassistant/components/zigbee/sensor.py similarity index 100% rename from homeassistant/components/sensor/zigbee.py rename to homeassistant/components/zigbee/sensor.py diff --git a/homeassistant/components/switch/zigbee.py b/homeassistant/components/zigbee/switch.py similarity index 100% rename from homeassistant/components/switch/zigbee.py rename to homeassistant/components/zigbee/switch.py diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 61dc49ce2ec..8fd3f7d053e 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -18,7 +18,7 @@ from homeassistant.components.notify import ( ATTR_MESSAGE, SERVICE_NOTIFY) from homeassistant.components.sun import ( STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON) -from homeassistant.components.switch.mysensors import ( +from homeassistant.components.mysensors.switch import ( ATTR_IR_CODE, SERVICE_SEND_IR_CODE) from homeassistant.components.climate import ( ATTR_AUX_HEAT, ATTR_AWAY_MODE, ATTR_FAN_MODE, ATTR_HOLD_MODE, @@ -27,7 +27,7 @@ from homeassistant.components.climate import ( SERVICE_SET_FAN_MODE, SERVICE_SET_HUMIDITY, SERVICE_SET_OPERATION_MODE, SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE, STATE_HEAT, STATE_COOL, STATE_IDLE) -from homeassistant.components.climate.ecobee import ( +from homeassistant.components.ecobee.climate import ( ATTR_FAN_MIN_ON_TIME, SERVICE_SET_FAN_MIN_ON_TIME, ATTR_RESUME_ALL, SERVICE_RESUME_PROGRAM) from homeassistant.components.cover import ( diff --git a/requirements_all.txt b/requirements_all.txt index b0bf06292e8..5a16f8b75b1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -275,14 +275,14 @@ colorlog==4.0.2 concord232==0.15 # homeassistant.components.climate.eq3btsmart -# homeassistant.components.device_tracker.xiaomi_miio -# homeassistant.components.fan.xiaomi_miio -# homeassistant.components.light.xiaomi_miio -# homeassistant.components.remote.xiaomi_miio # homeassistant.components.sensor.eddystone_temperature -# homeassistant.components.sensor.xiaomi_miio -# homeassistant.components.switch.xiaomi_miio -# homeassistant.components.vacuum.xiaomi_miio +# homeassistant.components.xiaomi_miio.device_tracker +# homeassistant.components.xiaomi_miio.fan +# homeassistant.components.xiaomi_miio.light +# homeassistant.components.xiaomi_miio.remote +# homeassistant.components.xiaomi_miio.sensor +# homeassistant.components.xiaomi_miio.switch +# homeassistant.components.xiaomi_miio.vacuum construct==2.9.45 # homeassistant.scripts.credstash @@ -342,7 +342,7 @@ dovado==0.4.1 dsmr_parser==0.12 # homeassistant.components.dweet -# homeassistant.components.sensor.dweet +# homeassistant.components.dweet.sensor dweepy==0.3.0 # homeassistant.components.ecoal_boiler @@ -439,7 +439,7 @@ freesms==0.1.2 # homeassistant.components.switch.fritzdect fritzhome==1.0.4 -# homeassistant.components.tts.google +# homeassistant.components.google.tts gTTS-token==1.1.3 # homeassistant.components.sensor.gearbest @@ -630,7 +630,7 @@ linode-api==4.1.9b1 liveboxplaytv==2.0.2 # homeassistant.components.lametric -# homeassistant.components.notify.lametric +# homeassistant.components.lametric.notify lmnotify==0.0.4 # homeassistant.components.device_tracker.google_maps @@ -1090,8 +1090,8 @@ pylaunches==0.2.0 # homeassistant.components.media_player.lg_netcast pylgnetcast-homeassistant==0.2.0.dev0 -# homeassistant.components.media_player.webostv -# homeassistant.components.notify.webostv +# homeassistant.components.webostv.media_player +# homeassistant.components.webostv.notify pylgtv==0.1.9 # homeassistant.components.sensor.linky @@ -1253,8 +1253,8 @@ pytautulli==0.5.0 # homeassistant.components.media_player.liveboxplaytv pyteleloisirs==3.4 -# homeassistant.components.sensor.thinkingcleaner -# homeassistant.components.switch.thinkingcleaner +# homeassistant.components.thinkingcleaner.sensor +# homeassistant.components.thinkingcleaner.switch pythinkingcleaner==0.0.3 # homeassistant.components.sensor.blockchain @@ -1292,7 +1292,7 @@ python-gitlab==1.6.0 python-hpilo==3.9 # homeassistant.components.joaoapps_join -# homeassistant.components.notify.joaoapps_join +# homeassistant.components.joaoapps_join.notify python-join-api==0.0.2 # homeassistant.components.juicenet @@ -1301,13 +1301,13 @@ python-juicenet==0.0.5 # homeassistant.components.lirc # python-lirc==1.2.3 -# homeassistant.components.device_tracker.xiaomi_miio -# homeassistant.components.fan.xiaomi_miio -# homeassistant.components.light.xiaomi_miio -# homeassistant.components.remote.xiaomi_miio -# homeassistant.components.sensor.xiaomi_miio -# homeassistant.components.switch.xiaomi_miio -# homeassistant.components.vacuum.xiaomi_miio +# homeassistant.components.xiaomi_miio.device_tracker +# homeassistant.components.xiaomi_miio.fan +# homeassistant.components.xiaomi_miio.light +# homeassistant.components.xiaomi_miio.remote +# homeassistant.components.xiaomi_miio.sensor +# homeassistant.components.xiaomi_miio.switch +# homeassistant.components.xiaomi_miio.vacuum python-miio==0.4.4 # homeassistant.components.media_player.mpd @@ -1726,7 +1726,7 @@ waterfurnace==1.1.0 # homeassistant.components.media_player.gpmdp websocket-client==0.54.0 -# homeassistant.components.media_player.webostv +# homeassistant.components.webostv.media_player websockets==6.0 # homeassistant.components.wirelesstag diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 50a61ee2acc..e531754ec71 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -86,7 +86,7 @@ feedparser==5.2.1 # homeassistant.components.sensor.foobot foobot_async==0.3.1 -# homeassistant.components.tts.google +# homeassistant.components.google.tts gTTS-token==1.1.3 # homeassistant.components.geo_location.geo_json_events diff --git a/tests/components/arlo/__init__.py b/tests/components/arlo/__init__.py new file mode 100644 index 00000000000..82c69bf3755 --- /dev/null +++ b/tests/components/arlo/__init__.py @@ -0,0 +1 @@ +"""Tests for the Arlo integration.""" diff --git a/tests/components/sensor/test_arlo.py b/tests/components/arlo/test_sensor.py similarity index 98% rename from tests/components/sensor/test_arlo.py rename to tests/components/arlo/test_sensor.py index 732e47099c4..ffb879571dc 100644 --- a/tests/components/sensor/test_arlo.py +++ b/tests/components/arlo/test_sensor.py @@ -4,7 +4,7 @@ from unittest.mock import patch, MagicMock import pytest from homeassistant.const import ( DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY, ATTR_ATTRIBUTION) -from homeassistant.components.sensor import arlo +from homeassistant.components.arlo import sensor as arlo from homeassistant.components.arlo import DATA_ARLO @@ -94,7 +94,7 @@ def sensor_with_hass_data(default_sensor, hass): @pytest.fixture() def mock_dispatch(): """Mock the dispatcher connect method.""" - target = 'homeassistant.components.sensor.arlo.async_dispatcher_connect' + target = 'homeassistant.components.arlo.sensor.async_dispatcher_connect' with patch(target, MagicMock()) as _mock: yield _mock diff --git a/tests/components/ecobee/__init__.py b/tests/components/ecobee/__init__.py new file mode 100644 index 00000000000..389dc7101f9 --- /dev/null +++ b/tests/components/ecobee/__init__.py @@ -0,0 +1 @@ +"""Tests for Ecobee integration.""" diff --git a/tests/components/climate/test_ecobee.py b/tests/components/ecobee/test_climate.py similarity index 99% rename from tests/components/climate/test_ecobee.py rename to tests/components/ecobee/test_climate.py index 8a03cbcd191..965fb37dcb8 100644 --- a/tests/components/climate/test_ecobee.py +++ b/tests/components/ecobee/test_climate.py @@ -2,7 +2,7 @@ import unittest from unittest import mock import homeassistant.const as const -import homeassistant.components.climate.ecobee as ecobee +from homeassistant.components.ecobee import climate as ecobee from homeassistant.components.climate import STATE_OFF diff --git a/tests/components/fritzbox/__init__.py b/tests/components/fritzbox/__init__.py new file mode 100644 index 00000000000..24bb7c3a181 --- /dev/null +++ b/tests/components/fritzbox/__init__.py @@ -0,0 +1 @@ +"""Tests for the FritzBox! integration.""" diff --git a/tests/components/climate/test_fritzbox.py b/tests/components/fritzbox/test_climate.py similarity index 99% rename from tests/components/climate/test_fritzbox.py rename to tests/components/fritzbox/test_climate.py index 1cd15e3655f..95361170a2c 100644 --- a/tests/components/climate/test_fritzbox.py +++ b/tests/components/fritzbox/test_climate.py @@ -4,7 +4,7 @@ from unittest.mock import Mock, patch import requests -from homeassistant.components.climate.fritzbox import FritzboxThermostat +from homeassistant.components.fritzbox.climate import FritzboxThermostat class TestFritzboxClimate(unittest.TestCase): diff --git a/tests/components/google/__init__.py b/tests/components/google/__init__.py new file mode 100644 index 00000000000..d5524765d07 --- /dev/null +++ b/tests/components/google/__init__.py @@ -0,0 +1 @@ +"""Tests for the Google integration.""" diff --git a/tests/components/calendar/test_google.py b/tests/components/google/test_calendar.py similarity index 97% rename from tests/components/calendar/test_google.py rename to tests/components/google/test_calendar.py index ec4089677d8..6329c2c1d14 100644 --- a/tests/components/calendar/test_google.py +++ b/tests/components/google/test_calendar.py @@ -7,7 +7,7 @@ from unittest.mock import patch, Mock import pytest import homeassistant.components.calendar as calendar_base -import homeassistant.components.calendar.google as calendar +from homeassistant.components.google import calendar import homeassistant.util.dt as dt_util from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON from homeassistant.helpers.template import DATE_STR_FORMAT @@ -40,7 +40,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase): self.hass.stop() - @patch('homeassistant.components.calendar.google.GoogleCalendarData') + @patch('homeassistant.components.google.calendar.GoogleCalendarData') def test_all_day_event(self, mock_next_event): """Test that we can create an event trigger on device.""" week_from_today = dt_util.dt.date.today() \ @@ -103,7 +103,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase): 'description': event['description'], } - @patch('homeassistant.components.calendar.google.GoogleCalendarData') + @patch('homeassistant.components.google.calendar.GoogleCalendarData') def test_future_event(self, mock_next_event): """Test that we can create an event trigger on device.""" one_hour_from_now = dt_util.now() \ @@ -164,7 +164,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase): 'description': '', } - @patch('homeassistant.components.calendar.google.GoogleCalendarData') + @patch('homeassistant.components.google.calendar.GoogleCalendarData') def test_in_progress_event(self, mock_next_event): """Test that we can create an event trigger on device.""" middle_of_event = dt_util.now() \ @@ -226,7 +226,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase): 'description': '', } - @patch('homeassistant.components.calendar.google.GoogleCalendarData') + @patch('homeassistant.components.google.calendar.GoogleCalendarData') def test_offset_in_progress_event(self, mock_next_event): """Test that we can create an event trigger on device.""" middle_of_event = dt_util.now() \ @@ -290,7 +290,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase): } @pytest.mark.skip - @patch('homeassistant.components.calendar.google.GoogleCalendarData') + @patch('homeassistant.components.google.calendar.GoogleCalendarData') def test_all_day_offset_in_progress_event(self, mock_next_event): """Test that we can create an event trigger on device.""" tomorrow = dt_util.dt.date.today() \ @@ -356,7 +356,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase): 'description': event['description'], } - @patch('homeassistant.components.calendar.google.GoogleCalendarData') + @patch('homeassistant.components.google.calendar.GoogleCalendarData') def test_all_day_offset_event(self, mock_next_event): """Test that we can create an event trigger on device.""" tomorrow = dt_util.dt.date.today() \ diff --git a/tests/components/tts/test_google.py b/tests/components/google/test_tts.py similarity index 99% rename from tests/components/tts/test_google.py rename to tests/components/google/test_tts.py index f328e3e9f16..2b5346bf639 100644 --- a/tests/components/tts/test_google.py +++ b/tests/components/google/test_tts.py @@ -12,7 +12,7 @@ from homeassistant.setup import setup_component from tests.common import ( get_test_home_assistant, assert_setup_component, mock_service) -from .test_init import mutagen_mock # noqa +from tests.components.tts.test_init import mutagen_mock # noqa class TestTTSGooglePlatform: diff --git a/tests/components/kira/__init__.py b/tests/components/kira/__init__.py new file mode 100644 index 00000000000..b92ba05bdb1 --- /dev/null +++ b/tests/components/kira/__init__.py @@ -0,0 +1 @@ +"""Tests for the Kira integration.""" diff --git a/tests/components/remote/test_kira.py b/tests/components/kira/test_remote.py similarity index 96% rename from tests/components/remote/test_kira.py rename to tests/components/kira/test_remote.py index 74c8e2854d0..afa5f201422 100644 --- a/tests/components/remote/test_kira.py +++ b/tests/components/kira/test_remote.py @@ -2,7 +2,7 @@ import unittest from unittest.mock import MagicMock -from homeassistant.components.remote import kira as kira +from homeassistant.components.kira import remote as kira from tests.common import get_test_home_assistant diff --git a/tests/components/sensor/test_kira.py b/tests/components/kira/test_sensor.py similarity index 96% rename from tests/components/sensor/test_kira.py rename to tests/components/kira/test_sensor.py index 76aba46d514..5fe4ca2ee0a 100644 --- a/tests/components/sensor/test_kira.py +++ b/tests/components/kira/test_sensor.py @@ -2,7 +2,7 @@ import unittest from unittest.mock import MagicMock -from homeassistant.components.sensor import kira as kira +from homeassistant.components.kira import sensor as kira from tests.common import get_test_home_assistant diff --git a/tests/components/mochad/__init__.py b/tests/components/mochad/__init__.py new file mode 100644 index 00000000000..12584aba239 --- /dev/null +++ b/tests/components/mochad/__init__.py @@ -0,0 +1 @@ +"""Tests for the Mochad integration.""" diff --git a/tests/components/light/test_mochad.py b/tests/components/mochad/test_light.py similarity index 97% rename from tests/components/light/test_mochad.py rename to tests/components/mochad/test_light.py index d96bf8f5abb..33bf1fd333b 100644 --- a/tests/components/light/test_mochad.py +++ b/tests/components/mochad/test_light.py @@ -5,7 +5,7 @@ import unittest.mock as mock import pytest from homeassistant.components import light -from homeassistant.components.light import mochad +from homeassistant.components.mochad import light as mochad from homeassistant.setup import setup_component from tests.common import get_test_home_assistant @@ -35,7 +35,7 @@ class TestMochadSwitchSetup(unittest.TestCase): """Stop everything that was started.""" self.hass.stop() - @mock.patch('homeassistant.components.light.mochad.MochadLight') + @mock.patch('homeassistant.components.mochad.light.MochadLight') def test_setup_adds_proper_devices(self, mock_light): """Test if setup adds devices.""" good_config = { diff --git a/tests/components/switch/test_mochad.py b/tests/components/mochad/test_switch.py similarity index 94% rename from tests/components/switch/test_mochad.py rename to tests/components/mochad/test_switch.py index 76640f88723..e5216b276fa 100644 --- a/tests/components/switch/test_mochad.py +++ b/tests/components/mochad/test_switch.py @@ -6,7 +6,7 @@ import pytest from homeassistant.setup import setup_component from homeassistant.components import switch -from homeassistant.components.switch import mochad +from homeassistant.components.mochad import switch as mochad from tests.common import get_test_home_assistant @@ -36,7 +36,7 @@ class TestMochadSwitchSetup(unittest.TestCase): """Stop everything that was started.""" self.hass.stop() - @mock.patch('homeassistant.components.switch.mochad.MochadSwitch') + @mock.patch('homeassistant.components.mochad.switch.MochadSwitch') def test_setup_adds_proper_devices(self, mock_switch): """Test if setup adds devices.""" good_config = { diff --git a/tests/components/verisure/__init__.py b/tests/components/verisure/__init__.py new file mode 100644 index 00000000000..0382661dbe3 --- /dev/null +++ b/tests/components/verisure/__init__.py @@ -0,0 +1 @@ +"""Tests for Verisure integration.""" diff --git a/tests/components/lock/test_verisure.py b/tests/components/verisure/test_lock.py similarity index 98% rename from tests/components/lock/test_verisure.py rename to tests/components/verisure/test_lock.py index 03dd202e838..20af71cfca5 100644 --- a/tests/components/lock/test_verisure.py +++ b/tests/components/verisure/test_lock.py @@ -46,7 +46,7 @@ LOCKS = ['door_lock'] @contextmanager def mock_hub(config, get_response=LOCKS[0]): """Extensively mock out a verisure hub.""" - hub_prefix = 'homeassistant.components.lock.verisure.hub' + hub_prefix = 'homeassistant.components.verisure.lock.hub' verisure_prefix = 'verisure.Session' with patch(verisure_prefix) as session, \ patch(hub_prefix) as hub: diff --git a/tests/components/webostv/__init__.py b/tests/components/webostv/__init__.py new file mode 100644 index 00000000000..adef8e9b86a --- /dev/null +++ b/tests/components/webostv/__init__.py @@ -0,0 +1 @@ +"""Tests for the WebOS TV integration.""" diff --git a/tests/components/media_player/test_webostv.py b/tests/components/webostv/test_media_player.py similarity index 96% rename from tests/components/media_player/test_webostv.py rename to tests/components/webostv/test_media_player.py index 8017ad6cd54..c552775c023 100644 --- a/tests/components/media_player/test_webostv.py +++ b/tests/components/webostv/test_media_player.py @@ -2,7 +2,7 @@ import unittest from unittest import mock -from homeassistant.components.media_player import webostv +from homeassistant.components.webostv import media_player as webostv class FakeLgWebOSDevice(webostv.LgWebOSDevice): diff --git a/tests/components/xiaomi_miio/__init__.py b/tests/components/xiaomi_miio/__init__.py new file mode 100644 index 00000000000..9f162e02f28 --- /dev/null +++ b/tests/components/xiaomi_miio/__init__.py @@ -0,0 +1 @@ +"""Tests for the Xiaomi Miio integration.""" diff --git a/tests/components/vacuum/test_xiaomi_miio.py b/tests/components/xiaomi_miio/test_vacuum.py similarity index 99% rename from tests/components/vacuum/test_xiaomi_miio.py rename to tests/components/xiaomi_miio/test_vacuum.py index c4c1fb0e1b4..a1e937cb244 100644 --- a/tests/components/vacuum/test_xiaomi_miio.py +++ b/tests/components/xiaomi_miio/test_vacuum.py @@ -11,7 +11,7 @@ from homeassistant.components.vacuum import ( SERVICE_CLEAN_SPOT, SERVICE_LOCATE, SERVICE_RETURN_TO_BASE, SERVICE_SEND_COMMAND, SERVICE_SET_FAN_SPEED, SERVICE_START_PAUSE, SERVICE_STOP, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON) -from homeassistant.components.vacuum.xiaomi_miio import ( +from homeassistant.components.xiaomi_miio.vacuum import ( ATTR_CLEANED_AREA, ATTR_CLEANING_TIME, ATTR_DO_NOT_DISTURB, ATTR_DO_NOT_DISTURB_START, ATTR_DO_NOT_DISTURB_END, ATTR_ERROR, ATTR_MAIN_BRUSH_LEFT, ATTR_SIDE_BRUSH_LEFT, ATTR_FILTER_LEFT, From fee3468b7ac86ec9f68a94eac5a30a46e7308295 Mon Sep 17 00:00:00 2001 From: Peter Nijssen Date: Sat, 2 Feb 2019 18:23:20 +0100 Subject: [PATCH 031/242] add peternijssen as codeowner of spider component (#20695) --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index a0d67c6191d..4f0727e1101 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -240,6 +240,7 @@ homeassistant/components/*/rfxtrx.py @danielhiversen # S homeassistant/components/simplisafe/* @bachya homeassistant/components/smartthings/* @andrewsayre +homeassistant/components/spider/* @peternijssen # T homeassistant/components/tahoma.py @philklei From bada9b5e0b455fd712d0f8e749b5dc26edb4c4e1 Mon Sep 17 00:00:00 2001 From: emontnemery Date: Sat, 2 Feb 2019 18:31:28 +0100 Subject: [PATCH 032/242] Add entity_namespace to PLATFORM_SCHEMA (#20693) * Add entity_namespace to base platform schema * Add test * Fix --- homeassistant/helpers/config_validation.py | 4 ++- tests/test_setup.py | 29 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index f3371a26725..b148a875398 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -16,7 +16,7 @@ from homeassistant.const import ( CONF_ALIAS, CONF_ENTITY_ID, CONF_VALUE_TEMPLATE, WEEKDAYS, CONF_CONDITION, CONF_BELOW, CONF_ABOVE, CONF_TIMEOUT, SUN_EVENT_SUNSET, SUN_EVENT_SUNRISE, CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC, - ENTITY_MATCH_ALL) + ENTITY_MATCH_ALL, CONF_ENTITY_NAMESPACE) from homeassistant.core import valid_entity_id, split_entity_id from homeassistant.exceptions import TemplateError import homeassistant.util.dt as dt_util @@ -554,12 +554,14 @@ def key_dependency(key, dependency): PLATFORM_SCHEMA = vol.Schema({ vol.Required(CONF_PLATFORM): string, + vol.Optional(CONF_ENTITY_NAMESPACE): string, vol.Optional(CONF_SCAN_INTERVAL): time_period }, extra=vol.ALLOW_EXTRA) # This will replace PLATFORM_SCHEMA once all base components are updated PLATFORM_SCHEMA_2 = vol.Schema({ vol.Required(CONF_PLATFORM): string, + vol.Optional(CONF_ENTITY_NAMESPACE): string, vol.Optional(CONF_SCAN_INTERVAL): time_period }) diff --git a/tests/test_setup.py b/tests/test_setup.py index 6d2cc770013..6d0d2a35847 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -259,6 +259,7 @@ class TestSetup: assert setup.setup_component(self.hass, 'platform_conf', { 'platform_conf': { # fail: no extra keys allowed + 'platform': 'whatever', 'hello': 'world', 'invalid': 'extra', } @@ -284,6 +285,34 @@ class TestSetup: self.hass.data.pop(setup.DATA_SETUP) self.hass.config.components.remove('platform_conf') + def test_validate_platform_config_4(self): + """Test entity_namespace in PLATFORM_SCHEMA.""" + component_schema = PLATFORM_SCHEMA_BASE + platform_schema = PLATFORM_SCHEMA + loader.set_component( + self.hass, + 'platform_conf', + MockModule('platform_conf', + platform_schema_base=component_schema)) + + loader.set_component( + self.hass, + 'platform_conf.whatever', + MockPlatform('whatever', + platform_schema=platform_schema)) + + with assert_setup_component(1): + assert setup.setup_component(self.hass, 'platform_conf', { + 'platform_conf': { + # pass: entity_namespace accepted by PLATFORM_SCHEMA + 'platform': 'whatever', + 'entity_namespace': 'yummy', + } + }) + + self.hass.data.pop(setup.DATA_SETUP) + self.hass.config.components.remove('platform_conf') + def test_component_not_found(self): """setup_component should not crash if component doesn't exist.""" assert not setup.setup_component(self.hass, 'non_existing') From acf5b042319123fa89ef43909f107a734d2f4f90 Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Sat, 2 Feb 2019 16:04:29 -0600 Subject: [PATCH 033/242] Add SmartThings Fan platform (#20681) * Add SmartThings fan * Removed unnecessary update method * Corrected usage of async_schedule_update_ha_state * Clean-up/optimization --- homeassistant/components/smartthings/const.py | 2 + homeassistant/components/smartthings/fan.py | 96 ++++++++ tests/components/smartthings/test_fan.py | 213 ++++++++++++++++++ 3 files changed, 311 insertions(+) create mode 100644 homeassistant/components/smartthings/fan.py create mode 100644 tests/components/smartthings/test_fan.py diff --git a/homeassistant/components/smartthings/const.py b/homeassistant/components/smartthings/const.py index a9f47fc7c72..2834de4dcf1 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -18,12 +18,14 @@ SETTINGS_INSTANCE_ID = "hassInstanceId" STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 SUPPORTED_PLATFORMS = [ + 'fan', 'light', 'switch' ] SUPPORTED_CAPABILITIES = [ 'colorControl', 'colorTemperature', + 'fanSpeed', 'switch', 'switchLevel' ] diff --git a/homeassistant/components/smartthings/fan.py b/homeassistant/components/smartthings/fan.py new file mode 100644 index 00000000000..7862736e60b --- /dev/null +++ b/homeassistant/components/smartthings/fan.py @@ -0,0 +1,96 @@ +""" +Support for fans through the SmartThings cloud API. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/smartthings.fan/ +""" + +from homeassistant.components.fan import ( + SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, SUPPORT_SET_SPEED, + FanEntity) + +from . import SmartThingsEntity +from .const import DATA_BROKERS, DOMAIN + +DEPENDENCIES = ['smartthings'] + +VALUE_TO_SPEED = { + 0: SPEED_OFF, + 1: SPEED_LOW, + 2: SPEED_MEDIUM, + 3: SPEED_HIGH, +} +SPEED_TO_VALUE = { + v: k for k, v in VALUE_TO_SPEED.items()} + + +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): + """Platform uses config entry setup.""" + pass + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Add fans for a config entry.""" + broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] + async_add_entities( + [SmartThingsFan(device) for device in broker.devices.values() + if is_fan(device)]) + + +def is_fan(device): + """Determine if the device should be represented as a fan.""" + from pysmartthings import Capability + # Must have switch and fan_speed + return all(capability in device.capabilities + for capability in [Capability.switch, Capability.fan_speed]) + + +class SmartThingsFan(SmartThingsEntity, FanEntity): + """Define a SmartThings Fan.""" + + async def async_set_speed(self, speed: str): + """Set the speed of the fan.""" + value = SPEED_TO_VALUE[speed] + await self._device.set_fan_speed(value, set_status=True) + # State is set optimistically in the command above, therefore update + # the entity state ahead of receiving the confirming push updates + self.async_schedule_update_ha_state() + + async def async_turn_on(self, speed: str = None, **kwargs) -> None: + """Turn the fan on.""" + if speed is not None: + value = SPEED_TO_VALUE[speed] + await self._device.set_fan_speed(value, set_status=True) + else: + await self._device.switch_on(set_status=True) + # State is set optimistically in the commands above, therefore update + # the entity state ahead of receiving the confirming push updates + self.async_schedule_update_ha_state() + + async def async_turn_off(self, **kwargs) -> None: + """Turn the fan off.""" + await self._device.switch_off(set_status=True) + # State is set optimistically in the command above, therefore update + # the entity state ahead of receiving the confirming push updates + self.async_schedule_update_ha_state() + + @property + def is_on(self) -> bool: + """Return true if fan is on.""" + return self._device.status.switch + + @property + def speed(self) -> str: + """Return the current speed.""" + return VALUE_TO_SPEED[self._device.status.fan_speed] + + @property + def speed_list(self) -> list: + """Get the list of available speeds.""" + return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] + + @property + def supported_features(self) -> int: + """Flag supported features.""" + return SUPPORT_SET_SPEED diff --git a/tests/components/smartthings/test_fan.py b/tests/components/smartthings/test_fan.py new file mode 100644 index 00000000000..99627e866d9 --- /dev/null +++ b/tests/components/smartthings/test_fan.py @@ -0,0 +1,213 @@ +""" +Test for the SmartThings fan platform. + +The only mocking required is of the underlying SmartThings API object so +real HTTP calls are not initiated during testing. +""" +from pysmartthings import Attribute, Capability + +from homeassistant.components.fan import ( + ATTR_SPEED, ATTR_SPEED_LIST, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, + SPEED_OFF, SUPPORT_SET_SPEED) +from homeassistant.components.smartthings import DeviceBroker, fan +from homeassistant.components.smartthings.const import ( + DATA_BROKERS, DOMAIN, SIGNAL_SMARTTHINGS_UPDATE) +from homeassistant.config_entries import ( + CONN_CLASS_CLOUD_PUSH, SOURCE_USER, ConfigEntry) +from homeassistant.const import ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES +from homeassistant.helpers.dispatcher import async_dispatcher_send + + +async def _setup_platform(hass, *devices): + """Set up the SmartThings fan platform and prerequisites.""" + hass.config.components.add(DOMAIN) + broker = DeviceBroker(hass, devices, '') + config_entry = ConfigEntry("1", DOMAIN, "Test", {}, + SOURCE_USER, CONN_CLASS_CLOUD_PUSH) + hass.data[DOMAIN] = { + DATA_BROKERS: { + config_entry.entry_id: broker + } + } + await hass.config_entries.async_forward_entry_setup(config_entry, 'fan') + await hass.async_block_till_done() + return config_entry + + +async def test_async_setup_platform(): + """Test setup platform does nothing (it uses config entries).""" + await fan.async_setup_platform(None, None, None) + + +def test_is_fan(device_factory): + """Test fans are correctly identified.""" + non_fans = [ + device_factory('Unknown', ['Unknown']), + device_factory("Switch 1", [Capability.switch]), + device_factory("Non-Switchable Fan", [Capability.fan_speed]), + device_factory("Color Light", + [Capability.switch, Capability.switch_level, + Capability.color_control, + Capability.color_temperature]) + ] + fan_device = device_factory( + "Fan 1", [Capability.switch, Capability.switch_level, + Capability.fan_speed]) + + assert fan.is_fan(fan_device), fan_device.name + for device in non_fans: + assert not fan.is_fan(device), device.name + + +async def test_entity_state(hass, device_factory): + """Tests the state attributes properly match the fan types.""" + device = device_factory( + "Fan 1", + capabilities=[Capability.switch, Capability.fan_speed], + status={Attribute.switch: 'on', Attribute.fan_speed: 2}) + await _setup_platform(hass, device) + + # Dimmer 1 + state = hass.states.get('fan.fan_1') + assert state.state == 'on' + assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_SET_SPEED + assert state.attributes[ATTR_SPEED] == SPEED_MEDIUM + assert state.attributes[ATTR_SPEED_LIST] == \ + [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] + + +async def test_entity_and_device_attributes(hass, device_factory): + """Test the attributes of the entity are correct.""" + # Arrange + device = device_factory( + "Fan 1", + capabilities=[Capability.switch, Capability.fan_speed], + status={Attribute.switch: 'on', Attribute.fan_speed: 2}) + await _setup_platform(hass, device) + entity_registry = await hass.helpers.entity_registry.async_get_registry() + device_registry = await hass.helpers.device_registry.async_get_registry() + # Act + await _setup_platform(hass, device) + # Assert + entry = entity_registry.async_get("fan.fan_1") + assert entry + assert entry.unique_id == device.device_id + + entry = device_registry.async_get_device( + {(DOMAIN, device.device_id)}, []) + assert entry + assert entry.name == device.label + assert entry.model == device.device_type_name + assert entry.manufacturer == 'Unavailable' + + +async def test_turn_off(hass, device_factory): + """Test the fan turns of successfully.""" + # Arrange + device = device_factory( + "Fan 1", + capabilities=[Capability.switch, Capability.fan_speed], + status={Attribute.switch: 'on', Attribute.fan_speed: 2}) + await _setup_platform(hass, device) + # Act + await hass.services.async_call( + 'fan', 'turn_off', {'entity_id': 'fan.fan_1'}, + blocking=True) + # Assert + state = hass.states.get('fan.fan_1') + assert state is not None + assert state.state == 'off' + + +async def test_turn_on(hass, device_factory): + """Test the fan turns of successfully.""" + # Arrange + device = device_factory( + "Fan 1", + capabilities=[Capability.switch, Capability.fan_speed], + status={Attribute.switch: 'off', Attribute.fan_speed: 0}) + await _setup_platform(hass, device) + # Act + await hass.services.async_call( + 'fan', 'turn_on', {ATTR_ENTITY_ID: "fan.fan_1"}, + blocking=True) + # Assert + state = hass.states.get("fan.fan_1") + assert state is not None + assert state.state == 'on' + + +async def test_turn_on_with_speed(hass, device_factory): + """Test the fan turns on to the specified speed.""" + # Arrange + device = device_factory( + "Fan 1", + capabilities=[Capability.switch, Capability.fan_speed], + status={Attribute.switch: 'off', Attribute.fan_speed: 0}) + await _setup_platform(hass, device) + # Act + await hass.services.async_call( + 'fan', 'turn_on', + {ATTR_ENTITY_ID: "fan.fan_1", + ATTR_SPEED: SPEED_HIGH}, + blocking=True) + # Assert + state = hass.states.get("fan.fan_1") + assert state is not None + assert state.state == 'on' + assert state.attributes[ATTR_SPEED] == SPEED_HIGH + + +async def test_set_speed(hass, device_factory): + """Test setting to specific fan speed.""" + # Arrange + device = device_factory( + "Fan 1", + capabilities=[Capability.switch, Capability.fan_speed], + status={Attribute.switch: 'off', Attribute.fan_speed: 0}) + await _setup_platform(hass, device) + # Act + await hass.services.async_call( + 'fan', 'set_speed', + {ATTR_ENTITY_ID: "fan.fan_1", + ATTR_SPEED: SPEED_HIGH}, + blocking=True) + # Assert + state = hass.states.get("fan.fan_1") + assert state is not None + assert state.state == 'on' + assert state.attributes[ATTR_SPEED] == SPEED_HIGH + + +async def test_update_from_signal(hass, device_factory): + """Test the fan updates when receiving a signal.""" + # Arrange + device = device_factory( + "Fan 1", + capabilities=[Capability.switch, Capability.fan_speed], + status={Attribute.switch: 'off', Attribute.fan_speed: 0}) + await _setup_platform(hass, device) + await device.switch_on(True) + # Act + async_dispatcher_send(hass, SIGNAL_SMARTTHINGS_UPDATE, + [device.device_id]) + # Assert + await hass.async_block_till_done() + state = hass.states.get('fan.fan_1') + assert state is not None + assert state.state == 'on' + + +async def test_unload_config_entry(hass, device_factory): + """Test the fan is removed when the config entry is unloaded.""" + # Arrange + device = device_factory( + "Fan 1", + capabilities=[Capability.switch, Capability.fan_speed], + status={Attribute.switch: 'off', Attribute.fan_speed: 0}) + config_entry = await _setup_platform(hass, device) + # Act + await hass.config_entries.async_forward_entry_unload( + config_entry, 'fan') + # Assert + assert not hass.states.get('fan.fan_1') From 6458abca2e8640f5975ed995739112de92c2a54c Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Sat, 2 Feb 2019 16:06:30 -0600 Subject: [PATCH 034/242] Add SmartThings Binary Sensor platform (#20699) * Add SmartThings binary_sensor platform * Fixed comment typo. --- .../components/smartthings/binary_sensor.py | 81 ++++++++++++++ homeassistant/components/smartthings/const.py | 12 ++- .../smartthings/test_binary_sensor.py | 100 ++++++++++++++++++ 3 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/smartthings/binary_sensor.py create mode 100644 tests/components/smartthings/test_binary_sensor.py diff --git a/homeassistant/components/smartthings/binary_sensor.py b/homeassistant/components/smartthings/binary_sensor.py new file mode 100644 index 00000000000..045944ccfa9 --- /dev/null +++ b/homeassistant/components/smartthings/binary_sensor.py @@ -0,0 +1,81 @@ +""" +Support for binary sensors through the SmartThings cloud API. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/smartthings.binary_sensor/ +""" +from homeassistant.components.binary_sensor import BinarySensorDevice + +from . import SmartThingsEntity +from .const import DATA_BROKERS, DOMAIN + +DEPENDENCIES = ['smartthings'] + +CAPABILITY_TO_ATTRIB = { + 'accelerationSensor': 'acceleration', + 'contactSensor': 'contact', + 'filterStatus': 'filterStatus', + 'motionSensor': 'motion', + 'presenceSensor': 'presence', + 'soundSensor': 'sound', + 'tamperAlert': 'tamper', + 'valve': 'valve', + 'waterSensor': 'water' +} +ATTRIB_TO_CLASS = { + 'acceleration': 'moving', + 'contact': 'opening', + 'filterStatus': 'problem', + 'motion': 'motion', + 'presence': 'presence', + 'sound': 'sound', + 'tamper': 'problem', + 'valve': 'opening', + 'water': 'moisture' +} + + +async def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): + """Platform uses config entry setup.""" + pass + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Add binary sensors for a config entry.""" + broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] + sensors = [] + for device in broker.devices.values(): + for capability, attrib in CAPABILITY_TO_ATTRIB.items(): + if capability in device.capabilities: + sensors.append(SmartThingsBinarySensor(device, attrib)) + async_add_entities(sensors) + + +class SmartThingsBinarySensor(SmartThingsEntity, BinarySensorDevice): + """Define a SmartThings Binary Sensor.""" + + def __init__(self, device, attribute): + """Init the class.""" + super().__init__(device) + self._attribute = attribute + + @property + def name(self) -> str: + """Return the name of the binary sensor.""" + return '{} {}'.format(self._device.label, self._attribute) + + @property + def unique_id(self) -> str: + """Return a unique ID.""" + return '{}.{}'.format(self._device.device_id, self._attribute) + + @property + def is_on(self): + """Return true if the binary sensor is on.""" + return self._device.status.is_on(self._attribute) + + @property + def device_class(self): + """Return the class of this device.""" + return ATTRIB_TO_CLASS[self._attribute] diff --git a/homeassistant/components/smartthings/const.py b/homeassistant/components/smartthings/const.py index 2834de4dcf1..f545f84832d 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -18,16 +18,26 @@ SETTINGS_INSTANCE_ID = "hassInstanceId" STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 SUPPORTED_PLATFORMS = [ + 'binary_sensor', 'fan', 'light', 'switch' ] SUPPORTED_CAPABILITIES = [ + 'accelerationSensor', 'colorControl', 'colorTemperature', + 'contactSensor', 'fanSpeed', + 'filterStatus', + 'motionSensor', + 'presenceSensor', + 'soundSensor', 'switch', - 'switchLevel' + 'switchLevel', + 'tamperAlert', + 'valve', + 'waterSensor' ] VAL_UID = "^(?:([0-9a-fA-F]{32})|([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]" \ "{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}))$" diff --git a/tests/components/smartthings/test_binary_sensor.py b/tests/components/smartthings/test_binary_sensor.py new file mode 100644 index 00000000000..2e0c46842b0 --- /dev/null +++ b/tests/components/smartthings/test_binary_sensor.py @@ -0,0 +1,100 @@ +""" +Test for the SmartThings binary_sensor platform. + +The only mocking required is of the underlying SmartThings API object so +real HTTP calls are not initiated during testing. +""" +from pysmartthings import Attribute, Capability + +from homeassistant.components.smartthings import DeviceBroker, binary_sensor +from homeassistant.components.smartthings.const import ( + DATA_BROKERS, DOMAIN, SIGNAL_SMARTTHINGS_UPDATE) +from homeassistant.config_entries import ( + CONN_CLASS_CLOUD_PUSH, SOURCE_USER, ConfigEntry) +from homeassistant.const import ATTR_FRIENDLY_NAME +from homeassistant.helpers.dispatcher import async_dispatcher_send + + +async def _setup_platform(hass, *devices): + """Set up the SmartThings binary_sensor platform and prerequisites.""" + hass.config.components.add(DOMAIN) + broker = DeviceBroker(hass, devices, '') + config_entry = ConfigEntry("1", DOMAIN, "Test", {}, + SOURCE_USER, CONN_CLASS_CLOUD_PUSH) + hass.data[DOMAIN] = { + DATA_BROKERS: { + config_entry.entry_id: broker + } + } + await hass.config_entries.async_forward_entry_setup( + config_entry, 'binary_sensor') + await hass.async_block_till_done() + return config_entry + + +async def test_async_setup_platform(): + """Test setup platform does nothing (it uses config entries).""" + await binary_sensor.async_setup_platform(None, None, None) + + +async def test_entity_state(hass, device_factory): + """Tests the state attributes properly match the light types.""" + device = device_factory('Motion Sensor 1', [Capability.motion_sensor], + {Attribute.motion: 'inactive'}) + await _setup_platform(hass, device) + state = hass.states.get('binary_sensor.motion_sensor_1_motion') + assert state.state == 'off' + assert state.attributes[ATTR_FRIENDLY_NAME] ==\ + device.label + ' ' + Attribute.motion + + +async def test_entity_and_device_attributes(hass, device_factory): + """Test the attributes of the entity are correct.""" + # Arrange + device = device_factory('Motion Sensor 1', [Capability.motion_sensor], + {Attribute.motion: 'inactive'}) + entity_registry = await hass.helpers.entity_registry.async_get_registry() + device_registry = await hass.helpers.device_registry.async_get_registry() + # Act + await _setup_platform(hass, device) + # Assert + entity = entity_registry.async_get('binary_sensor.motion_sensor_1_motion') + assert entity + assert entity.unique_id == device.device_id + '.' + Attribute.motion + device_entry = device_registry.async_get_device( + {(DOMAIN, device.device_id)}, []) + assert device_entry + assert device_entry.name == device.label + assert device_entry.model == device.device_type_name + assert device_entry.manufacturer == 'Unavailable' + + +async def test_update_from_signal(hass, device_factory): + """Test the binary_sensor updates when receiving a signal.""" + # Arrange + device = device_factory('Motion Sensor 1', [Capability.motion_sensor], + {Attribute.motion: 'inactive'}) + await _setup_platform(hass, device) + device.status.apply_attribute_update( + 'main', Capability.motion_sensor, Attribute.motion, 'active') + # Act + async_dispatcher_send(hass, SIGNAL_SMARTTHINGS_UPDATE, + [device.device_id]) + # Assert + await hass.async_block_till_done() + state = hass.states.get('binary_sensor.motion_sensor_1_motion') + assert state is not None + assert state.state == 'on' + + +async def test_unload_config_entry(hass, device_factory): + """Test the binary_sensor is removed when the config entry is unloaded.""" + # Arrange + device = device_factory('Motion Sensor 1', [Capability.motion_sensor], + {Attribute.motion: 'inactive'}) + config_entry = await _setup_platform(hass, device) + # Act + await hass.config_entries.async_forward_entry_unload( + config_entry, 'binary_sensor') + # Assert + assert not hass.states.get('binary_sensor.motion_sensor_1_motion') From c2eec16721738fe0e2d61bcdbc429bcb5f993330 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 2 Feb 2019 14:02:50 -0800 Subject: [PATCH 035/242] Update translations --- .../ambient_station/.translations/ca.json | 19 ++++++++ .../ambient_station/.translations/ko.json | 19 ++++++++ .../ambient_station/.translations/lb.json | 19 ++++++++ .../ambient_station/.translations/ru.json | 19 ++++++++ .../.translations/zh-Hant.json | 19 ++++++++ .../components/auth/.translations/pl.json | 2 +- .../components/auth/.translations/sl.json | 2 +- .../components/auth/.translations/uk.json | 5 ++ .../daikin/.translations/zh-Hans.json | 19 ++++++++ .../components/deconz/.translations/en.json | 2 +- .../components/deconz/.translations/pl.json | 4 +- .../dialogflow/.translations/sl.json | 4 +- .../dialogflow/.translations/zh-Hans.json | 5 ++ .../emulated_roku/.translations/es.json | 17 +++++++ .../emulated_roku/.translations/ko.json | 2 +- .../emulated_roku/.translations/lb.json | 21 ++++++++ .../emulated_roku/.translations/pl.json | 21 ++++++++ .../emulated_roku/.translations/sl.json | 21 ++++++++ .../emulated_roku/.translations/zh-Hans.json | 17 +++++++ .../emulated_roku/.translations/zh-Hant.json | 21 ++++++++ .../components/esphome/.translations/es.json | 25 ++++++++++ .../components/esphome/.translations/ko.json | 2 +- .../components/esphome/.translations/pl.json | 6 +-- .../esphome/.translations/zh-Hans.json | 27 +++++++++++ .../components/geofency/.translations/es.json | 8 ++++ .../components/geofency/.translations/pl.json | 18 +++++++ .../components/geofency/.translations/sl.json | 18 +++++++ .../geofency/.translations/zh-Hans.json | 15 ++++++ .../geofency/.translations/zh-Hant.json | 18 +++++++ .../gpslogger/.translations/ca.json | 18 +++++++ .../gpslogger/.translations/en.json | 30 ++++++------ .../gpslogger/.translations/es.json | 8 ++++ .../gpslogger/.translations/ko.json | 18 +++++++ .../gpslogger/.translations/lb.json | 18 +++++++ .../gpslogger/.translations/no.json | 18 +++++++ .../gpslogger/.translations/pl.json | 18 +++++++ .../gpslogger/.translations/ru.json | 17 +++++++ .../gpslogger/.translations/sl.json | 18 +++++++ .../gpslogger/.translations/zh-Hans.json | 7 +++ .../gpslogger/.translations/zh-Hant.json | 18 +++++++ .../homematicip_cloud/.translations/sl.json | 6 +-- .../.translations/zh-Hans.json | 2 +- .../components/hue/.translations/pl.json | 2 +- .../components/hue/.translations/sl.json | 2 +- .../components/ifttt/.translations/sl.json | 4 +- .../components/locative/.translations/ca.json | 18 +++++++ .../components/locative/.translations/en.json | 30 ++++++------ .../components/locative/.translations/ko.json | 18 +++++++ .../components/locative/.translations/lb.json | 18 +++++++ .../components/locative/.translations/no.json | 18 +++++++ .../components/locative/.translations/pl.json | 18 +++++++ .../components/locative/.translations/ru.json | 17 +++++++ .../components/locative/.translations/sl.json | 18 +++++++ .../locative/.translations/zh-Hans.json | 15 ++++++ .../locative/.translations/zh-Hant.json | 18 +++++++ .../components/mailgun/.translations/sl.json | 4 +- .../mailgun/.translations/zh-Hans.json | 9 +++- .../components/mqtt/.translations/sl.json | 2 +- .../nest/.translations/zh-Hans.json | 4 +- .../components/openuv/.translations/ca.json | 4 +- .../point/.translations/zh-Hans.json | 11 ++++- .../rainmachine/.translations/zh-Hans.json | 4 +- .../simplisafe/.translations/zh-Hans.json | 2 +- .../smartthings/.translations/ca.json | 27 +++++++++++ .../smartthings/.translations/en.json | 48 +++++++++---------- .../smartthings/.translations/lb.json | 27 +++++++++++ .../smartthings/.translations/pl.json | 10 ++++ .../smartthings/.translations/zh-Hant.json | 27 +++++++++++ .../tellduslive/.translations/ca.json | 4 ++ .../tellduslive/.translations/en.json | 1 + .../tellduslive/.translations/es.json | 10 ++++ .../tellduslive/.translations/ko.json | 4 ++ .../tellduslive/.translations/lb.json | 4 ++ .../tellduslive/.translations/no.json | 4 ++ .../tellduslive/.translations/pl.json | 4 ++ .../tellduslive/.translations/ru.json | 4 ++ .../tellduslive/.translations/sl.json | 4 ++ .../tellduslive/.translations/zh-Hans.json | 24 ++++++++++ .../tellduslive/.translations/zh-Hant.json | 4 ++ .../components/twilio/.translations/sl.json | 4 +- .../twilio/.translations/zh-Hans.json | 13 ++++- .../components/upnp/.translations/sl.json | 2 +- .../upnp/.translations/zh-Hans.json | 8 +++- .../components/zha/.translations/zh-Hans.json | 13 ++++- 84 files changed, 981 insertions(+), 92 deletions(-) create mode 100644 homeassistant/components/ambient_station/.translations/ca.json create mode 100644 homeassistant/components/ambient_station/.translations/ko.json create mode 100644 homeassistant/components/ambient_station/.translations/lb.json create mode 100644 homeassistant/components/ambient_station/.translations/ru.json create mode 100644 homeassistant/components/ambient_station/.translations/zh-Hant.json create mode 100644 homeassistant/components/daikin/.translations/zh-Hans.json create mode 100644 homeassistant/components/emulated_roku/.translations/es.json create mode 100644 homeassistant/components/emulated_roku/.translations/lb.json create mode 100644 homeassistant/components/emulated_roku/.translations/pl.json create mode 100644 homeassistant/components/emulated_roku/.translations/sl.json create mode 100644 homeassistant/components/emulated_roku/.translations/zh-Hans.json create mode 100644 homeassistant/components/emulated_roku/.translations/zh-Hant.json create mode 100644 homeassistant/components/esphome/.translations/es.json create mode 100644 homeassistant/components/esphome/.translations/zh-Hans.json create mode 100644 homeassistant/components/geofency/.translations/es.json create mode 100644 homeassistant/components/geofency/.translations/pl.json create mode 100644 homeassistant/components/geofency/.translations/sl.json create mode 100644 homeassistant/components/geofency/.translations/zh-Hans.json create mode 100644 homeassistant/components/geofency/.translations/zh-Hant.json create mode 100644 homeassistant/components/gpslogger/.translations/ca.json create mode 100644 homeassistant/components/gpslogger/.translations/es.json create mode 100644 homeassistant/components/gpslogger/.translations/ko.json create mode 100644 homeassistant/components/gpslogger/.translations/lb.json create mode 100644 homeassistant/components/gpslogger/.translations/no.json create mode 100644 homeassistant/components/gpslogger/.translations/pl.json create mode 100644 homeassistant/components/gpslogger/.translations/ru.json create mode 100644 homeassistant/components/gpslogger/.translations/sl.json create mode 100644 homeassistant/components/gpslogger/.translations/zh-Hans.json create mode 100644 homeassistant/components/gpslogger/.translations/zh-Hant.json create mode 100644 homeassistant/components/locative/.translations/ca.json create mode 100644 homeassistant/components/locative/.translations/ko.json create mode 100644 homeassistant/components/locative/.translations/lb.json create mode 100644 homeassistant/components/locative/.translations/no.json create mode 100644 homeassistant/components/locative/.translations/pl.json create mode 100644 homeassistant/components/locative/.translations/ru.json create mode 100644 homeassistant/components/locative/.translations/sl.json create mode 100644 homeassistant/components/locative/.translations/zh-Hans.json create mode 100644 homeassistant/components/locative/.translations/zh-Hant.json create mode 100644 homeassistant/components/smartthings/.translations/ca.json create mode 100644 homeassistant/components/smartthings/.translations/lb.json create mode 100644 homeassistant/components/smartthings/.translations/pl.json create mode 100644 homeassistant/components/smartthings/.translations/zh-Hant.json create mode 100644 homeassistant/components/tellduslive/.translations/es.json create mode 100644 homeassistant/components/tellduslive/.translations/zh-Hans.json diff --git a/homeassistant/components/ambient_station/.translations/ca.json b/homeassistant/components/ambient_station/.translations/ca.json new file mode 100644 index 00000000000..d3c451f3e3f --- /dev/null +++ b/homeassistant/components/ambient_station/.translations/ca.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "identifier_exists": "Clau d'aplicaci\u00f3 i/o clau API ja registrada", + "invalid_key": "Clau API i/o clau d'aplicaci\u00f3 inv\u00e0lida/es", + "no_devices": "No s'ha trobat cap dispositiu al compte" + }, + "step": { + "user": { + "data": { + "api_key": "Clau API", + "app_key": "Clau d'aplicaci\u00f3" + }, + "title": "Introdueix la teva informaci\u00f3" + } + }, + "title": "Ambient PWS" + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/.translations/ko.json b/homeassistant/components/ambient_station/.translations/ko.json new file mode 100644 index 00000000000..51a09514159 --- /dev/null +++ b/homeassistant/components/ambient_station/.translations/ko.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "identifier_exists": "Application \ud0a4 \ud639\uc740 API \ud0a4\uac00 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_key": "Application \ud0a4 \ud639\uc740 API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "no_devices": "\uacc4\uc815\uc5d0 \uae30\uae30\uac00 \uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "api_key": "API \ud0a4", + "app_key": "Application \ud0a4" + }, + "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694" + } + }, + "title": "Ambient PWS" + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/.translations/lb.json b/homeassistant/components/ambient_station/.translations/lb.json new file mode 100644 index 00000000000..0f0d60d4458 --- /dev/null +++ b/homeassistant/components/ambient_station/.translations/lb.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "identifier_exists": "Applikatioun's Schl\u00ebssel an/oder API Schl\u00ebssel ass scho registr\u00e9iert", + "invalid_key": "Ong\u00ebltegen API Schl\u00ebssel an/oder Applikatioun's Schl\u00ebssel", + "no_devices": "Keng Apparater am Kont fonnt" + }, + "step": { + "user": { + "data": { + "api_key": "API Schl\u00ebssel", + "app_key": "Applikatioun's Schl\u00ebssel" + }, + "title": "F\u00ebllt \u00e4r Informatiounen aus" + } + }, + "title": "Ambient PWS" + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/.translations/ru.json b/homeassistant/components/ambient_station/.translations/ru.json new file mode 100644 index 00000000000..d1264010b75 --- /dev/null +++ b/homeassistant/components/ambient_station/.translations/ru.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "identifier_exists": "\u041a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438/\u0438\u043b\u0438 \u043a\u043b\u044e\u0447 API \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d", + "invalid_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API \u0438/\u0438\u043b\u0438 \u043a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f", + "no_devices": "\u0412 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b" + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "app_key": "\u041a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f" + }, + "title": "Ambient PWS" + } + }, + "title": "Ambient PWS" + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/.translations/zh-Hant.json b/homeassistant/components/ambient_station/.translations/zh-Hant.json new file mode 100644 index 00000000000..7e3ed3ef888 --- /dev/null +++ b/homeassistant/components/ambient_station/.translations/zh-Hant.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "identifier_exists": "API \u5bc6\u9470\u53ca/\u6216\u61c9\u7528\u5bc6\u9470\u5df2\u8a3b\u518a", + "invalid_key": "API \u5bc6\u9470\u53ca/\u6216\u61c9\u7528\u5bc6\u9470\u7121\u6548", + "no_devices": "\u5e33\u865f\u4e2d\u627e\u4e0d\u5230\u4efb\u4f55\u88dd\u7f6e" + }, + "step": { + "user": { + "data": { + "api_key": "API \u5bc6\u9470", + "app_key": "\u61c9\u7528\u5bc6\u9470" + }, + "title": "\u586b\u5beb\u8cc7\u8a0a" + } + }, + "title": "\u74b0\u5883 PWS" + } +} \ No newline at end of file diff --git a/homeassistant/components/auth/.translations/pl.json b/homeassistant/components/auth/.translations/pl.json index 6adaaa019c5..f0e9f7b71ea 100644 --- a/homeassistant/components/auth/.translations/pl.json +++ b/homeassistant/components/auth/.translations/pl.json @@ -9,7 +9,7 @@ }, "step": { "init": { - "description": "Prosz\u0119 wybra\u0107 jedn\u0105 z us\u0142ugi powiadamiania:", + "description": "Prosz\u0119 wybra\u0107 jedn\u0105 us\u0142ug\u0119 powiadamiania:", "title": "Skonfiguruj has\u0142o jednorazowe dostarczone przez komponent powiadomie\u0144" }, "setup": { diff --git a/homeassistant/components/auth/.translations/sl.json b/homeassistant/components/auth/.translations/sl.json index 223dc91a480..f70bb81e700 100644 --- a/homeassistant/components/auth/.translations/sl.json +++ b/homeassistant/components/auth/.translations/sl.json @@ -21,7 +21,7 @@ }, "totp": { "error": { - "invalid_code": "Neveljavna koda, prosimo, poskusite znova. \u010ce dobite to sporo\u010dilo ve\u010dkrat, prosimo poskrbite, da bo ura va\u0161ega Home Assistenta to\u010dna." + "invalid_code": "Neveljavna koda, prosimo, poskusite znova. \u010ce dobite to sporo\u010dilo ve\u010dkrat, prosimo poskrbite, da bo ura va\u0161ega Home Assistanta to\u010dna." }, "step": { "init": { diff --git a/homeassistant/components/auth/.translations/uk.json b/homeassistant/components/auth/.translations/uk.json index 3d4d9a5b151..f826075078e 100644 --- a/homeassistant/components/auth/.translations/uk.json +++ b/homeassistant/components/auth/.translations/uk.json @@ -3,6 +3,11 @@ "notify": { "error": { "invalid_code": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043e\u0434, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437." + }, + "step": { + "setup": { + "title": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f" + } } } } diff --git a/homeassistant/components/daikin/.translations/zh-Hans.json b/homeassistant/components/daikin/.translations/zh-Hans.json new file mode 100644 index 00000000000..1330e3a932d --- /dev/null +++ b/homeassistant/components/daikin/.translations/zh-Hans.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u8bbe\u5907\u5df2\u914d\u7f6e\u5b8c\u6210", + "device_fail": "\u521b\u5efa\u8bbe\u5907\u65f6\u51fa\u73b0\u610f\u5916\u9519\u8bef\u3002", + "device_timeout": "\u8fde\u63a5\u8bbe\u5907\u8d85\u65f6\u3002" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u673a" + }, + "description": "\u8f93\u5165\u60a8\u7684 Daikin \u7a7a\u8c03\u7684IP\u5730\u5740\u3002", + "title": "\u914d\u7f6e Daikin \u7a7a\u8c03" + } + }, + "title": "Daikin \u7a7a\u8c03" + } +} \ No newline at end of file diff --git a/homeassistant/components/deconz/.translations/en.json b/homeassistant/components/deconz/.translations/en.json index 0c60953db56..d8bcc95a115 100644 --- a/homeassistant/components/deconz/.translations/en.json +++ b/homeassistant/components/deconz/.translations/en.json @@ -17,7 +17,7 @@ "title": "Define deCONZ gateway" }, "link": { - "description": "Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ system settings\n2. Press \"Unlock Gateway\" button", + "description": "Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ Settings -> Gateway -> Advanced\n2. Press \"Authenticate app\" button", "title": "Link with deCONZ" }, "options": { diff --git a/homeassistant/components/deconz/.translations/pl.json b/homeassistant/components/deconz/.translations/pl.json index 5dd87d9e462..5a8b710c006 100644 --- a/homeassistant/components/deconz/.translations/pl.json +++ b/homeassistant/components/deconz/.translations/pl.json @@ -12,7 +12,7 @@ "init": { "data": { "host": "Host", - "port": "Port (warto\u015b\u0107 domy\u015blna: \"80\")" + "port": "Port" }, "title": "Zdefiniuj bramk\u0119 deCONZ" }, @@ -28,6 +28,6 @@ "title": "Dodatkowe opcje konfiguracji dla deCONZ" } }, - "title": "deCONZ" + "title": "Brama deCONZ Zigbee" } } \ No newline at end of file diff --git a/homeassistant/components/dialogflow/.translations/sl.json b/homeassistant/components/dialogflow/.translations/sl.json index 597e65a7658..18a476b6870 100644 --- a/homeassistant/components/dialogflow/.translations/sl.json +++ b/homeassistant/components/dialogflow/.translations/sl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "not_internet_accessible": " \u010ce \u017eelite prejemati sporo\u010dila dialogflow, mora biti Home Assistent dostopen prek interneta.", + "not_internet_accessible": "\u010ce \u017eelite prejemati sporo\u010dila dialogflow, mora biti Home Assistant dostopen prek interneta.", "one_instance_allowed": "Potrebna je samo ena instanca." }, "create_entry": { - "default": "Za po\u0161iljanje dogodkov Home Assistent-u, boste morali nastaviti [webhook z dialogflow]({twilio_url}).\n\nIzpolnite naslednje informacije:\n\n- URL: `{webhook_url}`\n- Metoda: POST\n- Vrsta vsebine: application/x-www-form-urlencoded\n\nGlej [dokumentacijo]({docs_url}) za nadaljna navodila." + "default": "Za po\u0161iljanje dogodkov Home Assistant-u, boste morali nastaviti [webhook z dialogflow]({twilio_url}).\n\nIzpolnite naslednje informacije:\n\n- URL: `{webhook_url}`\n- Metoda: POST\n- Vrsta vsebine: application/x-www-form-urlencoded\n\nGlej [dokumentacijo]({docs_url}) za nadaljna navodila." }, "step": { "user": { diff --git a/homeassistant/components/dialogflow/.translations/zh-Hans.json b/homeassistant/components/dialogflow/.translations/zh-Hans.json index 6eecbed54ac..8a542dd0d62 100644 --- a/homeassistant/components/dialogflow/.translations/zh-Hans.json +++ b/homeassistant/components/dialogflow/.translations/zh-Hans.json @@ -1,10 +1,15 @@ { "config": { + "abort": { + "not_internet_accessible": "\u60a8\u7684 Home Assistant \u5b9e\u4f8b\u9700\u8981\u63a5\u5165\u4e92\u8054\u7f51\u4ee5\u63a5\u6536 Dialogflow \u6d88\u606f\u3002", + "one_instance_allowed": "\u4ec5\u9700\u4e00\u4e2a\u5b9e\u4f8b" + }, "create_entry": { "default": "\u8981\u5411 Home Assistant \u53d1\u9001\u4e8b\u4ef6\uff0c\u60a8\u9700\u8981\u914d\u7f6e [Dialogflow \u7684 Webhook \u96c6\u6210]({dialogflow_url})\u3002\n\n\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u8bf7\u53c2\u9605[\u6587\u6863]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u591a\u4fe1\u606f\u3002" }, "step": { "user": { + "description": "\u60a8\u786e\u5b9a\u8981\u8bbe\u7f6e Dialogflow \u5417?", "title": "\u8bbe\u7f6e Dialogflow Webhook" } }, diff --git a/homeassistant/components/emulated_roku/.translations/es.json b/homeassistant/components/emulated_roku/.translations/es.json new file mode 100644 index 00000000000..3491c784c19 --- /dev/null +++ b/homeassistant/components/emulated_roku/.translations/es.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "name_exists": "El nombre ya existe" + }, + "step": { + "user": { + "data": { + "host_ip": "IP del host", + "listen_port": "Puerto de escucha", + "name": "Nombre" + }, + "title": "Definir la configuraci\u00f3n del servidor" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/.translations/ko.json b/homeassistant/components/emulated_roku/.translations/ko.json index 54c3e079386..ddee892039f 100644 --- a/homeassistant/components/emulated_roku/.translations/ko.json +++ b/homeassistant/components/emulated_roku/.translations/ko.json @@ -11,7 +11,7 @@ "host_ip": "\ud638\uc2a4\ud2b8 IP", "listen_port": "\uc218\uc2e0 \ud3ec\ud2b8", "name": "\uc774\ub984", - "upnp_bind_multicast": "\uba40\ud2f0 \uce90\uc2a4\ud2b8 \ubc14\uc778\ub4dc (\ucc38/\uac70\uc9d3)" + "upnp_bind_multicast": "\uba40\ud2f0 \uce90\uc2a4\ud2b8 \ud560\ub2f9 (\ucc38/\uac70\uc9d3)" }, "title": "\uc11c\ubc84 \uad6c\uc131 \uc815\uc758" } diff --git a/homeassistant/components/emulated_roku/.translations/lb.json b/homeassistant/components/emulated_roku/.translations/lb.json new file mode 100644 index 00000000000..11d1aa3ff7a --- /dev/null +++ b/homeassistant/components/emulated_roku/.translations/lb.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "name_exists": "Numm g\u00ebtt et schonn" + }, + "step": { + "user": { + "data": { + "advertise_ip": "IP annonc\u00e9ieren", + "advertise_port": "Port annonc\u00e9ieren", + "host_ip": "IP vum Apparat", + "listen_port": "Port lauschteren", + "name": "Numm", + "upnp_bind_multicast": "Multicast abannen (Richteg/Falsch)" + }, + "title": "Server Konfiguratioun d\u00e9fin\u00e9ieren" + } + }, + "title": "EmulatedRoku" + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/.translations/pl.json b/homeassistant/components/emulated_roku/.translations/pl.json new file mode 100644 index 00000000000..0ed3cc3d14a --- /dev/null +++ b/homeassistant/components/emulated_roku/.translations/pl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "name_exists": "Nazwa ju\u017c istnieje" + }, + "step": { + "user": { + "data": { + "advertise_ip": "IP rozg\u0142aszania", + "advertise_port": "Port rozg\u0142aszania", + "host_ip": "IP hosta", + "listen_port": "Port nas\u0142uchu", + "name": "Nazwa", + "upnp_bind_multicast": "Powi\u0105\u017c multicast (prawda/fa\u0142sz)" + }, + "title": "Zdefiniuj konfiguracj\u0119 serwera" + } + }, + "title": "EmulatedRoku" + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/.translations/sl.json b/homeassistant/components/emulated_roku/.translations/sl.json new file mode 100644 index 00000000000..768feb83747 --- /dev/null +++ b/homeassistant/components/emulated_roku/.translations/sl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "name_exists": "Ime \u017ee obstaja" + }, + "step": { + "user": { + "data": { + "advertise_ip": "Advertise IP", + "advertise_port": "Advertise port", + "host_ip": "IP gostitelja", + "listen_port": "Vrata naprave", + "name": "Ime", + "upnp_bind_multicast": "Vezava multicasta (True / False)" + }, + "title": "Dolo\u010dite konfiguracijo stre\u017enika" + } + }, + "title": "EmulatedRoku" + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/.translations/zh-Hans.json b/homeassistant/components/emulated_roku/.translations/zh-Hans.json new file mode 100644 index 00000000000..9cb4cc33431 --- /dev/null +++ b/homeassistant/components/emulated_roku/.translations/zh-Hans.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "name_exists": "\u540d\u79f0\u5df2\u5b58\u5728" + }, + "step": { + "user": { + "data": { + "host_ip": "\u4e3b\u673aIP", + "listen_port": "\u76d1\u542c\u7aef\u53e3", + "name": "\u59d3\u540d" + }, + "title": "\u5b9a\u4e49\u670d\u52a1\u5668\u914d\u7f6e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/.translations/zh-Hant.json b/homeassistant/components/emulated_roku/.translations/zh-Hant.json new file mode 100644 index 00000000000..40b4307ae02 --- /dev/null +++ b/homeassistant/components/emulated_roku/.translations/zh-Hant.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "name_exists": "\u8a72\u540d\u7a31\u5df2\u5b58\u5728" + }, + "step": { + "user": { + "data": { + "advertise_ip": "\u5ee3\u64ad\u901a\u8a0a\u57e0", + "advertise_port": "\u5ee3\u64ad\u901a\u8a0a\u57e0", + "host_ip": "\u4e3b\u6a5f IP", + "listen_port": "\u76e3\u807d\u901a\u8a0a\u57e0", + "name": "\u540d\u7a31", + "upnp_bind_multicast": "\u7d81\u5b9a\u7fa4\u64ad\uff08Multicast\uff09True/False" + }, + "title": "\u5b9a\u7fa9\u4f3a\u670d\u5668\u8a2d\u5b9a" + } + }, + "title": "EmulatedRoku" + } +} \ No newline at end of file diff --git a/homeassistant/components/esphome/.translations/es.json b/homeassistant/components/esphome/.translations/es.json new file mode 100644 index 00000000000..8010b330b88 --- /dev/null +++ b/homeassistant/components/esphome/.translations/es.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "ESP ya est\u00e1 configurado" + }, + "error": { + "invalid_password": "\u00a1Contrase\u00f1a incorrecta!" + }, + "step": { + "authenticate": { + "data": { + "password": "Contrase\u00f1a" + }, + "description": "Escribe la contrase\u00f1a que hayas establecido en tu configuraci\u00f3n.", + "title": "Escribe la contrase\u00f1a" + }, + "user": { + "data": { + "host": "Host", + "port": "Puerto" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/esphome/.translations/ko.json b/homeassistant/components/esphome/.translations/ko.json index 514acbbbf18..24f84851254 100644 --- a/homeassistant/components/esphome/.translations/ko.json +++ b/homeassistant/components/esphome/.translations/ko.json @@ -4,7 +4,7 @@ "already_configured": "ESP \uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "connection_error": "ESP \uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. YAML \ud30c\uc77c\uc5d0 'api :' \ub97c \uad6c\uc131\ud588\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "connection_error": "ESP \uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. YAML \ud30c\uc77c\uc5d0 'api:' \ub97c \uad6c\uc131\ud588\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", "invalid_password": "\uc798\ubabb\ub41c \ube44\ubc00\ubc88\ud638", "resolve_error": "ESP \uc758 \uc8fc\uc18c\ub97c \ud655\uc778\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uac00 \uacc4\uc18d \ubc1c\uc0dd\ud558\uba74 \uace0\uc815 IP \uc8fc\uc18c (https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips)\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694" }, diff --git a/homeassistant/components/esphome/.translations/pl.json b/homeassistant/components/esphome/.translations/pl.json index 4f2a8b0e1bb..19fb581eb3f 100644 --- a/homeassistant/components/esphome/.translations/pl.json +++ b/homeassistant/components/esphome/.translations/pl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "ESP jest ju\u017c skonfigurowany" + "already_configured": "ESP jest ju\u017c skonfigurowane" }, "error": { - "connection_error": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z ESP. Upewnij si\u0119, \u017ce Tw\u00f3j plik YAML zawiera lini\u0119 \"api:\".", + "connection_error": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z ESP. Upewnij si\u0119, \u017ce Tw\u00f3j plik YAML zawiera lini\u0119 'api:'.", "invalid_password": "Nieprawid\u0142owe has\u0142o!", "resolve_error": "Nie mo\u017cna rozpozna\u0107 adresu ESP. Je\u015bli ten b\u0142\u0105d b\u0119dzie si\u0119 powtarza\u0142, nale\u017cy ustawi\u0107 statyczny adres IP: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" }, @@ -21,7 +21,7 @@ "host": "Host", "port": "Port" }, - "description": "Wprowad\u017a ustawienia po\u0142\u0105czenia swojego [ESPHome] (https://esphomelib.com/) w\u0119z\u0142a.", + "description": "Wprowad\u017a ustawienia po\u0142\u0105czenia swojego [ESPHome](https://esphomelib.com/) w\u0119z\u0142a.", "title": "ESPHome" } }, diff --git a/homeassistant/components/esphome/.translations/zh-Hans.json b/homeassistant/components/esphome/.translations/zh-Hans.json new file mode 100644 index 00000000000..8e5ca59fcef --- /dev/null +++ b/homeassistant/components/esphome/.translations/zh-Hans.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "connection_error": "\u65e0\u6cd5\u8fde\u63a5\u5230ESP\u3002\u8bf7\u786e\u4fdd\u60a8\u7684YAML\u6587\u4ef6\u5305\u542b'api:'\u884c\u3002", + "invalid_password": "\u65e0\u6548\u7684\u5bc6\u7801\uff01", + "resolve_error": "\u65e0\u6cd5\u89e3\u6790ESP\u7684\u5730\u5740\u3002\u5982\u679c\u6b64\u9519\u8bef\u4ecd\u7136\u5b58\u5728\uff0c\u8bf7\u8bbe\u7f6e\u9759\u6001IP\u5730\u5740\uff1ahttps://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" + }, + "step": { + "authenticate": { + "data": { + "password": "\u5bc6\u7801" + }, + "description": "\u8bf7\u8f93\u5165\u60a8\u5728\u914d\u7f6e\u4e2d\u8bbe\u7f6e\u7684\u5bc6\u7801\u3002", + "title": "\u8f93\u5165\u5bc6\u7801" + }, + "user": { + "data": { + "host": "\u4e3b\u673a", + "port": "\u7aef\u53e3" + }, + "description": "\u8bf7\u8f93\u5165\u60a8\u7684 [ESPHome](https://esphomelib.com/) \u8282\u70b9\u7684\u8fde\u63a5\u8bbe\u7f6e\u3002", + "title": "ESPHome" + } + }, + "title": "ESPHome" + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/.translations/es.json b/homeassistant/components/geofency/.translations/es.json new file mode 100644 index 00000000000..cd14e21db10 --- /dev/null +++ b/homeassistant/components/geofency/.translations/es.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Tu Home Assistant debe ser accesible desde Internet para recibir mensajes de GPSLogger.", + "one_instance_allowed": "Solo se necesita una instancia." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/.translations/pl.json b/homeassistant/components/geofency/.translations/pl.json new file mode 100644 index 00000000000..09d93e6911e --- /dev/null +++ b/homeassistant/components/geofency/.translations/pl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Tw\u00f3j Home Assistant musi by\u0107 dost\u0119pny z Internetu, aby odbiera\u0107 komunikaty z Geofency.", + "one_instance_allowed": "Wymagana jest tylko jedna instancja." + }, + "create_entry": { + "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 webhook w Geofency. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) by pozna\u0107 szczeg\u00f3\u0142y." + }, + "step": { + "user": { + "description": "Czy chcesz skonfigurowa\u0107 Geofency?", + "title": "Konfiguracja Geofency Webhook" + } + }, + "title": "Geofency Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/.translations/sl.json b/homeassistant/components/geofency/.translations/sl.json new file mode 100644 index 00000000000..e56d41d4f1a --- /dev/null +++ b/homeassistant/components/geofency/.translations/sl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Va\u0161 Home Assistant mora biti dostopen prek interneta, da boste lahko prejemali Geofency sporo\u010dila.", + "one_instance_allowed": "Potrebna je samo ena instanca." + }, + "create_entry": { + "default": "\u010ce \u017eelite dogodke poslati v Home Assistant, morate v Geofency-ju nastaviti funkcijo webhook. \n\n Izpolnite naslednje podatke: \n\n - URL: ` {webhook_url} ` \n - Metoda: POST \n\n Za ve\u010d podrobnosti si oglejte [dokumentacijo] ( {docs_url} )." + }, + "step": { + "user": { + "description": "Ali ste prepri\u010dani, da \u017eelite nastaviti geofency webhook?", + "title": "Nastavite Geofency Webhook" + } + }, + "title": "Geofency Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/.translations/zh-Hans.json b/homeassistant/components/geofency/.translations/zh-Hans.json new file mode 100644 index 00000000000..7ab8a128980 --- /dev/null +++ b/homeassistant/components/geofency/.translations/zh-Hans.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "\u60a8\u7684 Home Assistant \u5b9e\u4f8b\u9700\u8981\u63a5\u5165\u4e92\u8054\u7f51\u4ee5\u63a5\u6536 Geofency \u6d88\u606f\u3002", + "one_instance_allowed": "\u4ec5\u9700\u4e00\u4e2a\u5b9e\u4f8b" + }, + "step": { + "user": { + "description": "\u60a8\u786e\u5b9a\u8981\u8bbe\u7f6e Geofency Webhook \u5417?", + "title": "\u8bbe\u7f6e Geofency Webhook" + } + }, + "title": "Geofency Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/.translations/zh-Hant.json b/homeassistant/components/geofency/.translations/zh-Hant.json new file mode 100644 index 00000000000..bec33c26d10 --- /dev/null +++ b/homeassistant/components/geofency/.translations/zh-Hant.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Home Assistant \u5be6\u4f8b\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 Geofency \u8a0a\u606f\u3002", + "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002" + }, + "create_entry": { + "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u65bc Geofency \u5167\u8a2d\u5b9a webhook \u529f\u80fd\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002" + }, + "step": { + "user": { + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Geofency Webhook\uff1f", + "title": "\u8a2d\u5b9a Geofency Webhook" + } + }, + "title": "Geofency Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/ca.json b/homeassistant/components/gpslogger/.translations/ca.json new file mode 100644 index 00000000000..2d3b08d236e --- /dev/null +++ b/homeassistant/components/gpslogger/.translations/ca.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "La inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de GPSLogger.", + "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." + }, + "create_entry": { + "default": "Per enviar esdeveniments a Home Assistant, haur\u00e0s de configurar l'opci\u00f3 webhook de GPSLogger.\n\nCompleta la seg\u00fcent informaci\u00f3:\n\n- URL: `{webhook_url}` \n- M\u00e8tode: POST \n\nConsulta la [documentaci\u00f3]({docs_url}) per a m\u00e9s detalls." + }, + "step": { + "user": { + "description": "Est\u00e0s segur que vols configurar el Webhook GPSLogger?", + "title": "Configuraci\u00f3 del Webhook GPSLogger" + } + }, + "title": "Webhook GPSLogger" + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/en.json b/homeassistant/components/gpslogger/.translations/en.json index d5641ef5db8..ad8f978bc59 100644 --- a/homeassistant/components/gpslogger/.translations/en.json +++ b/homeassistant/components/gpslogger/.translations/en.json @@ -1,18 +1,18 @@ { - "config": { - "title": "GPSLogger Webhook", - "step": { - "user": { - "title": "Set up the GPSLogger Webhook", - "description": "Are you sure you want to set up the GPSLogger Webhook?" - } - }, - "abort": { - "one_instance_allowed": "Only a single instance is necessary.", - "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from GPSLogger." - }, - "create_entry": { - "default": "To send events to Home Assistant, you will need to setup the webhook feature in GPSLogger.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details." + "config": { + "abort": { + "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from GPSLogger.", + "one_instance_allowed": "Only a single instance is necessary." + }, + "create_entry": { + "default": "To send events to Home Assistant, you will need to setup the webhook feature in GPSLogger.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details." + }, + "step": { + "user": { + "description": "Are you sure you want to set up the GPSLogger Webhook?", + "title": "Set up the GPSLogger Webhook" + } + }, + "title": "GPSLogger Webhook" } - } } \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/es.json b/homeassistant/components/gpslogger/.translations/es.json new file mode 100644 index 00000000000..cd14e21db10 --- /dev/null +++ b/homeassistant/components/gpslogger/.translations/es.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Tu Home Assistant debe ser accesible desde Internet para recibir mensajes de GPSLogger.", + "one_instance_allowed": "Solo se necesita una instancia." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/ko.json b/homeassistant/components/gpslogger/.translations/ko.json new file mode 100644 index 00000000000..a65e51d7cae --- /dev/null +++ b/homeassistant/components/gpslogger/.translations/ko.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "GPSLogger \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4.", + "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." + }, + "create_entry": { + "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 GPSLogger \uc5d0\uc11c Webhook \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694." + }, + "step": { + "user": { + "description": "GPSLogger Webhook \uc744 \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "GPSLogger Webhook \uc124\uc815" + } + }, + "title": "GPSLogger Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/lb.json b/homeassistant/components/gpslogger/.translations/lb.json new file mode 100644 index 00000000000..78df911c868 --- /dev/null +++ b/homeassistant/components/gpslogger/.translations/lb.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "\u00c4r Home Assistant Instanz muss iwwert Internet accessibel si fir GPSLogger Noriichten z'empf\u00e4nken.", + "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg." + }, + "create_entry": { + "default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, muss den Webhook Feature am GPSLogger ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nLiest [Dokumentatioun]({docs_url}) fir w\u00e9ider D\u00e9tailer." + }, + "step": { + "user": { + "description": "S\u00e9cher fir GPSLogger Webhook anzeriichten?", + "title": "GPSLogger Webhook ariichten" + } + }, + "title": "GPSLogger Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/no.json b/homeassistant/components/gpslogger/.translations/no.json new file mode 100644 index 00000000000..836b5c8bc68 --- /dev/null +++ b/homeassistant/components/gpslogger/.translations/no.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Home Assistant m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta meldinger fra GPSLogger.", + "one_instance_allowed": "Kun en enkelt forekomst er n\u00f8dvendig." + }, + "create_entry": { + "default": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du sette opp webhook-funksjonen i GPSLogger. \n\n Fyll ut f\u00f8lgende informasjon: \n\n - URL: `{webhook_url}` \n - Metode: POST \n\n Se [dokumentasjonen]({docs_url}) for ytterligere detaljer." + }, + "step": { + "user": { + "description": "Er du sikker p\u00e5 at du vil sette opp GPSLogger Webhook?", + "title": "Sett opp GPSLogger Webhook" + } + }, + "title": "GPSLogger Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/pl.json b/homeassistant/components/gpslogger/.translations/pl.json new file mode 100644 index 00000000000..3d82ac6fa5a --- /dev/null +++ b/homeassistant/components/gpslogger/.translations/pl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Twoja instancja Home Assistant musi by\u0107 dost\u0119pna z Internetu, aby otrzymywa\u0107 wiadomo\u015bci z GPSlogger.", + "one_instance_allowed": "Wymagana jest tylko jedna instancja." + }, + "create_entry": { + "default": "Aby wysy\u0142a\u0107 lokalizacje do Home Assistant'a, musisz skonfigurowa\u0107 webhook w aplikacji GPSLogger. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) by pozna\u0107 szczeg\u00f3\u0142y." + }, + "step": { + "user": { + "description": "Czy chcesz skonfigurowa\u0107 Geofency?", + "title": "Konfiguracja Geofency Webhook" + } + }, + "title": "Konfiguracja Geofency Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/ru.json b/homeassistant/components/gpslogger/.translations/ru.json new file mode 100644 index 00000000000..34b7e907288 --- /dev/null +++ b/homeassistant/components/gpslogger/.translations/ru.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 GPSLogger." + }, + "create_entry": { + "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c webhooks \u0434\u043b\u044f GPSLogger.\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438." + }, + "step": { + "user": { + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c GPSLogger?", + "title": "GPSLogger" + } + }, + "title": "GPSLogger" + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/sl.json b/homeassistant/components/gpslogger/.translations/sl.json new file mode 100644 index 00000000000..8e205bef437 --- /dev/null +++ b/homeassistant/components/gpslogger/.translations/sl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Va\u0161 Home Assistant mora biti dostopek prek interneta, da boste lahko prejemali GPSlogger sporo\u010dila.", + "one_instance_allowed": "Potrebna je samo ena instanca." + }, + "create_entry": { + "default": "\u010ce \u017eelite dogodke poslati v Home Assistant, morate v GPSLoggerju nastaviti funkcijo webhook. \n\n Izpolnite naslednje podatke: \n\n - URL: ` {webhook_url} ` \n - Metoda: POST \n\n Za ve\u010d podrobnosti si oglejte [dokumentacijo] ( {docs_url} )." + }, + "step": { + "user": { + "description": "Ali ste prepri\u010dani, da \u017eelite nastaviti GPSloggerWebhook?", + "title": "Nastavite GPSlogger Webhook" + } + }, + "title": "GPSLogger Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/zh-Hans.json b/homeassistant/components/gpslogger/.translations/zh-Hans.json new file mode 100644 index 00000000000..dd5db73f582 --- /dev/null +++ b/homeassistant/components/gpslogger/.translations/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "\u53ea\u6709\u4e00\u4e2a\u5b9e\u4f8b\u662f\u5fc5\u9700\u7684\u3002" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/zh-Hant.json b/homeassistant/components/gpslogger/.translations/zh-Hant.json new file mode 100644 index 00000000000..c9d98da1afc --- /dev/null +++ b/homeassistant/components/gpslogger/.translations/zh-Hant.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Home Assistant \u5be6\u4f8b\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 GPSLogger \u8a0a\u606f\u3002", + "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002" + }, + "create_entry": { + "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u65bc GPSLogger \u5167\u8a2d\u5b9a webhook \u529f\u80fd\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002" + }, + "step": { + "user": { + "description": "\u662f\u5426\u8981\u8a2d\u5b9a GPSLogger Webhook\uff1f", + "title": "\u8a2d\u5b9a GPSLogger Webhook" + } + }, + "title": "GPSLogger Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/homematicip_cloud/.translations/sl.json b/homeassistant/components/homematicip_cloud/.translations/sl.json index eabb31ac833..cdde0f12d78 100644 --- a/homeassistant/components/homematicip_cloud/.translations/sl.json +++ b/homeassistant/components/homematicip_cloud/.translations/sl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Dostopna to\u010dka je \u017ee konfigurirana", + "already_configured": "Dostopna to\u010dka je \u017ee nastavljena", "connection_aborted": "Povezava s stre\u017enikom HMIP ni bila mogo\u010da", "unknown": "Pri\u0161lo je do neznane napake" }, @@ -21,8 +21,8 @@ "title": "Izberite dostopno to\u010dko HomematicIP" }, "link": { - "description": "Pritisnite modro tipko na dostopni to\u010dko in gumb po\u0161lji, da registrirate homematicIP s Home Assistentom. \n\n![Location of button on bridge](/static/images/config_flows/config_homematicip_cloud.png)", - "title": "Pove\u017eite dostopno to\u010dno" + "description": "Pritisnite modro tipko na dostopni to\u010dko in gumb po\u0161lji, da registrirate homematicIP s Home Assistantom. \n\n![Location of button on bridge](/static/images/config_flows/config_homematicip_cloud.png)", + "title": "Pove\u017eite dostopno to\u010dko" } }, "title": "HomematicIP Cloud" diff --git a/homeassistant/components/homematicip_cloud/.translations/zh-Hans.json b/homeassistant/components/homematicip_cloud/.translations/zh-Hans.json index 629ee4347fe..4c2b6268eec 100644 --- a/homeassistant/components/homematicip_cloud/.translations/zh-Hans.json +++ b/homeassistant/components/homematicip_cloud/.translations/zh-Hans.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u63a5\u5165\u70b9\u5df2\u7ecf\u914d\u7f6e\u5b8c\u6210", + "already_configured": "\u63a5\u5165\u70b9\u5df2\u914d\u7f6e", "connection_aborted": "\u65e0\u6cd5\u8fde\u63a5\u5230 HMIP \u670d\u52a1\u5668", "unknown": "\u53d1\u751f\u672a\u77e5\u9519\u8bef\u3002" }, diff --git a/homeassistant/components/hue/.translations/pl.json b/homeassistant/components/hue/.translations/pl.json index 784fa0d99a6..63cbbe016a2 100644 --- a/homeassistant/components/hue/.translations/pl.json +++ b/homeassistant/components/hue/.translations/pl.json @@ -24,6 +24,6 @@ "title": "Hub Link" } }, - "title": "Mostek Philips Hue" + "title": "Philips Hue" } } \ No newline at end of file diff --git a/homeassistant/components/hue/.translations/sl.json b/homeassistant/components/hue/.translations/sl.json index 05d52d5c37e..7ad7a2e6ade 100644 --- a/homeassistant/components/hue/.translations/sl.json +++ b/homeassistant/components/hue/.translations/sl.json @@ -20,7 +20,7 @@ "title": "Izberite Hue most" }, "link": { - "description": "Pritisnite gumb na mostu, da registrirate Philips Hue s Home Assistentom. \n\n ! [Polo\u017eaj gumba na mostu] (/static/images/config_philips_hue.jpg)", + "description": "Pritisnite gumb na mostu, da registrirate Philips Hue s Home Assistantom. \n\n ! [Polo\u017eaj gumba na mostu] (/static/images/config_philips_hue.jpg)", "title": "Link Hub" } }, diff --git a/homeassistant/components/ifttt/.translations/sl.json b/homeassistant/components/ifttt/.translations/sl.json index f5cc1dc572e..efb966880eb 100644 --- a/homeassistant/components/ifttt/.translations/sl.json +++ b/homeassistant/components/ifttt/.translations/sl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "not_internet_accessible": "Va\u0161 Home Assistent mora biti dostopek prek interneta, da boste lahko prejemali IFTTT sporo\u010dila.", + "not_internet_accessible": "Va\u0161 Home Assistant mora biti dostopek prek interneta, da boste lahko prejemali IFTTT sporo\u010dila.", "one_instance_allowed": "Potrebna je samo ena instanca." }, "create_entry": { - "default": "\u010ce \u017eelite poslati dogodke Home Assistent-u, boste morali uporabiti akcijo \u00bbNaredi spletno zahtevo\u00ab iz orodja [IFTTT Webhook applet] ( {applet_url} ). \n\n Izpolnite naslednje podatke: \n\n - URL: ` {webhook_url} ` \n - Metoda: POST \n - Vrsta vsebine: application/json \n\n Poglejte si [dokumentacijo] ( {docs_url} ) o tem, kako konfigurirati avtomatizacijo za obdelavo dohodnih podatkov." + "default": "\u010ce \u017eelite poslati dogodke Home Assistant-u, boste morali uporabiti akcijo \u00bbNaredi spletno zahtevo\u00ab iz orodja [IFTTT Webhook applet] ( {applet_url} ). \n\n Izpolnite naslednje podatke: \n\n - URL: ` {webhook_url} ` \n - Metoda: POST \n - Vrsta vsebine: application/json \n\n Poglejte si [dokumentacijo] ( {docs_url} ) o tem, kako konfigurirati avtomatizacijo za obdelavo dohodnih podatkov." }, "step": { "user": { diff --git a/homeassistant/components/locative/.translations/ca.json b/homeassistant/components/locative/.translations/ca.json new file mode 100644 index 00000000000..a08907a51ef --- /dev/null +++ b/homeassistant/components/locative/.translations/ca.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "La inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de Geofency.", + "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." + }, + "create_entry": { + "default": "Per enviar ubicacions a Home Assistant, haur\u00e0s de configurar l'opci\u00f3 webhook de l'aplicaci\u00f3 Locative.\n\nCompleta la seg\u00fcent informaci\u00f3:\n\n- URL: `{webhook_url}` \n- M\u00e8tode: POST \n\nConsulta la [documentaci\u00f3]({docs_url}) per a m\u00e9s detalls." + }, + "step": { + "user": { + "description": "Est\u00e0s segur que vols configurar el Webhook Locative?", + "title": "Configuraci\u00f3 del Webhook Locative" + } + }, + "title": "Webhook Locative" + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/.translations/en.json b/homeassistant/components/locative/.translations/en.json index b2a538a0fa5..052557408d8 100644 --- a/homeassistant/components/locative/.translations/en.json +++ b/homeassistant/components/locative/.translations/en.json @@ -1,18 +1,18 @@ { - "config": { - "title": "Locative Webhook", - "step": { - "user": { - "title": "Set up the Locative Webhook", - "description": "Are you sure you want to set up the Locative Webhook?" - } - }, - "abort": { - "one_instance_allowed": "Only a single instance is necessary.", - "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from Geofency." - }, - "create_entry": { - "default": "To send locations to Home Assistant, you will need to setup the webhook feature in the Locative app.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details." + "config": { + "abort": { + "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from Geofency.", + "one_instance_allowed": "Only a single instance is necessary." + }, + "create_entry": { + "default": "To send locations to Home Assistant, you will need to setup the webhook feature in the Locative app.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details." + }, + "step": { + "user": { + "description": "Are you sure you want to set up the Locative Webhook?", + "title": "Set up the Locative Webhook" + } + }, + "title": "Locative Webhook" } - } } \ No newline at end of file diff --git a/homeassistant/components/locative/.translations/ko.json b/homeassistant/components/locative/.translations/ko.json new file mode 100644 index 00000000000..a57b27cdd75 --- /dev/null +++ b/homeassistant/components/locative/.translations/ko.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Locative \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4.", + "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." + }, + "create_entry": { + "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Locative \uc571\uc5d0\uc11c Webhook \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694." + }, + "step": { + "user": { + "description": "Locative Webhook \uc744 \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Locative Webhook \uc124\uc815" + } + }, + "title": "Locative Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/.translations/lb.json b/homeassistant/components/locative/.translations/lb.json new file mode 100644 index 00000000000..25db0ecef81 --- /dev/null +++ b/homeassistant/components/locative/.translations/lb.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "\u00c4r Home Assistant Instanz muss iwwert Internet accessibel si fir Geofency Noriichten z'empf\u00e4nken.", + "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg." + }, + "create_entry": { + "default": "Fir Plazen un Home Assistant ze sch\u00e9cken, muss den Webhook Feature an der Locative App ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nLiest [Dokumentatioun]({docs_url}) fir w\u00e9ider D\u00e9tailer." + }, + "step": { + "user": { + "description": "S\u00e9cher fir Locative Webhook anzeriichten?", + "title": "Locative Webhook ariichten" + } + }, + "title": "Locative Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/.translations/no.json b/homeassistant/components/locative/.translations/no.json new file mode 100644 index 00000000000..00e3337dfe1 --- /dev/null +++ b/homeassistant/components/locative/.translations/no.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Home Assistant m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta meldinger fra Geofency.", + "one_instance_allowed": "Kun en enkelt forekomst er n\u00f8dvendig." + }, + "create_entry": { + "default": "For \u00e5 kunne sende steder til Home Assistant, m\u00e5 du sette opp webhook-funksjonen i Locative. \n\n Fyll ut f\u00f8lgende informasjon: \n\n - URL: `{webhook_url}` \n - Metode: POST \n\n Se [dokumentasjonen]({docs_url}) for ytterligere detaljer." + }, + "step": { + "user": { + "description": "Er du sikker p\u00e5 at du vil sette opp Locative Webhook?", + "title": "Sett opp Lokative Webhook" + } + }, + "title": "Lokative Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/.translations/pl.json b/homeassistant/components/locative/.translations/pl.json new file mode 100644 index 00000000000..89f6881593a --- /dev/null +++ b/homeassistant/components/locative/.translations/pl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Twoja instancja Home Assistant musi by\u0107 dost\u0119pna z Internetu, aby otrzymywa\u0107 wiadomo\u015bci z Geofency.", + "one_instance_allowed": "Wymagana jest tylko jedna instancja." + }, + "create_entry": { + "default": "Aby wysy\u0142a\u0107 lokalizacje do Home Assistant'a, musisz skonfigurowa\u0107 webhook w aplikacji Locative. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) by pozna\u0107 szczeg\u00f3\u0142y." + }, + "step": { + "user": { + "description": "Czy na pewno chcesz skonfigurowa\u0107 Locative Webhook?", + "title": "Skonfiguruj Locative Webhook" + } + }, + "title": "Locative Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/.translations/ru.json b/homeassistant/components/locative/.translations/ru.json new file mode 100644 index 00000000000..d8b8d55a608 --- /dev/null +++ b/homeassistant/components/locative/.translations/ru.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 Locative." + }, + "create_entry": { + "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c webhooks \u0434\u043b\u044f Locative.\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438." + }, + "step": { + "user": { + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Locative?", + "title": "Locative" + } + }, + "title": "Locative" + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/.translations/sl.json b/homeassistant/components/locative/.translations/sl.json new file mode 100644 index 00000000000..0b0bd45b7d6 --- /dev/null +++ b/homeassistant/components/locative/.translations/sl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Va\u0161 Home Assistant mora biti dostopek prek interneta, da boste lahko prejemali Geofency sporo\u010dila.", + "one_instance_allowed": "Potrebna je samo ena instanca." + }, + "create_entry": { + "default": "Za po\u0161iljanje lokacij v Home Assistant, morate namestiti funkcijo webhook v aplikaciji Locative. \n\n Izpolnite naslednje podatke: \n\n - URL: ` {webhook_url} ` \n - Metoda: POST \n\n Za ve\u010d podrobnosti si oglejte [dokumentacijo] ( {docs_url} )." + }, + "step": { + "user": { + "description": "Ali ste prepri\u010dani, da \u017eelite nastaviti Locative Webhook?", + "title": "Nastavite Locative Webhook" + } + }, + "title": "Locative Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/.translations/zh-Hans.json b/homeassistant/components/locative/.translations/zh-Hans.json new file mode 100644 index 00000000000..d98793d96e5 --- /dev/null +++ b/homeassistant/components/locative/.translations/zh-Hans.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "\u60a8\u7684Home Assistant\u5b9e\u4f8b\u9700\u8981\u53ef\u4ee5\u4eceInternet\u8bbf\u95ee\u4ee5\u63a5\u6536\u6765\u81eaGeofency\u7684\u6d88\u606f\u3002", + "one_instance_allowed": "\u53ea\u9700\u8981\u4e00\u4e2a\u5b9e\u4f8b\u3002" + }, + "step": { + "user": { + "description": "\u60a8\u786e\u5b9a\u8981\u8bbe\u7f6e\u5b9a\u4f4d Webhook\u5417\uff1f", + "title": "\u8bbe\u7f6e\u5b9a\u4f4d Webhook" + } + }, + "title": "\u5b9a\u4f4d Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/.translations/zh-Hant.json b/homeassistant/components/locative/.translations/zh-Hant.json new file mode 100644 index 00000000000..62bb6bb9d96 --- /dev/null +++ b/homeassistant/components/locative/.translations/zh-Hant.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Home Assistant \u5be6\u4f8b\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 Locative \u8a0a\u606f\u3002", + "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002" + }, + "create_entry": { + "default": "\u6b32\u50b3\u9001\u4f4d\u7f6e\u81f3 Home Assistant\uff0c\u5c07\u9700\u65bc Locative App \u5167\u8a2d\u5b9a webhook \u529f\u80fd\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002" + }, + "step": { + "user": { + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Locative Webhook\uff1f", + "title": "\u8a2d\u5b9a Locative Webhook" + } + }, + "title": "Locative Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/sl.json b/homeassistant/components/mailgun/.translations/sl.json index 4eb12d7343c..2f526826d31 100644 --- a/homeassistant/components/mailgun/.translations/sl.json +++ b/homeassistant/components/mailgun/.translations/sl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "not_internet_accessible": " \u010ce \u017eelite prejemati sporo\u010dila Mailgun, mora biti Home Assistent dostopen prek interneta.", + "not_internet_accessible": "\u010ce \u017eelite prejemati sporo\u010dila Mailgun, mora biti Home Assistant dostopen prek interneta.", "one_instance_allowed": "Potrebna je samo ena instanca." }, "create_entry": { - "default": "Za po\u0161iljanje dogodkov Home Assistentu boste morali nastaviti [Webhooks z Mailgun]({mailgun_url}).\n\nIzpolnite naslednje informacije:\n\n- URL: `{webhook_url}`\n- Metoda: POST\n- Vrsta vsebine: application/json\n\nGlej [dokumentacijo]({docs_url}) o tem, kako nastavite automations za obravnavo dohodnih podatkov." + "default": "Za po\u0161iljanje dogodkov Home Assistantu boste morali nastaviti [Webhooks z Mailgun]({mailgun_url}).\n\nIzpolnite naslednje informacije:\n\n- URL: `{webhook_url}`\n- Metoda: POST\n- Vrsta vsebine: application/json\n\nGlej [dokumentacijo]({docs_url}) o tem, kako nastavite automations za obravnavo dohodnih podatkov." }, "step": { "user": { diff --git a/homeassistant/components/mailgun/.translations/zh-Hans.json b/homeassistant/components/mailgun/.translations/zh-Hans.json index 06c1d3624f4..5dd0a7aeabf 100644 --- a/homeassistant/components/mailgun/.translations/zh-Hans.json +++ b/homeassistant/components/mailgun/.translations/zh-Hans.json @@ -6,6 +6,13 @@ }, "create_entry": { "default": "\u8981\u5411 Home Assistant \u53d1\u9001\u4e8b\u4ef6\uff0c\u60a8\u9700\u8981\u914d\u7f6e [Mailgun \u7684 Webhook]({mailgun_url})\u3002\n\n\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u6709\u5173\u5982\u4f55\u914d\u7f6e\u81ea\u52a8\u5316\u4ee5\u5904\u7406\u4f20\u5165\u7684\u6570\u636e\uff0c\u8bf7\u53c2\u9605[\u6587\u6863]({docs_url})\u3002" - } + }, + "step": { + "user": { + "description": "\u60a8\u786e\u5b9a\u8981\u8bbe\u7f6e Mailgun \u5417\uff1f", + "title": "\u8bbe\u7f6e Mailgun Webhook" + } + }, + "title": "Mailgun" } } \ No newline at end of file diff --git a/homeassistant/components/mqtt/.translations/sl.json b/homeassistant/components/mqtt/.translations/sl.json index d8d331449a2..0050d1b040d 100644 --- a/homeassistant/components/mqtt/.translations/sl.json +++ b/homeassistant/components/mqtt/.translations/sl.json @@ -22,7 +22,7 @@ "data": { "discovery": "Omogo\u010di odkrivanje" }, - "description": "\u017delite konfigurirati Home Assistent-a za povezavo s posrednikom MQTT, ki ga ponuja hass.io add-on {addon} ?", + "description": "\u017delite konfigurirati Home Assistant-a za povezavo s posrednikom MQTT, ki ga ponuja hass.io add-on {addon} ?", "title": "MQTT Broker prek dodatka Hass.io" } }, diff --git a/homeassistant/components/nest/.translations/zh-Hans.json b/homeassistant/components/nest/.translations/zh-Hans.json index 05ba5bdf155..0b5cbc989fd 100644 --- a/homeassistant/components/nest/.translations/zh-Hans.json +++ b/homeassistant/components/nest/.translations/zh-Hans.json @@ -24,8 +24,8 @@ "data": { "code": "PIN \u7801" }, - "description": "\u8981\u5173\u8054 Nest \u5e10\u6237\uff0c\u8bf7[\u6388\u6743\u5e10\u6237]({url})\u3002\n\n\u5b8c\u6210\u6388\u6743\u540e\uff0c\u5728\u4e0b\u9762\u7c98\u8d34\u83b7\u5f97\u7684 PIN \u7801\u3002", - "title": "\u5173\u8054 Nest \u5e10\u6237" + "description": "\u8981\u5173\u8054 Nest \u8d26\u6237\uff0c\u8bf7[\u6388\u6743\u8d26\u6237]({url})\u3002\n\n\u5b8c\u6210\u6388\u6743\u540e\uff0c\u5728\u4e0b\u9762\u7c98\u8d34\u83b7\u5f97\u7684 PIN \u7801\u3002", + "title": "\u5173\u8054 Nest \u8d26\u6237" } }, "title": "Nest" diff --git a/homeassistant/components/openuv/.translations/ca.json b/homeassistant/components/openuv/.translations/ca.json index 5cb9a8ce5a5..ad2f391886a 100644 --- a/homeassistant/components/openuv/.translations/ca.json +++ b/homeassistant/components/openuv/.translations/ca.json @@ -2,12 +2,12 @@ "config": { "error": { "identifier_exists": "Les coordenades ja estan registrades", - "invalid_api_key": "Contrasenya API no v\u00e0lida" + "invalid_api_key": "Clau API no v\u00e0lida" }, "step": { "user": { "data": { - "api_key": "Contrasenya API d'OpenUV", + "api_key": "Clau API d'OpenUV", "elevation": "Elevaci\u00f3", "latitude": "Latitud", "longitude": "Longitud" diff --git a/homeassistant/components/point/.translations/zh-Hans.json b/homeassistant/components/point/.translations/zh-Hans.json index 6b5cb91cfeb..ebd2b88b10e 100644 --- a/homeassistant/components/point/.translations/zh-Hans.json +++ b/homeassistant/components/point/.translations/zh-Hans.json @@ -1,8 +1,17 @@ { "config": { + "abort": { + "authorize_url_fail": "\u751f\u6210\u6388\u6743URL\u65f6\u53d1\u751f\u672a\u77e5\u9519\u8bef\u3002", + "authorize_url_timeout": "\u751f\u6210\u6388\u6743URL\u8d85\u65f6" + }, + "error": { + "follow_link": "\u8bf7\u5728\u70b9\u51fb\u63d0\u4ea4\u524d\u6309\u7167\u94fe\u63a5\u8fdb\u884c\u8eab\u4efd\u9a8c\u8bc1", + "no_token": "\u672a\u7ecfMinut\u9a8c\u8bc1" + }, "step": { "auth": { - "description": "\u8bf7\u8bbf\u95ee\u4e0b\u65b9\u7684\u94fe\u63a5\u5e76\u5141\u8bb8\u8bbf\u95ee\u60a8\u7684 Minut \u8d26\u6237\uff0c\u7136\u540e\u56de\u6765\u70b9\u51fb\u4e0b\u9762\u7684\u63d0\u4ea4\u3002\n\n[\u94fe\u63a5]({authorization_url})" + "description": "\u8bf7\u8bbf\u95ee\u4e0b\u65b9\u7684\u94fe\u63a5\u5e76\u5141\u8bb8\u8bbf\u95ee\u60a8\u7684 Minut \u8d26\u6237\uff0c\u7136\u540e\u56de\u6765\u70b9\u51fb\u4e0b\u9762\u7684\u63d0\u4ea4\u3002\n\n[\u94fe\u63a5]({authorization_url})", + "title": "\u8ba4\u8bc1\u70b9" }, "user": { "data": { diff --git a/homeassistant/components/rainmachine/.translations/zh-Hans.json b/homeassistant/components/rainmachine/.translations/zh-Hans.json index 7c6f07a7edd..e7171ca2867 100644 --- a/homeassistant/components/rainmachine/.translations/zh-Hans.json +++ b/homeassistant/components/rainmachine/.translations/zh-Hans.json @@ -1,11 +1,13 @@ { "config": { "error": { - "identifier_exists": "\u5e10\u6237\u5df2\u6ce8\u518c" + "identifier_exists": "\u8d26\u6237\u5df2\u6ce8\u518c", + "invalid_credentials": "\u65e0\u6548\u7684\u8eab\u4efd\u8ba4\u8bc1" }, "step": { "user": { "data": { + "ip_address": "\u4e3b\u673a\u540d\u6216IP\u5730\u5740", "password": "\u5bc6\u7801", "port": "\u7aef\u53e3" }, diff --git a/homeassistant/components/simplisafe/.translations/zh-Hans.json b/homeassistant/components/simplisafe/.translations/zh-Hans.json index 2316f5c7454..4c57baea77f 100644 --- a/homeassistant/components/simplisafe/.translations/zh-Hans.json +++ b/homeassistant/components/simplisafe/.translations/zh-Hans.json @@ -1,7 +1,7 @@ { "config": { "error": { - "identifier_exists": "\u5e10\u6237\u5df2\u6ce8\u518c", + "identifier_exists": "\u8d26\u6237\u5df2\u6ce8\u518c", "invalid_credentials": "\u65e0\u6548\u7684\u8eab\u4efd\u8ba4\u8bc1" }, "step": { diff --git a/homeassistant/components/smartthings/.translations/ca.json b/homeassistant/components/smartthings/.translations/ca.json new file mode 100644 index 00000000000..3c0ca05a8d5 --- /dev/null +++ b/homeassistant/components/smartthings/.translations/ca.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "app_not_installed": "Assegura't que has instal\u00b7lat i autoritzat l'aplicaci\u00f3 SmartApp de Home Assistant i torna-ho a provar.", + "app_setup_error": "No s'ha pogut configurar SmartApp. Siusplau, torna-ho a provar.", + "base_url_not_https": "L'`base_url` per al component `http` ha d'estar configurat i comen\u00e7ar amb `https://`.", + "token_already_setup": "El testimoni d'autenticaci\u00f3 ja ha estat configurat.", + "token_forbidden": "El testimoni d'autenticaci\u00f3 no t\u00e9 cont\u00e9 els apartats OAuth obligatoris.", + "token_invalid_format": "El testimoni d'autenticaci\u00f3 ha d'estar en format UID/GUID", + "token_unauthorized": "El testimoni d'autenticaci\u00f3 no \u00e9s v\u00e0lid o ja no t\u00e9 autoritzaci\u00f3." + }, + "step": { + "user": { + "data": { + "access_token": "Testimoni d'acc\u00e9s" + }, + "description": "Introdueix un [testimoni d'autenticaci\u00f3 d'acc\u00e9s personal] ({token_url}) de SmartThings que s'ha creat a trav\u00e9s les [instruccions] ({component_url}).", + "title": "Introdueix el testimoni d'autenticaci\u00f3 d'acc\u00e9s personal" + }, + "wait_install": { + "description": "Instal\u00b7la l'SmartApp de Home Assistant en almenys una ubicaci\u00f3 i fes clic a Enviar.", + "title": "Instal\u00b7laci\u00f3 de SmartApp" + } + }, + "title": "SmartThings" + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/.translations/en.json b/homeassistant/components/smartthings/.translations/en.json index 1fb4e878cb4..f2775b30ae2 100644 --- a/homeassistant/components/smartthings/.translations/en.json +++ b/homeassistant/components/smartthings/.translations/en.json @@ -1,27 +1,27 @@ { - "config": { - "title": "SmartThings", - "step": { - "user": { - "title": "Enter Personal Access Token", - "description": "Please enter a SmartThings [Personal Access Token]({token_url}) that has been created per the [instructions]({component_url}).", - "data": { - "access_token": "Access Token" - } - }, - "wait_install": { - "title": "Install SmartApp", - "description": "Please install the Home Assistant SmartApp in at least one location and click submit." - } - }, - "error": { - "token_invalid_format": "The token must be in the UID/GUID format", - "token_unauthorized": "The token is invalid or no longer authorized.", - "token_forbidden": "The token does not have the required OAuth scopes.", - "token_already_setup": "The token has already been setup.", - "app_setup_error": "Unable to setup the SmartApp. Please try again.", - "app_not_installed": "Please ensure you have installed and authorized the Home Assistant SmartApp and try again.", - "base_url_not_https": "The `base_url` for the `http` component must be configured and start with `https://`." + "config": { + "error": { + "app_not_installed": "Please ensure you have installed and authorized the Home Assistant SmartApp and try again.", + "app_setup_error": "Unable to setup the SmartApp. Please try again.", + "base_url_not_https": "The `base_url` for the `http` component must be configured and start with `https://`.", + "token_already_setup": "The token has already been setup.", + "token_forbidden": "The token does not have the required OAuth scopes.", + "token_invalid_format": "The token must be in the UID/GUID format", + "token_unauthorized": "The token is invalid or no longer authorized." + }, + "step": { + "user": { + "data": { + "access_token": "Access Token" + }, + "description": "Please enter a SmartThings [Personal Access Token]({token_url}) that has been created per the [instructions]({component_url}).", + "title": "Enter Personal Access Token" + }, + "wait_install": { + "description": "Please install the Home Assistant SmartApp in at least one location and click submit.", + "title": "Install SmartApp" + } + }, + "title": "SmartThings" } - } } \ No newline at end of file diff --git a/homeassistant/components/smartthings/.translations/lb.json b/homeassistant/components/smartthings/.translations/lb.json new file mode 100644 index 00000000000..fd59d187314 --- /dev/null +++ b/homeassistant/components/smartthings/.translations/lb.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "app_not_installed": "Stellt w.e.g s\u00e9cher dass d'Home Assistant SmartApp install\u00e9iert an autoris\u00e9iert ass, a prob\u00e9iert nach emol.", + "app_setup_error": "Kann SmartApp net install\u00e9ieren. Prob\u00e9iert w.e.g. nach emol.", + "base_url_not_https": "`base_url` fir den `http` Komponent muss konfigur\u00e9iert sinn a mat `https://`uf\u00e4nken.", + "token_already_setup": "Den Jeton gouf schonn ageriicht.", + "token_forbidden": "De Jeton huet net d\u00e9i n\u00e9ideg OAuth M\u00e9iglechkeeten.", + "token_invalid_format": "De Jeton muss am UID/GUID Format sinn", + "token_unauthorized": "De Jeton ass ong\u00eblteg oder net m\u00e9i autoris\u00e9iert." + }, + "step": { + "user": { + "data": { + "access_token": "Acc\u00e8ss Jeton" + }, + "description": "Gitt w.e.g. ee [Pers\u00e9inlechen Acc\u00e8s Jeton]({token_url}) vu SmartThings an dee via [d'Instruktiounen] ({component_url}) erstallt gouf.", + "title": "Pers\u00e9inlechen Acc\u00e8ss Jeton uginn" + }, + "wait_install": { + "description": "Install\u00e9iert d'Home Assistant SmartApp op mannst ee mol a klickt op Ofsch\u00e9cken.", + "title": "SmartApp install\u00e9ieren" + } + }, + "title": "SmartThings" + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/.translations/pl.json b/homeassistant/components/smartthings/.translations/pl.json new file mode 100644 index 00000000000..379cdf699b7 --- /dev/null +++ b/homeassistant/components/smartthings/.translations/pl.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "wait_install": { + "title": "Zainstaluj SmartApp" + } + }, + "title": "SmartThings" + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/.translations/zh-Hant.json b/homeassistant/components/smartthings/.translations/zh-Hant.json new file mode 100644 index 00000000000..952eafec60c --- /dev/null +++ b/homeassistant/components/smartthings/.translations/zh-Hant.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "app_not_installed": "\u8acb\u78ba\u8a8d\u5df2\u7d93\u5b89\u88dd\u4e26\u6388\u6b0a Home Assistant Smartapp \u5f8c\u518d\u8a66\u4e00\u6b21\u3002", + "app_setup_error": "\u7121\u6cd5\u8a2d\u5b9a SmartApp\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002", + "base_url_not_https": "\u5fc5\u9808\u8a2d\u5b9a\u300chttp\u300d\u5143\u4ef6\u4e4b\u300cbase_url\u300d\uff0c\u4e26\u4ee5\u300chttps://\u300d\u70ba\u958b\u982d\u3002", + "token_already_setup": "\u5bc6\u9470\u5df2\u8a2d\u5b9a\u904e\u3002", + "token_forbidden": "\u5bc6\u9470\u4e0d\u5177\u6240\u9700\u7684 OAuth \u7bc4\u570d\u3002", + "token_invalid_format": "\u5bc6\u9470\u5fc5\u9808\u70ba UID/GUID \u683c\u5f0f", + "token_unauthorized": "\u5bc6\u9470\u7121\u6548\u6216\u4e0d\u518d\u5177\u6709\u6388\u6b0a\u3002" + }, + "step": { + "user": { + "data": { + "access_token": "\u5b58\u53d6\u5bc6\u9470" + }, + "description": "\u8acb\u8f38\u5165\u8ddf\u8457[ \u6307\u5f15]({component_url})\u6240\u7522\u751f\u7684 SmartThings [\u500b\u4eba\u5b58\u53d6\u5bc6\u9470]({token_url})\u3002", + "title": "\u8f38\u5165\u500b\u4eba\u5b58\u53d6\u5bc6\u9470" + }, + "wait_install": { + "description": "\u8acb\u81f3\u5c11\u65bc\u4e00\u500b\u4f4d\u7f6e\u4e2d\u5b89\u88dd Home Assistant Smartapp\uff0c\u4e26\u9ede\u9078\u50b3\u9001\u3002", + "title": "\u5b89\u88dd SmartApp" + } + }, + "title": "SmartThings" + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/.translations/ca.json b/homeassistant/components/tellduslive/.translations/ca.json index db97b1ad6d8..75915735882 100644 --- a/homeassistant/components/tellduslive/.translations/ca.json +++ b/homeassistant/components/tellduslive/.translations/ca.json @@ -2,10 +2,14 @@ "config": { "abort": { "all_configured": "TelldusLive ja est\u00e0 configurat", + "already_setup": "TelldusLive ja est\u00e0 configurat", "authorize_url_fail": "S'ha produ\u00eft un error desconegut al generar l'URL d'autoritzaci\u00f3.", "authorize_url_timeout": "S'ha acabat el temps d'espera mentre \u00e9s generava l'URL d'autoritzaci\u00f3.", "unknown": "S'ha produ\u00eft un error desconegut" }, + "error": { + "auth_error": "Error d'autenticaci\u00f3, torna-ho a provar" + }, "step": { "auth": { "description": "Passos per enlla\u00e7ar el teu compte de TelldusLive:\n 1. Clica l'enlla\u00e7 de sota.\n 2. Inicia sessi\u00f3 a Telldus Live.\n 3. Autoritza **{app_name}** (clica **Yes**).\n 4. Torna aqu\u00ed i clica **SUBMIT**.\n \n [Enlla\u00e7 al compte de TelldusLive]({auth_url})", diff --git a/homeassistant/components/tellduslive/.translations/en.json b/homeassistant/components/tellduslive/.translations/en.json index 4ed9ef597f4..c2b00561858 100644 --- a/homeassistant/components/tellduslive/.translations/en.json +++ b/homeassistant/components/tellduslive/.translations/en.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "all_configured": "TelldusLive is already configured", "already_setup": "TelldusLive is already configured", "authorize_url_fail": "Unknown error generating an authorize url.", "authorize_url_timeout": "Timeout generating authorize url.", diff --git a/homeassistant/components/tellduslive/.translations/es.json b/homeassistant/components/tellduslive/.translations/es.json new file mode 100644 index 00000000000..4e7de72edc4 --- /dev/null +++ b/homeassistant/components/tellduslive/.translations/es.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "already_setup": "TelldusLive ya est\u00e1 configurado" + }, + "error": { + "auth_error": "Error de autenticaci\u00f3n, por favor int\u00e9ntalo de nuevo" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/.translations/ko.json b/homeassistant/components/tellduslive/.translations/ko.json index a7b68bbf8be..29f64a87cb3 100644 --- a/homeassistant/components/tellduslive/.translations/ko.json +++ b/homeassistant/components/tellduslive/.translations/ko.json @@ -2,10 +2,14 @@ "config": { "abort": { "all_configured": "TelldusLive \uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_setup": "TelldusLive \uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "authorize_url_fail": "\uc778\uc99d url \uc0dd\uc131\uc5d0 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d url \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "unknown": "\uc54c \uc218\uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, + "error": { + "auth_error": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574 \uc8fc\uc138\uc694." + }, "step": { "auth": { "description": "TelldusLive \uacc4\uc815\uc744 \uc5f0\uacb0\ud558\ub824\uba74:\n 1. \ud558\ub2e8\uc758 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694\n 2. Telldus Live \uc5d0 \ub85c\uadf8\uc778 \ud558\uc138\uc694\n 3. Authorize **{app_name}** (**Yes** \ub97c \ud074\ub9ad\ud558\uc138\uc694).\n 4. \ub2e4\uc2dc \uc5ec\uae30\ub85c \ub3cc\uc544\uc640\uc11c **SUBMIT** \uc744 \ud074\ub9ad\ud558\uc138\uc694.\n\n [TelldusLive \uacc4\uc815 \uc5f0\uacb0\ud558\uae30]({auth_url})", diff --git a/homeassistant/components/tellduslive/.translations/lb.json b/homeassistant/components/tellduslive/.translations/lb.json index 85de49776c1..5eb4d1b978a 100644 --- a/homeassistant/components/tellduslive/.translations/lb.json +++ b/homeassistant/components/tellduslive/.translations/lb.json @@ -2,10 +2,14 @@ "config": { "abort": { "all_configured": "TelldusLive ass scho konfigur\u00e9iert", + "already_setup": "TelldusLive ass scho konfigur\u00e9iert", "authorize_url_fail": "Onbekannte Feeler beim gener\u00e9ieren vun der Autorisatiouns URL.", "authorize_url_timeout": "Z\u00e4it Iwwerschreidung beim gener\u00e9ieren vun der Autorisatiouns URL.", "unknown": "Onbekannten Fehler opgetrueden" }, + "error": { + "auth_error": "Feeler bei der Authentifikatioun, prob\u00e9iert w.e.g. nach emol" + }, "step": { "auth": { "description": "Fir den TelldusLive Kont ze verbannen:\n1. Klickt op de Link \u00ebnnen\n2. Verbannt iech mat TelldusLive\n3. Autoris\u00e9iert **{app_name}** (klickt **Yes**)\n4. Kommt heihinner zer\u00e9ck a klickt **Ofsch\u00e9cken**\n\n[Tellduslive Kont verbannen]({auth_url})", diff --git a/homeassistant/components/tellduslive/.translations/no.json b/homeassistant/components/tellduslive/.translations/no.json index 5c3d343dd03..2c6439b364f 100644 --- a/homeassistant/components/tellduslive/.translations/no.json +++ b/homeassistant/components/tellduslive/.translations/no.json @@ -2,10 +2,14 @@ "config": { "abort": { "all_configured": "TelldusLive er allerede konfigurert", + "already_setup": "TelldusLive er allerede konfigurert", "authorize_url_fail": "Ukjent feil ved generering av autoriseringsadresse.", "authorize_url_timeout": "Tidsavbrudd ved generering av autoriseringsadresse.", "unknown": "Ukjent feil oppstod" }, + "error": { + "auth_error": "Autentiseringsfeil, vennligst pr\u00f8v igjen" + }, "step": { "auth": { "description": "For \u00e5 koble TelldusLive-kontoen din:\n 1. Klikk p\u00e5 linken under\n 2. Logg inn p\u00e5 Telldus Live \n 3. Tillat **{app_name}** (klikk**Ja**). \n 4. Kom tilbake hit og klikk **SUBMIT**. \n\n [Link TelldusLive-konto]({auth_url})", diff --git a/homeassistant/components/tellduslive/.translations/pl.json b/homeassistant/components/tellduslive/.translations/pl.json index 5ee9ac221a7..9d791e0e786 100644 --- a/homeassistant/components/tellduslive/.translations/pl.json +++ b/homeassistant/components/tellduslive/.translations/pl.json @@ -2,10 +2,14 @@ "config": { "abort": { "all_configured": "TelldusLive jest ju\u017c skonfigurowany", + "already_setup": "TelldusLive jest ju\u017c skonfigurowany", "authorize_url_fail": "Nieznany b\u0142\u0105d podczas generowania url autoryzacji.", "authorize_url_timeout": "Min\u0105\u0142 limit czasu generowania url autoryzacji.", "unknown": "Wyst\u0105pi\u0142 nieznany b\u0142\u0105d" }, + "error": { + "auth_error": "B\u0142\u0105d uwierzytelniania, spr\u00f3buj ponownie" + }, "step": { "auth": { "description": "Aby po\u0142\u0105czy\u0107 konto TelldusLive: \n 1. Kliknij poni\u017cszy link \n 2. Zaloguj si\u0119 do Telldus Live \n 3. Autoryzuj **{app_name}** (kliknij **Tak**). \n 4. Wr\u00f3\u0107 tutaj i kliknij **SUBMIT**. \n\n [Link do konta TelldusLive]({auth_url})", diff --git a/homeassistant/components/tellduslive/.translations/ru.json b/homeassistant/components/tellduslive/.translations/ru.json index 2e319b9400b..80dff6dc88a 100644 --- a/homeassistant/components/tellduslive/.translations/ru.json +++ b/homeassistant/components/tellduslive/.translations/ru.json @@ -2,10 +2,14 @@ "config": { "abort": { "all_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "already_setup": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", "authorize_url_fail": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", "authorize_url_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", "unknown": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430" }, + "error": { + "auth_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443" + }, "step": { "auth": { "description": "\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u0430\u043a\u043a\u0430\u0443\u043d\u0442 TelldusLive:\n 1. \u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435, \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0439 \u043d\u0438\u0436\u0435\n 2. \u0412\u043e\u0439\u0434\u0438\u0442\u0435 \u0432 Telldus Live\n 3. Authorize **{app_name}** (\u043d\u0430\u0436\u043c\u0438\u0442\u0435 **Yes**).\n 4. \u0412\u0435\u0440\u043d\u0438\u0442\u0435\u0441\u044c \u0441\u044e\u0434\u0430 \u0438 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 **\u041f\u041e\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u042c**.\n\n [\u0421\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 TelldusLive]({auth_url})", diff --git a/homeassistant/components/tellduslive/.translations/sl.json b/homeassistant/components/tellduslive/.translations/sl.json index f4b9f0fda98..16e6ddcb5f4 100644 --- a/homeassistant/components/tellduslive/.translations/sl.json +++ b/homeassistant/components/tellduslive/.translations/sl.json @@ -2,10 +2,14 @@ "config": { "abort": { "all_configured": "TelldusLive je \u017ee konfiguriran", + "already_setup": "TelldusLive je \u017ee konfiguriran", "authorize_url_fail": "Neznana napaka pri generiranju potrditvenega URL-ja.", "authorize_url_timeout": "\u010casovna omejitev za generiranje URL-ja je potekla.", "unknown": "Pri\u0161lo je do neznane napake" }, + "error": { + "auth_error": "Napaka pri preverjanju pristnosti, poskusite znova" + }, "step": { "auth": { "description": "\u010ce \u017eelite povezati svoj ra\u010dun TelldusLive: \n 1. Kliknite spodnjo povezavo \n 2. Prijavite se v Telldus Live \n 3. Dovolite ** {app_name} ** (kliknite ** Da **). \n 4. Pridi nazaj in kliknite ** SUBMIT **. \n\n [Link TelldusLive ra\u010dun] ( {auth_url} )", diff --git a/homeassistant/components/tellduslive/.translations/zh-Hans.json b/homeassistant/components/tellduslive/.translations/zh-Hans.json new file mode 100644 index 00000000000..f707b1f15f8 --- /dev/null +++ b/homeassistant/components/tellduslive/.translations/zh-Hans.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "all_configured": "Tellduslive \u5df2\u914d\u7f6e\u5b8c\u6210", + "authorize_url_fail": "\u751f\u6210\u6388\u6743URL\u65f6\u53d1\u751f\u672a\u77e5\u9519\u8bef\u3002", + "authorize_url_timeout": "\u751f\u6210\u6388\u6743URL\u8d85\u65f6", + "unknown": "\u53d1\u751f\u672a\u77e5\u7684\u9519\u8bef" + }, + "error": { + "auth_error": "\u53cc\u91cd\u8ba4\u8bc1\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5\u3002" + }, + "step": { + "auth": { + "description": "\u8981\u94fe\u63a5\u60a8\u7684TelldusLive\u8d26\u6237\uff1a \n 1.\u5355\u51fb\u4e0b\u9762\u7684\u94fe\u63a5\n 2.\u767b\u5f55Telldus Live \n 3.\u6388\u6743 **{app_name}** (\u70b9\u51fb **\u662f**)\u3002 \n 4.\u56de\u5230\u8fd9\u91cc\uff0c\u7136\u540e\u70b9\u51fb**\u63d0\u4ea4**\u3002 \n\n [TelldusLive\u8d26\u6237\u94fe\u63a5]({auth_url})" + }, + "user": { + "data": { + "host": "\u4e3b\u673a" + }, + "description": "\u7a7a\u767d" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/.translations/zh-Hant.json b/homeassistant/components/tellduslive/.translations/zh-Hant.json index a5e3c652c0c..c632b543634 100644 --- a/homeassistant/components/tellduslive/.translations/zh-Hant.json +++ b/homeassistant/components/tellduslive/.translations/zh-Hant.json @@ -2,10 +2,14 @@ "config": { "abort": { "all_configured": "TelldusLive \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_setup": "TelldusLive \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "authorize_url_fail": "\u7522\u751f\u8a8d\u8b49 URL \u6642\u767c\u751f\u672a\u77e5\u932f\u8aa4", "authorize_url_timeout": "\u7522\u751f\u8a8d\u8b49 URL \u6642\u903e\u6642", "unknown": "\u767c\u751f\u672a\u77e5\u932f\u8aa4\u3002" }, + "error": { + "auth_error": "\u8a8d\u8b49\u932f\u8aa4\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002" + }, "step": { "auth": { "description": "\u6b32\u9023\u7d50 TelldusLive \u5e33\u865f\uff1a\n 1. \u9ede\u9078\u4e0b\u65b9\u9023\u7d50\n 2. \u767b\u5165\u81f3 Telldus Live\n 3. \u5c0d **{app_name}** \u9032\u884c\u6388\u6b0a\uff08\u9ede\u9078 **Yes**\uff09\u3002\n 4. \u56de\u5230\u672c\u9801\u9762\u4e26\u9ede\u9078 **\u50b3\u9001**\u3002\n\n [Link TelldusLive account]({auth_url})", diff --git a/homeassistant/components/twilio/.translations/sl.json b/homeassistant/components/twilio/.translations/sl.json index 0321cb05452..86d2c44f11c 100644 --- a/homeassistant/components/twilio/.translations/sl.json +++ b/homeassistant/components/twilio/.translations/sl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "not_internet_accessible": " \u010ce \u017eelite prejemati sporo\u010dila Twilio, mora biti Home Assistent dostopen prek interneta.", + "not_internet_accessible": "\u010ce \u017eelite prejemati sporo\u010dila Twilio, mora biti Home Assistant dostopen prek interneta.", "one_instance_allowed": "Potrebna je samo ena instanca." }, "create_entry": { - "default": "Za po\u0161iljanje dogodkov Home Assistent-u, boste morali nastaviti [Webhooks z Twilio]({twilio_url}).\n\nIzpolnite naslednje informacije:\n\n- URL: `{webhook_url}`\n- Metoda: POST\n- Vrsta vsebine: application/x-www-form-urlencoded\n\nGlej [dokumentacijo]({docs_url}) o tem, kako nastavite avtomatizacijo za obravnavo dohodnih podatkov." + "default": "Za po\u0161iljanje dogodkov Home Assistant-u, boste morali nastaviti [Webhooks z Twilio]({twilio_url}).\n\nIzpolnite naslednje informacije:\n\n- URL: `{webhook_url}`\n- Metoda: POST\n- Vrsta vsebine: application/x-www-form-urlencoded\n\nGlej [dokumentacijo]({docs_url}) o tem, kako nastavite avtomatizacijo za obravnavo dohodnih podatkov." }, "step": { "user": { diff --git a/homeassistant/components/twilio/.translations/zh-Hans.json b/homeassistant/components/twilio/.translations/zh-Hans.json index e108fe12498..6fda9f0143c 100644 --- a/homeassistant/components/twilio/.translations/zh-Hans.json +++ b/homeassistant/components/twilio/.translations/zh-Hans.json @@ -1,7 +1,18 @@ { "config": { + "abort": { + "not_internet_accessible": "\u60a8\u7684 Home Assistant \u5b9e\u4f8b\u9700\u8981\u63a5\u5165\u4e92\u8054\u7f51\u4ee5\u63a5\u6536 Twilio \u6d88\u606f\u3002", + "one_instance_allowed": "\u4ec5\u9700\u4e00\u4e2a\u5b9e\u4f8b" + }, "create_entry": { "default": "\u8981\u5411 Home Assistant \u53d1\u9001\u4e8b\u4ef6\uff0c\u60a8\u9700\u8981\u914d\u7f6e [Twilio \u7684 Webhook]({twilio_url})\u3002\n\n\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u6709\u5173\u5982\u4f55\u914d\u7f6e\u81ea\u52a8\u5316\u4ee5\u5904\u7406\u4f20\u5165\u7684\u6570\u636e\uff0c\u8bf7\u53c2\u9605[\u6587\u6863]({docs_url})\u3002" - } + }, + "step": { + "user": { + "description": "\u60a8\u786e\u5b9a\u8981\u8bbe\u7f6e Twilio \u5417\uff1f", + "title": "\u8bbe\u7f6e Twilio Webhook" + } + }, + "title": "Twilio" } } \ No newline at end of file diff --git a/homeassistant/components/upnp/.translations/sl.json b/homeassistant/components/upnp/.translations/sl.json index 4bf6501bd2a..4c019d8f207 100644 --- a/homeassistant/components/upnp/.translations/sl.json +++ b/homeassistant/components/upnp/.translations/sl.json @@ -24,7 +24,7 @@ }, "user": { "data": { - "enable_port_mapping": "Omogo\u010dajo preslikavo vrat (port mapping) za Home Assistent-a", + "enable_port_mapping": "Omogo\u010dajo preslikavo vrat (port mapping) za Home Assistant-a", "enable_sensors": "Dodaj prometne senzorje", "igd": "UPnP/IGD" }, diff --git a/homeassistant/components/upnp/.translations/zh-Hans.json b/homeassistant/components/upnp/.translations/zh-Hans.json index b16172e97d7..2194a2dc264 100644 --- a/homeassistant/components/upnp/.translations/zh-Hans.json +++ b/homeassistant/components/upnp/.translations/zh-Hans.json @@ -4,9 +4,15 @@ "already_configured": "UPnP/IGD \u5df2\u914d\u7f6e\u5b8c\u6210", "incomplete_device": "\u5ffd\u7565\u4e0d\u5b8c\u6574\u7684 UPnP \u8bbe\u5907", "no_devices_discovered": "\u672a\u53d1\u73b0 UPnP/IGD", - "no_sensors_or_port_mapping": "\u81f3\u5c11\u542f\u7528\u4f20\u611f\u5668\u6216\u7aef\u53e3\u6620\u5c04" + "no_devices_found": "\u6ca1\u6709\u5728\u7f51\u7edc\u4e0a\u627e\u5230 UPnP/IGD \u8bbe\u5907\u3002", + "no_sensors_or_port_mapping": "\u81f3\u5c11\u542f\u7528\u4f20\u611f\u5668\u6216\u7aef\u53e3\u6620\u5c04", + "single_instance_allowed": "UPnP/IGD \u53ea\u9700\u8981\u914d\u7f6e\u4e00\u6b21\u3002" }, "step": { + "confirm": { + "description": "\u60a8\u60f3\u8981\u914d\u7f6e UPnP/IGD \u5417\uff1f", + "title": "UPnP/IGD" + }, "init": { "title": "UPnP/IGD" }, diff --git a/homeassistant/components/zha/.translations/zh-Hans.json b/homeassistant/components/zha/.translations/zh-Hans.json index 8befb2ee114..ce458fa32f1 100644 --- a/homeassistant/components/zha/.translations/zh-Hans.json +++ b/homeassistant/components/zha/.translations/zh-Hans.json @@ -1,11 +1,20 @@ { "config": { + "abort": { + "single_instance_allowed": "\u53ea\u5141\u8bb8\u4e00\u4e2a ZHA \u914d\u7f6e\u3002" + }, + "error": { + "cannot_connect": "\u65e0\u6cd5\u8fde\u63a5\u5230 ZHA \u8bbe\u5907\u3002" + }, "step": { "user": { "data": { "usb_path": "USB \u8bbe\u5907\u8def\u5f84" - } + }, + "description": "\u7a7a\u767d", + "title": "ZHA" } - } + }, + "title": "ZHA" } } \ No newline at end of file From 3553d26f6b3e66056b8d786c9b09f88baab9d7df Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 2 Feb 2019 14:03:13 -0800 Subject: [PATCH 036/242] Updated frontend to 20190202.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 8f3afef16cd..b5836e67ffc 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==20190201.0'] +REQUIREMENTS = ['home-assistant-frontend==20190202.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index 5a16f8b75b1..388498d487a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -526,7 +526,7 @@ hole==0.3.0 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190201.0 +home-assistant-frontend==20190202.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e531754ec71..c46596e209e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -113,7 +113,7 @@ hdate==0.8.7 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190201.0 +home-assistant-frontend==20190202.0 # homeassistant.components.homekit_controller homekit==0.12.2 From 38ea43b678485db952286d9cfb34396084b37b5b Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Sun, 3 Feb 2019 00:08:37 -0600 Subject: [PATCH 037/242] Add SmartThings button support via events (#20707) * Add event support for buttons * binary_sensor test clean-up --- .../components/smartthings/__init__.py | 15 ++++++++- homeassistant/components/smartthings/const.py | 2 ++ tests/components/smartthings/conftest.py | 22 ++++++++----- .../smartthings/test_binary_sensor.py | 31 +++++++++++++------ tests/components/smartthings/test_init.py | 31 ++++++++++++++++++- 5 files changed, 82 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index c705a3df73e..d86524ef62b 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -19,7 +19,7 @@ from homeassistant.helpers.typing import ConfigType, HomeAssistantType from .config_flow import SmartThingsFlowHandler # noqa from .const import ( CONF_APP_ID, CONF_INSTALLED_APP_ID, DATA_BROKERS, DATA_MANAGER, DOMAIN, - SIGNAL_SMARTTHINGS_UPDATE, SUPPORTED_PLATFORMS) + EVENT_BUTTON, SIGNAL_SMARTTHINGS_UPDATE, SUPPORTED_PLATFORMS) from .smartapp import ( setup_smartapp, setup_smartapp_endpoint, validate_installed_app) @@ -154,6 +154,19 @@ class DeviceBroker: continue device.status.apply_attribute_update( evt.component_id, evt.capability, evt.attribute, evt.value) + + # Fire events for buttons + if evt.capability == 'button' and evt.attribute == 'button': + data = { + 'component_id': evt.component_id, + 'device_id': evt.device_id, + 'location_id': evt.location_id, + 'value': evt.value, + 'name': device.label + } + self._hass.bus.async_fire(EVENT_BUTTON, data) + _LOGGER.debug("Fired button event: %s", data) + updated_devices.add(device.device_id) _LOGGER.debug("Update received with %s events and updated %s devices", len(req.events), len(updated_devices)) diff --git a/homeassistant/components/smartthings/const.py b/homeassistant/components/smartthings/const.py index f545f84832d..3d0e5cb95f8 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -12,6 +12,7 @@ CONF_LOCATION_ID = 'location_id' DATA_MANAGER = 'manager' DATA_BROKERS = 'brokers' DOMAIN = 'smartthings' +EVENT_BUTTON = "smartthings.button" SIGNAL_SMARTTHINGS_UPDATE = 'smartthings_update' SIGNAL_SMARTAPP_PREFIX = 'smartthings_smartap_' SETTINGS_INSTANCE_ID = "hassInstanceId" @@ -25,6 +26,7 @@ SUPPORTED_PLATFORMS = [ ] SUPPORTED_CAPABILITIES = [ 'accelerationSensor', + 'button', 'colorControl', 'colorTemperature', 'contactSensor', diff --git a/tests/components/smartthings/conftest.py b/tests/components/smartthings/conftest.py index 56bb5a62888..7358e05f346 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -254,14 +254,16 @@ def device_factory_fixture(): @pytest.fixture(name="event_factory") def event_factory_fixture(): """Fixture for creating mock devices.""" - def _factory(device_id, event_type="DEVICE_EVENT"): + def _factory(device_id, event_type="DEVICE_EVENT", capability='', + attribute='Updated', value='Value'): event = Mock() event.event_type = event_type event.device_id = device_id event.component_id = 'main' - event.capability = '' - event.attribute = 'Updated' - event.value = 'Value' + event.capability = capability + event.attribute = attribute + event.value = value + event.location_id = str(uuid4()) return event return _factory @@ -269,11 +271,15 @@ def event_factory_fixture(): @pytest.fixture(name="event_request_factory") def event_request_factory_fixture(event_factory): """Fixture for creating mock smartapp event requests.""" - def _factory(device_ids): + def _factory(device_ids=None, events=None): request = Mock() request.installed_app_id = uuid4() - request.events = [event_factory(id) for id in device_ids] - request.events.append(event_factory(uuid4())) - request.events.append(event_factory(device_ids[0], event_type="OTHER")) + if events is None: + events = [] + if device_ids: + events.extend([event_factory(id) for id in device_ids]) + events.append(event_factory(uuid4())) + events.append(event_factory(device_ids[0], event_type="OTHER")) + request.events = events return request return _factory diff --git a/tests/components/smartthings/test_binary_sensor.py b/tests/components/smartthings/test_binary_sensor.py index 2e0c46842b0..92d891c06d6 100644 --- a/tests/components/smartthings/test_binary_sensor.py +++ b/tests/components/smartthings/test_binary_sensor.py @@ -6,9 +6,10 @@ real HTTP calls are not initiated during testing. """ from pysmartthings import Attribute, Capability +from homeassistant.components.binary_sensor import DEVICE_CLASSES from homeassistant.components.smartthings import DeviceBroker, binary_sensor from homeassistant.components.smartthings.const import ( - DATA_BROKERS, DOMAIN, SIGNAL_SMARTTHINGS_UPDATE) + DATA_BROKERS, DOMAIN, SIGNAL_SMARTTHINGS_UPDATE, SUPPORTED_CAPABILITIES) from homeassistant.config_entries import ( CONN_CLASS_CLOUD_PUSH, SOURCE_USER, ConfigEntry) from homeassistant.const import ATTR_FRIENDLY_NAME @@ -32,6 +33,18 @@ async def _setup_platform(hass, *devices): return config_entry +async def test_mapping_integrity(): + """Test ensures the map dicts have proper integrity.""" + # Ensure every CAPABILITY_TO_ATTRIB key is in SUPPORTED_CAPABILITIES + # Ensure every CAPABILITY_TO_ATTRIB value is in ATTRIB_TO_CLASS keys + for capability, attrib in binary_sensor.CAPABILITY_TO_ATTRIB.items(): + assert capability in SUPPORTED_CAPABILITIES, capability + assert attrib in binary_sensor.ATTRIB_TO_CLASS.keys(), attrib + # Ensure every ATTRIB_TO_CLASS value is in DEVICE_CLASSES + for device_class in binary_sensor.ATTRIB_TO_CLASS.values(): + assert device_class in DEVICE_CLASSES + + async def test_async_setup_platform(): """Test setup platform does nothing (it uses config entries).""" await binary_sensor.async_setup_platform(None, None, None) @@ -58,15 +71,15 @@ async def test_entity_and_device_attributes(hass, device_factory): # Act await _setup_platform(hass, device) # Assert - entity = entity_registry.async_get('binary_sensor.motion_sensor_1_motion') - assert entity - assert entity.unique_id == device.device_id + '.' + Attribute.motion - device_entry = device_registry.async_get_device( + entry = entity_registry.async_get('binary_sensor.motion_sensor_1_motion') + assert entry + assert entry.unique_id == device.device_id + '.' + Attribute.motion + entry = device_registry.async_get_device( {(DOMAIN, device.device_id)}, []) - assert device_entry - assert device_entry.name == device.label - assert device_entry.model == device.device_type_name - assert device_entry.manufacturer == 'Unavailable' + assert entry + assert entry.name == device.label + assert entry.model == device.device_type_name + assert entry.manufacturer == 'Unavailable' async def test_update_from_signal(hass, device_factory): diff --git a/tests/components/smartthings/test_init.py b/tests/components/smartthings/test_init.py index d20d2d4e047..4aef42c1b6f 100644 --- a/tests/components/smartthings/test_init.py +++ b/tests/components/smartthings/test_init.py @@ -8,7 +8,8 @@ import pytest from homeassistant.components import smartthings from homeassistant.components.smartthings.const import ( - DATA_BROKERS, DOMAIN, SIGNAL_SMARTTHINGS_UPDATE, SUPPORTED_PLATFORMS) + DATA_BROKERS, DOMAIN, EVENT_BUTTON, SIGNAL_SMARTTHINGS_UPDATE, + SUPPORTED_PLATFORMS) from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -181,3 +182,31 @@ async def test_event_handler_ignores_other_installed_app( await hass.async_block_till_done() assert not called + + +async def test_event_handler_fires_button_events( + hass, device_factory, event_factory, event_request_factory): + """Test the event handler fires button events.""" + device = device_factory('Button 1', ['button']) + event = event_factory(device.device_id, capability='button', + attribute='button', value='pushed') + request = event_request_factory(events=[event]) + called = False + + def handler(evt): + nonlocal called + called = True + assert evt.data == { + 'component_id': 'main', + 'device_id': device.device_id, + 'location_id': event.location_id, + 'value': 'pushed', + 'name': device.label + } + hass.bus.async_listen(EVENT_BUTTON, handler) + broker = smartthings.DeviceBroker( + hass, [device], request.installed_app_id) + await broker.event_handler(request, None, None) + await hass.async_block_till_done() + + assert called From 74cdf7c34708d5dc701ca1bf05ee375685a02541 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Sun, 3 Feb 2019 07:03:31 -0500 Subject: [PATCH 038/242] Add tests for ZHA switch (#20691) * start test setup test cleanup test deps update switch test actually update test deps cleanup and remove switch from coveragerc comment refactor to use fixtures and shared components lint * remove availability part that isn't in zha yet * review comments and cleanup * review comments * add switch back unil post reorg merge --- requirements_test_all.txt | 6 ++ script/gen_requirements_all.py | 2 + tests/components/zha/common.py | 152 ++++++++++++++++++++++++++++ tests/components/zha/conftest.py | 38 +++++++ tests/components/zha/test_switch.py | 66 ++++++++++++ 5 files changed, 264 insertions(+) create mode 100644 tests/components/zha/common.py create mode 100644 tests/components/zha/conftest.py create mode 100644 tests/components/zha/test_switch.py diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c46596e209e..5feb4165155 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -49,6 +49,9 @@ aiounifi==4 # homeassistant.components.notify.apns apns2==0.3.0 +# homeassistant.components.zha +bellows==0.7.0 + # homeassistant.components.calendar.caldav caldav==0.5.0 @@ -298,3 +301,6 @@ wakeonlan==1.1.6 # homeassistant.components.cloud warrant==0.6.1 + +# homeassistant.components.zha +zigpy==0.2.0 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 398b2791848..4a99ef84bc9 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -124,6 +124,8 @@ TEST_REQUIREMENTS = ( 'vultr', 'YesssSMS', 'ruamel.yaml', + 'zigpy', + 'bellows', ) IGNORE_PACKAGES = ( diff --git a/tests/components/zha/common.py b/tests/components/zha/common.py new file mode 100644 index 00000000000..ea0e5f43467 --- /dev/null +++ b/tests/components/zha/common.py @@ -0,0 +1,152 @@ +"""Common test objects.""" +import time +from unittest.mock import Mock +from homeassistant.components.zha.core.helpers import convert_ieee +from homeassistant.components.zha.core.const import ( + DATA_ZHA, DATA_ZHA_CONFIG, DATA_ZHA_DISPATCHERS, DATA_ZHA_BRIDGE_ID +) +from homeassistant.util import slugify + + +class FakeApplication: + """Fake application for mocking zigpy.""" + + def __init__(self): + """Init fake application.""" + self.ieee = convert_ieee("00:15:8d:00:02:32:4f:32") + self.nwk = 0x087d + + +APPLICATION = FakeApplication() + + +class FakeEndpoint: + """Fake endpoint for moking zigpy.""" + + def __init__(self): + """Init fake endpoint.""" + from zigpy.profiles.zha import PROFILE_ID + self.device = None + self.endpoint_id = 1 + self.in_clusters = {} + self.out_clusters = {} + self._cluster_attr = {} + self.status = 1 + self.manufacturer = 'FakeManufacturer' + self.model = 'FakeModel' + self.profile_id = PROFILE_ID + self.device_type = None + + def add_input_cluster(self, cluster_id): + """Add an input cluster.""" + from zigpy.zcl import Cluster + cluster = Cluster.from_id(self, cluster_id) + patch_cluster(cluster) + self.in_clusters[cluster_id] = cluster + if hasattr(cluster, 'ep_attribute'): + setattr(self, cluster.ep_attribute, cluster) + + def add_output_cluster(self, cluster_id): + """Add an output cluster.""" + from zigpy.zcl import Cluster + cluster = Cluster.from_id(self, cluster_id) + patch_cluster(cluster) + self.out_clusters[cluster_id] = cluster + + +def patch_cluster(cluster): + """Patch a cluster for testing.""" + cluster.deserialize = Mock() + cluster.handle_cluster_request = Mock() + cluster.handle_cluster_general_request = Mock() + cluster.read_attributes_raw = Mock() + cluster.read_attributes = Mock() + cluster.write_attributes = Mock() + cluster.bind = Mock() + cluster.unbind = Mock() + cluster.configure_reporting = Mock() + + +class FakeDevice: + """Fake device for mocking zigpy.""" + + def __init__(self): + """Init fake device.""" + self._application = APPLICATION + self.ieee = convert_ieee("00:0d:6f:00:0a:90:69:e7") + self.nwk = 0xb79c + self.zdo = Mock() + self.endpoints = {0: self.zdo} + self.lqi = 255 + self.rssi = 8 + self.last_seen = time.time() + self.status = 2 + self.initializing = False + self.manufacturer = 'FakeManufacturer' + self.model = 'FakeModel' + + +def make_device(in_cluster_ids, out_cluster_ids, device_type): + """Make a fake device using the specified cluster classes.""" + device = FakeDevice() + endpoint = FakeEndpoint() + endpoint.device = device + device.endpoints[endpoint.endpoint_id] = endpoint + endpoint.device_type = device_type + + for cluster_id in in_cluster_ids: + endpoint.add_input_cluster(cluster_id) + + for cluster_id in out_cluster_ids: + endpoint.add_output_cluster(cluster_id) + + return device + + +async def async_init_zigpy_device( + hass, in_cluster_ids, out_cluster_ids, device_type, gateway): + """Create and initialize a device.""" + device = make_device(in_cluster_ids, out_cluster_ids, device_type) + await gateway.async_device_initialized(device, False) + await hass.async_block_till_done() + return device + + +def make_attribute(attrid, value, status=0): + """Make an attribute.""" + from zigpy.zcl.foundation import Attribute, TypeValue + attr = Attribute() + attr.attrid = attrid + attr.value = TypeValue() + attr.value.value = value + return attr + + +async def async_setup_entry(hass, config_entry): + """Mock setup entry for zha.""" + hass.data[DATA_ZHA][DATA_ZHA_CONFIG] = {} + hass.data[DATA_ZHA][DATA_ZHA_DISPATCHERS] = [] + hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = APPLICATION.ieee + return True + + +def make_entity_id(domain, device, cluster): + """Make the entity id for the entity under testing.""" + ieee = device.ieee + ieeetail = ''.join(['%02x' % (o, ) for o in ieee[-4:]]) + entity_id = "{}.{}_{}_{}_{}{}".format( + domain, + slugify(device.manufacturer), + slugify(device.model), + ieeetail, + cluster.endpoint.endpoint_id, + "_{}".format(cluster.cluster_id), + ) + return entity_id + + +async def async_enable_traffic(hass, zha_gateway, zha_device): + """Allow traffic to flow through the gateway and the zha device.""" + await zha_gateway.accept_zigbee_messages({}) + zha_device.update_available(True) + await hass.async_block_till_done() diff --git a/tests/components/zha/conftest.py b/tests/components/zha/conftest.py new file mode 100644 index 00000000000..59509e2ab03 --- /dev/null +++ b/tests/components/zha/conftest.py @@ -0,0 +1,38 @@ +"""Test configuration for the ZHA component.""" +from unittest.mock import patch +import pytest +from homeassistant import config_entries +from homeassistant.components.zha.core.const import ( + DOMAIN, DATA_ZHA +) +from homeassistant.components.zha.core.gateway import ZHAGateway +from .common import async_setup_entry + + +@pytest.fixture(name='config_entry') +def config_entry_fixture(hass): + """Fixture representing a config entry.""" + config_entry = config_entries.ConfigEntry( + 1, DOMAIN, 'Mock Title', {}, 'test', + config_entries.CONN_CLASS_LOCAL_PUSH) + return config_entry + + +@pytest.fixture(name='zha_gateway') +def zha_gateway_fixture(hass): + """Fixture representing a zha gateway.""" + return ZHAGateway(hass, {}) + + +@pytest.fixture(autouse=True) +async def setup_zha(hass, config_entry): + """Load the ZHA component.""" + # this prevents needing an actual radio and zigbee network available + with patch('homeassistant.components.zha.async_setup_entry', + async_setup_entry): + hass.data[DATA_ZHA] = {} + + # init ZHA + await hass.config_entries.async_forward_entry_setup( + config_entry, DOMAIN) + await hass.async_block_till_done() diff --git a/tests/components/zha/test_switch.py b/tests/components/zha/test_switch.py new file mode 100644 index 00000000000..e86eb7fdd9b --- /dev/null +++ b/tests/components/zha/test_switch.py @@ -0,0 +1,66 @@ +"""Test zha switch.""" +from unittest.mock import call, patch +from homeassistant.components.switch import DOMAIN +from homeassistant.const import STATE_ON, STATE_OFF +from tests.common import mock_coro +from .common import ( + async_init_zigpy_device, make_attribute, make_entity_id +) + +ON = 1 +OFF = 0 + + +async def test_switch(hass, config_entry, zha_gateway): + """Test zha switch platform.""" + from zigpy.zcl.clusters.general import OnOff + from zigpy.zcl.foundation import Status + + # create zigpy device + zigpy_device = await async_init_zigpy_device( + hass, [OnOff.cluster_id], [], None, zha_gateway) + + # load up switch domain + await hass.config_entries.async_forward_entry_setup( + config_entry, DOMAIN) + await hass.async_block_till_done() + + cluster = zigpy_device.endpoints.get(1).on_off + entity_id = make_entity_id(DOMAIN, zigpy_device, cluster) + + # test that the state has changed from unavailable to off + assert hass.states.get(entity_id).state == STATE_OFF + + # turn on at switch + attr = make_attribute(0, 1) + cluster.handle_message(False, 1, 0x0a, [[attr]]) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == STATE_ON + + # turn off at switch + attr.value.value = 0 + cluster.handle_message(False, 0, 0x0a, [[attr]]) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == STATE_OFF + + with patch( + 'zigpy.zcl.Cluster.request', + return_value=mock_coro([Status.SUCCESS, Status.SUCCESS])): + # turn on via UI + await hass.services.async_call(DOMAIN, 'turn_on', { + 'entity_id': entity_id + }, blocking=True) + assert len(cluster.request.mock_calls) == 1 + assert cluster.request.call_args == call( + False, ON, (), expect_reply=True, manufacturer=None) + + with patch( + 'zigpy.zcl.Cluster.request', + return_value=mock_coro([Status.SUCCESS, Status.SUCCESS])): + # turn off via UI + await hass.services.async_call(DOMAIN, 'turn_off', { + 'entity_id': entity_id + }, blocking=True) + assert len(cluster.request.mock_calls) == 1 + assert cluster.request.call_args == call( + False, OFF, (), expect_reply=True, manufacturer=None) From 0e5aa5801abe3a00d242625540c048e9ba570a3c Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Sun, 3 Feb 2019 09:27:49 -0800 Subject: [PATCH 039/242] Remove SUPPORT_VOLUME_SET from Fire TV component Volume control isn't actually implemented, so it shouldn't show as being supported. --- homeassistant/components/media_player/firetv.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/firetv.py b/homeassistant/components/media_player/firetv.py index c04ed96d6e0..3b8f296eec0 100644 --- a/homeassistant/components/media_player/firetv.py +++ b/homeassistant/components/media_player/firetv.py @@ -12,7 +12,7 @@ import voluptuous as vol from homeassistant.components.media_player import ( MediaPlayerDevice, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_STOP, - SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_SET, ) + SUPPORT_TURN_OFF, SUPPORT_TURN_ON) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PORT, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, STATE_STANDBY) @@ -25,7 +25,7 @@ _LOGGER = logging.getLogger(__name__) SUPPORT_FIRETV = SUPPORT_PAUSE | \ SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \ SUPPORT_NEXT_TRACK | SUPPORT_SELECT_SOURCE | SUPPORT_STOP | \ - SUPPORT_VOLUME_SET | SUPPORT_PLAY + SUPPORT_PLAY CONF_ADBKEY = 'adbkey' CONF_GET_SOURCE = 'get_source' From 9c116026740597424cc727ff3a256f6eb1f65ee0 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Sun, 3 Feb 2019 16:03:35 -0500 Subject: [PATCH 040/242] Add ZHA sensor tests (#20710) * add sensor tests * update switch test * add sensor back to coveragerc * review comments * added comments --- tests/components/zha/common.py | 90 +++++++++++---- tests/components/zha/conftest.py | 13 ++- tests/components/zha/test_sensor.py | 164 ++++++++++++++++++++++++++++ tests/components/zha/test_switch.py | 6 +- 4 files changed, 247 insertions(+), 26 deletions(-) create mode 100644 tests/components/zha/test_sensor.py diff --git a/tests/components/zha/common.py b/tests/components/zha/common.py index ea0e5f43467..c7a9c786054 100644 --- a/tests/components/zha/common.py +++ b/tests/components/zha/common.py @@ -1,11 +1,13 @@ """Common test objects.""" import time -from unittest.mock import Mock +from unittest.mock import patch, Mock +from homeassistant.const import STATE_UNKNOWN from homeassistant.components.zha.core.helpers import convert_ieee from homeassistant.components.zha.core.const import ( DATA_ZHA, DATA_ZHA_CONFIG, DATA_ZHA_DISPATCHERS, DATA_ZHA_BRIDGE_ID ) from homeassistant.util import slugify +from tests.common import mock_coro class FakeApplication: @@ -23,7 +25,7 @@ APPLICATION = FakeApplication() class FakeEndpoint: """Fake endpoint for moking zigpy.""" - def __init__(self): + def __init__(self, manufacturer, model): """Init fake endpoint.""" from zigpy.profiles.zha import PROFILE_ID self.device = None @@ -32,8 +34,8 @@ class FakeEndpoint: self.out_clusters = {} self._cluster_attr = {} self.status = 1 - self.manufacturer = 'FakeManufacturer' - self.model = 'FakeModel' + self.manufacturer = manufacturer + self.model = model self.profile_id = PROFILE_ID self.device_type = None @@ -61,19 +63,16 @@ def patch_cluster(cluster): cluster.handle_cluster_general_request = Mock() cluster.read_attributes_raw = Mock() cluster.read_attributes = Mock() - cluster.write_attributes = Mock() - cluster.bind = Mock() cluster.unbind = Mock() - cluster.configure_reporting = Mock() class FakeDevice: """Fake device for mocking zigpy.""" - def __init__(self): + def __init__(self, ieee, manufacturer, model): """Init fake device.""" self._application = APPLICATION - self.ieee = convert_ieee("00:0d:6f:00:0a:90:69:e7") + self.ieee = convert_ieee(ieee) self.nwk = 0xb79c self.zdo = Mock() self.endpoints = {0: self.zdo} @@ -82,14 +81,15 @@ class FakeDevice: self.last_seen = time.time() self.status = 2 self.initializing = False - self.manufacturer = 'FakeManufacturer' - self.model = 'FakeModel' + self.manufacturer = manufacturer + self.model = model -def make_device(in_cluster_ids, out_cluster_ids, device_type): +def make_device(in_cluster_ids, out_cluster_ids, device_type, ieee, + manufacturer, model): """Make a fake device using the specified cluster classes.""" - device = FakeDevice() - endpoint = FakeEndpoint() + device = FakeDevice(ieee, manufacturer, model) + endpoint = FakeEndpoint(manufacturer, model) endpoint.device = device device.endpoints[endpoint.endpoint_id] = endpoint endpoint.device_type = device_type @@ -104,10 +104,20 @@ def make_device(in_cluster_ids, out_cluster_ids, device_type): async def async_init_zigpy_device( - hass, in_cluster_ids, out_cluster_ids, device_type, gateway): - """Create and initialize a device.""" - device = make_device(in_cluster_ids, out_cluster_ids, device_type) - await gateway.async_device_initialized(device, False) + hass, in_cluster_ids, out_cluster_ids, device_type, gateway, + ieee="00:0d:6f:00:0a:90:69:e7", manufacturer="FakeManufacturer", + model="FakeModel", is_new_join=False): + """Create and initialize a device. + + This creates a fake device and adds it to the "network". It can be used to + test existing device functionality and new device pairing functionality. + The is_new_join parameter influences whether or not the device will go + through cluster binding and zigbee cluster configure reporting. That only + happens when the device is paired to the network for the first time. + """ + device = make_device(in_cluster_ids, out_cluster_ids, device_type, ieee, + manufacturer, model) + await gateway.async_device_initialized(device, is_new_join) await hass.async_block_till_done() return device @@ -130,8 +140,12 @@ async def async_setup_entry(hass, config_entry): return True -def make_entity_id(domain, device, cluster): - """Make the entity id for the entity under testing.""" +def make_entity_id(domain, device, cluster, use_suffix=True): + """Make the entity id for the entity under testing. + + This is used to get the entity id in order to get the state from the state + machine so that we can test state changes. + """ ieee = device.ieee ieeetail = ''.join(['%02x' % (o, ) for o in ieee[-4:]]) entity_id = "{}.{}_{}_{}_{}{}".format( @@ -140,13 +154,43 @@ def make_entity_id(domain, device, cluster): slugify(device.model), ieeetail, cluster.endpoint.endpoint_id, - "_{}".format(cluster.cluster_id), + ("", "_{}".format(cluster.cluster_id))[use_suffix], ) return entity_id -async def async_enable_traffic(hass, zha_gateway, zha_device): +async def async_enable_traffic(hass, zha_gateway, zha_devices): """Allow traffic to flow through the gateway and the zha device.""" await zha_gateway.accept_zigbee_messages({}) - zha_device.update_available(True) + for zha_device in zha_devices: + zha_device.update_available(True) await hass.async_block_till_done() + + +async def async_test_device_join( + hass, zha_gateway, cluster_id, domain, device_type=None, + expected_state=STATE_UNKNOWN): + """Test a newly joining device. + + This creates a new fake device and adds it to the network. It is meant to + simulate pairing a new device to the network so that code pathways that + only trigger during device joins can be tested. + """ + from zigpy.zcl.foundation import Status + # create zigpy device mocking out the zigbee network operations + with patch( + 'zigpy.zcl.Cluster.configure_reporting', + return_value=mock_coro([Status.SUCCESS, Status.SUCCESS])): + with patch( + 'zigpy.zcl.Cluster.bind', + return_value=mock_coro([Status.SUCCESS, Status.SUCCESS])): + zigpy_device = await async_init_zigpy_device( + hass, [cluster_id], [], device_type, zha_gateway, + ieee="00:0d:6f:00:0a:90:69:f7", + manufacturer="FakeMan{}".format(cluster_id), + model="FakeMod{}".format(cluster_id), + is_new_join=True) + cluster = zigpy_device.endpoints.get(1).in_clusters[cluster_id] + entity_id = make_entity_id( + domain, zigpy_device, cluster, use_suffix=device_type is None) + assert hass.states.get(entity_id).state == expected_state diff --git a/tests/components/zha/conftest.py b/tests/components/zha/conftest.py index 59509e2ab03..624c6a02964 100644 --- a/tests/components/zha/conftest.py +++ b/tests/components/zha/conftest.py @@ -20,13 +20,22 @@ def config_entry_fixture(hass): @pytest.fixture(name='zha_gateway') def zha_gateway_fixture(hass): - """Fixture representing a zha gateway.""" + """Fixture representing a zha gateway. + + Create a ZHAGateway object that can be used to interact with as if we + had a real zigbee network running. + """ return ZHAGateway(hass, {}) @pytest.fixture(autouse=True) async def setup_zha(hass, config_entry): - """Load the ZHA component.""" + """Load the ZHA component. + + This will init the ZHA component. It loads the component in HA so that + we can test the domains that ZHA supports without actually having a zigbee + network running. + """ # this prevents needing an actual radio and zigbee network available with patch('homeassistant.components.zha.async_setup_entry', async_setup_entry): diff --git a/tests/components/zha/test_sensor.py b/tests/components/zha/test_sensor.py new file mode 100644 index 00000000000..3933f416e3d --- /dev/null +++ b/tests/components/zha/test_sensor.py @@ -0,0 +1,164 @@ +"""Test zha sensor.""" +from homeassistant.components.sensor import DOMAIN +from homeassistant.const import STATE_UNKNOWN +from .common import ( + async_init_zigpy_device, make_attribute, make_entity_id, + async_test_device_join +) + + +async def test_sensor(hass, config_entry, zha_gateway): + """Test zha sensor platform.""" + from zigpy.zcl.clusters.measurement import ( + RelativeHumidity, TemperatureMeasurement, PressureMeasurement, + IlluminanceMeasurement + ) + from zigpy.zcl.clusters.smartenergy import Metering + from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement + + # list of cluster ids to create devices and sensor entities for + cluster_ids = [ + RelativeHumidity.cluster_id, + TemperatureMeasurement.cluster_id, + PressureMeasurement.cluster_id, + IlluminanceMeasurement.cluster_id, + Metering.cluster_id, + ElectricalMeasurement.cluster_id + ] + + # devices that were created from cluster_ids list above + zigpy_device_infos = await async_build_devices( + hass, zha_gateway, config_entry, cluster_ids) + + # ensure the sensor entity was created for each id in cluster_ids + for cluster_id in cluster_ids: + zigpy_device_info = zigpy_device_infos[cluster_id] + entity_id = zigpy_device_info["entity_id"] + assert hass.states.get(entity_id).state == STATE_UNKNOWN + + # get the humidity device info and test the associated sensor logic + device_info = zigpy_device_infos[RelativeHumidity.cluster_id] + await async_test_humidity(hass, device_info) + + # get the temperature device info and test the associated sensor logic + device_info = zigpy_device_infos[TemperatureMeasurement.cluster_id] + await async_test_temperature(hass, device_info) + + # get the pressure device info and test the associated sensor logic + device_info = zigpy_device_infos[PressureMeasurement.cluster_id] + await async_test_pressure(hass, device_info) + + # get the illuminance device info and test the associated sensor logic + device_info = zigpy_device_infos[IlluminanceMeasurement.cluster_id] + await async_test_illuminance(hass, device_info) + + # get the metering device info and test the associated sensor logic + device_info = zigpy_device_infos[Metering.cluster_id] + await async_test_metering(hass, device_info) + + # get the electrical_measurement device info and test the associated + # sensor logic + device_info = zigpy_device_infos[ElectricalMeasurement.cluster_id] + await async_test_electrical_measurement(hass, device_info) + + # test joining a new temperature sensor to the network + await async_test_device_join( + hass, zha_gateway, TemperatureMeasurement.cluster_id, DOMAIN) + + +async def async_build_devices(hass, zha_gateway, config_entry, cluster_ids): + """Build a zigpy device for each cluster id. + + This will build devices for all cluster ids that exist in cluster_ids. + They get added to the network and then the sensor component is loaded + which will cause sensor entites to get created for each device. + A dict containing relevant device info for testing is returned. It contains + the entity id, zigpy device, and the zigbee cluster for the sensor. + """ + device_infos = {} + counter = 0 + for cluster_id in cluster_ids: + # create zigpy device + device_infos[cluster_id] = {"zigpy_device": None} + device_infos[cluster_id]["zigpy_device"] = await \ + async_init_zigpy_device( + hass, [cluster_id], [], None, zha_gateway, + ieee="{}0:15:8d:00:02:32:4f:32".format(counter), + manufacturer="Fake{}".format(cluster_id), + model="FakeModel{}".format(cluster_id)) + + counter += 1 + + # load up sensor domain + await hass.config_entries.async_forward_entry_setup( + config_entry, DOMAIN) + await hass.async_block_till_done() + + # put the other relevant info in the device info dict + for cluster_id in cluster_ids: + device_info = device_infos[cluster_id] + zigpy_device = device_info["zigpy_device"] + device_info["cluster"] = zigpy_device.endpoints.get( + 1).in_clusters[cluster_id] + device_info["entity_id"] = make_entity_id( + DOMAIN, zigpy_device, device_info["cluster"]) + return device_infos + + +async def async_test_humidity(hass, device_info): + """Test humidity sensor.""" + await send_attribute_report(hass, device_info["cluster"], 0, 1000) + assert_state(hass, device_info, '10.0', '%') + + +async def async_test_temperature(hass, device_info): + """Test temperature sensor.""" + await send_attribute_report(hass, device_info["cluster"], 0, 2900) + assert_state(hass, device_info, '29.0', '°C') + + +async def async_test_pressure(hass, device_info): + """Test pressure sensor.""" + await send_attribute_report(hass, device_info["cluster"], 0, 1000) + assert_state(hass, device_info, '1000', 'hPa') + + +async def async_test_illuminance(hass, device_info): + """Test illuminance sensor.""" + await send_attribute_report(hass, device_info["cluster"], 0, 10) + assert_state(hass, device_info, '10', 'lx') + + +async def async_test_metering(hass, device_info): + """Test metering sensor.""" + await send_attribute_report(hass, device_info["cluster"], 1024, 10) + assert_state(hass, device_info, '10', 'W') + + +async def async_test_electrical_measurement(hass, device_info): + """Test electrical measurement sensor.""" + await send_attribute_report(hass, device_info["cluster"], 1291, 100) + assert_state(hass, device_info, '10.0', 'W') + + +async def send_attribute_report(hass, cluster, attrid, value): + """Cause the sensor to receive an attribute report from the network. + + This is to simulate the normal device communication that happens when a + device is paired to the zigbee network. + """ + attr = make_attribute(attrid, value) + cluster.handle_message(False, 1, 0x0a, [[attr]]) + await hass.async_block_till_done() + + +def assert_state(hass, device_info, state, unit_of_measurement): + """Check that the state is what is expected. + + This is used to ensure that the logic in each sensor class handled the + attribute report it received correctly. + """ + hass_state = hass.states.get(device_info["entity_id"]) + assert hass_state.state == state + assert hass_state.attributes.get('unit_of_measurement') == \ + unit_of_measurement diff --git a/tests/components/zha/test_switch.py b/tests/components/zha/test_switch.py index e86eb7fdd9b..d3415bde59b 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -4,7 +4,8 @@ from homeassistant.components.switch import DOMAIN from homeassistant.const import STATE_ON, STATE_OFF from tests.common import mock_coro from .common import ( - async_init_zigpy_device, make_attribute, make_entity_id + async_init_zigpy_device, make_attribute, make_entity_id, + async_test_device_join ) ON = 1 @@ -64,3 +65,6 @@ async def test_switch(hass, config_entry, zha_gateway): assert len(cluster.request.mock_calls) == 1 assert cluster.request.call_args == call( False, OFF, (), expect_reply=True, manufacturer=None) + + await async_test_device_join( + hass, zha_gateway, OnOff.cluster_id, DOMAIN, expected_state=STATE_OFF) From 5506569c3a2a5d53cf733e20a740592621b9a53a Mon Sep 17 00:00:00 2001 From: Dane Date: Sun, 3 Feb 2019 21:06:39 +0000 Subject: [PATCH 041/242] Change log level for 'loading devices' message (#20721) The 'Loading [wireless] devices from Mikrotik ([ip address])' message is incredibly spammy at the info log level, such that in the last 24 hours on my installation, that log message has appeared 6732 times, versus 70 for every other log message. I've moved this message to the debug log level as I don't believe it adds anything at the info level, and makes it harder to diagnose other problems. --- homeassistant/components/device_tracker/mikrotik.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/mikrotik.py b/homeassistant/components/device_tracker/mikrotik.py index cddcd1f26ee..c4635f8dc43 100644 --- a/homeassistant/components/device_tracker/mikrotik.py +++ b/homeassistant/components/device_tracker/mikrotik.py @@ -174,7 +174,7 @@ class MikrotikScanner(DeviceScanner): else: devices_tracker = 'ip' - _LOGGER.info( + _LOGGER.debug( "Loading %s devices from Mikrotik (%s) ...", devices_tracker, self.host) From 5c4dc3a54fed041a8d5693ee28390bf779df2731 Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Sun, 3 Feb 2019 13:57:17 -0800 Subject: [PATCH 042/242] Add app_id property to Fire TV component (#20719) --- homeassistant/components/media_player/firetv.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/media_player/firetv.py b/homeassistant/components/media_player/firetv.py index c04ed96d6e0..6e9b223817a 100644 --- a/homeassistant/components/media_player/firetv.py +++ b/homeassistant/components/media_player/firetv.py @@ -171,6 +171,11 @@ class FireTVDevice(MediaPlayerDevice): """Return whether or not the ADB connection is valid.""" return self._available + @property + def app_id(self): + """Return the current app.""" + return self._current_app + @property def source(self): """Return the current app.""" From ce05af272016a6ead951361661ad429d58948639 Mon Sep 17 00:00:00 2001 From: David Lie Date: Sun, 3 Feb 2019 17:47:38 -0500 Subject: [PATCH 043/242] Revert pyfoscam back to libpyfoscam (#20727) * Change foscam python library to pyfoscam, which is more up to date and has several critical bug fixes. * Update requirements_all.txt to match. * Inserting automatically generated requirements.txt * Revert changes until pyfoscam captures recent bug fixes. The pyfoscam version pulled by pip is currently broken. * Updated requirements_all.txt based on changing pyfoscam back to libpyfoscam. --- homeassistant/components/camera/foscam.py | 4 ++-- requirements_all.txt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/camera/foscam.py b/homeassistant/components/camera/foscam.py index 173e115cbaf..ceec57f7755 100644 --- a/homeassistant/components/camera/foscam.py +++ b/homeassistant/components/camera/foscam.py @@ -15,7 +15,7 @@ from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pyfoscam==1.2'] +REQUIREMENTS = ['libpyfoscam==1.0'] CONF_IP = 'ip' @@ -43,7 +43,7 @@ class FoscamCam(Camera): def __init__(self, device_info): """Initialize a Foscam camera.""" - from foscam import FoscamCamera + from libpyfoscam import FoscamCamera super(FoscamCam, self).__init__() diff --git a/requirements_all.txt b/requirements_all.txt index 388498d487a..072ec06f2c7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -605,6 +605,9 @@ libnacl==1.6.1 # homeassistant.components.dyson libpurecoollink==0.4.2 +# homeassistant.components.camera.foscam +libpyfoscam==1.0 + # homeassistant.components.device_tracker.mikrotik librouteros==2.2.0 @@ -1014,9 +1017,6 @@ pyflunearyou==1.0.1 # homeassistant.components.light.futurenow pyfnip==0.2 -# homeassistant.components.camera.foscam -pyfoscam==1.2 - # homeassistant.components.fritzbox pyfritzhome==0.4.0 From a05031c22e31b87e8a52d893178b5d469f19c1b9 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sun, 3 Feb 2019 16:23:30 -0700 Subject: [PATCH 044/242] Fix temperature unit conversion in Ambient PWS (#20723) --- .../components/ambient_station/__init__.py | 75 +++++++++---------- .../components/ambient_station/const.py | 3 - .../components/ambient_station/sensor.py | 21 +----- 3 files changed, 38 insertions(+), 61 deletions(-) diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index 788927a2700..0991336f42a 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -11,7 +11,7 @@ import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_NAME, ATTR_LOCATION, CONF_API_KEY, CONF_MONITORED_CONDITIONS, - CONF_UNIT_SYSTEM, EVENT_HOMEASSISTANT_STOP) + EVENT_HOMEASSISTANT_STOP) from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -19,8 +19,7 @@ from homeassistant.helpers.event import async_call_later from .config_flow import configured_instances from .const import ( - ATTR_LAST_DATA, CONF_APP_KEY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE, UNITS_SI, - UNITS_US) + ATTR_LAST_DATA, CONF_APP_KEY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE) REQUIREMENTS = ['aioambient==0.1.0'] _LOGGER = logging.getLogger(__name__) @@ -28,36 +27,36 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_SOCKET_MIN_RETRY = 15 SENSOR_TYPES = { - '24hourrainin': ['24 Hr Rain', 'in'], - 'baromabsin': ['Abs Pressure', 'inHg'], - 'baromrelin': ['Rel Pressure', 'inHg'], - 'battout': ['Battery', ''], - 'co2': ['co2', 'ppm'], - 'dailyrainin': ['Daily Rain', 'in'], - 'dewPoint': ['Dew Point', ['°F', '°C']], - 'eventrainin': ['Event Rain', 'in'], - 'feelsLike': ['Feels Like', ['°F', '°C']], - 'hourlyrainin': ['Hourly Rain Rate', 'in/hr'], - 'humidity': ['Humidity', '%'], - 'humidityin': ['Humidity In', '%'], - 'lastRain': ['Last Rain', ''], - 'maxdailygust': ['Max Gust', 'mph'], - 'monthlyrainin': ['Monthly Rain', 'in'], - 'solarradiation': ['Solar Rad', 'W/m^2'], - 'tempf': ['Temp', ['°F', '°C']], - 'tempinf': ['Inside Temp', ['°F', '°C']], - 'totalrainin': ['Lifetime Rain', 'in'], - 'uv': ['uv', 'Index'], - 'weeklyrainin': ['Weekly Rain', 'in'], - 'winddir': ['Wind Dir', '°'], - 'winddir_avg10m': ['Wind Dir Avg 10m', '°'], - 'winddir_avg2m': ['Wind Dir Avg 2m', 'mph'], - 'windgustdir': ['Gust Dir', '°'], - 'windgustmph': ['Wind Gust', 'mph'], - 'windspdmph_avg10m': ['Wind Avg 10m', 'mph'], - 'windspdmph_avg2m': ['Wind Avg 2m', 'mph'], - 'windspeedmph': ['Wind Speed', 'mph'], - 'yearlyrainin': ['Yearly Rain', 'in'], + '24hourrainin': ('24 Hr Rain', 'in'), + 'baromabsin': ('Abs Pressure', 'inHg'), + 'baromrelin': ('Rel Pressure', 'inHg'), + 'battout': ('Battery', ''), + 'co2': ('co2', 'ppm'), + 'dailyrainin': ('Daily Rain', 'in'), + 'dewPoint': ('Dew Point', '°F'), + 'eventrainin': ('Event Rain', 'in'), + 'feelsLike': ('Feels Like', '°F'), + 'hourlyrainin': ('Hourly Rain Rate', 'in/hr'), + 'humidity': ('Humidity', '%'), + 'humidityin': ('Humidity In', '%'), + 'lastRain': ('Last Rain', ''), + 'maxdailygust': ('Max Gust', 'mph'), + 'monthlyrainin': ('Monthly Rain', 'in'), + 'solarradiation': ('Solar Rad', 'W/m^2'), + 'tempf': ('Temp', '°F'), + 'tempinf': ('Inside Temp', '°F'), + 'totalrainin': ('Lifetime Rain', 'in'), + 'uv': ('uv', 'Index'), + 'weeklyrainin': ('Weekly Rain', 'in'), + 'winddir': ('Wind Dir', '°'), + 'winddir_avg10m': ('Wind Dir Avg 10m', '°'), + 'winddir_avg2m': ('Wind Dir Avg 2m', 'mph'), + 'windgustdir': ('Gust Dir', '°'), + 'windgustmph': ('Wind Gust', 'mph'), + 'windspdmph_avg10m': ('Wind Avg 10m', 'mph'), + 'windspdmph_avg2m': ('Wind Avg 2m', 'mph'), + 'windspeedmph': ('Wind Speed', 'mph'), + 'yearlyrainin': ('Yearly Rain', 'in'), } CONFIG_SCHEMA = vol.Schema({ @@ -70,8 +69,6 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional( CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), - vol.Optional(CONF_UNIT_SYSTEM): - vol.In([UNITS_SI, UNITS_US]), }) }, extra=vol.ALLOW_EXTRA) @@ -111,8 +108,7 @@ async def async_setup_entry(hass, config_entry): config_entry.data[CONF_API_KEY], config_entry.data[CONF_APP_KEY], session), config_entry.data.get( - CONF_MONITORED_CONDITIONS, list(SENSOR_TYPES)), - config_entry.data.get(CONF_UNIT_SYSTEM)) + CONF_MONITORED_CONDITIONS, list(SENSOR_TYPES))) hass.loop.create_task(ambient.ws_connect()) hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = ambient except WebsocketConnectionError as err: @@ -139,9 +135,7 @@ async def async_unload_entry(hass, config_entry): class AmbientStation: """Define a class to handle the Ambient websocket.""" - def __init__( - self, hass, config_entry, client, monitored_conditions, - unit_system): + def __init__(self, hass, config_entry, client, monitored_conditions): """Initialize.""" self._config_entry = config_entry self._hass = hass @@ -149,7 +143,6 @@ class AmbientStation: self.client = client self.monitored_conditions = monitored_conditions self.stations = {} - self.unit_system = unit_system async def ws_connect(self): """Register handlers and connect to the websocket.""" diff --git a/homeassistant/components/ambient_station/const.py b/homeassistant/components/ambient_station/const.py index df2c5462e66..75606a1c699 100644 --- a/homeassistant/components/ambient_station/const.py +++ b/homeassistant/components/ambient_station/const.py @@ -8,6 +8,3 @@ CONF_APP_KEY = 'app_key' DATA_CLIENT = 'data_client' TOPIC_UPDATE = 'update' - -UNITS_SI = 'si' -UNITS_US = 'us' diff --git a/homeassistant/components/ambient_station/sensor.py b/homeassistant/components/ambient_station/sensor.py index d2d89233472..9e0833e3441 100644 --- a/homeassistant/components/ambient_station/sensor.py +++ b/homeassistant/components/ambient_station/sensor.py @@ -12,14 +12,11 @@ from homeassistant.const import ATTR_NAME from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from .const import ( - ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TOPIC_UPDATE, UNITS_SI, UNITS_US) +from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TOPIC_UPDATE DEPENDENCIES = ['ambient_station'] _LOGGER = logging.getLogger(__name__) -UNIT_SYSTEM = {UNITS_US: 0, UNITS_SI: 1} - async def async_setup_platform( hass, config, async_add_entities, discovery_info=None): @@ -31,20 +28,10 @@ async def async_setup_entry(hass, entry, async_add_entities): """Set up an Ambient PWS sensor based on a config entry.""" ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] - if ambient.unit_system: - sys_units = ambient.unit_system - elif hass.config.units.is_metric: - sys_units = UNITS_SI - else: - sys_units = UNITS_US - sensor_list = [] for mac_address, station in ambient.stations.items(): for condition in ambient.monitored_conditions: name, unit = SENSOR_TYPES[condition] - if isinstance(unit, list): - unit = unit[UNIT_SYSTEM[sys_units]] - sensor_list.append( AmbientWeatherSensor( ambient, mac_address, station[ATTR_NAME], condition, name, @@ -58,7 +45,7 @@ class AmbientWeatherSensor(Entity): def __init__( self, ambient, mac_address, station_name, sensor_type, sensor_name, - units): + unit): """Initialize the sensor.""" self._ambient = ambient self._async_unsub_dispatcher_connect = None @@ -67,7 +54,7 @@ class AmbientWeatherSensor(Entity): self._sensor_type = sensor_type self._state = None self._station_name = station_name - self._units = units + self._unit = unit @property def name(self): @@ -87,7 +74,7 @@ class AmbientWeatherSensor(Entity): @property def unit_of_measurement(self): """Return the unit of measurement.""" - return self._units + return self._unit @property def unique_id(self): From 1fe67fb1b0ceb7859be8192bfe9a29fca3f6eb30 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 3 Feb 2019 11:32:15 -0800 Subject: [PATCH 045/242] Updated frontend to 20190203.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 b5836e67ffc..46652b4d7b0 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==20190202.0'] +REQUIREMENTS = ['home-assistant-frontend==20190203.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index 072ec06f2c7..93e5b45fcf5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -526,7 +526,7 @@ hole==0.3.0 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190202.0 +home-assistant-frontend==20190203.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5feb4165155..deee1efca3f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -116,7 +116,7 @@ hdate==0.8.7 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190202.0 +home-assistant-frontend==20190203.0 # homeassistant.components.homekit_controller homekit==0.12.2 From ec625f02fcc852aed667eabebc7d5a7270364f58 Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Sun, 3 Feb 2019 22:43:59 -0800 Subject: [PATCH 046/242] Clean up fastdotcom by doing time tracking outside of the data object (#20725) --- homeassistant/components/fastdotcom/__init__.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/fastdotcom/__init__.py b/homeassistant/components/fastdotcom/__init__.py index b5df45216e5..aef8e4c48f9 100644 --- a/homeassistant/components/fastdotcom/__init__.py +++ b/homeassistant/components/fastdotcom/__init__.py @@ -41,9 +41,12 @@ CONFIG_SCHEMA = vol.Schema({ async def async_setup(hass, config): """Set up the Fast.com component.""" conf = config[DOMAIN] - data = hass.data[DOMAIN] = SpeedtestData( - hass, conf[CONF_UPDATE_INTERVAL], conf[CONF_MANUAL] - ) + data = hass.data[DOMAIN] = SpeedtestData(hass) + + if not conf[CONF_MANUAL]: + async_track_time_interval( + hass, data.update, conf[CONF_UPDATE_INTERVAL] + ) def update(call=None): """Service call to manually update the data.""" @@ -61,14 +64,12 @@ async def async_setup(hass, config): class SpeedtestData: """Get the latest data from fast.com.""" - def __init__(self, hass, interval, manual): + def __init__(self, hass): """Initialize the data object.""" self.data = None self._hass = hass - if not manual: - async_track_time_interval(self._hass, self.update, interval) - def update(self): + def update(self, now=None): """Get the latest data from fast.com.""" from fastdotcom import fast_com _LOGGER.debug("Executing fast.com speedtest") From a3c8439ce27659ebb717b215284b09151c219626 Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Mon, 4 Feb 2019 00:47:04 -0800 Subject: [PATCH 047/242] Split out speedtest into a component and a sensor platform (#20527) * Move sensor.speedtest to the new speedtestdotnet component. * Split out speedtest.net into a component and sensor platform * Remove the throttle and add async_track_time_interval * Add should_poll and cleanup * Update requirements_all.txt * Move time interval tracking out of the data class and into the setup method * Add now=None argument to update --- .coveragerc | 2 +- homeassistant/components/sensor/speedtest.py | 171 ------------------ .../components/speedtestdotnet/__init__.py | 93 ++++++++++ .../components/speedtestdotnet/const.py | 10 + .../components/speedtestdotnet/sensor.py | 123 +++++++++++++ .../components/speedtestdotnet/services.yaml | 2 + requirements_all.txt | 2 +- 7 files changed, 230 insertions(+), 173 deletions(-) delete mode 100644 homeassistant/components/sensor/speedtest.py create mode 100644 homeassistant/components/speedtestdotnet/__init__.py create mode 100644 homeassistant/components/speedtestdotnet/const.py create mode 100644 homeassistant/components/speedtestdotnet/sensor.py create mode 100644 homeassistant/components/speedtestdotnet/services.yaml diff --git a/.coveragerc b/.coveragerc index 65e4656297f..e7454ccfa9c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -537,7 +537,6 @@ omit = homeassistant/components/sensor/socialblade.py homeassistant/components/sensor/solaredge.py homeassistant/components/sensor/sonarr.py - homeassistant/components/sensor/speedtest.py homeassistant/components/sensor/spotcrime.py homeassistant/components/sensor/srp_energy.py homeassistant/components/sensor/starlingbank.py @@ -580,6 +579,7 @@ omit = homeassistant/components/skybell/* homeassistant/components/smappee/* homeassistant/components/sonos/* + homeassistant/components/speedtestdotnet/* homeassistant/components/spc.py homeassistant/components/spider/* homeassistant/components/switch/acer_projector.py diff --git a/homeassistant/components/sensor/speedtest.py b/homeassistant/components/sensor/speedtest.py deleted file mode 100644 index f834b51b064..00000000000 --- a/homeassistant/components/sensor/speedtest.py +++ /dev/null @@ -1,171 +0,0 @@ -""" -Support for Speedtest.net. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.speedtest/ -""" -import logging - -import voluptuous as vol - -from homeassistant.components.sensor import DOMAIN, PLATFORM_SCHEMA -from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.event import track_time_change -from homeassistant.helpers.restore_state import RestoreEntity -import homeassistant.util.dt as dt_util - -REQUIREMENTS = ['speedtest-cli==2.0.2'] - -_LOGGER = logging.getLogger(__name__) - -ATTR_BYTES_RECEIVED = 'bytes_received' -ATTR_BYTES_SENT = 'bytes_sent' -ATTR_SERVER_COUNTRY = 'server_country' -ATTR_SERVER_HOST = 'server_host' -ATTR_SERVER_ID = 'server_id' -ATTR_SERVER_LATENCY = 'latency' -ATTR_SERVER_NAME = 'server_name' - -CONF_ATTRIBUTION = "Data retrieved from Speedtest by Ookla" -CONF_SECOND = 'second' -CONF_MINUTE = 'minute' -CONF_HOUR = 'hour' -CONF_SERVER_ID = 'server_id' -CONF_MANUAL = 'manual' - -ICON = 'mdi:speedometer' - -SENSOR_TYPES = { - 'ping': ['Ping', 'ms'], - 'download': ['Download', 'Mbit/s'], - 'upload': ['Upload', 'Mbit/s'], -} - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_MONITORED_CONDITIONS): - vol.All(cv.ensure_list, [vol.In(list(SENSOR_TYPES))]), - vol.Optional(CONF_HOUR): - vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(0, 23))]), - vol.Optional(CONF_MANUAL, default=False): cv.boolean, - vol.Optional(CONF_MINUTE, default=[0]): - vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(0, 59))]), - vol.Optional(CONF_SECOND, default=[0]): - vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(0, 59))]), - vol.Optional(CONF_SERVER_ID): cv.positive_int, -}) - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Speedtest sensor.""" - data = SpeedtestData(hass, config) - - dev = [] - for sensor in config[CONF_MONITORED_CONDITIONS]: - dev.append(SpeedtestSensor(data, sensor)) - - add_entities(dev) - - def update(call=None): - """Update service for manual updates.""" - data.update(dt_util.now()) - for sensor in dev: - sensor.update() - - hass.services.register(DOMAIN, 'update_speedtest', update) - - -class SpeedtestSensor(RestoreEntity): - """Implementation of a speedtest.net sensor.""" - - def __init__(self, speedtest_data, sensor_type): - """Initialize the sensor.""" - self._name = SENSOR_TYPES[sensor_type][0] - self.speedtest_client = speedtest_data - self.type = sensor_type - self._state = None - self._data = None - self._unit_of_measurement = SENSOR_TYPES[self.type][1] - - @property - def name(self): - """Return the name of the sensor.""" - return '{} {}'.format('Speedtest', self._name) - - @property - def state(self): - """Return the state of the device.""" - return self._state - - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity, if any.""" - return self._unit_of_measurement - - @property - def icon(self): - """Return icon.""" - return ICON - - @property - def device_state_attributes(self): - """Return the state attributes.""" - if self._data is not None: - return { - ATTR_ATTRIBUTION: CONF_ATTRIBUTION, - ATTR_BYTES_RECEIVED: self._data['bytes_received'], - ATTR_BYTES_SENT: self._data['bytes_sent'], - ATTR_SERVER_COUNTRY: self._data['server']['country'], - ATTR_SERVER_ID: self._data['server']['id'], - ATTR_SERVER_LATENCY: self._data['server']['latency'], - ATTR_SERVER_NAME: self._data['server']['name'], - } - - def update(self): - """Get the latest data and update the states.""" - self._data = self.speedtest_client.data - if self._data is None: - return - - if self.type == 'ping': - self._state = self._data['ping'] - elif self.type == 'download': - self._state = round(self._data['download'] / 10**6, 2) - elif self.type == 'upload': - self._state = round(self._data['upload'] / 10**6, 2) - - async def async_added_to_hass(self): - """Handle all entity which are about to be added.""" - await super().async_added_to_hass() - state = await self.async_get_last_state() - if not state: - return - self._state = state.state - - -class SpeedtestData: - """Get the latest data from speedtest.net.""" - - def __init__(self, hass, config): - """Initialize the data object.""" - self.data = None - self._server_id = config.get(CONF_SERVER_ID) - if not config.get(CONF_MANUAL): - track_time_change( - hass, self.update, second=config.get(CONF_SECOND), - minute=config.get(CONF_MINUTE), hour=config.get(CONF_HOUR)) - - def update(self, now): - """Get the latest data from speedtest.net.""" - import speedtest - _LOGGER.debug("Executing speedtest...") - - servers = [] if self._server_id is None else [self._server_id] - - speed = speedtest.Speedtest() - speed.get_servers(servers) - speed.get_best_server() - speed.download() - speed.upload() - - self.data = speed.results.dict() diff --git a/homeassistant/components/speedtestdotnet/__init__.py b/homeassistant/components/speedtestdotnet/__init__.py new file mode 100644 index 00000000000..ce6f376d1b0 --- /dev/null +++ b/homeassistant/components/speedtestdotnet/__init__.py @@ -0,0 +1,93 @@ +""" +Support for testing internet speed via Speedtest.net. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/speedtestdotnet/ +""" +import logging +from datetime import timedelta + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.speedtestdotnet.const import DOMAIN, \ + DATA_UPDATED, SENSOR_TYPES +from homeassistant.const import CONF_MONITORED_CONDITIONS, \ + CONF_UPDATE_INTERVAL +from homeassistant.helpers.discovery import async_load_platform +from homeassistant.helpers.dispatcher import dispatcher_send +from homeassistant.helpers.event import async_track_time_interval +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN + +REQUIREMENTS = ['speedtest-cli==2.0.2'] + +_LOGGER = logging.getLogger(__name__) + +CONF_SERVER_ID = 'server_id' +CONF_MANUAL = 'manual' + +DEFAULT_INTERVAL = timedelta(hours=1) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Optional(CONF_SERVER_ID): cv.positive_int, + vol.Optional(CONF_UPDATE_INTERVAL, default=DEFAULT_INTERVAL): + vol.All( + cv.time_period, cv.positive_timedelta + ), + vol.Optional(CONF_MANUAL, default=False): cv.boolean, + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): + vol.All(cv.ensure_list, [vol.In(list(SENSOR_TYPES))]) + }) +}, extra=vol.ALLOW_EXTRA) + + +async def async_setup(hass, config): + """Set up the Speedtest.net component.""" + conf = config[DOMAIN] + data = hass.data[DOMAIN] = SpeedtestData(hass, conf.get(CONF_SERVER_ID)) + + if not conf[CONF_MANUAL]: + async_track_time_interval( + hass, data.update, conf[CONF_UPDATE_INTERVAL] + ) + + def update(call=None): + """Service call to manually update the data.""" + data.update() + + hass.services.async_register(DOMAIN, 'speedtest', update) + + hass.async_create_task( + async_load_platform( + hass, + SENSOR_DOMAIN, + DOMAIN, + conf[CONF_MONITORED_CONDITIONS], + config + ) + ) + + return True + + +class SpeedtestData: + """Get the latest data from speedtest.net.""" + + def __init__(self, hass, server_id): + """Initialize the data object.""" + self.data = None + self._hass = hass + self._servers = [] if server_id is None else [server_id] + + def update(self, now=None): + """Get the latest data from speedtest.net.""" + import speedtest + _LOGGER.debug("Executing speedtest.net speedtest") + speed = speedtest.Speedtest() + speed.get_servers(self._servers) + speed.get_best_server() + speed.download() + speed.upload() + self.data = speed.results.dict() + dispatcher_send(self._hass, DATA_UPDATED) diff --git a/homeassistant/components/speedtestdotnet/const.py b/homeassistant/components/speedtestdotnet/const.py new file mode 100644 index 00000000000..7f19d796fd0 --- /dev/null +++ b/homeassistant/components/speedtestdotnet/const.py @@ -0,0 +1,10 @@ +"""Consts used by Speedtest.net.""" + +DOMAIN = 'speedtestdotnet' +DATA_UPDATED = '{}_data_updated'.format(DOMAIN) + +SENSOR_TYPES = { + 'ping': ['Ping', 'ms'], + 'download': ['Download', 'Mbit/s'], + 'upload': ['Upload', 'Mbit/s'], +} diff --git a/homeassistant/components/speedtestdotnet/sensor.py b/homeassistant/components/speedtestdotnet/sensor.py new file mode 100644 index 00000000000..33557ddacab --- /dev/null +++ b/homeassistant/components/speedtestdotnet/sensor.py @@ -0,0 +1,123 @@ +""" +Support for Speedtest.net internet speed testing sensor. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.speedtestdotnet/ +""" +import logging + +from homeassistant.components.speedtestdotnet.const import \ + DOMAIN as SPEEDTESTDOTNET_DOMAIN, DATA_UPDATED, SENSOR_TYPES +from homeassistant.const import ATTR_ATTRIBUTION +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.restore_state import RestoreEntity + +DEPENDENCIES = ['speedtestdotnet'] + +_LOGGER = logging.getLogger(__name__) + +ATTR_BYTES_RECEIVED = 'bytes_received' +ATTR_BYTES_SENT = 'bytes_sent' +ATTR_SERVER_COUNTRY = 'server_country' +ATTR_SERVER_HOST = 'server_host' +ATTR_SERVER_ID = 'server_id' +ATTR_SERVER_LATENCY = 'latency' +ATTR_SERVER_NAME = 'server_name' + +ATTRIBUTION = 'Data retrieved from Speedtest.net by Ookla' + +ICON = 'mdi:speedometer' + + +async def async_setup_platform(hass, config, async_add_entities, + discovery_info): + """Set up the Speedtest.net sensor.""" + data = hass.data[SPEEDTESTDOTNET_DOMAIN] + async_add_entities( + [SpeedtestSensor(data, sensor) for sensor in discovery_info] + ) + + +class SpeedtestSensor(RestoreEntity): + """Implementation of a speedtest.net sensor.""" + + def __init__(self, speedtest_data, sensor_type): + """Initialize the sensor.""" + self._name = SENSOR_TYPES[sensor_type][0] + self.speedtest_client = speedtest_data + self.type = sensor_type + self._state = None + self._data = None + self._unit_of_measurement = SENSOR_TYPES[self.type][1] + + @property + def name(self): + """Return the name of the sensor.""" + return '{} {}'.format('Speedtest', self._name) + + @property + def state(self): + """Return the state of the device.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return self._unit_of_measurement + + @property + def icon(self): + """Return icon.""" + return ICON + + @property + def should_poll(self): + """Return the polling requirement for this sensor.""" + return False + + @property + def device_state_attributes(self): + """Return the state attributes.""" + attributes = { + ATTR_ATTRIBUTION: ATTRIBUTION + } + if self._data is not None: + return attributes.update({ + ATTR_BYTES_RECEIVED: self._data['bytes_received'], + ATTR_BYTES_SENT: self._data['bytes_sent'], + ATTR_SERVER_COUNTRY: self._data['server']['country'], + ATTR_SERVER_ID: self._data['server']['id'], + ATTR_SERVER_LATENCY: self._data['server']['latency'], + ATTR_SERVER_NAME: self._data['server']['name'], + }) + return attributes + + async def async_added_to_hass(self): + """Handle entity which will be added.""" + await super().async_added_to_hass() + state = await self.async_get_last_state() + if not state: + return + self._state = state.state + + async_dispatcher_connect( + self.hass, DATA_UPDATED, self._schedule_immediate_update + ) + + def update(self): + """Get the latest data and update the states.""" + self._data = self.speedtest_client.data + if self._data is None: + return + + if self.type == 'ping': + self._state = self._data['ping'] + elif self.type == 'download': + self._state = round(self._data['download'] / 10**6, 2) + elif self.type == 'upload': + self._state = round(self._data['upload'] / 10**6, 2) + + @callback + def _schedule_immediate_update(self): + self.async_schedule_update_ha_state(True) diff --git a/homeassistant/components/speedtestdotnet/services.yaml b/homeassistant/components/speedtestdotnet/services.yaml new file mode 100644 index 00000000000..db813affe76 --- /dev/null +++ b/homeassistant/components/speedtestdotnet/services.yaml @@ -0,0 +1,2 @@ +speedtest: + description: Immediately take a speedest with Speedtest.net \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index 93e5b45fcf5..4ce0ae263aa 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1566,7 +1566,7 @@ solaredge==0.0.2 # homeassistant.components.climate.honeywell somecomfort==0.5.2 -# homeassistant.components.sensor.speedtest +# homeassistant.components.speedtestdotnet speedtest-cli==2.0.2 # homeassistant.components.spider From 07b5b68a513f8d0f1646887c0eb5424a60e4bd3d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 4 Feb 2019 01:14:30 -0800 Subject: [PATCH 048/242] Improve cloud error handling (#20729) * Improve cloud error handling * Lint --- homeassistant/components/cloud/http_api.py | 9 ++++++--- tests/components/cloud/test_http_api.py | 12 ++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index 03a77c08d4b..a2825eb6d7b 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -107,13 +107,16 @@ def _handle_cloud_errors(handler): result = await handler(view, request, *args, **kwargs) return result - except (auth_api.CloudError, asyncio.TimeoutError) as err: + except Exception as err: # pylint: disable=broad-except err_info = _CLOUD_ERRORS.get(err.__class__) if err_info is None: + _LOGGER.exception( + "Unexpected error processing request for %s", request.path) err_info = (502, 'Unexpected error: {}'.format(err)) status, msg = err_info - return view.json_message(msg, status_code=status, - message_code=err.__class__.__name__) + return view.json_message( + msg, status_code=status, + message_code=err.__class__.__name__.lower()) return error_handler diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py index 84d35f4bdd8..06de6bf0b59 100644 --- a/tests/components/cloud/test_http_api.py +++ b/tests/components/cloud/test_http_api.py @@ -111,6 +111,18 @@ def test_login_view(hass, cloud_client, mock_cognito): assert result_pass == 'my_password' +async def test_login_view_random_exception(cloud_client): + """Try logging in with invalid JSON.""" + with patch('async_timeout.timeout', side_effect=ValueError('Boom')): + req = await cloud_client.post('/api/cloud/login', json={ + 'email': 'my_username', + 'password': 'my_password' + }) + assert req.status == 502 + resp = await req.json() + assert resp == {'code': 'valueerror', 'message': 'Unexpected error: Boom'} + + @asyncio.coroutine def test_login_view_invalid_json(cloud_client): """Try logging in with invalid JSON.""" From b9d108284b7f153da4bfb6f4c80c668b9d715a8e Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Mon, 4 Feb 2019 06:51:13 -0500 Subject: [PATCH 049/242] Add ZHA binary sensor tests (#20711) * add sensor tests * add binary sensor tests * add comments * fix coveragerc after rebase --- tests/components/zha/test_binary_sensor.py | 149 +++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 tests/components/zha/test_binary_sensor.py diff --git a/tests/components/zha/test_binary_sensor.py b/tests/components/zha/test_binary_sensor.py new file mode 100644 index 00000000000..7c0c8b5350f --- /dev/null +++ b/tests/components/zha/test_binary_sensor.py @@ -0,0 +1,149 @@ +"""Test zha binary sensor.""" +from homeassistant.components.binary_sensor import DOMAIN +from homeassistant.const import STATE_ON, STATE_OFF +from .common import ( + async_init_zigpy_device, make_attribute, make_entity_id, + async_test_device_join +) + + +async def test_binary_sensor(hass, config_entry, zha_gateway): + """Test zha binary_sensor platform.""" + from zigpy.zcl.clusters.security import IasZone + from zigpy.zcl.clusters.measurement import OccupancySensing + from zigpy.zcl.clusters.general import OnOff, LevelControl + from zigpy.profiles.zha import DeviceType + + # create zigpy devices + zigpy_device_zone = await async_init_zigpy_device( + hass, + [IasZone.cluster_id], + [], + None, + zha_gateway + ) + + zigpy_device_remote = await async_init_zigpy_device( + hass, + [], + [OnOff.cluster_id, LevelControl.cluster_id], + DeviceType.LEVEL_CONTROL_SWITCH, + zha_gateway, + ieee="00:0d:6f:11:0a:90:69:e7", + manufacturer="FakeManufacturer", + model="FakeRemoteModel" + ) + + zigpy_device_occupancy = await async_init_zigpy_device( + hass, + [OccupancySensing.cluster_id], + [], + None, + zha_gateway, + ieee="00:0d:6f:11:9a:90:69:e7", + manufacturer="FakeOccupancy", + model="FakeOccupancyModel" + ) + + # load up binary_sensor domain + await hass.config_entries.async_forward_entry_setup( + config_entry, DOMAIN) + await hass.async_block_till_done() + + # on off binary_sensor + zone_cluster = zigpy_device_zone.endpoints.get( + 1).ias_zone + zone_entity_id = make_entity_id(DOMAIN, zigpy_device_zone, zone_cluster) + + # occupancy binary_sensor + occupancy_cluster = zigpy_device_occupancy.endpoints.get( + 1).occupancy + occupancy_entity_id = make_entity_id( + DOMAIN, zigpy_device_occupancy, occupancy_cluster) + + # dimmable binary_sensor + remote_on_off_cluster = zigpy_device_remote.endpoints.get( + 1).out_clusters[OnOff.cluster_id] + remote_level_cluster = zigpy_device_remote.endpoints.get( + 1).out_clusters[LevelControl.cluster_id] + remote_entity_id = make_entity_id(DOMAIN, zigpy_device_remote, + remote_on_off_cluster, + use_suffix=False) + + # test that the sensors exist and are in the off state + assert hass.states.get(zone_entity_id).state == STATE_OFF + assert hass.states.get(remote_entity_id).state == STATE_OFF + assert hass.states.get(occupancy_entity_id).state == STATE_OFF + + # test getting messages that trigger and reset the sensors + await async_test_binary_sensor_on_off(hass, occupancy_cluster, + occupancy_entity_id) + await async_test_binary_sensor_on_off(hass, remote_on_off_cluster, + remote_entity_id) + + # test changing the level attribute for dimming remotes + await async_test_remote_level( + hass, remote_level_cluster, remote_entity_id, 150, STATE_ON) + await async_test_remote_level( + hass, remote_level_cluster, remote_entity_id, 0, STATE_OFF) + await async_test_remote_level( + hass, remote_level_cluster, remote_entity_id, 255, STATE_ON) + + await async_test_remote_move_level( + hass, remote_level_cluster, remote_entity_id, 20, STATE_ON) + + # test IASZone binary sensors + await async_test_iaszone_on_off(hass, zone_cluster, zone_entity_id) + + # test new sensor join + await async_test_device_join( + hass, zha_gateway, OccupancySensing.cluster_id, DOMAIN, + expected_state=STATE_OFF) + + +async def async_test_binary_sensor_on_off(hass, cluster, entity_id): + """Test getting on and off messages for binary sensors.""" + # binary sensor on + attr = make_attribute(0, 1) + cluster.handle_message(False, 1, 0x0a, [[attr]]) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == STATE_ON + + # binary sensor off + attr.value.value = 0 + cluster.handle_message(False, 0, 0x0a, [[attr]]) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == STATE_OFF + + +async def async_test_remote_level(hass, cluster, entity_id, level, + expected_state): + """Test dimmer functionality from the remote.""" + attr = make_attribute(0, level) + cluster.handle_message(False, 1, 0x0a, [[attr]]) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == expected_state + assert hass.states.get(entity_id).attributes.get('level') == level + + +async def async_test_remote_move_level(hass, cluster, entity_id, change, + expected_state): + """Test move to level command.""" + level = hass.states.get(entity_id).attributes.get('level') + cluster.listener_event('cluster_command', 1, 1, [1, change]) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == expected_state + assert hass.states.get(entity_id).attributes.get('level') == level - change + + +async def async_test_iaszone_on_off(hass, cluster, entity_id): + """Test getting on and off messages for iaszone binary sensors.""" + # binary sensor on + cluster.listener_event('cluster_command', 1, 0, [1]) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == STATE_ON + + # binary sensor off + cluster.listener_event('cluster_command', 1, 0, [0]) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == STATE_OFF From ff9a33ba363ab70eccbb24566bdad75cd3f0c1c0 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Mon, 4 Feb 2019 06:51:32 -0500 Subject: [PATCH 050/242] Add ZHA fan tests (#20712) * add sensor tests * add fan tests * hound * fix coveragerc * update comments --- tests/components/zha/test_fan.py | 115 +++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 tests/components/zha/test_fan.py diff --git a/tests/components/zha/test_fan.py b/tests/components/zha/test_fan.py new file mode 100644 index 00000000000..c19225bf310 --- /dev/null +++ b/tests/components/zha/test_fan.py @@ -0,0 +1,115 @@ +"""Test zha fan.""" +from unittest.mock import call, patch +from homeassistant.components import fan +from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.components.fan import ( + ATTR_SPEED, DOMAIN, SERVICE_SET_SPEED +) +from homeassistant.const import ( + ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF) +from tests.common import mock_coro +from .common import ( + async_init_zigpy_device, make_attribute, make_entity_id, + async_test_device_join +) + + +async def test_fan(hass, config_entry, zha_gateway): + """Test zha fan platform.""" + from zigpy.zcl.clusters.hvac import Fan + from zigpy.zcl.foundation import Status + + # create zigpy device + zigpy_device = await async_init_zigpy_device( + hass, [Fan.cluster_id], [], None, zha_gateway) + + # load up fan domain + await hass.config_entries.async_forward_entry_setup( + config_entry, DOMAIN) + await hass.async_block_till_done() + + cluster = zigpy_device.endpoints.get(1).fan + entity_id = make_entity_id(DOMAIN, zigpy_device, cluster) + + # test that the fan was created and that it is off + assert hass.states.get(entity_id).state == STATE_OFF + + # turn on at fan + attr = make_attribute(0, 1) + cluster.handle_message(False, 1, 0x0a, [[attr]]) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == STATE_ON + + # turn off at fan + attr.value.value = 0 + cluster.handle_message(False, 0, 0x0a, [[attr]]) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == STATE_OFF + + # turn on from HA + with patch( + 'zigpy.zcl.Cluster.write_attributes', + return_value=mock_coro([Status.SUCCESS, Status.SUCCESS])): + # turn on via UI + await async_turn_on(hass, entity_id) + assert len(cluster.write_attributes.mock_calls) == 1 + assert cluster.write_attributes.call_args == call( + {'fan_mode': 2}) + + # turn off from HA + with patch( + 'zigpy.zcl.Cluster.write_attributes', + return_value=mock_coro([Status.SUCCESS, Status.SUCCESS])): + # turn off via UI + await async_turn_off(hass, entity_id) + assert len(cluster.write_attributes.mock_calls) == 1 + assert cluster.write_attributes.call_args == call( + {'fan_mode': 0}) + + # change speed from HA + with patch( + 'zigpy.zcl.Cluster.write_attributes', + return_value=mock_coro([Status.SUCCESS, Status.SUCCESS])): + # turn on via UI + await async_set_speed(hass, entity_id, speed=fan.SPEED_HIGH) + assert len(cluster.write_attributes.mock_calls) == 1 + assert cluster.write_attributes.call_args == call( + {'fan_mode': 3}) + + # test adding new fan to the network and HA + await async_test_device_join(hass, zha_gateway, Fan.cluster_id, DOMAIN, + expected_state=STATE_OFF) + + +async def async_turn_on(hass, entity_id, speed=None): + """Turn fan on.""" + data = { + key: value for key, value in [ + (ATTR_ENTITY_ID, entity_id), + (ATTR_SPEED, speed), + ] if value is not None + } + + await hass.services.async_call( + DOMAIN, SERVICE_TURN_ON, data, blocking=True) + + +async def async_turn_off(hass, entity_id): + """Turn fan off.""" + data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} + + await hass.services.async_call( + DOMAIN, SERVICE_TURN_OFF, data, blocking=True) + + +async def async_set_speed(hass, entity_id, speed=None): + """Set speed for specified fan.""" + data = { + key: value for key, value in [ + (ATTR_ENTITY_ID, entity_id), + (ATTR_SPEED, speed), + ] if value is not None + } + + await hass.services.async_call( + DOMAIN, SERVICE_SET_SPEED, data, blocking=True) From 0cf71d5bcb656377a45b7c5bc872a2f0d07e4c09 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Mon, 4 Feb 2019 06:51:47 -0500 Subject: [PATCH 051/242] Add ZHA light tests (#20713) * add sensor tests * add light test * update comments * fix coveragerc after rebase --- tests/components/zha/test_light.py | 175 +++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 tests/components/zha/test_light.py diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py new file mode 100644 index 00000000000..d9063b4885a --- /dev/null +++ b/tests/components/zha/test_light.py @@ -0,0 +1,175 @@ +"""Test zha light.""" +from unittest.mock import call, patch +from homeassistant.components.light import DOMAIN +from homeassistant.const import STATE_ON, STATE_OFF +from tests.common import mock_coro +from .common import ( + async_init_zigpy_device, make_attribute, make_entity_id, + async_test_device_join +) + +ON = 1 +OFF = 0 + + +async def test_light(hass, config_entry, zha_gateway): + """Test zha light platform.""" + from zigpy.zcl.clusters.general import OnOff, LevelControl + from zigpy.profiles.zha import DeviceType + + # create zigpy devices + zigpy_device_on_off = await async_init_zigpy_device( + hass, + [OnOff.cluster_id], + [], + DeviceType.ON_OFF_LIGHT, + zha_gateway + ) + + zigpy_device_level = await async_init_zigpy_device( + hass, + [OnOff.cluster_id, LevelControl.cluster_id], + [], + DeviceType.ON_OFF_LIGHT, + zha_gateway, + ieee="00:0d:6f:11:0a:90:69:e7", + manufacturer="FakeLevelManufacturer", + model="FakeLevelModel" + ) + + # load up light domain + await hass.config_entries.async_forward_entry_setup( + config_entry, DOMAIN) + await hass.async_block_till_done() + + # on off light + on_off_device_on_off_cluster = zigpy_device_on_off.endpoints.get(1).on_off + on_off_entity_id = make_entity_id(DOMAIN, zigpy_device_on_off, + on_off_device_on_off_cluster, + use_suffix=False) + + # dimmable light + level_device_on_off_cluster = zigpy_device_level.endpoints.get(1).on_off + level_device_level_cluster = zigpy_device_level.endpoints.get(1).level + level_entity_id = make_entity_id(DOMAIN, zigpy_device_level, + level_device_on_off_cluster, + use_suffix=False) + + # test that the lights were created and are off + assert hass.states.get(on_off_entity_id).state == STATE_OFF + assert hass.states.get(level_entity_id).state == STATE_OFF + + # test turning the lights on and off from the light + await async_test_on_off_from_light( + hass, on_off_device_on_off_cluster, on_off_entity_id) + + await async_test_on_off_from_light( + hass, level_device_on_off_cluster, level_entity_id) + + # test turning the lights on and off from the HA + await async_test_on_off_from_hass( + hass, on_off_device_on_off_cluster, on_off_entity_id) + + await async_test_level_on_off_from_hass( + hass, level_device_on_off_cluster, level_entity_id) + + # test turning the lights on and off from the light + await async_test_on_from_light( + hass, level_device_on_off_cluster, level_entity_id) + + # test getting a brightness change from the network + await async_test_dimmer_from_light( + hass, level_device_level_cluster, level_entity_id, 150, STATE_ON) + + # test adding a new light to the network and HA + await async_test_device_join( + hass, zha_gateway, OnOff.cluster_id, + DOMAIN, device_type=DeviceType.ON_OFF_LIGHT, expected_state=STATE_OFF) + + +async def async_test_on_off_from_light(hass, cluster, entity_id): + """Test on off functionality from the light.""" + # turn on at light + attr = make_attribute(0, 1) + cluster.handle_message(False, 1, 0x0a, [[attr]]) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == STATE_ON + + # turn off at light + attr.value.value = 0 + cluster.handle_message(False, 0, 0x0a, [[attr]]) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == STATE_OFF + + +async def async_test_on_from_light(hass, cluster, entity_id): + """Test on off functionality from the light.""" + # turn on at light + attr = make_attribute(0, 1) + cluster.handle_message(False, 1, 0x0a, [[attr]]) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == STATE_ON + + +async def async_test_on_off_from_hass(hass, cluster, entity_id): + """Test on off functionality from hass.""" + from zigpy.zcl.foundation import Status + with patch( + 'zigpy.zcl.Cluster.request', + return_value=mock_coro([Status.SUCCESS, Status.SUCCESS])): + # turn on via UI + await hass.services.async_call(DOMAIN, 'turn_on', { + 'entity_id': entity_id + }, blocking=True) + assert len(cluster.request.mock_calls) == 1 + assert cluster.request.call_args == call( + False, ON, (), expect_reply=True, manufacturer=None) + + await async_test_off_from_hass(hass, cluster, entity_id) + + +async def async_test_off_from_hass(hass, cluster, entity_id): + """Test turning off the light from homeassistant.""" + from zigpy.zcl.foundation import Status + with patch( + 'zigpy.zcl.Cluster.request', + return_value=mock_coro([Status.SUCCESS, Status.SUCCESS])): + # turn off via UI + await hass.services.async_call(DOMAIN, 'turn_off', { + 'entity_id': entity_id + }, blocking=True) + assert len(cluster.request.mock_calls) == 1 + assert cluster.request.call_args == call( + False, OFF, (), expect_reply=True, manufacturer=None) + + +async def async_test_level_on_off_from_hass(hass, cluster, entity_id): + """Test on off functionality from hass.""" + from zigpy import types + from zigpy.zcl.foundation import Status + with patch( + 'zigpy.zcl.Cluster.request', + return_value=mock_coro([Status.SUCCESS, Status.SUCCESS])): + # turn on via UI + await hass.services.async_call(DOMAIN, 'turn_on', { + 'entity_id': entity_id + }, blocking=True) + assert len(cluster.request.mock_calls) == 1 + assert cluster.request.call_args == call( + False, 4, (types.uint8_t, types.uint16_t), 255, 5.0, + expect_reply=True, manufacturer=None) + + await async_test_off_from_hass(hass, cluster, entity_id) + + +async def async_test_dimmer_from_light(hass, cluster, entity_id, + level, expected_state): + """Test dimmer functionality from the light.""" + attr = make_attribute(0, level) + cluster.handle_message(False, 1, 0x0a, [[attr]]) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == expected_state + # hass uses None for brightness of 0 in state attributes + if level == 0: + level = None + assert hass.states.get(entity_id).attributes.get('brightness') == level From a40c5bf70e31f2a851da9824fcc92c24f038aaa2 Mon Sep 17 00:00:00 2001 From: Eliseo Martelli Date: Mon, 4 Feb 2019 16:44:23 +0100 Subject: [PATCH 052/242] Add google home alarm sensor (#20709) * added googlehome alarm sensor * splitted update method * fix linting * remove whitespace * removed whitespace in line * changed accordingly to the review * removed redundant method * Update homeassistant/components/googlehome/__init__.py Co-Authored-By: eliseomartelli --- .../components/googlehome/__init__.py | 31 ++++- .../components/googlehome/device_tracker.py | 4 +- homeassistant/components/googlehome/sensor.py | 110 ++++++++++++++++++ 3 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 homeassistant/components/googlehome/sensor.py diff --git a/homeassistant/components/googlehome/__init__.py b/homeassistant/components/googlehome/__init__.py index 78bd2d7df3f..f2d5ad09350 100644 --- a/homeassistant/components/googlehome/__init__.py +++ b/homeassistant/components/googlehome/__init__.py @@ -24,6 +24,7 @@ NAME = 'GoogleHome' CONF_DEVICE_TYPES = 'device_types' CONF_RSSI_THRESHOLD = 'rssi_threshold' +CONF_TRACK_ALARMS = 'track_alarms' DEVICE_TYPES = [1, 2, 3] DEFAULT_RSSI_THRESHOLD = -70 @@ -35,6 +36,7 @@ DEVICE_CONFIG = vol.Schema({ [vol.In(DEVICE_TYPES)]), vol.Optional(CONF_RSSI_THRESHOLD, default=DEFAULT_RSSI_THRESHOLD): vol.Coerce(int), + vol.Optional(CONF_TRACK_ALARMS, default=False): cv.boolean, }) @@ -56,6 +58,11 @@ async def async_setup(hass, config): discovery.async_load_platform( hass, 'device_tracker', DOMAIN, device, config)) + if device[CONF_TRACK_ALARMS]: + hass.async_create_task( + discovery.async_load_platform( + hass, 'sensor', DOMAIN, device, config)) + return True @@ -67,20 +74,38 @@ class GoogleHomeClient: self.hass = hass self._connected = None - async def update_data(self, host): + async def update_info(self, host): """Update data from Google Home.""" from googledevices.api.connect import Cast - _LOGGER.debug("Updating Google Home data for %s", host) + _LOGGER.debug("Updating Google Home info for %s", host) session = async_get_clientsession(self.hass) device_info = await Cast(host, self.hass.loop, session).info() device_info_data = await device_info.get_device_info() self._connected = bool(device_info_data) + self.hass.data[DOMAIN][host]['info'] = device_info_data + + async def update_bluetooth(self, host): + """Update bluetooth from Google Home.""" + from googledevices.api.connect import Cast + _LOGGER.debug("Updating Google Home bluetooth for %s", host) + session = async_get_clientsession(self.hass) + bluetooth = await Cast(host, self.hass.loop, session).bluetooth() await bluetooth.scan_for_devices() await asyncio.sleep(5) bluetooth_data = await bluetooth.get_scan_result() - self.hass.data[DOMAIN][host]['info'] = device_info_data self.hass.data[DOMAIN][host]['bluetooth'] = bluetooth_data + + async def update_alarms(self, host): + """Update alarms from Google Home.""" + from googledevices.api.connect import Cast + _LOGGER.debug("Updating Google Home bluetooth for %s", host) + session = async_get_clientsession(self.hass) + + assistant = await Cast(host, self.hass.loop, session).assistant() + alarms_data = await assistant.get_alarms() + + self.hass.data[DOMAIN][host]['alarms'] = alarms_data diff --git a/homeassistant/components/googlehome/device_tracker.py b/homeassistant/components/googlehome/device_tracker.py index ba6d708295a..c4b490ab316 100644 --- a/homeassistant/components/googlehome/device_tracker.py +++ b/homeassistant/components/googlehome/device_tracker.py @@ -45,7 +45,7 @@ class GoogleHomeDeviceScanner(DeviceScanner): async def async_init(self): """Further initialize connection to Google Home.""" - await self.client.update_data(self.host) + await self.client.update_info(self.host) data = self.hass.data[GOOGLEHOME_DOMAIN][self.host] info = data.get('info', {}) connected = bool(info) @@ -59,7 +59,7 @@ class GoogleHomeDeviceScanner(DeviceScanner): async def async_update(self, now=None): """Ensure the information from Google Home is up to date.""" _LOGGER.debug('Checking Devices on %s', self.host) - await self.client.update_data(self.host) + await self.client.update_bluetooth(self.host) data = self.hass.data[GOOGLEHOME_DOMAIN][self.host] info = data.get('info') bluetooth = data.get('bluetooth') diff --git a/homeassistant/components/googlehome/sensor.py b/homeassistant/components/googlehome/sensor.py new file mode 100644 index 00000000000..f2a0f822dbf --- /dev/null +++ b/homeassistant/components/googlehome/sensor.py @@ -0,0 +1,110 @@ +""" +Support for Google Home alarm sensor. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.googlehome/ +""" +import logging +from datetime import timedelta + +from homeassistant.components.sensor import ENTITY_ID_FORMAT +from homeassistant.components.googlehome import ( + CLIENT, DOMAIN as GOOGLEHOME_DOMAIN, NAME) +from homeassistant.const import DEVICE_CLASS_TIMESTAMP +from homeassistant.helpers.entity import Entity, async_generate_entity_id +import homeassistant.util.dt as dt_util + + +DEPENDENCIES = ['googlehome'] + +SCAN_INTERVAL = timedelta(seconds=10) + +_LOGGER = logging.getLogger(__name__) + +ICON = 'mdi:alarm' + +SENSOR_TYPES = { + 'timer': "Timer", + 'alarm': "Alarm", +} + + +async def async_setup_platform(hass, config, + async_add_entities, discovery_info=None): + """Set up the googlehome sensor platform.""" + if discovery_info is None: + _LOGGER.warning( + "To use this you need to configure the 'googlehome' component") + + devices = [] + for condition in SENSOR_TYPES: + device = GoogleHomeAlarm(hass.data[CLIENT], condition, + discovery_info) + devices.append(device) + + async_add_entities(devices, True) + + +class GoogleHomeAlarm(Entity): + """Representation of a GoogleHomeAlarm.""" + + def __init__(self, client, condition, config): + """Initialize the GoogleHomeAlarm sensor.""" + self._host = config['host'] + self._client = client + self._condition = condition + self._name = None + self._state = None + self._available = True + + async def async_added_to_hass(self): + """Subscribe GoogleHome events.""" + await self._client.update_info(self._host) + data = self.hass.data[GOOGLEHOME_DOMAIN][self._host] + info = data.get('info', {}) + if info is None: + return + self._name = "{} {}".format(info.get('name', NAME), + SENSOR_TYPES[self._condition]) + self.entity_id = async_generate_entity_id( + ENTITY_ID_FORMAT, self._name, hass=self.hass) + + async def async_update(self): + """Update the data.""" + await self._client.update_alarms(self._host) + data = self.hass.data[GOOGLEHOME_DOMAIN][self._host] + + alarms = data.get('alarms')[self._condition] + if not alarms: + self._available = False + return + self._available = True + time_date = dt_util.utc_from_timestamp(min(element['fire_time'] + for element in alarms) + / 1000) + self._state = time_date.isoformat() + + @property + def state(self): + """Return the state.""" + return self._state + + @property + def name(self): + """Return the name.""" + return self._name + + @property + def device_class(self): + """Return the device class.""" + return DEVICE_CLASS_TIMESTAMP + + @property + def available(self): + """Return the availability state.""" + return self._available + + @property + def icon(self): + """Return the icon.""" + return ICON From 7455d950b13740e1e06c2fe9c45a0f8163eb17be Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Mon, 4 Feb 2019 09:57:22 -0800 Subject: [PATCH 053/242] Fix ffmpeg v4 stream issue (#20314) * Add ffmpeg version * Add ffmpeg stream content type * Change ffmpeg camera stream content type * Change ffmpeg stream content type * Lint * Add a none guard * Fix * Fix * Update onvif.py * Fix version match regrex * Fix regrex * Upgrade ha-ffmpeg to 1.11 * Lint * Get ffmpeg version in ffmpeg component setup --- homeassistant/components/amcrest/camera.py | 2 +- homeassistant/components/arlo/camera.py | 2 +- homeassistant/components/camera/canary.py | 2 +- homeassistant/components/camera/ffmpeg.py | 2 +- homeassistant/components/camera/onvif.py | 5 ++-- homeassistant/components/camera/ring.py | 2 +- homeassistant/components/camera/xiaomi.py | 2 +- homeassistant/components/camera/yi.py | 2 +- homeassistant/components/ffmpeg.py | 30 +++++++++++++++++++++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 11 files changed, 41 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/amcrest/camera.py b/homeassistant/components/amcrest/camera.py index 4ba527b4805..3b3368c2f5c 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -82,7 +82,7 @@ class AmcrestCam(Camera): try: return await async_aiohttp_proxy_stream( self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') + self._ffmpeg.ffmpeg_stream_content_type) finally: await stream.close() diff --git a/homeassistant/components/arlo/camera.py b/homeassistant/components/arlo/camera.py index d56616218e7..7857995b4af 100644 --- a/homeassistant/components/arlo/camera.py +++ b/homeassistant/components/arlo/camera.py @@ -104,7 +104,7 @@ class ArloCam(Camera): try: return await async_aiohttp_proxy_stream( self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') + self._ffmpeg.ffmpeg_stream_content_type) finally: await stream.close() diff --git a/homeassistant/components/camera/canary.py b/homeassistant/components/camera/canary.py index 7a83e2da4d1..eb0c8f3fc6d 100644 --- a/homeassistant/components/camera/canary.py +++ b/homeassistant/components/camera/canary.py @@ -101,7 +101,7 @@ class CanaryCamera(Camera): try: return await async_aiohttp_proxy_stream( self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') + self._ffmpeg.ffmpeg_stream_content_type) finally: await stream.close() diff --git a/homeassistant/components/camera/ffmpeg.py b/homeassistant/components/camera/ffmpeg.py index 6bd68b05bb5..db9e73f3e1b 100644 --- a/homeassistant/components/camera/ffmpeg.py +++ b/homeassistant/components/camera/ffmpeg.py @@ -68,7 +68,7 @@ class FFmpegCamera(Camera): try: return await async_aiohttp_proxy_stream( self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') + self._manager.ffmpeg_stream_content_type) finally: await stream.close() diff --git a/homeassistant/components/camera/onvif.py b/homeassistant/components/camera/onvif.py index d1afd39ca7b..da0bae7c50b 100644 --- a/homeassistant/components/camera/onvif.py +++ b/homeassistant/components/camera/onvif.py @@ -213,7 +213,8 @@ class ONVIFHassCamera(Camera): if not self._input: return None - stream = CameraMjpeg(self.hass.data[DATA_FFMPEG].binary, + ffmpeg_manager = self.hass.data[DATA_FFMPEG] + stream = CameraMjpeg(ffmpeg_manager.binary, loop=self.hass.loop) await stream.open_camera( self._input, extra_cmd=self._ffmpeg_arguments) @@ -221,7 +222,7 @@ class ONVIFHassCamera(Camera): try: return await async_aiohttp_proxy_stream( self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') + ffmpeg_manager.ffmpeg_stream_content_type) finally: await stream.close() diff --git a/homeassistant/components/camera/ring.py b/homeassistant/components/camera/ring.py index ad351fb59cf..da1119281b3 100644 --- a/homeassistant/components/camera/ring.py +++ b/homeassistant/components/camera/ring.py @@ -142,7 +142,7 @@ class RingCam(Camera): try: return await async_aiohttp_proxy_stream( self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') + self._ffmpeg.ffmpeg_stream_content_type) finally: await stream.close() diff --git a/homeassistant/components/camera/xiaomi.py b/homeassistant/components/camera/xiaomi.py index 207dd17ed9b..93e9dd4a07c 100644 --- a/homeassistant/components/camera/xiaomi.py +++ b/homeassistant/components/camera/xiaomi.py @@ -161,6 +161,6 @@ class XiaomiCamera(Camera): try: return await async_aiohttp_proxy_stream( self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') + self._manager.ffmpeg_stream_content_type) finally: await stream.close() diff --git a/homeassistant/components/camera/yi.py b/homeassistant/components/camera/yi.py index 8b5b865ee57..7d731d2a433 100644 --- a/homeassistant/components/camera/yi.py +++ b/homeassistant/components/camera/yi.py @@ -147,6 +147,6 @@ class YiCamera(Camera): try: return await async_aiohttp_proxy_stream( self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') + self._manager.ffmpeg_stream_content_type) finally: await stream.close() diff --git a/homeassistant/components/ffmpeg.py b/homeassistant/components/ffmpeg.py index a2f0ca19231..3184b5a5d54 100644 --- a/homeassistant/components/ffmpeg.py +++ b/homeassistant/components/ffmpeg.py @@ -5,6 +5,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/ffmpeg/ """ import logging +import re import voluptuous as vol @@ -16,7 +17,7 @@ from homeassistant.helpers.dispatcher import ( import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['ha-ffmpeg==1.9'] +REQUIREMENTS = ['ha-ffmpeg==1.11'] DOMAIN = 'ffmpeg' @@ -60,6 +61,8 @@ async def async_setup(hass, config): conf.get(CONF_FFMPEG_BIN, DEFAULT_BINARY) ) + await manager.async_get_version() + # Register service async def async_service_handle(service): """Handle service ffmpeg process.""" @@ -96,12 +99,37 @@ class FFmpegManager: self.hass = hass self._cache = {} self._bin = ffmpeg_bin + self._version = None + self._major_version = None @property def binary(self): """Return ffmpeg binary from config.""" return self._bin + async def async_get_version(self): + """Return ffmpeg version.""" + from haffmpeg.tools import FFVersion + + ffversion = FFVersion(self._bin, self.hass.loop) + self._version = await ffversion.get_version() + + self._major_version = None + if self._version is not None: + result = re.search(r"(\d+)\.", self._version) + if result is not None: + self._major_version = int(result.group(1)) + + return self._version, self._major_version + + @property + def ffmpeg_stream_content_type(self): + """Return HTTP content type for ffmpeg stream.""" + if self._major_version is not None and self._major_version > 3: + return 'multipart/x-mixed-replace;boundary=ffmpeg' + + return 'multipart/x-mixed-replace;boundary=ffserver' + class FFmpegBase(Entity): """Interface object for FFmpeg.""" diff --git a/requirements_all.txt b/requirements_all.txt index 4ce0ae263aa..f8085401d98 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -487,7 +487,7 @@ greenwavereality==0.5.1 gstreamer-player==1.1.2 # homeassistant.components.ffmpeg -ha-ffmpeg==1.9 +ha-ffmpeg==1.11 # homeassistant.components.media_player.philips_js ha-philipsjs==0.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index deee1efca3f..3ec1c7d35fa 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -101,7 +101,7 @@ geojson_client==0.3 georss_client==0.5 # homeassistant.components.ffmpeg -ha-ffmpeg==1.9 +ha-ffmpeg==1.11 # homeassistant.components.hangouts hangups==0.4.6 From 79d3f533a9e2263e52a0a5452cca44c5a3b69d50 Mon Sep 17 00:00:00 2001 From: emontnemery Date: Mon, 4 Feb 2019 19:54:40 +0100 Subject: [PATCH 054/242] Add missing abbreviations (#20741) --- homeassistant/components/mqtt/discovery.py | 21 ++++++++++++++++++++- tests/components/mqtt/test_discovery.py | 10 +++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index 9a2daf388cb..688912070bd 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -11,7 +11,7 @@ import re from homeassistant.components import mqtt from homeassistant.components.mqtt import ATTR_DISCOVERY_HASH, CONF_STATE_TOPIC -from homeassistant.const import CONF_PLATFORM +from homeassistant.const import CONF_DEVICE, CONF_PLATFORM from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.typing import HomeAssistantType @@ -81,6 +81,7 @@ ABBREVIATIONS = { 'cln_tpl': 'cleaning_template', 'cmd_t': 'command_topic', 'curr_temp_t': 'current_temperature_topic', + 'dev': 'device', 'dev_cla': 'device_class', 'dock_t': 'docked_topic', 'dock_tpl': 'docked_template', @@ -104,6 +105,7 @@ ABBREVIATIONS = { 'ic': 'icon', 'init': 'initial', 'json_attr': 'json_attributes', + 'json_attr_t': 'json_attributes_topic', 'max_temp': 'max_temp', 'min_temp': 'min_temp', 'mode_cmd_t': 'mode_command_topic', @@ -172,6 +174,7 @@ ABBREVIATIONS = { 'unit_of_meas': 'unit_of_measurement', 'val_tpl': 'value_template', 'whit_val_cmd_t': 'white_value_command_topic', + 'whit_val_scl': 'white_value_scale', 'whit_val_stat_t': 'white_value_state_topic', 'whit_val_tpl': 'white_value_template', 'xy_cmd_t': 'xy_command_topic', @@ -179,6 +182,15 @@ ABBREVIATIONS = { 'xy_val_tpl': 'xy_value_template', } +DEVICE_ABBREVIATIONS = { + 'cns': 'connections', + 'ids': 'identifiers', + 'name': 'name', + 'mf': 'manufacturer', + 'mdl': 'model', + 'sw': 'sw_version', +} + def clear_discovery_hash(hass, discovery_hash): """Clear entry in ALREADY_DISCOVERED list.""" @@ -216,6 +228,13 @@ async def async_start(hass: HomeAssistantType, discovery_topic, hass_config, key = ABBREVIATIONS.get(key, key) payload[key] = payload.pop(abbreviated_key) + if CONF_DEVICE in payload: + device = payload[CONF_DEVICE] + for key in list(device.keys()): + abbreviated_key = key + key = DEVICE_ABBREVIATIONS.get(key, key) + device[key] = device.pop(abbreviated_key) + base = payload.pop(TOPIC_BASE, None) if base: for key, value in payload.items(): diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index 47bd912fbc8..ffc385021d7 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -222,7 +222,15 @@ def test_discovery_expansion(hass, mqtt_mock, caplog): '{ "~": "some/base/topic",' ' "name": "DiscoveryExpansionTest1",' ' "stat_t": "test_topic/~",' - ' "cmd_t": "~/test_topic" }' + ' "cmd_t": "~/test_topic",' + ' "dev":{' + ' "ids":["5706DF"],' + ' "name":"DiscoveryExpansionTest1 Device",' + ' "mdl":"Generic",' + ' "sw":"1.2.3.4",' + ' "mf":"Noone"' + ' }' + '}' ) async_fire_mqtt_message( From c812176e940e690a8fb1013070c77d5e15bba025 Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Mon, 4 Feb 2019 10:58:06 -0800 Subject: [PATCH 055/242] Fix the line reference in config error message (#20743) * Fix the line reference in config error message * Fix platform config validation * Fix test * Handle error in error handling routine --- homeassistant/config.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index 2a9f8f64835..5dbf226ca25 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -446,7 +446,11 @@ def _format_config_error(ex: vol.Invalid, domain: str, config: Dict) -> str: else: message += '{}.'.format(humanize_error(config, ex)) - domain_config = config.get(domain, config) + try: + domain_config = config.get(domain, config) + except AttributeError: + domain_config = config + message += " (See {}, line {}). ".format( getattr(domain_config, '__config_file__', '?'), getattr(domain_config, '__line__', '?')) @@ -759,7 +763,7 @@ def async_process_component_config( p_validated = component.PLATFORM_SCHEMA( # type: ignore p_config) except vol.Invalid as ex: - async_log_exception(ex, domain, config, hass) + async_log_exception(ex, domain, p_config, hass) continue # Not all platform components follow same pattern for platforms @@ -779,10 +783,10 @@ def async_process_component_config( # pylint: disable=no-member try: p_validated = platform.PLATFORM_SCHEMA( # type: ignore - p_validated) + p_config) except vol.Invalid as ex: async_log_exception(ex, '{}.{}'.format(domain, p_name), - p_validated, hass) + p_config, hass) continue platforms.append(p_validated) From 29b64d56be537f53d367afd0a796df9b55b69957 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 4 Feb 2019 19:58:38 +0100 Subject: [PATCH 056/242] Fix cloud webhook body (#20739) * Bugfix cloud webhooks text response * address comments * Fix lint --- homeassistant/components/cloud/iot.py | 7 +++---- homeassistant/components/cloud/utils.py | 13 +++++++++++++ homeassistant/util/aiohttp.py | 10 ---------- tests/components/cloud/test_utils.py | 24 ++++++++++++++++++++++++ tests/util/test_aiohttp.py | 21 --------------------- 5 files changed, 40 insertions(+), 35 deletions(-) create mode 100644 homeassistant/components/cloud/utils.py create mode 100644 tests/components/cloud/test_utils.py diff --git a/homeassistant/components/cloud/iot.py b/homeassistant/components/cloud/iot.py index ed24fe48d40..d725cb309bc 100644 --- a/homeassistant/components/cloud/iot.py +++ b/homeassistant/components/cloud/iot.py @@ -12,9 +12,10 @@ from homeassistant.components.alexa import smart_home as alexa from homeassistant.components.google_assistant import smart_home as ga from homeassistant.core import callback from homeassistant.util.decorator import Registry -from homeassistant.util.aiohttp import MockRequest, serialize_response +from homeassistant.util.aiohttp import MockRequest from homeassistant.helpers.aiohttp_client import async_get_clientsession from . import auth_api +from . import utils from .const import MESSAGE_EXPIRATION, MESSAGE_AUTH_FAIL HANDLERS = Registry() @@ -360,10 +361,8 @@ async def async_handle_webhook(hass, cloud, payload): response = await hass.components.webhook.async_handle_webhook( found['webhook_id'], request) - response_dict = serialize_response(response) + response_dict = utils.aiohttp_serialize_response(response) body = response_dict.get('body') - if body: - body = body.decode('utf-8') return { 'body': body, diff --git a/homeassistant/components/cloud/utils.py b/homeassistant/components/cloud/utils.py new file mode 100644 index 00000000000..da1d3809989 --- /dev/null +++ b/homeassistant/components/cloud/utils.py @@ -0,0 +1,13 @@ +"""Helper functions for cloud components.""" +from typing import Any, Dict + +from aiohttp import web + + +def aiohttp_serialize_response(response: web.Response) -> Dict[str, Any]: + """Serialize an aiohttp response to a dictionary.""" + return { + 'status': response.status, + 'body': response.text, + 'headers': dict(response.headers), + } diff --git a/homeassistant/util/aiohttp.py b/homeassistant/util/aiohttp.py index d648ed43110..16fea129573 100644 --- a/homeassistant/util/aiohttp.py +++ b/homeassistant/util/aiohttp.py @@ -3,7 +3,6 @@ import json from urllib.parse import parse_qsl from typing import Any, Dict, Optional -from aiohttp import web from multidict import CIMultiDict, MultiDict @@ -42,12 +41,3 @@ class MockRequest: async def text(self) -> str: """Return the body as text.""" return self._text - - -def serialize_response(response: web.Response) -> Dict[str, Any]: - """Serialize an aiohttp response to a dictionary.""" - return { - 'status': response.status, - 'body': response.body, - 'headers': dict(response.headers), - } diff --git a/tests/components/cloud/test_utils.py b/tests/components/cloud/test_utils.py new file mode 100644 index 00000000000..24de4ce6214 --- /dev/null +++ b/tests/components/cloud/test_utils.py @@ -0,0 +1,24 @@ +"""Test aiohttp request helper.""" +from aiohttp import web + +from homeassistant.components.cloud import utils + + +def test_serialize_text(): + """Test serializing a text response.""" + response = web.Response(status=201, text='Hello') + assert utils.aiohttp_serialize_response(response) == { + 'status': 201, + 'body': 'Hello', + 'headers': {'Content-Type': 'text/plain; charset=utf-8'}, + } + + +def test_serialize_json(): + """Test serializing a JSON response.""" + response = web.json_response({"how": "what"}) + assert utils.aiohttp_serialize_response(response) == { + 'status': 200, + 'body': '{"how": "what"}', + 'headers': {'Content-Type': 'application/json; charset=utf-8'}, + } diff --git a/tests/util/test_aiohttp.py b/tests/util/test_aiohttp.py index 8f528376cce..5df1582da32 100644 --- a/tests/util/test_aiohttp.py +++ b/tests/util/test_aiohttp.py @@ -1,5 +1,4 @@ """Test aiohttp request helper.""" -from aiohttp import web from homeassistant.util import aiohttp @@ -32,23 +31,3 @@ async def test_request_post_query(): assert request.query == { 'get': 'true' } - - -def test_serialize_text(): - """Test serializing a text response.""" - response = web.Response(status=201, text='Hello') - assert aiohttp.serialize_response(response) == { - 'status': 201, - 'body': b'Hello', - 'headers': {'Content-Type': 'text/plain; charset=utf-8'}, - } - - -def test_serialize_json(): - """Test serializing a JSON response.""" - response = web.json_response({"how": "what"}) - assert aiohttp.serialize_response(response) == { - 'status': 200, - 'body': b'{"how": "what"}', - 'headers': {'Content-Type': 'application/json; charset=utf-8'}, - } From cd046611010d781ba89024fe121ee84205730bd0 Mon Sep 17 00:00:00 2001 From: jonudewux Date: Mon, 4 Feb 2019 22:08:38 +0200 Subject: [PATCH 057/242] Add Transmission component 'scan_interval' option (#20575) * Transmission component fix 'scan_interval' option * Fix dict[key] comments * Fix latest mess --- .../components/transmission/__init__.py | 16 ++++++++++------ homeassistant/components/transmission/sensor.py | 6 +++++- homeassistant/components/transmission/switch.py | 6 +++++- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index cdf55c8e049..b14881fccca 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -15,7 +15,8 @@ from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, CONF_PORT, - CONF_USERNAME + CONF_USERNAME, + CONF_SCAN_INTERVAL ) from homeassistant.helpers import discovery, config_validation as cv from homeassistant.helpers.event import track_time_interval @@ -42,6 +43,8 @@ SENSOR_TYPES = { 'started_torrents': ['Started Torrents', None], } +DEFAULT_SCAN_INTERVAL = timedelta(seconds=120) + CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_HOST): cv.string, @@ -50,20 +53,21 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(TURTLE_MODE, default=False): cv.boolean, + vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): + cv.time_period, vol.Optional(CONF_MONITORED_CONDITIONS, default=['current_status']): vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), }) }, extra=vol.ALLOW_EXTRA) -SCAN_INTERVAL = timedelta(minutes=2) - def setup(hass, config): """Set up the Transmission Component.""" host = config[DOMAIN][CONF_HOST] - username = config[DOMAIN][CONF_USERNAME] - password = config[DOMAIN][CONF_PASSWORD] + username = config[DOMAIN].get(CONF_USERNAME) + password = config[DOMAIN].get(CONF_PASSWORD) port = config[DOMAIN][CONF_PORT] + scan_interval = config[DOMAIN][CONF_SCAN_INTERVAL] import transmissionrpc from transmissionrpc.error import TransmissionError @@ -85,7 +89,7 @@ def setup(hass, config): """Get the latest data from Transmission.""" tm_data.update() - track_time_interval(hass, refresh, SCAN_INTERVAL) + track_time_interval(hass, refresh, scan_interval) sensorconfig = { 'sensors': config[DOMAIN][CONF_MONITORED_CONDITIONS], diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index efe32b07fc0..84c7d54306e 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -4,10 +4,12 @@ Support for monitoring the Transmission BitTorrent client API. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.transmission/ """ +from datetime import timedelta + import logging from homeassistant.components.transmission import ( - DATA_TRANSMISSION, SENSOR_TYPES, SCAN_INTERVAL) + DATA_TRANSMISSION, SENSOR_TYPES) from homeassistant.const import STATE_IDLE from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -18,6 +20,8 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'Transmission' +SCAN_INTERVAL = timedelta(seconds=120) + def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Transmission sensors.""" diff --git a/homeassistant/components/transmission/switch.py b/homeassistant/components/transmission/switch.py index 3ce3c7a98f9..8e6c0a8cb44 100644 --- a/homeassistant/components/transmission/switch.py +++ b/homeassistant/components/transmission/switch.py @@ -4,10 +4,12 @@ Support for setting the Transmission BitTorrent client Turtle Mode. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.transmission/ """ +from datetime import timedelta + import logging from homeassistant.components.transmission import ( - DATA_TRANSMISSION, SCAN_INTERVAL) + DATA_TRANSMISSION) from homeassistant.const import ( STATE_OFF, STATE_ON) from homeassistant.helpers.entity import ToggleEntity @@ -19,6 +21,8 @@ _LOGGING = logging.getLogger(__name__) DEFAULT_NAME = 'Transmission Turtle Mode' +SCAN_INTERVAL = timedelta(seconds=120) + def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Transmission switch.""" From 3880a709659e42b65a6bd1bf624475c14b895f85 Mon Sep 17 00:00:00 2001 From: Erik Hendrix Date: Mon, 4 Feb 2019 16:48:35 -0700 Subject: [PATCH 058/242] Update version for pymyq to 1.1.0 Update version of pymyq to 1.1.0; this version brings improved functionality, reducing errors for retrieving current state for the MyQ covers. --- homeassistant/components/cover/myq.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/cover/myq.py b/homeassistant/components/cover/myq.py index bdff232fec9..b2587c06512 100644 --- a/homeassistant/components/cover/myq.py +++ b/homeassistant/components/cover/myq.py @@ -15,7 +15,7 @@ from homeassistant.const import ( STATE_OPEN, STATE_OPENING) from homeassistant.helpers import aiohttp_client, config_validation as cv -REQUIREMENTS = ['pymyq==1.0.0'] +REQUIREMENTS = ['pymyq==1.1.0'] _LOGGER = logging.getLogger(__name__) MYQ_TO_HASS = { diff --git a/requirements_all.txt b/requirements_all.txt index f8085401d98..05d9c07603c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1131,7 +1131,7 @@ pymonoprice==0.3 pymusiccast==0.1.6 # homeassistant.components.cover.myq -pymyq==1.0.0 +pymyq==1.1.0 # homeassistant.components.mysensors pymysensors==0.18.0 From e0d534c3fb5869c7cb9f79e007554b6b75266e09 Mon Sep 17 00:00:00 2001 From: Steven Looman Date: Tue, 5 Feb 2019 01:36:25 +0100 Subject: [PATCH 059/242] Upgrade to async_upnp_client==0.14.4 (#20751) --- homeassistant/components/media_player/dlna_dmr.py | 2 +- homeassistant/components/upnp/__init__.py | 2 +- requirements_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/media_player/dlna_dmr.py b/homeassistant/components/media_player/dlna_dmr.py index 0121f6e98d3..802b2b597fc 100644 --- a/homeassistant/components/media_player/dlna_dmr.py +++ b/homeassistant/components/media_player/dlna_dmr.py @@ -26,7 +26,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.util import get_local_ip -REQUIREMENTS = ['async-upnp-client==0.14.3'] +REQUIREMENTS = ['async-upnp-client==0.14.4'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index 494c39c06ba..2a1b8c52d79 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -29,7 +29,7 @@ from .const import LOGGER as _LOGGER from .device import Device -REQUIREMENTS = ['async-upnp-client==0.14.3'] +REQUIREMENTS = ['async-upnp-client==0.14.4'] NOTIFICATION_ID = 'upnp_notification' NOTIFICATION_TITLE = 'UPnP/IGD Setup' diff --git a/requirements_all.txt b/requirements_all.txt index f8085401d98..fc4adb0309c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -167,7 +167,7 @@ asterisk_mbox==0.5.0 # homeassistant.components.upnp # homeassistant.components.media_player.dlna_dmr -async-upnp-client==0.14.3 +async-upnp-client==0.14.4 # homeassistant.components.light.avion # avion==0.10 From f84317e325b409bd3037b9125c2e92f68d78ff81 Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Mon, 4 Feb 2019 23:42:30 -0600 Subject: [PATCH 060/242] Update pysmartthings to 0.5.0 (#20759) --- homeassistant/components/smartthings/__init__.py | 6 ++++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index d86524ef62b..bdbbfcf2590 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -23,7 +23,7 @@ from .const import ( from .smartapp import ( setup_smartapp, setup_smartapp_endpoint, validate_installed_app) -REQUIREMENTS = ['pysmartapp==0.3.0', 'pysmartthings==0.4.2'] +REQUIREMENTS = ['pysmartapp==0.3.0', 'pysmartthings==0.5.0'] DEPENDENCIES = ['webhook'] _LOGGER = logging.getLogger(__name__) @@ -139,6 +139,7 @@ class DeviceBroker: async def event_handler(self, req, resp, app): """Broker for incoming events.""" from pysmartapp.event import EVENT_TYPE_DEVICE + from pysmartthings import Capability, Attribute # Do not process events received from a different installed app # under the same parent SmartApp (valid use-scenario) @@ -156,7 +157,8 @@ class DeviceBroker: evt.component_id, evt.capability, evt.attribute, evt.value) # Fire events for buttons - if evt.capability == 'button' and evt.attribute == 'button': + if evt.capability == Capability.button and \ + evt.attribute == Attribute.button: data = { 'component_id': evt.component_id, 'device_id': evt.device_id, diff --git a/requirements_all.txt b/requirements_all.txt index fc4adb0309c..791e6d5ddd6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1228,7 +1228,7 @@ pysma==0.3.1 pysmartapp==0.3.0 # homeassistant.components.smartthings -pysmartthings==0.4.2 +pysmartthings==0.5.0 # homeassistant.components.device_tracker.snmp # homeassistant.components.sensor.snmp diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3ec1c7d35fa..611b20d6a0c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -217,7 +217,7 @@ pyqwikswitch==0.8 pysmartapp==0.3.0 # homeassistant.components.smartthings -pysmartthings==0.4.2 +pysmartthings==0.5.0 # homeassistant.components.sonos pysonos==0.0.6 From b1faad0a50a0dd27d0f471a1ce22bff951649e57 Mon Sep 17 00:00:00 2001 From: emontnemery Date: Tue, 5 Feb 2019 06:52:19 +0100 Subject: [PATCH 061/242] Use PLATFORM_SCHEMA_BASE as base schema for additional components. (#20578) * Disable extra=vol.ALLOW_EXTRA for additional platforms. * Remove PLATFORM_SCHEMA_2 * Add entity_namespace to base platform schema --- .../components/air_quality/__init__.py | 3 +- .../alarm_control_panel/__init__.py | 2 +- .../components/binary_sensor/__init__.py | 3 +- homeassistant/components/calendar/__init__.py | 3 +- homeassistant/components/camera/__init__.py | 3 +- homeassistant/components/climate/__init__.py | 3 +- homeassistant/components/cover/__init__.py | 3 +- .../components/device_tracker/__init__.py | 3 +- homeassistant/components/fan/__init__.py | 3 +- .../components/geo_location/__init__.py | 3 +- .../components/image_processing/__init__.py | 1 + homeassistant/components/light/__init__.py | 3 +- homeassistant/components/lock/__init__.py | 3 +- .../components/media_player/__init__.py | 3 +- homeassistant/components/mqtt/__init__.py | 2 +- homeassistant/components/mqtt/camera.py | 9 ++++-- homeassistant/components/mqtt/climate.py | 12 +++++--- homeassistant/components/remote/__init__.py | 3 +- homeassistant/components/sensor/__init__.py | 3 +- homeassistant/components/sensor/mqtt_room.py | 3 +- homeassistant/components/switch/__init__.py | 3 +- homeassistant/components/tts/__init__.py | 1 + homeassistant/components/vacuum/__init__.py | 3 +- .../components/water_heater/__init__.py | 3 +- homeassistant/components/weather/__init__.py | 3 +- homeassistant/helpers/config_validation.py | 9 +----- tests/components/binary_sensor/test_rest.py | 2 -- tests/components/cover/test_rflink.py | 27 ----------------- tests/components/mqtt/test_climate.py | 29 ++++++++++--------- tests/components/sensor/test_filter.py | 1 - tests/helpers/test_config_validation.py | 2 +- tests/test_setup.py | 2 +- 32 files changed, 75 insertions(+), 81 deletions(-) diff --git a/homeassistant/components/air_quality/__init__.py b/homeassistant/components/air_quality/__init__.py index 5f770e84b37..66af51efcb1 100644 --- a/homeassistant/components/air_quality/__init__.py +++ b/homeassistant/components/air_quality/__init__.py @@ -8,7 +8,8 @@ from datetime import timedelta import logging from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +from homeassistant.helpers.config_validation import ( # noqa + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index e02e074189c..86bb3e73bda 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -14,7 +14,7 @@ from homeassistant.const import ( SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_ARM_NIGHT, SERVICE_ALARM_ARM_CUSTOM_BYPASS) from homeassistant.helpers.config_validation import ( # noqa - PLATFORM_SCHEMA_BASE, PLATFORM_SCHEMA_2 as PLATFORM_SCHEMA) + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 7b2da21ff6a..9972e4dca3b 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -13,7 +13,8 @@ import voluptuous as vol from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity from homeassistant.const import (STATE_ON, STATE_OFF) -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +from homeassistant.helpers.config_validation import ( # noqa + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) DOMAIN = 'binary_sensor' SCAN_INTERVAL = timedelta(seconds=30) diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index 9d105fb02d0..024dc1ac9de 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -13,7 +13,8 @@ from aiohttp import web from homeassistant.components.google import ( CONF_OFFSET, CONF_DEVICE_ID, CONF_NAME) from homeassistant.const import STATE_OFF, STATE_ON -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +from homeassistant.helpers.config_validation import ( # noqa + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) from homeassistant.helpers.config_validation import time_period_str from homeassistant.helpers.entity import Entity, generate_entity_id from homeassistant.helpers.entity_component import EntityComponent diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 653d0315ad4..474f9594610 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -25,7 +25,8 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import bind_hass from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +from homeassistant.helpers.config_validation import ( # noqa + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) from homeassistant.components.http import HomeAssistantView, KEY_AUTHENTICATED from homeassistant.components import websocket_api import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 6d7f9432e39..362c4ee93e4 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -14,7 +14,8 @@ from homeassistant.helpers.temperature import display_temp as show_temp from homeassistant.util.temperature import convert as convert_temperature from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +from homeassistant.helpers.config_validation import ( # noqa + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) import homeassistant.helpers.config_validation as cv from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_ON, SERVICE_TURN_OFF, diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index b5b2a91b097..bd003f1ad67 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -13,7 +13,8 @@ import voluptuous as vol from homeassistant.loader import bind_hass from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +from homeassistant.helpers.config_validation import ( # noqa + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) import homeassistant.helpers.config_validation as cv from homeassistant.components import group from homeassistant.helpers import intent diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 202883713c7..af33453c9d5 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -22,10 +22,10 @@ from homeassistant.components.zone.zone import async_active_zone from homeassistant.config import load_yaml_config_file, async_log_exception from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_per_platform, discovery +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import GPSType, ConfigType, HomeAssistantType -import homeassistant.helpers.config_validation as cv from homeassistant import util from homeassistant.util.async_ import run_coroutine_threadsafe import homeassistant.util.dt as dt_util @@ -96,6 +96,7 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NEW_DEVICE_DEFAULTS, default={}): NEW_DEVICE_DEFAULTS_SCHEMA }) +PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE.extend(PLATFORM_SCHEMA.schema) SERVICE_SEE_PAYLOAD_SCHEMA = vol.Schema(vol.All( cv.has_at_least_one_key(ATTR_MAC, ATTR_DEV_ID), { ATTR_MAC: cv.string, diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 3525b95c007..50d6802c4d2 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -16,7 +16,8 @@ from homeassistant.const import (SERVICE_TURN_ON, SERVICE_TOGGLE, from homeassistant.loader import bind_hass from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +from homeassistant.helpers.config_validation import ( # noqa + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/geo_location/__init__.py b/homeassistant/components/geo_location/__init__.py index 4597a56c61a..9095ce617aa 100644 --- a/homeassistant/components/geo_location/__init__.py +++ b/homeassistant/components/geo_location/__init__.py @@ -9,7 +9,8 @@ import logging from typing import Optional from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +from homeassistant.helpers.config_validation import ( # noqa + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent diff --git a/homeassistant/components/image_processing/__init__.py b/homeassistant/components/image_processing/__init__.py index b2cbb2b2391..f854384bb03 100644 --- a/homeassistant/components/image_processing/__init__.py +++ b/homeassistant/components/image_processing/__init__.py @@ -60,6 +60,7 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ vol.Optional(CONF_CONFIDENCE, default=DEFAULT_CONFIDENCE): vol.All(vol.Coerce(float), vol.Range(min=0, max=100)), }) +PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE.extend(PLATFORM_SCHEMA.schema) SERVICE_SCAN_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index a16d1aaf87e..816f93b5881 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -20,7 +20,8 @@ from homeassistant.const import ( STATE_ON) from homeassistant.exceptions import UnknownUser, Unauthorized import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +from homeassistant.helpers.config_validation import ( # noqa + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers import intent diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py index 750977fac87..c5c1ce1339d 100644 --- a/homeassistant/components/lock/__init__.py +++ b/homeassistant/components/lock/__init__.py @@ -13,7 +13,8 @@ import voluptuous as vol from homeassistant.loader import bind_hass from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +from homeassistant.helpers.config_validation import ( # noqa + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) import homeassistant.helpers.config_validation as cv from homeassistant.const import ( ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED, diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index b526d1659ba..6f74380728c 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -30,7 +30,8 @@ from homeassistant.const import ( STATE_OFF, STATE_PLAYING) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +from homeassistant.helpers.config_validation import ( # noqa + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.loader import bind_hass diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index ed2a3cd6c52..e430b1fbc9f 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -234,7 +234,7 @@ MQTT_JSON_ATTRS_SCHEMA = vol.Schema({ vol.Optional(CONF_JSON_ATTRS_TOPIC): valid_subscribe_topic, }) -MQTT_BASE_PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA_2.extend(SCHEMA_BASE) +MQTT_BASE_PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(SCHEMA_BASE) # Sensor type platforms subscribe to MQTT events MQTT_RO_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend({ diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index be176a39a25..e5fb8fc66b0 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -13,7 +13,8 @@ import voluptuous as vol from homeassistant.components import camera, mqtt from homeassistant.components.camera import PLATFORM_SCHEMA, Camera from homeassistant.components.mqtt import ( - ATTR_DISCOVERY_HASH, CONF_UNIQUE_ID, MqttDiscoveryUpdate, subscription) + ATTR_DISCOVERY_HASH, CONF_STATE_TOPIC, CONF_UNIQUE_ID, MqttDiscoveryUpdate, + subscription) from homeassistant.components.mqtt.discovery import ( MQTT_DISCOVERY_NEW, clear_discovery_hash) from homeassistant.const import CONF_NAME @@ -47,7 +48,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async def async_discover(discovery_payload): """Discover and add a MQTT camera.""" try: - discovery_hash = discovery_payload[ATTR_DISCOVERY_HASH] + discovery_hash = discovery_payload.pop(ATTR_DISCOVERY_HASH) + # state_topic is implicitly set by MQTT discovery, remove it + discovery_payload.pop(CONF_STATE_TOPIC, None) config = PLATFORM_SCHEMA(discovery_payload) await _async_setup_entity(config, async_add_entities, discovery_hash) @@ -89,6 +92,8 @@ class MqttCamera(MqttDiscoveryUpdate, Camera): async def discovery_update(self, discovery_payload): """Handle updated discovery message.""" + # state_topic is implicitly set by MQTT discovery, remove it + discovery_payload.pop(CONF_STATE_TOPIC, None) config = PLATFORM_SCHEMA(discovery_payload) self._config = config await self._subscribe_topics() diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index db46f11b88e..c028ca5a6f6 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -17,9 +17,9 @@ from homeassistant.components.climate import ( SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice) from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM from homeassistant.components.mqtt import ( - ATTR_DISCOVERY_HASH, CONF_QOS, CONF_RETAIN, CONF_UNIQUE_ID, - MQTT_BASE_PLATFORM_SCHEMA, MqttAttributes, MqttAvailability, - MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription) + ATTR_DISCOVERY_HASH, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, + CONF_UNIQUE_ID, MQTT_BASE_PLATFORM_SCHEMA, MqttAttributes, + MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription) from homeassistant.components.mqtt.discovery import ( MQTT_DISCOVERY_NEW, clear_discovery_hash) from homeassistant.const import ( @@ -156,7 +156,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async def async_discover(discovery_payload): """Discover and add a MQTT climate device.""" try: - discovery_hash = discovery_payload[ATTR_DISCOVERY_HASH] + discovery_hash = discovery_payload.pop(ATTR_DISCOVERY_HASH) + # state_topic is implicitly set by MQTT discovery, remove it + discovery_payload.pop(CONF_STATE_TOPIC, None) config = PLATFORM_SCHEMA(discovery_payload) await _async_setup_entity(hass, config, async_add_entities, config_entry, discovery_hash) @@ -217,6 +219,8 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, async def discovery_update(self, discovery_payload): """Handle updated discovery message.""" + # state_topic is implicitly set by MQTT discovery, remove it + discovery_payload.pop(CONF_STATE_TOPIC, None) config = PLATFORM_SCHEMA(discovery_payload) self._config = config self._setup_from_config(config) diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index 162cb41d92e..e04b0ef2758 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -18,7 +18,8 @@ from homeassistant.const import ( STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID) from homeassistant.components import group -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +from homeassistant.helpers.config_validation import ( # noqa + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 2800b689dc6..50549f28fd7 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -11,7 +11,8 @@ import logging import voluptuous as vol from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +from homeassistant.helpers.config_validation import ( # noqa + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP, DEVICE_CLASS_PRESSURE) diff --git a/homeassistant/components/sensor/mqtt_room.py b/homeassistant/components/sensor/mqtt_room.py index 39c202ef01c..b52f039281c 100644 --- a/homeassistant/components/sensor/mqtt_room.py +++ b/homeassistant/components/sensor/mqtt_room.py @@ -38,12 +38,11 @@ DEFAULT_TOPIC = 'room_presence' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_DEVICE_ID): cv.string, - vol.Required(CONF_STATE_TOPIC, default=DEFAULT_TOPIC): cv.string, vol.Required(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, vol.Optional(CONF_AWAY_TIMEOUT, default=DEFAULT_AWAY_TIMEOUT): cv.positive_int, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string -}) +}).extend(mqtt.MQTT_RO_PLATFORM_SCHEMA.schema) MQTT_PAYLOAD = vol.Schema(vol.All(json.loads, vol.Schema({ vol.Required(ATTR_ID): cv.string, diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 513ebbcb5ea..d517f635a92 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -12,7 +12,8 @@ import voluptuous as vol from homeassistant.loader import bind_hass from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import ToggleEntity -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +from homeassistant.helpers.config_validation import ( # noqa + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) import homeassistant.helpers.config_validation as cv from homeassistant.const import ( STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index 063ba428d4a..3f8d1b6fdec 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -68,6 +68,7 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ vol.All(vol.Coerce(int), vol.Range(min=60, max=57600)), vol.Optional(CONF_BASE_URL): cv.string, }) +PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE.extend(PLATFORM_SCHEMA.schema) SCHEMA_SERVICE_SAY = vol.Schema({ vol.Required(ATTR_MESSAGE): cv.string, diff --git a/homeassistant/components/vacuum/__init__.py b/homeassistant/components/vacuum/__init__.py index 6341c9661ed..3fdc7cb1a51 100644 --- a/homeassistant/components/vacuum/__init__.py +++ b/homeassistant/components/vacuum/__init__.py @@ -16,7 +16,8 @@ from homeassistant.const import ( SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_ON, STATE_PAUSED, STATE_IDLE) from homeassistant.loader import bind_hass import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +from homeassistant.helpers.config_validation import ( # noqa + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import (ToggleEntity, Entity) from homeassistant.helpers.icon import icon_for_battery_level diff --git a/homeassistant/components/water_heater/__init__.py b/homeassistant/components/water_heater/__init__.py index fee2846e8d5..07acb15b765 100644 --- a/homeassistant/components/water_heater/__init__.py +++ b/homeassistant/components/water_heater/__init__.py @@ -14,7 +14,8 @@ from homeassistant.helpers.temperature import display_temp as show_temp from homeassistant.util.temperature import convert as convert_temperature from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +from homeassistant.helpers.config_validation import ( # noqa + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) import homeassistant.helpers.config_validation as cv from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_ON, SERVICE_TURN_OFF, diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index b9cb300ee21..d479725657b 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -10,7 +10,8 @@ import logging from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.temperature import display_temp as show_temp from homeassistant.const import PRECISION_WHOLE, PRECISION_TENTHS, TEMP_CELSIUS -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +from homeassistant.helpers.config_validation import ( # noqa + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index b148a875398..36dca2bbcaf 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -556,16 +556,9 @@ PLATFORM_SCHEMA = vol.Schema({ vol.Required(CONF_PLATFORM): string, vol.Optional(CONF_ENTITY_NAMESPACE): string, vol.Optional(CONF_SCAN_INTERVAL): time_period -}, extra=vol.ALLOW_EXTRA) - -# This will replace PLATFORM_SCHEMA once all base components are updated -PLATFORM_SCHEMA_2 = vol.Schema({ - vol.Required(CONF_PLATFORM): string, - vol.Optional(CONF_ENTITY_NAMESPACE): string, - vol.Optional(CONF_SCAN_INTERVAL): time_period }) -PLATFORM_SCHEMA_BASE = PLATFORM_SCHEMA_2.extend({ +PLATFORM_SCHEMA_BASE = PLATFORM_SCHEMA.extend({ }, extra=vol.ALLOW_EXTRA) EVENT_SCHEMA = vol.Schema({ diff --git a/tests/components/binary_sensor/test_rest.py b/tests/components/binary_sensor/test_rest.py index d3df1a32ac7..befeca07251 100644 --- a/tests/components/binary_sensor/test_rest.py +++ b/tests/components/binary_sensor/test_rest.py @@ -99,7 +99,6 @@ class TestRestBinarySensorSetup(unittest.TestCase): 'method': 'GET', 'value_template': '{{ value_json.key }}', 'name': 'foo', - 'unit_of_measurement': 'MB', 'verify_ssl': 'true', 'authentication': 'basic', 'username': 'my username', @@ -122,7 +121,6 @@ class TestRestBinarySensorSetup(unittest.TestCase): 'value_template': '{{ value_json.key }}', 'payload': '{ "device": "toaster"}', 'name': 'foo', - 'unit_of_measurement': 'MB', 'verify_ssl': 'true', 'authentication': 'basic', 'username': 'my username', diff --git a/tests/components/cover/test_rflink.py b/tests/components/cover/test_rflink.py index 4f88d24d97f..9c6d41a26c0 100644 --- a/tests/components/cover/test_rflink.py +++ b/tests/components/cover/test_rflink.py @@ -416,33 +416,6 @@ async def test_nogroup_device_id(hass, monkeypatch): assert hass.states.get(DOMAIN + '.test').state == STATE_OPEN -async def test_disable_automatic_add(hass, monkeypatch): - """If disabled new devices should not be automatically added.""" - config = { - 'rflink': { - 'port': '/dev/ttyABC0', - }, - DOMAIN: { - 'platform': 'rflink', - 'automatic_add': False, - }, - } - - # setup mocking rflink module - event_callback, _, _, _ = await mock_rflink( - hass, config, DOMAIN, monkeypatch) - - # test event for new unconfigured sensor - event_callback({ - 'id': 'protocol_0_0', - 'command': 'down', - }) - await hass.async_block_till_done() - - # make sure new device is not added - assert not hass.states.get(DOMAIN + '.protocol_0_0') - - async def test_restore_state(hass, monkeypatch): """Ensure states are restored on startup.""" config = { diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index c9b7c748ea5..ecbdc39e22b 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -679,7 +679,8 @@ async def test_setting_attribute_via_mqtt_json_message(hass, mqtt_mock): climate.DOMAIN: { 'platform': 'mqtt', 'name': 'test', - 'state_topic': 'test-topic', + 'power_state_topic': 'test-topic', + 'power_command_topic': 'test_topic', 'json_attributes_topic': 'attr-topic' } }) @@ -697,7 +698,8 @@ async def test_update_with_json_attrs_not_dict(hass, mqtt_mock, caplog): climate.DOMAIN: { 'platform': 'mqtt', 'name': 'test', - 'state_topic': 'test-topic', + 'power_state_topic': 'test-topic', + 'power_command_topic': 'test_topic', 'json_attributes_topic': 'attr-topic' } }) @@ -716,7 +718,8 @@ async def test_update_with_json_attrs_bad_JSON(hass, mqtt_mock, caplog): climate.DOMAIN: { 'platform': 'mqtt', 'name': 'test', - 'state_topic': 'test-topic', + 'power_state_topic': 'test-topic', + 'power_command_topic': 'test_topic', 'json_attributes_topic': 'attr-topic' } }) @@ -735,12 +738,14 @@ async def test_discovery_update_attr(hass, mqtt_mock, caplog): await async_start(hass, 'homeassistant', {}, entry) data1 = ( '{ "name": "Beer",' - ' "command_topic": "test_topic",' + ' "power_state_topic": "test-topic",' + ' "power_command_topic": "test_topic",' ' "json_attributes_topic": "attr-topic1" }' ) data2 = ( '{ "name": "Beer",' - ' "command_topic": "test_topic",' + ' "power_state_topic": "test-topic",' + ' "power_command_topic": "test_topic",' ' "json_attributes_topic": "attr-topic2" }' ) async_fire_mqtt_message(hass, 'homeassistant/climate/bla/config', @@ -780,14 +785,14 @@ async def test_unique_id(hass): climate.DOMAIN: [{ 'platform': 'mqtt', 'name': 'Test 1', - 'status_topic': 'test-topic', - 'command_topic': 'test_topic', + 'power_state_topic': 'test-topic', + 'power_command_topic': 'test_topic', 'unique_id': 'TOTALLY_UNIQUE' }, { 'platform': 'mqtt', 'name': 'Test 2', - 'status_topic': 'test-topic', - 'command_topic': 'test_topic', + 'power_state_topic': 'test-topic', + 'power_command_topic': 'test_topic', 'unique_id': 'TOTALLY_UNIQUE' }] }) @@ -891,8 +896,6 @@ async def test_entity_device_info_with_identifier(hass, mqtt_mock): data = json.dumps({ 'platform': 'mqtt', 'name': 'Test 1', - 'state_topic': 'test-topic', - 'command_topic': 'test-topic', 'device': { 'identifiers': ['helloworld'], 'connections': [ @@ -930,8 +933,8 @@ async def test_entity_device_info_update(hass, mqtt_mock): config = { 'platform': 'mqtt', 'name': 'Test 1', - 'state_topic': 'test-topic', - 'command_topic': 'test-command-topic', + 'power_state_topic': 'test-topic', + 'power_command_topic': 'test-command-topic', 'device': { 'identifiers': ['helloworld'], 'connections': [ diff --git a/tests/components/sensor/test_filter.py b/tests/components/sensor/test_filter.py index 29718314ef4..b43d38da5e8 100644 --- a/tests/components/sensor/test_filter.py +++ b/tests/components/sensor/test_filter.py @@ -59,7 +59,6 @@ class TestFilterSensor(unittest.TestCase): 'platform': 'filter', 'name': 'test', 'entity_id': 'sensor.test_monitored', - 'history_period': '00:05', 'filters': [{ 'filter': 'outlier', 'window_size': 10, diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 1bae84b5320..53ed8004f7d 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -110,7 +110,7 @@ def test_platform_config(): {'platform': 'mqtt', 'beer': 'yes'}, ) for value in options: - cv.PLATFORM_SCHEMA(value) + cv.PLATFORM_SCHEMA_BASE(value) def test_ensure_list(): diff --git a/tests/test_setup.py b/tests/test_setup.py index 6d0d2a35847..8575b023d37 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -15,7 +15,7 @@ import homeassistant.config as config_util from homeassistant import setup, loader import homeassistant.util.dt as dt_util from homeassistant.helpers.config_validation import ( - PLATFORM_SCHEMA_2 as PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) + PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) from homeassistant.helpers import discovery from tests.common import \ From 2733919cd88935bafafdc2cd9a6b94a4810f21d6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 5 Feb 2019 01:45:03 -0800 Subject: [PATCH 062/242] Keep cloud tokens always valid (#20762) * Keep auth token always valid * Remove unused refresh_auth message * Capture EndpointConnectionError * Lint --- homeassistant/components/cloud/__init__.py | 3 +- homeassistant/components/cloud/auth_api.py | 73 +++++++++++++++++++--- homeassistant/components/cloud/iot.py | 30 +++++++-- tests/components/cloud/test_auth_api.py | 29 +++++++++ tests/components/cloud/test_iot.py | 23 +------ 5 files changed, 122 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index d938dd20e67..98e649e1742 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -106,6 +106,7 @@ async def async_setup(hass, config): ) cloud = hass.data[DOMAIN] = Cloud(hass, **kwargs) + await auth_api.async_setup(hass, cloud) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, cloud.async_start) await http_api.async_setup(hass) return True @@ -263,7 +264,7 @@ class Cloud: self.access_token = info['access_token'] self.refresh_token = info['refresh_token'] - self.hass.add_job(self.iot.connect()) + self.hass.async_create_task(self.iot.connect()) def _decode_claims(self, token): # pylint: disable=no-self-use """Decode the claims in a token.""" diff --git a/homeassistant/components/cloud/auth_api.py b/homeassistant/components/cloud/auth_api.py index 954d28b803f..6019dac87b9 100644 --- a/homeassistant/components/cloud/auth_api.py +++ b/homeassistant/components/cloud/auth_api.py @@ -1,4 +1,10 @@ """Package to communicate with the authentication API.""" +import asyncio +import logging +import random + + +_LOGGER = logging.getLogger(__name__) class CloudError(Exception): @@ -39,6 +45,40 @@ AWS_EXCEPTIONS = { } +async def async_setup(hass, cloud): + """Configure the auth api.""" + refresh_task = None + + async def handle_token_refresh(): + """Handle Cloud access token refresh.""" + sleep_time = 5 + sleep_time = random.randint(2400, 3600) + while True: + try: + await asyncio.sleep(sleep_time) + await hass.async_add_executor_job(renew_access_token, cloud) + except CloudError as err: + _LOGGER.error("Can't refresh cloud token: %s", err) + except asyncio.CancelledError: + # Task is canceled, stop it. + break + + sleep_time = random.randint(3100, 3600) + + async def on_connect(): + """When the instance is connected.""" + nonlocal refresh_task + refresh_task = hass.async_create_task(handle_token_refresh()) + + async def on_disconnect(): + """When the instance is disconnected.""" + nonlocal refresh_task + refresh_task.cancel() + + cloud.iot.register_on_connect(on_connect) + cloud.iot.register_on_disconnect(on_disconnect) + + def _map_aws_exception(err): """Map AWS exception to our exceptions.""" ex = AWS_EXCEPTIONS.get(err.response['Error']['Code'], UnknownError) @@ -47,7 +87,7 @@ def _map_aws_exception(err): def register(cloud, email, password): """Register a new account.""" - from botocore.exceptions import ClientError + from botocore.exceptions import ClientError, EndpointConnectionError cognito = _cognito(cloud) # Workaround for bug in Warrant. PR with fix: @@ -55,13 +95,16 @@ def register(cloud, email, password): cognito.add_base_attributes() try: cognito.register(email, password) + except ClientError as err: raise _map_aws_exception(err) + except EndpointConnectionError: + raise UnknownError() def resend_email_confirm(cloud, email): """Resend email confirmation.""" - from botocore.exceptions import ClientError + from botocore.exceptions import ClientError, EndpointConnectionError cognito = _cognito(cloud, username=email) @@ -72,18 +115,23 @@ def resend_email_confirm(cloud, email): ) except ClientError as err: raise _map_aws_exception(err) + except EndpointConnectionError: + raise UnknownError() def forgot_password(cloud, email): """Initialize forgotten password flow.""" - from botocore.exceptions import ClientError + from botocore.exceptions import ClientError, EndpointConnectionError cognito = _cognito(cloud, username=email) try: cognito.initiate_forgot_password() + except ClientError as err: raise _map_aws_exception(err) + except EndpointConnectionError: + raise UnknownError() def login(cloud, email, password): @@ -97,7 +145,7 @@ def login(cloud, email, password): def check_token(cloud): """Check that the token is valid and verify if needed.""" - from botocore.exceptions import ClientError + from botocore.exceptions import ClientError, EndpointConnectionError cognito = _cognito( cloud, @@ -109,13 +157,17 @@ def check_token(cloud): cloud.id_token = cognito.id_token cloud.access_token = cognito.access_token cloud.write_user_info() + except ClientError as err: raise _map_aws_exception(err) + except EndpointConnectionError: + raise UnknownError() + def renew_access_token(cloud): """Renew access token.""" - from botocore.exceptions import ClientError + from botocore.exceptions import ClientError, EndpointConnectionError cognito = _cognito( cloud, @@ -127,13 +179,17 @@ def renew_access_token(cloud): cloud.id_token = cognito.id_token cloud.access_token = cognito.access_token cloud.write_user_info() + except ClientError as err: raise _map_aws_exception(err) + except EndpointConnectionError: + raise UnknownError() + def _authenticate(cloud, email, password): """Log in and return an authenticated Cognito instance.""" - from botocore.exceptions import ClientError + from botocore.exceptions import ClientError, EndpointConnectionError from warrant.exceptions import ForceChangePasswordException assert not cloud.is_logged_in, 'Cannot login if already logged in.' @@ -145,11 +201,14 @@ def _authenticate(cloud, email, password): return cognito except ForceChangePasswordException: - raise PasswordChangeRequired + raise PasswordChangeRequired() except ClientError as err: raise _map_aws_exception(err) + except EndpointConnectionError: + raise UnknownError() + def _cognito(cloud, **kwargs): """Get the client credentials.""" diff --git a/homeassistant/components/cloud/iot.py b/homeassistant/components/cloud/iot.py index d725cb309bc..055c4dbaa64 100644 --- a/homeassistant/components/cloud/iot.py +++ b/homeassistant/components/cloud/iot.py @@ -62,12 +62,18 @@ class CloudIoT: # Local code waiting for a response self._response_handler = {} self._on_connect = [] + self._on_disconnect = [] @callback def register_on_connect(self, on_connect_cb): """Register an async on_connect callback.""" self._on_connect.append(on_connect_cb) + @callback + def register_on_disconnect(self, on_disconnect_cb): + """Register an async on_disconnect callback.""" + self._on_disconnect.append(on_disconnect_cb) + @property def connected(self): """Return if we're currently connected.""" @@ -102,6 +108,17 @@ class CloudIoT: # Still adding it here to make sure we can always reconnect _LOGGER.exception("Unexpected error") + if self.state == STATE_CONNECTED and self._on_disconnect: + try: + yield from asyncio.wait([ + cb() for cb in self._on_disconnect + ]) + except Exception: # pylint: disable=broad-except + # Safety net. This should never hit. + # Still adding it here to make sure we don't break the flow + _LOGGER.exception( + "Unexpected error in on_disconnect callbacks") + if self.close_requested: break @@ -192,7 +209,13 @@ class CloudIoT: self.state = STATE_CONNECTED if self._on_connect: - yield from asyncio.wait([cb() for cb in self._on_connect]) + try: + yield from asyncio.wait([cb() for cb in self._on_connect]) + except Exception: # pylint: disable=broad-except + # Safety net. This should never hit. + # Still adding it here to make sure we don't break the flow + _LOGGER.exception( + "Unexpected error in on_connect callbacks") while not client.closed: msg = yield from client.receive() @@ -326,11 +349,6 @@ async def async_handle_cloud(hass, cloud, payload): await cloud.logout() _LOGGER.error("You have been logged out from Home Assistant cloud: %s", payload['reason']) - elif action == 'refresh_auth': - # Refresh the auth token between now and payload['seconds'] - hass.helpers.event.async_call_later( - random.randint(0, payload['seconds']), - lambda now: auth_api.check_token(cloud)) else: _LOGGER.warning("Received unknown cloud action: %s", action) diff --git a/tests/components/cloud/test_auth_api.py b/tests/components/cloud/test_auth_api.py index a50a4d796aa..bdf9939cb2b 100644 --- a/tests/components/cloud/test_auth_api.py +++ b/tests/components/cloud/test_auth_api.py @@ -1,4 +1,5 @@ """Tests for the tools to communicate with the cloud.""" +import asyncio from unittest.mock import MagicMock, patch from botocore.exceptions import ClientError @@ -165,3 +166,31 @@ def test_check_token_raises(mock_cognito): assert cloud.id_token != mock_cognito.id_token assert cloud.access_token != mock_cognito.access_token assert len(cloud.write_user_info.mock_calls) == 0 + + +async def test_async_setup(hass): + """Test async setup.""" + cloud = MagicMock() + await auth_api.async_setup(hass, cloud) + assert len(cloud.iot.mock_calls) == 2 + on_connect = cloud.iot.mock_calls[0][1][0] + on_disconnect = cloud.iot.mock_calls[1][1][0] + + with patch('random.randint', return_value=0), patch( + 'homeassistant.components.cloud.auth_api.renew_access_token' + ) as mock_renew: + await on_connect() + # Let handle token sleep once + await asyncio.sleep(0) + # Let handle token refresh token + await asyncio.sleep(0) + + assert len(mock_renew.mock_calls) == 1 + assert mock_renew.mock_calls[0][1][0] is cloud + + await on_disconnect() + + # Make sure task is no longer being called + await asyncio.sleep(0) + await asyncio.sleep(0) + assert len(mock_renew.mock_calls) == 1 diff --git a/tests/components/cloud/test_iot.py b/tests/components/cloud/test_iot.py index 1a528f8cedf..10a94f46833 100644 --- a/tests/components/cloud/test_iot.py +++ b/tests/components/cloud/test_iot.py @@ -10,9 +10,8 @@ from homeassistant.components.cloud import ( Cloud, iot, auth_api, MODE_DEV) from homeassistant.components.cloud.const import ( PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE) -from homeassistant.util import dt as dt_util from tests.components.alexa import test_smart_home as test_alexa -from tests.common import mock_coro, async_fire_time_changed +from tests.common import mock_coro from . import mock_cloud_prefs @@ -158,26 +157,6 @@ async def test_handling_core_messages_logout(hass, mock_cloud): assert len(mock_cloud.logout.mock_calls) == 1 -async def test_handling_core_messages_refresh_auth(hass, mock_cloud): - """Test handling core messages.""" - mock_cloud.hass = hass - with patch('random.randint', return_value=0) as mock_rand, patch( - 'homeassistant.components.cloud.auth_api.check_token' - ) as mock_check: - await iot.async_handle_cloud(hass, mock_cloud, { - 'action': 'refresh_auth', - 'seconds': 230, - }) - async_fire_time_changed(hass, dt_util.utcnow()) - await hass.async_block_till_done() - - assert len(mock_rand.mock_calls) == 1 - assert mock_rand.mock_calls[0][1] == (0, 230) - - assert len(mock_check.mock_calls) == 1 - assert mock_check.mock_calls[0][1][0] is mock_cloud - - @asyncio.coroutine def test_cloud_getting_disconnected_by_server(mock_client, caplog, mock_cloud): """Test server disconnecting instance.""" From ef6b0b8e0b228cc2ccc0d68f0f0cdef878afb190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Tue, 5 Feb 2019 11:12:09 +0100 Subject: [PATCH 063/242] Update flake8 to 3.7.5 (#20761) * Upgrade flake8 * Upgrade flake8 * Add noqa for hound --- homeassistant/components/cast/media_player.py | 1 + homeassistant/components/device_tracker/bbox.py | 2 ++ homeassistant/components/isy994/__init__.py | 2 +- homeassistant/core.py | 6 +++--- requirements_test.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/sensor/test_dsmr.py | 2 +- tests/components/zwave/test_init.py | 6 +++--- 8 files changed, 13 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 20a44c0e910..c66f74f74a9 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -323,6 +323,7 @@ class CastDevice(MediaPlayerDevice): def __init__(self, cast_info): """Initialize the cast device.""" + import pychromecast # noqa: pylint: disable=unused-import self._cast_info = cast_info # type: ChromecastInfo self._chromecast = None # type: Optional[pychromecast.Chromecast] self.cast_status = None diff --git a/homeassistant/components/device_tracker/bbox.py b/homeassistant/components/device_tracker/bbox.py index 297e98e548a..f59c922577b 100644 --- a/homeassistant/components/device_tracker/bbox.py +++ b/homeassistant/components/device_tracker/bbox.py @@ -45,6 +45,8 @@ class BboxDeviceScanner(DeviceScanner): def __init__(self, config): """Get host from config.""" + from typing import List # noqa: pylint: disable=unused-import + self.host = config[CONF_HOST] """Initialize the scanner.""" diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index a9916ed54fe..2b5f8fcb13f 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -265,7 +265,7 @@ def _is_sensor_a_binary_sensor(hass: HomeAssistant, node) -> bool: def _categorize_nodes(hass: HomeAssistant, nodes, ignore_identifier: str, - sensor_identifier: str)-> None: + sensor_identifier: str) -> None: """Sort the nodes to their proper domains.""" for (path, node) in nodes: ignored = ignore_identifier in path or ignore_identifier in node.name diff --git a/homeassistant/core.py b/homeassistant/core.py index 6ddefd2022d..5cd23e9f9a2 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -778,7 +778,7 @@ class StateMachine: self._bus = bus self._loop = loop - def entity_ids(self, domain_filter: Optional[str] = None)-> List[str]: + def entity_ids(self, domain_filter: Optional[str] = None) -> List[str]: """List of entity ids that are being tracked.""" future = run_callback_threadsafe( self._loop, self.async_entity_ids, domain_filter @@ -800,13 +800,13 @@ class StateMachine: return [state.entity_id for state in self._states.values() if state.domain == domain_filter] - def all(self)-> List[State]: + def all(self) -> List[State]: """Create a list of all states.""" return run_callback_threadsafe( # type: ignore self._loop, self.async_all).result() @callback - def async_all(self)-> List[State]: + def async_all(self) -> List[State]: """Create a list of all states. This method must be run in the event loop. diff --git a/requirements_test.txt b/requirements_test.txt index af256efc709..d99d878ef63 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -4,7 +4,7 @@ asynctest==0.12.2 coveralls==1.2.0 flake8-docstrings==1.3.0 -flake8==3.6.0 +flake8==3.7.5 mock-open==1.3.1 mypy==0.650 pydocstyle==3.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 611b20d6a0c..cb6d000dca8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -5,7 +5,7 @@ asynctest==0.12.2 coveralls==1.2.0 flake8-docstrings==1.3.0 -flake8==3.6.0 +flake8==3.7.5 mock-open==1.3.1 mypy==0.650 pydocstyle==3.0.0 diff --git a/tests/components/sensor/test_dsmr.py b/tests/components/sensor/test_dsmr.py index 8d9df11116a..dbf1e1fe7dd 100644 --- a/tests/components/sensor/test_dsmr.py +++ b/tests/components/sensor/test_dsmr.py @@ -82,7 +82,7 @@ def test_default_setup(hass, mock_connection_factory): # ensure entities have new state value after incoming telegram power_consumption = hass.states.get('sensor.power_consumption') assert power_consumption.state == '0.0' - assert power_consumption.attributes.get('unit_of_measurement') is 'kWh' + assert power_consumption.attributes.get('unit_of_measurement') == 'kWh' # tariff should be translated in human readable and have no unit power_tariff = hass.states.get('sensor.power_tariff') diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index 85cca89eefc..212d3e02802 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -214,7 +214,7 @@ async def test_node_discovery(hass, mock_openzwave): hass.async_add_job(mock_receivers[0], node) await hass.async_block_till_done() - assert hass.states.get('zwave.mock_node').state is 'unknown' + assert hass.states.get('zwave.mock_node').state == 'unknown' async def test_unparsed_node_discovery(hass, mock_openzwave): @@ -257,7 +257,7 @@ async def test_unparsed_node_discovery(hass, mock_openzwave): assert len(mock_logger.warning.mock_calls) == 1 assert mock_logger.warning.mock_calls[0][1][1:] == \ (14, const.NODE_READY_WAIT_SECS) - assert hass.states.get('zwave.unknown_node_14').state is 'unknown' + assert hass.states.get('zwave.unknown_node_14').state == 'unknown' async def test_node_ignored(hass, mock_openzwave): @@ -307,7 +307,7 @@ async def test_value_discovery(hass, mock_openzwave): await hass.async_block_till_done() assert hass.states.get( - 'binary_sensor.mock_node_mock_value').state is 'off' + 'binary_sensor.mock_node_mock_value').state == 'off' async def test_value_discovery_existing_entity(hass, mock_openzwave): From aa4a3d8b9618d31fdfae69201c01983eb476b862 Mon Sep 17 00:00:00 2001 From: Jean Gauthier Date: Tue, 5 Feb 2019 05:26:28 -0500 Subject: [PATCH 064/242] Modifying MTUs acquisition (#20654) **Description:** I modified the file because it should not disable a MTU based on voltage or power. If one has programmed a certain amount of MTUs in his Gateway, they should be all visible and show 0W or 0V. The problem arises when you have a device that shuts off at night (e.g.: pool pump). It pulls 0W for a while, I don't want my interface to show a big yellow error during that time because the sensor no longer exists. Even the 0V is not a good idea because we can use it to indicate the breaker has tripped. Hopefully it would be accepted :-) --- homeassistant/components/sensor/ted5000.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/homeassistant/components/sensor/ted5000.py b/homeassistant/components/sensor/ted5000.py index a6ea7cbd534..82a1ec8bb68 100644 --- a/homeassistant/components/sensor/ted5000.py +++ b/homeassistant/components/sensor/ted5000.py @@ -114,7 +114,4 @@ class Ted5000Gateway: voltage = int(doc["LiveData"]["Voltage"]["MTU%d" % mtu] ["VoltageNow"]) - if power == 0 or voltage == 0: - continue - else: - self.data[mtu] = {'W': power, 'V': voltage / 10} + self.data[mtu] = {'W': power, 'V': voltage / 10} From e581d41ded30aac220875c4e5f8fd41e56889742 Mon Sep 17 00:00:00 2001 From: Eliseo Martelli Date: Tue, 5 Feb 2019 13:42:14 +0100 Subject: [PATCH 065/242] Fix googlehome alarm sensor platform (#20742) * fixed googlehome alarm sensor platform * removed info and moved info discovery out of loop * moved device info up --- homeassistant/components/googlehome/sensor.py | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/googlehome/sensor.py b/homeassistant/components/googlehome/sensor.py index f2a0f822dbf..90b9cda80bb 100644 --- a/homeassistant/components/googlehome/sensor.py +++ b/homeassistant/components/googlehome/sensor.py @@ -7,11 +7,10 @@ https://home-assistant.io/components/sensor.googlehome/ import logging from datetime import timedelta -from homeassistant.components.sensor import ENTITY_ID_FORMAT from homeassistant.components.googlehome import ( CLIENT, DOMAIN as GOOGLEHOME_DOMAIN, NAME) from homeassistant.const import DEVICE_CLASS_TIMESTAMP -from homeassistant.helpers.entity import Entity, async_generate_entity_id +from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util @@ -35,11 +34,16 @@ async def async_setup_platform(hass, config, if discovery_info is None: _LOGGER.warning( "To use this you need to configure the 'googlehome' component") + return + + await hass.data[CLIENT].update_info(discovery_info['host']) + data = hass.data[GOOGLEHOME_DOMAIN][discovery_info['host']] + info = data.get('info', {}) devices = [] for condition in SENSOR_TYPES: device = GoogleHomeAlarm(hass.data[CLIENT], condition, - discovery_info) + discovery_info, info.get('name', NAME)) devices.append(device) async_add_entities(devices, True) @@ -48,7 +52,7 @@ async def async_setup_platform(hass, config, class GoogleHomeAlarm(Entity): """Representation of a GoogleHomeAlarm.""" - def __init__(self, client, condition, config): + def __init__(self, client, condition, config, name): """Initialize the GoogleHomeAlarm sensor.""" self._host = config['host'] self._client = client @@ -56,18 +60,7 @@ class GoogleHomeAlarm(Entity): self._name = None self._state = None self._available = True - - async def async_added_to_hass(self): - """Subscribe GoogleHome events.""" - await self._client.update_info(self._host) - data = self.hass.data[GOOGLEHOME_DOMAIN][self._host] - info = data.get('info', {}) - if info is None: - return - self._name = "{} {}".format(info.get('name', NAME), - SENSOR_TYPES[self._condition]) - self.entity_id = async_generate_entity_id( - ENTITY_ID_FORMAT, self._name, hass=self.hass) + self._name = "{} {}".format(name, SENSOR_TYPES[self._condition]) async def async_update(self): """Update the data.""" From 208ea6eae471d32aa2862576a7a3f10e990ef3d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomas=20Hellstr=C3=B6m?= Date: Tue, 5 Feb 2019 13:43:04 +0100 Subject: [PATCH 066/242] SMHI component: Bugfix - calc precipitation (#20745) * Bugfix - calc precipitation * Feedback: better way to skip first forecast * Even less messy way * lint error --- homeassistant/components/smhi/__init__.py | 2 +- homeassistant/components/smhi/weather.py | 25 +++++++++++------------ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/smhi/test_weather.py | 20 ++++++++++-------- 5 files changed, 26 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/smhi/__init__.py b/homeassistant/components/smhi/__init__.py index 0ca3bac3e35..ba7ccc2f682 100644 --- a/homeassistant/components/smhi/__init__.py +++ b/homeassistant/components/smhi/__init__.py @@ -11,7 +11,7 @@ from homeassistant.core import Config, HomeAssistant from .config_flow import smhi_locations # noqa: F401 from .const import DOMAIN # noqa: F401 -REQUIREMENTS = ['smhi-pkg==1.0.5'] +REQUIREMENTS = ['smhi-pkg==1.0.8'] DEFAULT_NAME = 'smhi' diff --git a/homeassistant/components/smhi/weather.py b/homeassistant/components/smhi/weather.py index 94873b03bd6..79fd1166a4b 100644 --- a/homeassistant/components/smhi/weather.py +++ b/homeassistant/components/smhi/weather.py @@ -22,7 +22,7 @@ from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS) from homeassistant.core import HomeAssistant from homeassistant.helpers import aiohttp_client -from homeassistant.util import Throttle, dt, slugify +from homeassistant.util import Throttle, slugify DEPENDENCIES = ['smhi'] @@ -208,25 +208,24 @@ class SmhiWeather(WeatherEntity): @property def forecast(self) -> List: """Return the forecast.""" - if self._forecasts is None: + if self._forecasts is None or len(self._forecasts) < 2: return None data = [] - for forecast in self._forecasts: + + for forecast in self._forecasts[1:]: condition = next(( k for k, v in CONDITION_CLASSES.items() if forecast.symbol in v), None) - # Only get mid day forecasts - if forecast.valid_time.hour == 12: - data.append({ - ATTR_FORECAST_TIME: dt.as_local(forecast.valid_time), - ATTR_FORECAST_TEMP: forecast.temperature_max, - ATTR_FORECAST_TEMP_LOW: forecast.temperature_min, - ATTR_FORECAST_PRECIPITATION: - round(forecast.mean_precipitation*24), - ATTR_FORECAST_CONDITION: condition, - }) + data.append({ + ATTR_FORECAST_TIME: forecast.valid_time.isoformat(), + ATTR_FORECAST_TEMP: forecast.temperature_max, + ATTR_FORECAST_TEMP_LOW: forecast.temperature_min, + ATTR_FORECAST_PRECIPITATION: + round(forecast.total_precipitation), + ATTR_FORECAST_CONDITION: condition, + }) return data diff --git a/requirements_all.txt b/requirements_all.txt index 68a7bc3e931..35019f74408 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1552,7 +1552,7 @@ smappy==0.2.16 # smbus-cffi==0.5.1 # homeassistant.components.smhi -smhi-pkg==1.0.5 +smhi-pkg==1.0.8 # homeassistant.components.media_player.snapcast snapcast==2.0.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cb6d000dca8..dd455ef88fd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -269,7 +269,7 @@ simplisafe-python==3.1.14 sleepyq==0.6 # homeassistant.components.smhi -smhi-pkg==1.0.5 +smhi-pkg==1.0.8 # homeassistant.components.climate.honeywell somecomfort==0.5.2 diff --git a/tests/components/smhi/test_weather.py b/tests/components/smhi/test_weather.py index aaf22ffce65..f0acd231ebe 100644 --- a/tests/components/smhi/test_weather.py +++ b/tests/components/smhi/test_weather.py @@ -1,7 +1,7 @@ """Test for the smhi weather entity.""" import asyncio import logging -from datetime import datetime, timezone +from datetime import datetime from unittest.mock import Mock, patch from homeassistant.components.weather import ( @@ -61,12 +61,11 @@ async def test_setup_hass(hass: HomeAssistant, aioclient_mock) -> None: assert state.attributes[ATTR_WEATHER_WIND_SPEED] == 7 assert state.attributes[ATTR_WEATHER_WIND_BEARING] == 134 _LOGGER.error(state.attributes) - assert len(state.attributes['forecast']) == 1 + assert len(state.attributes['forecast']) == 4 - forecast = state.attributes['forecast'][0] - assert forecast[ATTR_FORECAST_TIME] == datetime(2018, 9, 2, 12, 0, - tzinfo=timezone.utc) - assert forecast[ATTR_FORECAST_TEMP] == 20 + forecast = state.attributes['forecast'][1] + assert forecast[ATTR_FORECAST_TIME] == '2018-09-02T12:00:00' + assert forecast[ATTR_FORECAST_TEMP] == 21 assert forecast[ATTR_FORECAST_TEMP_LOW] == 6 assert forecast[ATTR_FORECAST_PRECIPITATION] == 0 assert forecast[ATTR_FORECAST_CONDITION] == 'partlycloudy' @@ -102,7 +101,8 @@ def test_properties_unknown_symbol() -> None: hass = Mock() data = Mock() data.temperature = 5 - data.mean_precipitation = 1 + data.mean_precipitation = 0.5 + data.total_precipitation = 1 data.humidity = 5 data.wind_speed = 10 data.wind_direction = 180 @@ -114,7 +114,8 @@ def test_properties_unknown_symbol() -> None: data2 = Mock() data2.temperature = 5 - data2.mean_precipitation = 1 + data2.mean_precipitation = 0.5 + data2.total_precipitation = 1 data2.humidity = 5 data2.wind_speed = 10 data2.wind_direction = 180 @@ -126,7 +127,8 @@ def test_properties_unknown_symbol() -> None: data3 = Mock() data3.temperature = 5 - data3.mean_precipitation = 1 + data3.mean_precipitation = 0.5 + data3.total_precipitation = 1 data3.humidity = 5 data3.wind_speed = 10 data3.wind_direction = 180 From a94a24f6f83508642e220fadf2799789dc32a25b Mon Sep 17 00:00:00 2001 From: Andreas Hartl Date: Tue, 5 Feb 2019 16:11:19 +0100 Subject: [PATCH 067/242] Added HomeKit fan speed based on speed_list (#19767) Speed_list needs to be in ascending order. --- homeassistant/components/homekit/const.py | 1 + homeassistant/components/homekit/type_fans.py | 44 +++++++++++-- homeassistant/components/homekit/util.py | 54 +++++++++++++-- tests/components/homekit/test_type_fans.py | 49 +++++++++++++- tests/components/homekit/test_util.py | 66 +++++++++++++++++-- 5 files changed, 194 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index d0e3d52b363..1b2a4dbf05d 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -113,6 +113,7 @@ CHAR_OUTLET_IN_USE = 'OutletInUse' CHAR_ON = 'On' CHAR_POSITION_STATE = 'PositionState' CHAR_ROTATION_DIRECTION = 'RotationDirection' +CHAR_ROTATION_SPEED = 'RotationSpeed' CHAR_SATURATION = 'Saturation' CHAR_SERIAL_NUMBER = 'SerialNumber' CHAR_SMOKE_DETECTED = 'SmokeDetected' diff --git a/homeassistant/components/homekit/type_fans.py b/homeassistant/components/homekit/type_fans.py index 2b4e55c4c8d..dcc93b7cf9e 100644 --- a/homeassistant/components/homekit/type_fans.py +++ b/homeassistant/components/homekit/type_fans.py @@ -4,17 +4,20 @@ import logging from pyhap.const import CATEGORY_FAN from homeassistant.components.fan import ( - ATTR_DIRECTION, ATTR_OSCILLATING, DIRECTION_FORWARD, DIRECTION_REVERSE, - DOMAIN, SERVICE_OSCILLATE, SERVICE_SET_DIRECTION, SUPPORT_DIRECTION, - SUPPORT_OSCILLATE) + ATTR_DIRECTION, ATTR_OSCILLATING, ATTR_SPEED, ATTR_SPEED_LIST, + DIRECTION_FORWARD, DIRECTION_REVERSE, DOMAIN, SERVICE_OSCILLATE, + SERVICE_SET_DIRECTION, SERVICE_SET_SPEED, SUPPORT_DIRECTION, + SUPPORT_OSCILLATE, SUPPORT_SET_SPEED) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, SERVICE_TURN_OFF, - SERVICE_TURN_ON, STATE_OFF, STATE_ON) + ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, SERVICE_TURN_OFF, SERVICE_TURN_ON, + STATE_OFF, STATE_ON) from . import TYPES -from .accessories import HomeAccessory +from .accessories import debounce, HomeAccessory from .const import ( - CHAR_ACTIVE, CHAR_ROTATION_DIRECTION, CHAR_SWING_MODE, SERV_FANV2) + CHAR_ACTIVE, CHAR_ROTATION_DIRECTION, CHAR_ROTATION_SPEED, CHAR_SWING_MODE, + SERV_FANV2) +from .util import HomeKitSpeedMapping _LOGGER = logging.getLogger(__name__) @@ -41,12 +44,18 @@ class Fan(HomeAccessory): chars.append(CHAR_ROTATION_DIRECTION) if features & SUPPORT_OSCILLATE: chars.append(CHAR_SWING_MODE) + if features & SUPPORT_SET_SPEED: + speed_list = self.hass.states.get(self.entity_id) \ + .attributes.get(ATTR_SPEED_LIST) + self.speed_mapping = HomeKitSpeedMapping(speed_list) + chars.append(CHAR_ROTATION_SPEED) serv_fan = self.add_preload_service(SERV_FANV2, chars) self.char_active = serv_fan.configure_char( CHAR_ACTIVE, value=0, setter_callback=self.set_state) self.char_direction = None + self.char_speed = None self.char_swing = None if CHAR_ROTATION_DIRECTION in chars: @@ -54,6 +63,10 @@ class Fan(HomeAccessory): CHAR_ROTATION_DIRECTION, value=0, setter_callback=self.set_direction) + if CHAR_ROTATION_SPEED in chars: + self.char_speed = serv_fan.configure_char( + CHAR_ROTATION_SPEED, value=0, setter_callback=self.set_speed) + if CHAR_SWING_MODE in chars: self.char_swing = serv_fan.configure_char( CHAR_SWING_MODE, value=0, setter_callback=self.set_oscillating) @@ -83,6 +96,15 @@ class Fan(HomeAccessory): ATTR_OSCILLATING: oscillating} self.call_service(DOMAIN, SERVICE_OSCILLATE, params, oscillating) + @debounce + def set_speed(self, value): + """Set state if call came from HomeKit.""" + _LOGGER.debug('%s: Set speed to %d', self.entity_id, value) + speed = self.speed_mapping.speed_to_states(value) + params = {ATTR_ENTITY_ID: self.entity_id, + ATTR_SPEED: speed} + self.call_service(DOMAIN, SERVICE_SET_SPEED, params, speed) + def update_state(self, new_state): """Update fan after state change.""" # Handle State @@ -104,6 +126,14 @@ class Fan(HomeAccessory): self.char_direction.set_value(hk_direction) self._flag[CHAR_ROTATION_DIRECTION] = False + # Handle Speed + if self.char_speed is not None: + speed = new_state.attributes.get(ATTR_SPEED) + hk_speed_value = self.speed_mapping.speed_to_homekit(speed) + if hk_speed_value is not None and \ + self.char_speed.value != hk_speed_value: + self.char_speed.set_value(hk_speed_value) + # Handle Oscillating if self.char_swing is not None: oscillating = new_state.attributes.get(ATTR_OSCILLATING) diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 10fdc07e7b4..7ad0cea48e7 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -1,17 +1,19 @@ """Collection of useful functions for the HomeKit component.""" +from collections import namedtuple, OrderedDict import logging import voluptuous as vol -from homeassistant.components import media_player -from homeassistant.core import split_entity_id +from homeassistant.components import fan, media_player from homeassistant.const import ( ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_NAME, CONF_TYPE, TEMP_CELSIUS) +from homeassistant.core import split_entity_id import homeassistant.helpers.config_validation as cv import homeassistant.util.temperature as temp_util + from .const import ( - CONF_FEATURE, CONF_FEATURE_LIST, HOMEKIT_NOTIFY_ID, FEATURE_ON_OFF, - FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, TYPE_FAUCET, + CONF_FEATURE, CONF_FEATURE_LIST, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, + FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, HOMEKIT_NOTIFY_ID, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER, TYPE_SWITCH, TYPE_VALVE) _LOGGER = logging.getLogger(__name__) @@ -110,6 +112,50 @@ def validate_media_player_features(state, feature_list): return True +SpeedRange = namedtuple('SpeedRange', ('start', 'target')) +SpeedRange.__doc__ += """ Maps Home Assistant speed \ +values to percentage based HomeKit speeds. +start: Start of the range (inclusive). +target: Percentage to use to determine HomeKit percentages \ +from HomeAssistant speed. +""" + + +class HomeKitSpeedMapping: + """Supports conversion between Home Assistant and HomeKit fan speeds.""" + + def __init__(self, speed_list): + """Initialize a new SpeedMapping object.""" + if speed_list[0] != fan.SPEED_OFF: + _LOGGER.warning("%s does not contain the speed setting " + "%s as its first element. " + "Assuming that %s is equivalent to 'off'.", + speed_list, fan.SPEED_OFF, speed_list[0]) + self.speed_ranges = OrderedDict() + list_size = len(speed_list) + for index, speed in enumerate(speed_list): + # By dividing by list_size -1 the following + # desired attributes hold true: + # * index = 0 => 0%, equal to "off" + # * index = len(speed_list) - 1 => 100 % + # * all other indices are equally distributed + target = index * 100 / (list_size - 1) + start = index * 100 / list_size + self.speed_ranges[speed] = SpeedRange(start, target) + + def speed_to_homekit(self, speed): + """Map Home Assistant speed state to HomeKit speed.""" + speed_range = self.speed_ranges[speed] + return speed_range.target + + def speed_to_states(self, speed): + """Map HomeKit speed to Home Assistant speed state.""" + for state, speed_range in reversed(self.speed_ranges.items()): + if speed_range.start <= speed: + return state + return list(self.speed_ranges.keys())[0] + + def show_setup_message(hass, pincode): """Display persistent notification with setup information.""" pin = pincode.decode() diff --git a/tests/components/homekit/test_type_fans.py b/tests/components/homekit/test_type_fans.py index 27b6cec0790..b620ef50e0f 100644 --- a/tests/components/homekit/test_type_fans.py +++ b/tests/components/homekit/test_type_fans.py @@ -1,14 +1,17 @@ """Test different accessory types: Fans.""" from collections import namedtuple +from unittest.mock import Mock import pytest from homeassistant.components.fan import ( - ATTR_DIRECTION, ATTR_OSCILLATING, DIRECTION_FORWARD, DIRECTION_REVERSE, - DOMAIN, SUPPORT_DIRECTION, SUPPORT_OSCILLATE) + ATTR_DIRECTION, ATTR_OSCILLATING, ATTR_SPEED, ATTR_SPEED_LIST, + DIRECTION_FORWARD, DIRECTION_REVERSE, DOMAIN, SPEED_HIGH, SPEED_LOW, + SPEED_OFF, SUPPORT_DIRECTION, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED) from homeassistant.components.homekit.const import ATTR_VALUE +from homeassistant.components.homekit.util import HomeKitSpeedMapping from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, STATE_ON, STATE_OFF, + ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON, STATE_UNKNOWN) from tests.common import async_mock_service @@ -39,6 +42,9 @@ async def test_fan_basic(hass, hk_driver, cls, events): assert acc.category == 3 # Fan assert acc.char_active.value == 0 + # If there are no speed_list values, then HomeKit speed is unsupported + assert acc.char_speed is None + await hass.async_add_job(acc.run) await hass.async_block_till_done() assert acc.char_active.value == 1 @@ -155,3 +161,40 @@ async def test_fan_oscillate(hass, hk_driver, cls, events): assert call_oscillate[1].data[ATTR_OSCILLATING] is True assert len(events) == 2 assert events[-1].data[ATTR_VALUE] is True + + +async def test_fan_speed(hass, hk_driver, cls, events): + """Test fan with speed.""" + entity_id = 'fan.demo' + speed_list = [SPEED_OFF, SPEED_LOW, SPEED_HIGH] + + hass.states.async_set(entity_id, STATE_ON, { + ATTR_SUPPORTED_FEATURES: SUPPORT_SET_SPEED, ATTR_SPEED: SPEED_OFF, + ATTR_SPEED_LIST: speed_list}) + await hass.async_block_till_done() + acc = cls.fan(hass, hk_driver, 'Fan', entity_id, 2, None) + assert acc.char_speed.value == 0 + + await hass.async_add_job(acc.run) + assert acc.speed_mapping.speed_ranges == \ + HomeKitSpeedMapping(speed_list).speed_ranges + + acc.speed_mapping.speed_to_homekit = Mock(return_value=42) + acc.speed_mapping.speed_to_states = Mock(return_value='ludicrous') + + hass.states.async_set(entity_id, STATE_ON, {ATTR_SPEED: SPEED_HIGH}) + await hass.async_block_till_done() + acc.speed_mapping.speed_to_homekit.assert_called_with(SPEED_HIGH) + assert acc.char_speed.value == 42 + + # Set from HomeKit + call_set_speed = async_mock_service(hass, DOMAIN, 'set_speed') + + await hass.async_add_job(acc.char_speed.client_update_value, 42) + await hass.async_block_till_done() + acc.speed_mapping.speed_to_states.assert_called_with(42) + assert call_set_speed[0] + assert call_set_speed[0].data[ATTR_ENTITY_ID] == entity_id + assert call_set_speed[0].data[ATTR_SPEED] == 'ludicrous' + assert len(events) == 1 + assert events[-1].data[ATTR_VALUE] == 'ludicrous' diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py index a2849a77396..c86b1353c48 100644 --- a/tests/components/homekit/test_util.py +++ b/tests/components/homekit/test_util.py @@ -3,15 +3,14 @@ import pytest import voluptuous as vol from homeassistant.components.homekit.const import ( - CONF_FEATURE, CONF_FEATURE_LIST, HOMEKIT_NOTIFY_ID, FEATURE_ON_OFF, - FEATURE_PLAY_PAUSE, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER, + CONF_FEATURE, CONF_FEATURE_LIST, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, + HOMEKIT_NOTIFY_ID, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER, TYPE_SWITCH, TYPE_VALVE) from homeassistant.components.homekit.util import ( - convert_to_float, density_to_air_quality, dismiss_setup_message, - show_setup_message, temperature_to_homekit, temperature_to_states, + HomeKitSpeedMapping, SpeedRange, convert_to_float, density_to_air_quality, + dismiss_setup_message, show_setup_message, temperature_to_homekit, + temperature_to_states, validate_entity_config as vec, validate_media_player_features) -from homeassistant.components.homekit.util import validate_entity_config \ - as vec from homeassistant.components.persistent_notification import ( ATTR_MESSAGE, ATTR_NOTIFICATION_ID, DOMAIN) from homeassistant.const import ( @@ -144,3 +143,58 @@ async def test_dismiss_setup_msg(hass): assert call_dismiss_notification assert call_dismiss_notification[0].data[ATTR_NOTIFICATION_ID] == \ HOMEKIT_NOTIFY_ID + + +def test_homekit_speed_mapping(): + """Test if the SpeedRanges from a speed_list are as expected.""" + # A standard 2-speed fan + speed_mapping = HomeKitSpeedMapping(['off', 'low', 'high']) + assert speed_mapping.speed_ranges == { + 'off': SpeedRange(0, 0), + 'low': SpeedRange(100 / 3, 50), + 'high': SpeedRange(200 / 3, 100), + } + + # A standard 3-speed fan + speed_mapping = HomeKitSpeedMapping(['off', 'low', 'medium', 'high']) + assert speed_mapping.speed_ranges == { + 'off': SpeedRange(0, 0), + 'low': SpeedRange(100 / 4, 100 / 3), + 'medium': SpeedRange(200 / 4, 200 / 3), + 'high': SpeedRange(300 / 4, 100), + } + + # a Dyson-like fan with 10 speeds + speed_mapping = HomeKitSpeedMapping([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + assert speed_mapping.speed_ranges == { + 0: SpeedRange(0, 0), + 1: SpeedRange(10, 100 / 9), + 2: SpeedRange(20, 200 / 9), + 3: SpeedRange(30, 300 / 9), + 4: SpeedRange(40, 400 / 9), + 5: SpeedRange(50, 500 / 9), + 6: SpeedRange(60, 600 / 9), + 7: SpeedRange(70, 700 / 9), + 8: SpeedRange(80, 800 / 9), + 9: SpeedRange(90, 100), + } + + +def test_speed_to_homekit(): + """Test speed conversion from HA to Homekit.""" + speed_mapping = HomeKitSpeedMapping(['off', 'low', 'high']) + assert speed_mapping.speed_to_homekit('off') == 0 + assert speed_mapping.speed_to_homekit('low') == 50 + assert speed_mapping.speed_to_homekit('high') == 100 + + +def test_speed_to_states(): + """Test speed conversion from Homekit to HA.""" + speed_mapping = HomeKitSpeedMapping(['off', 'low', 'high']) + assert speed_mapping.speed_to_states(0) == 'off' + assert speed_mapping.speed_to_states(33) == 'off' + assert speed_mapping.speed_to_states(34) == 'low' + assert speed_mapping.speed_to_states(50) == 'low' + assert speed_mapping.speed_to_states(66) == 'low' + assert speed_mapping.speed_to_states(67) == 'high' + assert speed_mapping.speed_to_states(100) == 'high' From 5b4cc20ce4e6ab55e1b50054413cbdc76184ac83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Tue, 5 Feb 2019 16:15:41 +0100 Subject: [PATCH 068/242] opensensemap doc url --- homeassistant/components/air_quality/opensensemap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/air_quality/opensensemap.py b/homeassistant/components/air_quality/opensensemap.py index fe3cca4876e..d77c0c9bfe2 100644 --- a/homeassistant/components/air_quality/opensensemap.py +++ b/homeassistant/components/air_quality/opensensemap.py @@ -2,7 +2,7 @@ Support for openSenseMap Air Quality data. For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/air_quality/opensensemap/ +https://home-assistant.io/components/air_quality.opensensemap/ """ from datetime import timedelta import logging From c76a61ad1663e18351fa75ba0f88c02e48b662aa Mon Sep 17 00:00:00 2001 From: Fredrik Erlandsson Date: Wed, 6 Feb 2019 00:20:23 +0100 Subject: [PATCH 069/242] Fix tellduslive responsiveness (#20603) * use async_call_later for update * no need to timeout * fixes * move init tasks to hass.loop * version bump of tellduslive * fixes for @MartinHjelmare * fixes task cancel * don't return from new client --- .../components/tellduslive/__init__.py | 65 ++++++++++--------- requirements_all.txt | 2 +- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/tellduslive/__init__.py b/homeassistant/components/tellduslive/__init__.py index d9cd1be59da..2a57a78ee9e 100644 --- a/homeassistant/components/tellduslive/__init__.py +++ b/homeassistant/components/tellduslive/__init__.py @@ -5,27 +5,26 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/tellduslive/ """ import asyncio -from datetime import timedelta from functools import partial import logging import voluptuous as vol from homeassistant import config_entries -import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_UPDATE_INTERVAL +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.event import async_call_later from . import config_flow # noqa pylint_disable=unused-import from .const import ( - CONF_HOST, DOMAIN, KEY_HOST, KEY_SCAN_INTERVAL, - KEY_SESSION, MIN_UPDATE_INTERVAL, NOT_SO_PRIVATE_KEY, PUBLIC_KEY, - SCAN_INTERVAL, SIGNAL_UPDATE_ENTITY, TELLDUS_DISCOVERY_NEW) + CONF_HOST, DOMAIN, KEY_HOST, KEY_SCAN_INTERVAL, KEY_SESSION, + MIN_UPDATE_INTERVAL, NOT_SO_PRIVATE_KEY, PUBLIC_KEY, SCAN_INTERVAL, + SIGNAL_UPDATE_ENTITY, TELLDUS_DISCOVERY_NEW) APPLICATION_NAME = 'Home Assistant' -REQUIREMENTS = ['tellduslive==0.10.8'] +REQUIREMENTS = ['tellduslive==0.10.10'] _LOGGER = logging.getLogger(__name__) @@ -45,6 +44,7 @@ CONFIG_SCHEMA = vol.Schema( DATA_CONFIG_ENTRY_LOCK = 'tellduslive_config_entry_lock' CONFIG_ENTRY_IS_SETUP = 'telldus_config_entry_is_setup' +NEW_CLIENT_TASK = 'telldus_new_client_task' INTERVAL_TRACKER = '{}_INTERVAL'.format(DOMAIN) @@ -71,33 +71,30 @@ async def async_setup_entry(hass, entry): hass.data[DATA_CONFIG_ENTRY_LOCK] = asyncio.Lock() hass.data[CONFIG_ENTRY_IS_SETUP] = set() - - client = TelldusLiveClient(hass, entry, session) - hass.data[DOMAIN] = client - await async_add_hubs(hass, client, entry.entry_id) - hass.async_create_task(client.update()) - - interval = timedelta(seconds=entry.data[KEY_SCAN_INTERVAL]) - _LOGGER.debug('Update interval %s', interval) - hass.data[INTERVAL_TRACKER] = async_track_time_interval( - hass, client.update, interval) + hass.data[NEW_CLIENT_TASK] = hass.loop.create_task( + async_new_client(hass, session, entry)) return True -async def async_add_hubs(hass, client, entry_id): +async def async_new_client(hass, session, entry): """Add the hubs associated with the current client to device_registry.""" + interval = entry.data[KEY_SCAN_INTERVAL] + _LOGGER.debug('Update interval %s seconds.', interval) + client = TelldusLiveClient(hass, entry, session, interval) + hass.data[DOMAIN] = client dev_reg = await hass.helpers.device_registry.async_get_registry() for hub in await client.async_get_hubs(): _LOGGER.debug("Connected hub %s", hub['name']) dev_reg.async_get_or_create( - config_entry_id=entry_id, + config_entry_id=entry.entry_id, identifiers={(DOMAIN, hub['id'])}, manufacturer='Telldus', name=hub['name'], model=hub['type'], sw_version=hub['version'], ) + await client.update() async def async_setup(hass, config): @@ -118,6 +115,8 @@ async def async_setup(hass, config): async def async_unload_entry(hass, config_entry): """Unload a config entry.""" + if not hass.data[NEW_CLIENT_TASK].done(): + hass.data[NEW_CLIENT_TASK].cancel() interval_tracker = hass.data.pop(INTERVAL_TRACKER) interval_tracker() await asyncio.wait([ @@ -132,7 +131,7 @@ async def async_unload_entry(hass, config_entry): class TelldusLiveClient: """Get the latest data and update the states.""" - def __init__(self, hass, config_entry, session): + def __init__(self, hass, config_entry, session, interval): """Initialize the Tellus data object.""" self._known_devices = set() self._device_infos = {} @@ -140,6 +139,7 @@ class TelldusLiveClient: self._hass = hass self._config_entry = config_entry self._client = session + self._interval = interval async def async_get_hubs(self): """Return hubs registered for the user.""" @@ -195,16 +195,21 @@ class TelldusLiveClient: async def update(self, *args): """Periodically poll the servers for current state.""" - if not await self._hass.async_add_executor_job(self._client.update): - _LOGGER.warning('Failed request') - - dev_ids = {dev.device_id for dev in self._client.devices} - new_devices = dev_ids - self._known_devices - # just await each discover as `gather` use up all HTTPAdapter pools - for d_id in new_devices: - await self._discover(d_id) - self._known_devices |= new_devices - async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY) + try: + if not await self._hass.async_add_executor_job( + self._client.update): + _LOGGER.warning('Failed request') + return + dev_ids = {dev.device_id for dev in self._client.devices} + new_devices = dev_ids - self._known_devices + # just await each discover as `gather` use up all HTTPAdapter pools + for d_id in new_devices: + await self._discover(d_id) + self._known_devices |= new_devices + async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY) + finally: + self._hass.data[INTERVAL_TRACKER] = async_call_later( + self._hass, self._interval, self.update) def device(self, device_id): """Return device representation.""" diff --git a/requirements_all.txt b/requirements_all.txt index 35019f74408..ed107f224c8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1628,7 +1628,7 @@ tellcore-net==0.4 tellcore-py==1.1.2 # homeassistant.components.tellduslive -tellduslive==0.10.8 +tellduslive==0.10.10 # homeassistant.components.media_player.lg_soundbar temescal==0.1 From 3bb5caabe20a18ff4309cd8415280a50c30d707e Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Wed, 6 Feb 2019 02:25:27 +0100 Subject: [PATCH 070/242] Reproduce states by letting each component opt in on handling state recovery itself (#18700) * Move group to it's own setup * Let each component to handle restore of state * Move constants for climate into const.py For now import all into __init__.py to keep backword compat * Move media plyaer constants to const.py file For now import all constants into __init__.py to keep backword compatibility * Move media player to it's own file * Move climate to it's own file * Remove ecobee service from common components BREAKING CHANGE * Add tests for climate * Add test for media_player * Make sure we clone timestamps of state * Add tests for groups * Remove old tests for media player, it's handled by other tests * Add tests for calls to component functions * Add docstring for climate const * Add docstring for media_player const * Explicitly import constants in climate * Explicitly import constants in media_player * Add period to climate const * Add period to media_player const * Fix some lint errors in climate * Fix some lint errors in media_player * Fix lint warnings on climate tests * Fix lint warnings on group tests * Fix lint warnings on media_player tests * Fix lint warnings on state tests * Adjust indent for state tests --- homeassistant/components/climate/__init__.py | 63 +++--- homeassistant/components/climate/const.py | 32 +++ .../components/climate/reproduce_state.py | 91 ++++++++ homeassistant/components/group/__init__.py | 2 + .../components/group/reproduce_state.py | 28 +++ .../components/media_player/__init__.py | 68 +++--- .../components/media_player/const.py | 35 +++ .../media_player/reproduce_state.py | 87 ++++++++ homeassistant/helpers/state.py | 130 ++++++------ .../climate/test_reproduce_state.py | 162 ++++++++++++++ .../components/group/test_reproduce_state.py | 45 ++++ .../media_player/test_reproduce_state.py | 199 ++++++++++++++++++ tests/helpers/test_state.py | 132 +++--------- 13 files changed, 846 insertions(+), 228 deletions(-) create mode 100644 homeassistant/components/climate/const.py create mode 100644 homeassistant/components/climate/reproduce_state.py create mode 100644 homeassistant/components/group/reproduce_state.py create mode 100644 homeassistant/components/media_player/const.py create mode 100644 homeassistant/components/media_player/reproduce_state.py create mode 100644 tests/components/climate/test_reproduce_state.py create mode 100644 tests/components/group/test_reproduce_state.py create mode 100644 tests/components/media_player/test_reproduce_state.py diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 362c4ee93e4..e1d3093995c 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -22,25 +22,46 @@ from homeassistant.const import ( STATE_ON, STATE_OFF, TEMP_CELSIUS, PRECISION_WHOLE, PRECISION_TENTHS) +from .const import ( + ATTR_AUX_HEAT, + ATTR_AWAY_MODE, + ATTR_CURRENT_HUMIDITY, + ATTR_CURRENT_TEMPERATURE, + ATTR_FAN_LIST, + ATTR_FAN_MODE, + ATTR_HOLD_MODE, + ATTR_HUMIDITY, + ATTR_MAX_HUMIDITY, + ATTR_MAX_TEMP, + ATTR_MIN_HUMIDITY, + ATTR_MIN_TEMP, + ATTR_OPERATION_LIST, + ATTR_OPERATION_MODE, + ATTR_SWING_LIST, + ATTR_SWING_MODE, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + ATTR_TARGET_TEMP_STEP, + DOMAIN, + SERVICE_SET_AUX_HEAT, + SERVICE_SET_AWAY_MODE, + SERVICE_SET_FAN_MODE, + SERVICE_SET_HOLD_MODE, + SERVICE_SET_HUMIDITY, + SERVICE_SET_OPERATION_MODE, + SERVICE_SET_SWING_MODE, + SERVICE_SET_TEMPERATURE, +) +from .reproduce_state import async_reproduce_states # noqa + DEFAULT_MIN_TEMP = 7 DEFAULT_MAX_TEMP = 35 DEFAULT_MIN_HUMITIDY = 30 DEFAULT_MAX_HUMIDITY = 99 -DOMAIN = 'climate' - ENTITY_ID_FORMAT = DOMAIN + '.{}' SCAN_INTERVAL = timedelta(seconds=60) -SERVICE_SET_AWAY_MODE = 'set_away_mode' -SERVICE_SET_AUX_HEAT = 'set_aux_heat' -SERVICE_SET_TEMPERATURE = 'set_temperature' -SERVICE_SET_FAN_MODE = 'set_fan_mode' -SERVICE_SET_HOLD_MODE = 'set_hold_mode' -SERVICE_SET_OPERATION_MODE = 'set_operation_mode' -SERVICE_SET_SWING_MODE = 'set_swing_mode' -SERVICE_SET_HUMIDITY = 'set_humidity' - STATE_HEAT = 'heat' STATE_COOL = 'cool' STATE_IDLE = 'idle' @@ -64,26 +85,6 @@ SUPPORT_AWAY_MODE = 1024 SUPPORT_AUX_HEAT = 2048 SUPPORT_ON_OFF = 4096 -ATTR_CURRENT_TEMPERATURE = 'current_temperature' -ATTR_MAX_TEMP = 'max_temp' -ATTR_MIN_TEMP = 'min_temp' -ATTR_TARGET_TEMP_HIGH = 'target_temp_high' -ATTR_TARGET_TEMP_LOW = 'target_temp_low' -ATTR_TARGET_TEMP_STEP = 'target_temp_step' -ATTR_AWAY_MODE = 'away_mode' -ATTR_AUX_HEAT = 'aux_heat' -ATTR_FAN_MODE = 'fan_mode' -ATTR_FAN_LIST = 'fan_list' -ATTR_CURRENT_HUMIDITY = 'current_humidity' -ATTR_HUMIDITY = 'humidity' -ATTR_MAX_HUMIDITY = 'max_humidity' -ATTR_MIN_HUMIDITY = 'min_humidity' -ATTR_HOLD_MODE = 'hold_mode' -ATTR_OPERATION_MODE = 'operation_mode' -ATTR_OPERATION_LIST = 'operation_list' -ATTR_SWING_MODE = 'swing_mode' -ATTR_SWING_LIST = 'swing_list' - CONVERTIBLE_ATTRIBUTE = [ ATTR_TEMPERATURE, ATTR_TARGET_TEMP_LOW, diff --git a/homeassistant/components/climate/const.py b/homeassistant/components/climate/const.py new file mode 100644 index 00000000000..2f84ee27bbd --- /dev/null +++ b/homeassistant/components/climate/const.py @@ -0,0 +1,32 @@ +"""Proides the constants needed for component.""" + +ATTR_AUX_HEAT = 'aux_heat' +ATTR_AWAY_MODE = 'away_mode' +ATTR_CURRENT_HUMIDITY = 'current_humidity' +ATTR_CURRENT_TEMPERATURE = 'current_temperature' +ATTR_FAN_LIST = 'fan_list' +ATTR_FAN_MODE = 'fan_mode' +ATTR_HOLD_MODE = 'hold_mode' +ATTR_HUMIDITY = 'humidity' +ATTR_MAX_HUMIDITY = 'max_humidity' +ATTR_MAX_TEMP = 'max_temp' +ATTR_MIN_HUMIDITY = 'min_humidity' +ATTR_MIN_TEMP = 'min_temp' +ATTR_OPERATION_LIST = 'operation_list' +ATTR_OPERATION_MODE = 'operation_mode' +ATTR_SWING_LIST = 'swing_list' +ATTR_SWING_MODE = 'swing_mode' +ATTR_TARGET_TEMP_HIGH = 'target_temp_high' +ATTR_TARGET_TEMP_LOW = 'target_temp_low' +ATTR_TARGET_TEMP_STEP = 'target_temp_step' + +DOMAIN = 'climate' + +SERVICE_SET_AUX_HEAT = 'set_aux_heat' +SERVICE_SET_AWAY_MODE = 'set_away_mode' +SERVICE_SET_FAN_MODE = 'set_fan_mode' +SERVICE_SET_HOLD_MODE = 'set_hold_mode' +SERVICE_SET_HUMIDITY = 'set_humidity' +SERVICE_SET_OPERATION_MODE = 'set_operation_mode' +SERVICE_SET_SWING_MODE = 'set_swing_mode' +SERVICE_SET_TEMPERATURE = 'set_temperature' diff --git a/homeassistant/components/climate/reproduce_state.py b/homeassistant/components/climate/reproduce_state.py new file mode 100644 index 00000000000..3259e4084cf --- /dev/null +++ b/homeassistant/components/climate/reproduce_state.py @@ -0,0 +1,91 @@ +"""Module that groups code required to handle state restore for component.""" +import asyncio +from typing import Iterable, Optional + +from homeassistant.const import ( + ATTR_TEMPERATURE, SERVICE_TURN_OFF, + SERVICE_TURN_ON, STATE_OFF, STATE_ON) +from homeassistant.core import Context, State +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.loader import bind_hass + +from .const import ( + ATTR_AUX_HEAT, + ATTR_AWAY_MODE, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + ATTR_HOLD_MODE, + ATTR_OPERATION_MODE, + ATTR_SWING_MODE, + ATTR_HUMIDITY, + SERVICE_SET_AWAY_MODE, + SERVICE_SET_AUX_HEAT, + SERVICE_SET_TEMPERATURE, + SERVICE_SET_HOLD_MODE, + SERVICE_SET_OPERATION_MODE, + SERVICE_SET_SWING_MODE, + SERVICE_SET_HUMIDITY, + DOMAIN, +) + + +async def _async_reproduce_states(hass: HomeAssistantType, + state: State, + context: Optional[Context] = None) -> None: + """Reproduce component states.""" + async def call_service(service: str, keys: Iterable): + """Call service with set of attributes given.""" + data = {} + data['entity_id'] = state.entity_id + for key in keys: + if key in state.attributes: + data[key] = state.attributes[key] + + await hass.services.async_call( + DOMAIN, service, data, + blocking=True, context=context) + + if state.state == STATE_ON: + await call_service(SERVICE_TURN_ON, []) + elif state.state == STATE_OFF: + await call_service(SERVICE_TURN_OFF, []) + + if ATTR_AUX_HEAT in state.attributes: + await call_service(SERVICE_SET_AUX_HEAT, [ATTR_AUX_HEAT]) + + if ATTR_AWAY_MODE in state.attributes: + await call_service(SERVICE_SET_AWAY_MODE, [ATTR_AWAY_MODE]) + + if (ATTR_TEMPERATURE in state.attributes) or \ + (ATTR_TARGET_TEMP_HIGH in state.attributes) or \ + (ATTR_TARGET_TEMP_LOW in state.attributes): + await call_service(SERVICE_SET_TEMPERATURE, + [ATTR_TEMPERATURE, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW]) + + if ATTR_HOLD_MODE in state.attributes: + await call_service(SERVICE_SET_HOLD_MODE, + [ATTR_HOLD_MODE]) + + if ATTR_OPERATION_MODE in state.attributes: + await call_service(SERVICE_SET_OPERATION_MODE, + [ATTR_OPERATION_MODE]) + + if ATTR_SWING_MODE in state.attributes: + await call_service(SERVICE_SET_SWING_MODE, + [ATTR_SWING_MODE]) + + if ATTR_HUMIDITY in state.attributes: + await call_service(SERVICE_SET_HUMIDITY, + [ATTR_HUMIDITY]) + + +@bind_hass +async def async_reproduce_states(hass: HomeAssistantType, + states: Iterable[State], + context: Optional[Context] = None) -> None: + """Reproduce component states.""" + await asyncio.gather(*[ + _async_reproduce_states(hass, state, context) + for state in states]) diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index b6dcd65fc2c..d1cd88a8438 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -23,6 +23,8 @@ from homeassistant.helpers.event import async_track_state_change import homeassistant.helpers.config_validation as cv from homeassistant.util.async_ import run_coroutine_threadsafe +from .reproduce_state import async_reproduce_states # noqa + DOMAIN = 'group' ENTITY_ID_FORMAT = DOMAIN + '.{}' diff --git a/homeassistant/components/group/reproduce_state.py b/homeassistant/components/group/reproduce_state.py new file mode 100644 index 00000000000..1cf1793e6f6 --- /dev/null +++ b/homeassistant/components/group/reproduce_state.py @@ -0,0 +1,28 @@ +"""Module that groups code required to handle state restore for component.""" +from typing import Iterable, Optional + +from homeassistant.core import Context, State +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.loader import bind_hass + + +@bind_hass +async def async_reproduce_states(hass: HomeAssistantType, + states: Iterable[State], + context: Optional[Context] = None) -> None: + """Reproduce component states.""" + from . import get_entity_ids + from homeassistant.helpers.state import async_reproduce_state + states_copy = [] + for state in states: + members = get_entity_ids(hass, state.entity_id) + for member in members: + states_copy.append( + State(member, + state.state, + state.attributes, + last_changed=state.last_changed, + last_updated=state.last_updated, + context=state.context)) + await async_reproduce_state(hass, states_copy, blocking=True, + context=context) diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 6f74380728c..840b745eebd 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -36,10 +36,44 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.loader import bind_hass +from .const import ( + ATTR_APP_ID, + ATTR_APP_NAME, + ATTR_INPUT_SOURCE, + ATTR_INPUT_SOURCE_LIST, + ATTR_MEDIA_ALBUM_ARTIST, + ATTR_MEDIA_ALBUM_NAME, + ATTR_MEDIA_ARTIST, + ATTR_MEDIA_CHANNEL, + ATTR_MEDIA_CONTENT_ID, + ATTR_MEDIA_CONTENT_TYPE, + ATTR_MEDIA_DURATION, + ATTR_MEDIA_ENQUEUE, + ATTR_MEDIA_EPISODE, + ATTR_MEDIA_PLAYLIST, + ATTR_MEDIA_POSITION, + ATTR_MEDIA_POSITION_UPDATED_AT, + ATTR_MEDIA_SEASON, + ATTR_MEDIA_SEEK_POSITION, + ATTR_MEDIA_SERIES_TITLE, + ATTR_MEDIA_SHUFFLE, + ATTR_MEDIA_TITLE, + ATTR_MEDIA_TRACK, + ATTR_MEDIA_VOLUME_LEVEL, + ATTR_MEDIA_VOLUME_MUTED, + ATTR_SOUND_MODE, + ATTR_SOUND_MODE_LIST, + DOMAIN, + SERVICE_CLEAR_PLAYLIST, + SERVICE_PLAY_MEDIA, + SERVICE_SELECT_SOUND_MODE, + SERVICE_SELECT_SOURCE, +) +from .reproduce_state import async_reproduce_states # noqa + _LOGGER = logging.getLogger(__name__) _RND = SystemRandom() -DOMAIN = 'media_player' DEPENDENCIES = ['http'] ENTITY_ID_FORMAT = DOMAIN + '.{}' @@ -55,38 +89,6 @@ ENTITY_IMAGE_CACHE = { CACHE_MAXSIZE: 16 } -SERVICE_PLAY_MEDIA = 'play_media' -SERVICE_SELECT_SOURCE = 'select_source' -SERVICE_SELECT_SOUND_MODE = 'select_sound_mode' -SERVICE_CLEAR_PLAYLIST = 'clear_playlist' - -ATTR_MEDIA_VOLUME_LEVEL = 'volume_level' -ATTR_MEDIA_VOLUME_MUTED = 'is_volume_muted' -ATTR_MEDIA_SEEK_POSITION = 'seek_position' -ATTR_MEDIA_CONTENT_ID = 'media_content_id' -ATTR_MEDIA_CONTENT_TYPE = 'media_content_type' -ATTR_MEDIA_DURATION = 'media_duration' -ATTR_MEDIA_POSITION = 'media_position' -ATTR_MEDIA_POSITION_UPDATED_AT = 'media_position_updated_at' -ATTR_MEDIA_TITLE = 'media_title' -ATTR_MEDIA_ARTIST = 'media_artist' -ATTR_MEDIA_ALBUM_NAME = 'media_album_name' -ATTR_MEDIA_ALBUM_ARTIST = 'media_album_artist' -ATTR_MEDIA_TRACK = 'media_track' -ATTR_MEDIA_SERIES_TITLE = 'media_series_title' -ATTR_MEDIA_SEASON = 'media_season' -ATTR_MEDIA_EPISODE = 'media_episode' -ATTR_MEDIA_CHANNEL = 'media_channel' -ATTR_MEDIA_PLAYLIST = 'media_playlist' -ATTR_APP_ID = 'app_id' -ATTR_APP_NAME = 'app_name' -ATTR_INPUT_SOURCE = 'source' -ATTR_INPUT_SOURCE_LIST = 'source_list' -ATTR_SOUND_MODE = 'sound_mode' -ATTR_SOUND_MODE_LIST = 'sound_mode_list' -ATTR_MEDIA_ENQUEUE = 'enqueue' -ATTR_MEDIA_SHUFFLE = 'shuffle' - MEDIA_TYPE_MUSIC = 'music' MEDIA_TYPE_TVSHOW = 'tvshow' MEDIA_TYPE_MOVIE = 'movie' diff --git a/homeassistant/components/media_player/const.py b/homeassistant/components/media_player/const.py new file mode 100644 index 00000000000..b926d893414 --- /dev/null +++ b/homeassistant/components/media_player/const.py @@ -0,0 +1,35 @@ +"""Proides the constants needed for component.""" + +ATTR_APP_ID = 'app_id' +ATTR_APP_NAME = 'app_name' +ATTR_INPUT_SOURCE = 'source' +ATTR_INPUT_SOURCE_LIST = 'source_list' +ATTR_MEDIA_ALBUM_ARTIST = 'media_album_artist' +ATTR_MEDIA_ALBUM_NAME = 'media_album_name' +ATTR_MEDIA_ARTIST = 'media_artist' +ATTR_MEDIA_CHANNEL = 'media_channel' +ATTR_MEDIA_CONTENT_ID = 'media_content_id' +ATTR_MEDIA_CONTENT_TYPE = 'media_content_type' +ATTR_MEDIA_DURATION = 'media_duration' +ATTR_MEDIA_ENQUEUE = 'enqueue' +ATTR_MEDIA_EPISODE = 'media_episode' +ATTR_MEDIA_PLAYLIST = 'media_playlist' +ATTR_MEDIA_POSITION = 'media_position' +ATTR_MEDIA_POSITION_UPDATED_AT = 'media_position_updated_at' +ATTR_MEDIA_SEASON = 'media_season' +ATTR_MEDIA_SEEK_POSITION = 'seek_position' +ATTR_MEDIA_SERIES_TITLE = 'media_series_title' +ATTR_MEDIA_SHUFFLE = 'shuffle' +ATTR_MEDIA_TITLE = 'media_title' +ATTR_MEDIA_TRACK = 'media_track' +ATTR_MEDIA_VOLUME_LEVEL = 'volume_level' +ATTR_MEDIA_VOLUME_MUTED = 'is_volume_muted' +ATTR_SOUND_MODE = 'sound_mode' +ATTR_SOUND_MODE_LIST = 'sound_mode_list' + +DOMAIN = 'media_player' + +SERVICE_CLEAR_PLAYLIST = 'clear_playlist' +SERVICE_PLAY_MEDIA = 'play_media' +SERVICE_SELECT_SOUND_MODE = 'select_sound_mode' +SERVICE_SELECT_SOURCE = 'select_source' diff --git a/homeassistant/components/media_player/reproduce_state.py b/homeassistant/components/media_player/reproduce_state.py new file mode 100644 index 00000000000..cbe98704615 --- /dev/null +++ b/homeassistant/components/media_player/reproduce_state.py @@ -0,0 +1,87 @@ +"""Module that groups code required to handle state restore for component.""" +import asyncio +from typing import Iterable, Optional + +from homeassistant.const import ( + SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_SEEK, + SERVICE_MEDIA_STOP, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_MUTE, + SERVICE_VOLUME_SET, STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED, + STATE_PLAYING) +from homeassistant.core import Context, State +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.loader import bind_hass + +from .const import ( + ATTR_MEDIA_VOLUME_LEVEL, + ATTR_MEDIA_VOLUME_MUTED, + ATTR_MEDIA_SEEK_POSITION, + ATTR_INPUT_SOURCE, + ATTR_SOUND_MODE, + ATTR_MEDIA_CONTENT_TYPE, + ATTR_MEDIA_CONTENT_ID, + ATTR_MEDIA_ENQUEUE, + SERVICE_PLAY_MEDIA, + SERVICE_SELECT_SOURCE, + SERVICE_SELECT_SOUND_MODE, + DOMAIN, +) + + +async def _async_reproduce_states(hass: HomeAssistantType, + state: State, + context: Optional[Context] = None) -> None: + """Reproduce component states.""" + async def call_service(service: str, keys: Iterable): + """Call service with set of attributes given.""" + data = {} + data['entity_id'] = state.entity_id + for key in keys: + if key in state.attributes: + data[key] = state.attributes[key] + + await hass.services.async_call( + DOMAIN, service, data, + blocking=True, context=context) + + if state.state == STATE_ON: + await call_service(SERVICE_TURN_ON, []) + elif state.state == STATE_OFF: + await call_service(SERVICE_TURN_OFF, []) + elif state.state == STATE_PLAYING: + await call_service(SERVICE_MEDIA_PLAY, []) + elif state.state == STATE_IDLE: + await call_service(SERVICE_MEDIA_STOP, []) + elif state.state == STATE_PAUSED: + await call_service(SERVICE_MEDIA_PAUSE, []) + + if ATTR_MEDIA_VOLUME_LEVEL in state.attributes: + await call_service(SERVICE_VOLUME_SET, [ATTR_MEDIA_VOLUME_LEVEL]) + + if ATTR_MEDIA_VOLUME_MUTED in state.attributes: + await call_service(SERVICE_VOLUME_MUTE, [ATTR_MEDIA_VOLUME_MUTED]) + + if ATTR_MEDIA_SEEK_POSITION in state.attributes: + await call_service(SERVICE_MEDIA_SEEK, [ATTR_MEDIA_SEEK_POSITION]) + + if ATTR_INPUT_SOURCE in state.attributes: + await call_service(SERVICE_SELECT_SOURCE, [ATTR_INPUT_SOURCE]) + + if ATTR_SOUND_MODE in state.attributes: + await call_service(SERVICE_SELECT_SOUND_MODE, [ATTR_SOUND_MODE]) + + if (ATTR_MEDIA_CONTENT_TYPE in state.attributes) and \ + (ATTR_MEDIA_CONTENT_ID in state.attributes): + await call_service(SERVICE_PLAY_MEDIA, + [ATTR_MEDIA_CONTENT_TYPE, + ATTR_MEDIA_CONTENT_ID, + ATTR_MEDIA_ENQUEUE]) + + +@bind_hass +async def async_reproduce_states(hass: HomeAssistantType, + states: Iterable[State], + context: Optional[Context] = None) -> None: + """Reproduce component states.""" + await asyncio.gather(*[ + _async_reproduce_states(hass, state, context) + for state in states]) diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 8fd3f7d053e..7d69defed48 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -10,41 +10,27 @@ from typing import ( # noqa: F401 pylint: disable=unused-import from homeassistant.loader import bind_hass import homeassistant.util.dt as dt_util -from homeassistant.components.media_player import ( - ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_SEEK_POSITION, - ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, SERVICE_PLAY_MEDIA, - SERVICE_SELECT_SOURCE, ATTR_INPUT_SOURCE) from homeassistant.components.notify import ( ATTR_MESSAGE, SERVICE_NOTIFY) from homeassistant.components.sun import ( STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON) from homeassistant.components.mysensors.switch import ( ATTR_IR_CODE, SERVICE_SEND_IR_CODE) -from homeassistant.components.climate import ( - ATTR_AUX_HEAT, ATTR_AWAY_MODE, ATTR_FAN_MODE, ATTR_HOLD_MODE, - ATTR_HUMIDITY, ATTR_OPERATION_MODE, ATTR_SWING_MODE, - SERVICE_SET_AUX_HEAT, SERVICE_SET_AWAY_MODE, SERVICE_SET_HOLD_MODE, - SERVICE_SET_FAN_MODE, SERVICE_SET_HUMIDITY, SERVICE_SET_OPERATION_MODE, - SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE, STATE_HEAT, STATE_COOL, - STATE_IDLE) -from homeassistant.components.ecobee.climate import ( - ATTR_FAN_MIN_ON_TIME, SERVICE_SET_FAN_MIN_ON_TIME, - ATTR_RESUME_ALL, SERVICE_RESUME_PROGRAM) from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_OPTION, ATTR_TEMPERATURE, SERVICE_ALARM_ARM_AWAY, + ATTR_ENTITY_ID, ATTR_OPTION, SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_DISARM, SERVICE_ALARM_TRIGGER, - SERVICE_LOCK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_STOP, - SERVICE_MEDIA_SEEK, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_UNLOCK, - SERVICE_VOLUME_MUTE, SERVICE_VOLUME_SET, SERVICE_OPEN_COVER, + SERVICE_LOCK, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_UNLOCK, + SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, SERVICE_SET_COVER_POSITION, SERVICE_SET_COVER_TILT_POSITION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED, STATE_CLOSED, STATE_HOME, STATE_LOCKED, STATE_NOT_HOME, STATE_OFF, - STATE_ON, STATE_OPEN, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN, + STATE_ON, STATE_OPEN, STATE_UNKNOWN, STATE_UNLOCKED, SERVICE_SELECT_OPTION) -from homeassistant.core import State, DOMAIN as HASS_DOMAIN +from homeassistant.core import ( + Context, State, DOMAIN as HASS_DOMAIN) from homeassistant.util.async_ import run_coroutine_threadsafe from .typing import HomeAssistantType @@ -55,22 +41,7 @@ GROUP_DOMAIN = 'group' # Update this dict of lists when new services are added to HA. # Each item is a service with a list of required attributes. SERVICE_ATTRIBUTES = { - SERVICE_PLAY_MEDIA: [ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_CONTENT_ID], - SERVICE_MEDIA_SEEK: [ATTR_MEDIA_SEEK_POSITION], - SERVICE_VOLUME_MUTE: [ATTR_MEDIA_VOLUME_MUTED], - SERVICE_VOLUME_SET: [ATTR_MEDIA_VOLUME_LEVEL], SERVICE_NOTIFY: [ATTR_MESSAGE], - SERVICE_SET_AWAY_MODE: [ATTR_AWAY_MODE], - SERVICE_SET_FAN_MODE: [ATTR_FAN_MODE], - SERVICE_SET_FAN_MIN_ON_TIME: [ATTR_FAN_MIN_ON_TIME], - SERVICE_RESUME_PROGRAM: [ATTR_RESUME_ALL], - SERVICE_SET_TEMPERATURE: [ATTR_TEMPERATURE], - SERVICE_SET_HUMIDITY: [ATTR_HUMIDITY], - SERVICE_SET_SWING_MODE: [ATTR_SWING_MODE], - SERVICE_SET_HOLD_MODE: [ATTR_HOLD_MODE], - SERVICE_SET_OPERATION_MODE: [ATTR_OPERATION_MODE], - SERVICE_SET_AUX_HEAT: [ATTR_AUX_HEAT], - SERVICE_SELECT_SOURCE: [ATTR_INPUT_SOURCE], SERVICE_SEND_IR_CODE: [ATTR_IR_CODE], SERVICE_SELECT_OPTION: [ATTR_OPTION], SERVICE_SET_COVER_POSITION: [ATTR_POSITION], @@ -82,9 +53,6 @@ SERVICE_ATTRIBUTES = { SERVICE_TO_STATE = { SERVICE_TURN_ON: STATE_ON, SERVICE_TURN_OFF: STATE_OFF, - SERVICE_MEDIA_PLAY: STATE_PLAYING, - SERVICE_MEDIA_PAUSE: STATE_PAUSED, - SERVICE_MEDIA_STOP: STATE_IDLE, SERVICE_ALARM_ARM_AWAY: STATE_ALARM_ARMED_AWAY, SERVICE_ALARM_ARM_HOME: STATE_ALARM_ARMED_HOME, SERVICE_ALARM_DISARM: STATE_ALARM_DISARMED, @@ -142,14 +110,56 @@ def reproduce_state(hass: HomeAssistantType, @bind_hass -async def async_reproduce_state(hass: HomeAssistantType, - states: Union[State, Iterable[State]], - blocking: bool = False) -> None: - """Reproduce given state.""" +async def async_reproduce_state( + hass: HomeAssistantType, + states: Union[State, Iterable[State]], + blocking: bool = False, + context: Optional[Context] = None) -> None: + """Reproduce a list of states on multiple domains.""" if isinstance(states, State): states = [states] - to_call = defaultdict(list) # type: Dict[Tuple[str, str, str], List[str]] + to_call = defaultdict(list) # type: Dict[str, List[State]] + + for state in states: + to_call[state.domain].append(state) + + async def worker(domain: str, data: List[State]) -> None: + component = getattr(hass.components, domain) + if hasattr(component, 'async_reproduce_states'): + await component.async_reproduce_states( + data, + context=context) + else: + await async_reproduce_state_legacy( + hass, + domain, + data, + blocking=blocking, + context=context) + + if to_call: + # run all domains in parallel + await asyncio.gather(*[ + worker(domain, data) + for domain, data in to_call.items() + ]) + + +@bind_hass +async def async_reproduce_state_legacy( + hass: HomeAssistantType, + domain: str, + states: Iterable[State], + blocking: bool = False, + context: Optional[Context] = None) -> None: + """Reproduce given state.""" + to_call = defaultdict(list) # type: Dict[Tuple[str, str], List[str]] + + if domain == GROUP_DOMAIN: + service_domain = HASS_DOMAIN + else: + service_domain = domain for state in states: @@ -158,11 +168,6 @@ async def async_reproduce_state(hass: HomeAssistantType, state.entity_id) continue - if state.domain == GROUP_DOMAIN: - service_domain = HASS_DOMAIN - else: - service_domain = state.domain - domain_services = hass.services.async_services().get(service_domain) if not domain_services: @@ -189,32 +194,22 @@ async def async_reproduce_state(hass: HomeAssistantType, # We group service calls for entities by service call # json used to create a hashable version of dict with maybe lists in it - key = (service_domain, service, + key = (service, json.dumps(dict(state.attributes), sort_keys=True)) to_call[key].append(state.entity_id) - domain_tasks = {} # type: Dict[str, List[Awaitable[Optional[bool]]]] - for (service_domain, service, service_data), entity_ids in to_call.items(): + domain_tasks = [] # type: List[Awaitable[Optional[bool]]] + for (service, service_data), entity_ids in to_call.items(): data = json.loads(service_data) data[ATTR_ENTITY_ID] = entity_ids - if service_domain not in domain_tasks: - domain_tasks[service_domain] = [] - - domain_tasks[service_domain].append( - hass.services.async_call(service_domain, service, data, blocking) + domain_tasks.append( + hass.services.async_call(service_domain, service, data, blocking, + context) ) - async def async_handle_service_calls( - coro_list: Iterable[Awaitable]) -> None: - """Handle service calls by domain sequence.""" - for coro in coro_list: - await coro - - execute_tasks = [async_handle_service_calls(coro_list) - for coro_list in domain_tasks.values()] - if execute_tasks: - await asyncio.wait(execute_tasks, loop=hass.loop) + if domain_tasks: + await asyncio.wait(domain_tasks, loop=hass.loop) def state_as_number(state: State) -> float: @@ -223,6 +218,9 @@ def state_as_number(state: State) -> float: Raises ValueError if this is not possible. """ + from homeassistant.components.climate import ( + STATE_HEAT, STATE_COOL, STATE_IDLE) + if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON, STATE_OPEN, STATE_HOME, STATE_HEAT, STATE_COOL): return 1 diff --git a/tests/components/climate/test_reproduce_state.py b/tests/components/climate/test_reproduce_state.py new file mode 100644 index 00000000000..c16151320b4 --- /dev/null +++ b/tests/components/climate/test_reproduce_state.py @@ -0,0 +1,162 @@ +"""The tests for reproduction of state.""" + +import pytest + +from homeassistant.components.climate import STATE_HEAT, async_reproduce_states +from homeassistant.components.climate.const import ( + ATTR_AUX_HEAT, ATTR_AWAY_MODE, ATTR_HOLD_MODE, ATTR_HUMIDITY, + ATTR_OPERATION_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, DOMAIN, SERVICE_SET_AUX_HEAT, SERVICE_SET_AWAY_MODE, + SERVICE_SET_HOLD_MODE, SERVICE_SET_HUMIDITY, SERVICE_SET_OPERATION_MODE, + SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE) +from homeassistant.const import ( + ATTR_TEMPERATURE, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, STATE_ON) +from homeassistant.core import Context, State + +from tests.common import async_mock_service + +ENTITY_1 = 'climate.test1' +ENTITY_2 = 'climate.test2' + + +@pytest.mark.parametrize( + 'service,state', [ + (SERVICE_TURN_ON, STATE_ON), + (SERVICE_TURN_OFF, STATE_OFF), + ]) +async def test_state(hass, service, state): + """Test that we can turn a state into a service call.""" + calls_1 = async_mock_service(hass, DOMAIN, service) + + await async_reproduce_states(hass, [ + State(ENTITY_1, state) + ]) + + await hass.async_block_till_done() + + assert len(calls_1) == 1 + assert calls_1[0].data == {'entity_id': ENTITY_1} + + +async def test_turn_on_with_mode(hass): + """Test that state with additional attributes call multiple services.""" + calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) + calls_2 = async_mock_service(hass, DOMAIN, SERVICE_SET_OPERATION_MODE) + + await async_reproduce_states(hass, [ + State(ENTITY_1, 'on', + {ATTR_OPERATION_MODE: STATE_HEAT}) + ]) + + await hass.async_block_till_done() + + assert len(calls_1) == 1 + assert calls_1[0].data == {'entity_id': ENTITY_1} + + assert len(calls_2) == 1 + assert calls_2[0].data == {'entity_id': ENTITY_1, + ATTR_OPERATION_MODE: STATE_HEAT} + + +async def test_multiple_same_state(hass): + """Test that multiple states with same state gets calls.""" + calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) + + await async_reproduce_states(hass, [ + State(ENTITY_1, 'on'), + State(ENTITY_2, 'on'), + ]) + + await hass.async_block_till_done() + + assert len(calls_1) == 2 + # order is not guaranteed + assert any(call.data == {'entity_id': ENTITY_1} for call in calls_1) + assert any(call.data == {'entity_id': ENTITY_2} for call in calls_1) + + +async def test_multiple_different_state(hass): + """Test that multiple states with different state gets calls.""" + calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) + calls_2 = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF) + + await async_reproduce_states(hass, [ + State(ENTITY_1, 'on'), + State(ENTITY_2, 'off'), + ]) + + await hass.async_block_till_done() + + assert len(calls_1) == 1 + assert calls_1[0].data == {'entity_id': ENTITY_1} + assert len(calls_2) == 1 + assert calls_2[0].data == {'entity_id': ENTITY_2} + + +async def test_state_with_context(hass): + """Test that context is forwarded.""" + calls = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) + + context = Context() + + await async_reproduce_states(hass, [ + State(ENTITY_1, 'on') + ], context) + + await hass.async_block_till_done() + + assert len(calls) == 1 + assert calls[0].data == {'entity_id': ENTITY_1} + assert calls[0].context == context + + +async def test_attribute_no_state(hass): + """Test that no state service call is made with none state.""" + calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) + calls_2 = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF) + calls_3 = async_mock_service(hass, DOMAIN, SERVICE_SET_OPERATION_MODE) + + value = "dummy" + + await async_reproduce_states(hass, [ + State(ENTITY_1, None, + {ATTR_OPERATION_MODE: value}) + ]) + + await hass.async_block_till_done() + + assert len(calls_1) == 0 + assert len(calls_2) == 0 + assert len(calls_3) == 1 + assert calls_3[0].data == {'entity_id': ENTITY_1, + ATTR_OPERATION_MODE: value} + + +@pytest.mark.parametrize( + 'service,attribute', [ + (SERVICE_SET_OPERATION_MODE, ATTR_OPERATION_MODE), + (SERVICE_SET_AUX_HEAT, ATTR_AUX_HEAT), + (SERVICE_SET_AWAY_MODE, ATTR_AWAY_MODE), + (SERVICE_SET_HOLD_MODE, ATTR_HOLD_MODE), + (SERVICE_SET_SWING_MODE, ATTR_SWING_MODE), + (SERVICE_SET_HUMIDITY, ATTR_HUMIDITY), + (SERVICE_SET_TEMPERATURE, ATTR_TEMPERATURE), + (SERVICE_SET_TEMPERATURE, ATTR_TARGET_TEMP_HIGH), + (SERVICE_SET_TEMPERATURE, ATTR_TARGET_TEMP_LOW), + ]) +async def test_attribute(hass, service, attribute): + """Test that service call is made for each attribute.""" + calls_1 = async_mock_service(hass, DOMAIN, service) + + value = "dummy" + + await async_reproduce_states(hass, [ + State(ENTITY_1, None, + {attribute: value}) + ]) + + await hass.async_block_till_done() + + assert len(calls_1) == 1 + assert calls_1[0].data == {'entity_id': ENTITY_1, + attribute: value} diff --git a/tests/components/group/test_reproduce_state.py b/tests/components/group/test_reproduce_state.py new file mode 100644 index 00000000000..43bc2a03fe9 --- /dev/null +++ b/tests/components/group/test_reproduce_state.py @@ -0,0 +1,45 @@ +"""The tests for reproduction of state.""" + +from asyncio import Future +from unittest.mock import patch +from homeassistant.components.group import async_reproduce_states +from homeassistant.core import Context, State + + +async def test_reproduce_group(hass): + """Test reproduce_state with group.""" + context = Context() + + def clone_state(state, entity_id): + """Return a cloned state with different entity_id.""" + return State(entity_id, + state.state, + state.attributes, + last_changed=state.last_changed, + last_updated=state.last_updated, + context=state.context) + + with patch('homeassistant.helpers.state.async_reproduce_state') as fun: + fun.return_value = Future() + fun.return_value.set_result(None) + + hass.states.async_set('group.test', 'off', { + 'entity_id': ['light.test1', 'light.test2', 'switch.test1']}) + hass.states.async_set('light.test1', 'off') + hass.states.async_set('light.test2', 'off') + hass.states.async_set('switch.test1', 'off') + + state = State('group.test', 'on') + + await async_reproduce_states( + hass, + [state], + context) + + fun.assert_called_once_with( + hass, + [clone_state(state, 'light.test1'), + clone_state(state, 'light.test2'), + clone_state(state, 'switch.test1')], + blocking=True, + context=context) diff --git a/tests/components/media_player/test_reproduce_state.py b/tests/components/media_player/test_reproduce_state.py new file mode 100644 index 00000000000..f39733178b1 --- /dev/null +++ b/tests/components/media_player/test_reproduce_state.py @@ -0,0 +1,199 @@ +"""The tests for reproduction of state.""" + +import pytest + +from homeassistant.components.media_player import async_reproduce_states +from homeassistant.components.media_player.const import ( + ATTR_INPUT_SOURCE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, + ATTR_MEDIA_ENQUEUE, ATTR_MEDIA_SEEK_POSITION, ATTR_MEDIA_VOLUME_LEVEL, + ATTR_MEDIA_VOLUME_MUTED, ATTR_SOUND_MODE, DOMAIN, SERVICE_PLAY_MEDIA, + SERVICE_SELECT_SOUND_MODE, SERVICE_SELECT_SOURCE) +from homeassistant.const import ( + SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_SEEK, + SERVICE_MEDIA_STOP, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_MUTE, + SERVICE_VOLUME_SET, STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED, + STATE_PLAYING) +from homeassistant.core import Context, State + +from tests.common import async_mock_service + +ENTITY_1 = 'media_player.test1' +ENTITY_2 = 'media_player.test2' + + +@pytest.mark.parametrize( + 'service,state', [ + (SERVICE_TURN_ON, STATE_ON), + (SERVICE_TURN_OFF, STATE_OFF), + (SERVICE_MEDIA_PLAY, STATE_PLAYING), + (SERVICE_MEDIA_STOP, STATE_IDLE), + (SERVICE_MEDIA_PAUSE, STATE_PAUSED), + ]) +async def test_state(hass, service, state): + """Test that we can turn a state into a service call.""" + calls_1 = async_mock_service(hass, DOMAIN, service) + + await async_reproduce_states(hass, [ + State(ENTITY_1, state) + ]) + + await hass.async_block_till_done() + + assert len(calls_1) == 1 + assert calls_1[0].data == {'entity_id': ENTITY_1} + + +async def test_turn_on_with_mode(hass): + """Test that state with additional attributes call multiple services.""" + calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) + calls_2 = async_mock_service(hass, DOMAIN, SERVICE_SELECT_SOUND_MODE) + + await async_reproduce_states(hass, [ + State(ENTITY_1, 'on', + {ATTR_SOUND_MODE: 'dummy'}) + ]) + + await hass.async_block_till_done() + + assert len(calls_1) == 1 + assert calls_1[0].data == {'entity_id': ENTITY_1} + + assert len(calls_2) == 1 + assert calls_2[0].data == {'entity_id': ENTITY_1, + ATTR_SOUND_MODE: 'dummy'} + + +async def test_multiple_same_state(hass): + """Test that multiple states with same state gets calls.""" + calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) + + await async_reproduce_states(hass, [ + State(ENTITY_1, 'on'), + State(ENTITY_2, 'on'), + ]) + + await hass.async_block_till_done() + + assert len(calls_1) == 2 + # order is not guaranteed + assert any(call.data == {'entity_id': 'media_player.test1'} + for call in calls_1) + assert any(call.data == {'entity_id': 'media_player.test2'} + for call in calls_1) + + +async def test_multiple_different_state(hass): + """Test that multiple states with different state gets calls.""" + calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) + calls_2 = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF) + + await async_reproduce_states(hass, [ + State(ENTITY_1, 'on'), + State(ENTITY_2, 'off'), + ]) + + await hass.async_block_till_done() + + assert len(calls_1) == 1 + assert calls_1[0].data == {'entity_id': 'media_player.test1'} + assert len(calls_2) == 1 + assert calls_2[0].data == {'entity_id': 'media_player.test2'} + + +async def test_state_with_context(hass): + """Test that context is forwarded.""" + calls = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) + + context = Context() + + await async_reproduce_states(hass, [ + State(ENTITY_1, 'on') + ], context) + + await hass.async_block_till_done() + + assert len(calls) == 1 + assert calls[0].data == {'entity_id': ENTITY_1} + assert calls[0].context == context + + +async def test_attribute_no_state(hass): + """Test that no state service call is made with none state.""" + calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) + calls_2 = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF) + calls_3 = async_mock_service(hass, DOMAIN, SERVICE_SELECT_SOUND_MODE) + + value = "dummy" + + await async_reproduce_states(hass, [ + State(ENTITY_1, None, + {ATTR_SOUND_MODE: value}) + ]) + + await hass.async_block_till_done() + + assert len(calls_1) == 0 + assert len(calls_2) == 0 + assert len(calls_3) == 1 + assert calls_3[0].data == {'entity_id': ENTITY_1, + ATTR_SOUND_MODE: value} + + +@pytest.mark.parametrize( + 'service,attribute', [ + (SERVICE_VOLUME_SET, ATTR_MEDIA_VOLUME_LEVEL), + (SERVICE_VOLUME_MUTE, ATTR_MEDIA_VOLUME_MUTED), + (SERVICE_MEDIA_SEEK, ATTR_MEDIA_SEEK_POSITION), + (SERVICE_SELECT_SOURCE, ATTR_INPUT_SOURCE), + (SERVICE_SELECT_SOUND_MODE, ATTR_SOUND_MODE), + ]) +async def test_attribute(hass, service, attribute): + """Test that service call is made for each attribute.""" + calls_1 = async_mock_service(hass, DOMAIN, service) + + value = "dummy" + + await async_reproduce_states(hass, [ + State(ENTITY_1, None, + {attribute: value}) + ]) + + await hass.async_block_till_done() + + assert len(calls_1) == 1 + assert calls_1[0].data == {'entity_id': ENTITY_1, + attribute: value} + + +async def test_play_media(hass): + """Test that no state service call is made with none state.""" + calls_1 = async_mock_service(hass, DOMAIN, SERVICE_PLAY_MEDIA) + + value_1 = "dummy_1" + value_2 = "dummy_2" + value_3 = "dummy_3" + + await async_reproduce_states(hass, [ + State(ENTITY_1, None, + {ATTR_MEDIA_CONTENT_TYPE: value_1, + ATTR_MEDIA_CONTENT_ID: value_2}) + ]) + + await async_reproduce_states(hass, [ + State(ENTITY_1, None, + {ATTR_MEDIA_CONTENT_TYPE: value_1, + ATTR_MEDIA_CONTENT_ID: value_2, + ATTR_MEDIA_ENQUEUE: value_3}) + ]) + + await hass.async_block_till_done() + + assert len(calls_1) == 2 + assert calls_1[0].data == {'entity_id': ENTITY_1, + ATTR_MEDIA_CONTENT_TYPE: value_1, + ATTR_MEDIA_CONTENT_ID: value_2} + + assert calls_1[1].data == {'entity_id': ENTITY_1, + ATTR_MEDIA_CONTENT_TYPE: value_1, + ATTR_MEDIA_CONTENT_ID: value_2, + ATTR_MEDIA_ENQUEUE: value_3} diff --git a/tests/helpers/test_state.py b/tests/helpers/test_state.py index 07654744492..5c04f085c86 100644 --- a/tests/helpers/test_state.py +++ b/tests/helpers/test_state.py @@ -15,8 +15,6 @@ from homeassistant.const import ( STATE_LOCKED, STATE_UNLOCKED, STATE_ON, STATE_OFF, STATE_HOME, STATE_NOT_HOME) -from homeassistant.components.media_player import ( - SERVICE_PLAY_MEDIA, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE) from homeassistant.components.sun import (STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON) @@ -50,6 +48,40 @@ def test_async_track_states(hass): sorted(states, key=lambda state: state.entity_id) +@asyncio.coroutine +def test_call_to_component(hass): + """Test calls to components state reproduction functions.""" + with patch(('homeassistant.components.media_player.' + 'async_reproduce_states')) as media_player_fun: + media_player_fun.return_value = asyncio.Future() + media_player_fun.return_value.set_result(None) + + with patch(('homeassistant.components.climate.' + 'async_reproduce_states')) as climate_fun: + climate_fun.return_value = asyncio.Future() + climate_fun.return_value.set_result(None) + + state_media_player = ha.State('media_player.test', 'bad') + state_climate = ha.State('climate.test', 'bad') + context = "dummy_context" + + yield from state.async_reproduce_state( + hass, + [state_media_player, state_climate], + blocking=True, + context=context) + + media_player_fun.assert_called_once_with( + hass, + [state_media_player], + context=context) + + climate_fun.assert_called_once_with( + hass, + [state_climate], + context=context) + + class TestStateHelpers(unittest.TestCase): """Test the Home Assistant event helpers.""" @@ -147,63 +179,6 @@ class TestStateHelpers(unittest.TestCase): assert SERVICE_TURN_ON == last_call.service assert complex_data == last_call.data.get('complex') - def test_reproduce_media_data(self): - """Test reproduce_state with SERVICE_PLAY_MEDIA.""" - calls = mock_service(self.hass, 'media_player', SERVICE_PLAY_MEDIA) - - self.hass.states.set('media_player.test', 'off') - - media_attributes = {'media_content_type': 'movie', - 'media_content_id': 'batman'} - - state.reproduce_state(self.hass, ha.State('media_player.test', 'None', - media_attributes)) - - self.hass.block_till_done() - - assert len(calls) > 0 - last_call = calls[-1] - assert 'media_player' == last_call.domain - assert SERVICE_PLAY_MEDIA == last_call.service - assert 'movie' == last_call.data.get('media_content_type') - assert 'batman' == last_call.data.get('media_content_id') - - def test_reproduce_media_play(self): - """Test reproduce_state with SERVICE_MEDIA_PLAY.""" - calls = mock_service(self.hass, 'media_player', SERVICE_MEDIA_PLAY) - - self.hass.states.set('media_player.test', 'off') - - state.reproduce_state( - self.hass, ha.State('media_player.test', 'playing')) - - self.hass.block_till_done() - - assert len(calls) > 0 - last_call = calls[-1] - assert 'media_player' == last_call.domain - assert SERVICE_MEDIA_PLAY == last_call.service - assert ['media_player.test'] == \ - last_call.data.get('entity_id') - - def test_reproduce_media_pause(self): - """Test reproduce_state with SERVICE_MEDIA_PAUSE.""" - calls = mock_service(self.hass, 'media_player', SERVICE_MEDIA_PAUSE) - - self.hass.states.set('media_player.test', 'playing') - - state.reproduce_state( - self.hass, ha.State('media_player.test', 'paused')) - - self.hass.block_till_done() - - assert len(calls) > 0 - last_call = calls[-1] - assert 'media_player' == last_call.domain - assert SERVICE_MEDIA_PAUSE == last_call.service - assert ['media_player.test'] == \ - last_call.data.get('entity_id') - def test_reproduce_bad_state(self): """Test reproduce_state with bad state.""" calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) @@ -217,45 +192,6 @@ class TestStateHelpers(unittest.TestCase): assert len(calls) == 0 assert 'off' == self.hass.states.get('light.test').state - def test_reproduce_group(self): - """Test reproduce_state with group.""" - light_calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) - - self.hass.states.set('group.test', 'off', { - 'entity_id': ['light.test1', 'light.test2']}) - - state.reproduce_state(self.hass, ha.State('group.test', 'on')) - - self.hass.block_till_done() - - assert 1 == len(light_calls) - last_call = light_calls[-1] - assert 'light' == last_call.domain - assert SERVICE_TURN_ON == last_call.service - assert ['light.test1', 'light.test2'] == \ - last_call.data.get('entity_id') - - def test_reproduce_group_same_data(self): - """Test reproduce_state with group with same domain and data.""" - light_calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) - - self.hass.states.set('light.test1', 'off') - self.hass.states.set('light.test2', 'off') - - state.reproduce_state(self.hass, [ - ha.State('light.test1', 'on', {'brightness': 95}), - ha.State('light.test2', 'on', {'brightness': 95})]) - - self.hass.block_till_done() - - assert 1 == len(light_calls) - last_call = light_calls[-1] - assert 'light' == last_call.domain - assert SERVICE_TURN_ON == last_call.service - assert ['light.test1', 'light.test2'] == \ - last_call.data.get('entity_id') - assert 95 == last_call.data.get('brightness') - def test_as_number_states(self): """Test state_as_number with states.""" zero_states = (STATE_OFF, STATE_CLOSED, STATE_UNLOCKED, From d13b2ca6efff4e9584099a3a9ab5f915cfbe9733 Mon Sep 17 00:00:00 2001 From: William Scanlon Date: Tue, 5 Feb 2019 21:26:54 -0500 Subject: [PATCH 071/242] Added egg age to the eggminder sensor (#20758) * Added egg age to the eggminder sensor --- homeassistant/components/wink/sensor.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/wink/sensor.py b/homeassistant/components/wink/sensor.py index 8c2abc0f875..62f4638820f 100644 --- a/homeassistant/components/wink/sensor.py +++ b/homeassistant/components/wink/sensor.py @@ -8,7 +8,6 @@ import logging from homeassistant.components.wink import DOMAIN, WinkDevice from homeassistant.const import TEMP_CELSIUS -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -47,7 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.info("Device is not a sensor") -class WinkSensorDevice(WinkDevice, Entity): +class WinkSensorDevice(WinkDevice): """Representation of a Wink sensor.""" def __init__(self, wink, hass): @@ -87,3 +86,15 @@ class WinkSensorDevice(WinkDevice, Entity): def unit_of_measurement(self): """Return the unit of measurement of this entity, if any.""" return self._unit_of_measurement + + @property + def device_state_attributes(self): + """Return the state attributes.""" + super_attrs = super().device_state_attributes + _LOGGER.debug("Adding in eggs if egg minder") + try: + super_attrs['egg_times'] = self.wink.eggs() + _LOGGER.debug("Its an egg minder") + except AttributeError: + _LOGGER.debug("Not an eggtray") + return super_attrs From b8cc547fa305dc075ba1e51e0389af2f9e5cea47 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 5 Feb 2019 19:31:15 -0800 Subject: [PATCH 072/242] Move components to folders (#20774) * Move all components into folders * Move component tests into folders * Fix init moving * Move tests * Lint * Update coverage * Fix service descriptions * Update CODEOWNERS --- .coveragerc | 66 +-- CODEOWNERS | 90 +-- homeassistant/components/abode/services.yaml | 13 + .../{alert.py => alert/__init__.py} | 0 homeassistant/components/alert/services.yaml | 12 + .../components/{api.py => api/__init__.py} | 0 .../components/apple_tv/services.yaml | 5 + .../{asuswrt.py => asuswrt/__init__.py} | 0 .../{browser.py => browser/__init__.py} | 0 .../{canary.py => canary/__init__.py} | 0 .../{cloudflare.py => cloudflare/__init__.py} | 0 .../{coinbase.py => coinbase/__init__.py} | 0 .../__init__.py} | 0 .../{datadog.py => datadog/__init__.py} | 0 .../components/{demo.py => demo/__init__.py} | 0 .../__init__.py} | 0 .../{discovery.py => discovery/__init__.py} | 0 .../{dominos.py => dominos/__init__.py} | 0 .../{downloader.py => downloader/__init__.py} | 0 .../{duckdns.py => duckdns/__init__.py} | 0 .../{dyson.py => dyson/__init__.py} | 0 .../components/eight_sleep/services.yaml | 6 + .../__init__.py} | 0 .../{feedreader.py => feedreader/__init__.py} | 0 .../{ffmpeg.py => ffmpeg/__init__.py} | 0 homeassistant/components/ffmpeg/services.yaml | 15 + .../__init__.py} | 0 .../{foursquare.py => foursquare/__init__.py} | 0 .../components/foursquare/services.yaml | 29 + .../{freedns.py => freedns/__init__.py} | 0 .../{goalfeed.py => goalfeed/__init__.py} | 0 .../__init__.py} | 0 .../{graphite.py => graphite/__init__.py} | 0 .../__init__.py} | 0 homeassistant/components/hassio/services.yaml | 37 ++ .../components/hdmi_cec/services.yaml | 32 + .../{history.py => history/__init__.py} | 0 .../__init__.py} | 0 .../__init__.py} | 0 .../{influxdb.py => influxdb/__init__.py} | 0 .../__init__.py} | 0 .../components/input_boolean/services.yaml | 12 + .../__init__.py} | 0 .../__init__.py} | 0 .../components/input_number/services.yaml | 16 + .../__init__.py} | 0 .../components/input_select/services.yaml | 22 + .../{input_text.py => input_text/__init__.py} | 0 .../components/input_text/services.yaml | 6 + .../__init__.py} | 0 .../__init__.py} | 0 .../__init__.py} | 0 .../__init__.py} | 0 .../{keyboard.py => keyboard/__init__.py} | 0 .../__init__.py} | 0 homeassistant/components/knx/services.yaml | 5 + .../components/{lirc.py => lirc/__init__.py} | 2 +- .../{litejet.py => litejet/__init__.py} | 0 .../{logbook.py => logbook/__init__.py} | 0 .../{logentries.py => logentries/__init__.py} | 0 .../{logger.py => logger/__init__.py} | 0 homeassistant/components/logger/services.yaml | 6 + .../components/{map.py => map/__init__.py} | 0 .../__init__.py} | 0 .../{melissa.py => melissa/__init__.py} | 0 .../__init__.py} | 0 .../components/microsoft_face/services.yaml | 28 + homeassistant/components/modbus/services.yaml | 12 + .../__init__.py} | 0 .../__init__.py} | 0 .../{mycroft.py => mycroft/__init__.py} | 0 .../__init__.py} | 0 .../__init__.py} | 0 .../{ness_alarm.py => ness_alarm/__init__.py} | 0 homeassistant/components/nest/services.yaml | 49 +- .../{no_ip.py => no_ip/__init__.py} | 0 .../{nuheat.py => nuheat/__init__.py} | 0 .../__init__.py} | 0 .../__init__.py} | 0 .../__init__.py} | 0 .../{plant.py => plant/__init__.py} | 0 .../{prometheus.py => prometheus/__init__.py} | 0 .../{proximity.py => proximity/__init__.py} | 0 .../__init__.py} | 0 .../{rainbird.py => rainbird/__init__.py} | 0 .../__init__.py} | 0 .../{rflink.py => rflink/__init__.py} | 0 homeassistant/components/rflink/services.yaml | 5 + .../components/{ring.py => ring/__init__.py} | 0 .../{route53.py => route53/__init__.py} | 0 .../__init__.py} | 0 .../{script.py => script/__init__.py} | 0 homeassistant/components/services.yaml | 557 ------------------ .../__init__.py} | 0 .../{shiftr.py => shiftr/__init__.py} | 0 .../__init__.py} | 0 .../components/shopping_list/services.yaml | 9 + .../{sleepiq.py => sleepiq/__init__.py} | 0 .../{snips.py => snips/__init__.py} | 0 homeassistant/components/snips/services.yaml | 31 + .../{spaceapi.py => spaceapi/__init__.py} | 0 .../components/{spc.py => spc/__init__.py} | 0 .../{splunk.py => splunk/__init__.py} | 0 .../{statsd.py => statsd/__init__.py} | 0 .../components/{sun.py => sun/__init__.py} | 0 .../{thingspeak.py => thingspeak/__init__.py} | 0 .../{updater.py => updater/__init__.py} | 0 .../components/verisure/services.yaml | 5 + .../{vultr.py => vultr/__init__.py} | 0 .../__init__.py} | 0 .../components/wake_on_lan/services.yaml | 6 + .../{watson_iot.py => watson_iot/__init__.py} | 0 .../{webhook.py => webhook/__init__.py} | 0 .../{weblink.py => weblink/__init__.py} | 0 .../components/xiaomi_aqara/services.yaml | 22 + .../{zeroconf.py => zeroconf/__init__.py} | 0 tests/components/alert/__init__.py | 1 + .../{test_alert.py => alert/test_init.py} | 0 tests/components/api/__init__.py | 1 + .../{test_api.py => api/test_init.py} | 0 tests/components/binary_sensor/test_rflink.py | 2 +- tests/components/binary_sensor/test_ring.py | 2 +- .../components/binary_sensor/test_sleepiq.py | 2 +- tests/components/binary_sensor/test_vultr.py | 2 +- tests/components/canary/__init__.py | 1 + .../{test_canary.py => canary/test_init.py} | 0 tests/components/configurator/__init__.py | 1 + .../test_init.py} | 0 tests/components/conversation/__init__.py | 1 + .../test_init.py} | 0 tests/components/cover/test_rflink.py | 2 +- tests/components/datadog/__init__.py | 1 + .../{test_datadog.py => datadog/test_init.py} | 0 tests/components/demo/__init__.py | 1 + .../{test_demo.py => demo/test_init.py} | 0 .../device_sun_light_trigger/__init__.py | 1 + .../test_init.py} | 0 tests/components/discovery/__init__.py | 1 + .../test_init.py} | 0 tests/components/duckdns/__init__.py | 1 + .../{test_duckdns.py => duckdns/test_init.py} | 0 tests/components/dyson/__init__.py | 1 + .../{test_dyson.py => dyson/test_init.py} | 0 tests/components/feedreader/__init__.py | 1 + .../test_init.py} | 0 tests/components/ffmpeg/__init__.py | 1 + .../{test_ffmpeg.py => ffmpeg/test_init.py} | 0 tests/components/folder_watcher/__init__.py | 1 + .../test_init.py} | 0 tests/components/freedns/__init__.py | 1 + .../{test_freedns.py => freedns/test_init.py} | 0 .../{test_google.py => google/test_init.py} | 0 tests/components/google_domains/__init__.py | 1 + .../test_init.py} | 0 tests/components/graphite/__init__.py | 1 + .../test_init.py} | 0 tests/components/history/__init__.py | 1 + .../{test_history.py => history/test_init.py} | 0 tests/components/history_graph/__init__.py | 1 + .../test_init.py} | 0 tests/components/huawei_lte/__init__.py | 1 + .../test_init.py} | 0 tests/components/influxdb/__init__.py | 1 + .../test_init.py} | 0 tests/components/init/__init__.py | 1 + tests/components/{ => init}/test_init.py | 0 tests/components/input_boolean/__init__.py | 1 + .../test_init.py} | 0 tests/components/input_datetime/__init__.py | 1 + .../test_init.py} | 0 tests/components/input_number/__init__.py | 1 + .../test_init.py} | 0 tests/components/input_select/__init__.py | 1 + .../test_init.py} | 0 tests/components/input_text/__init__.py | 1 + .../test_init.py} | 0 tests/components/intent_script/__init__.py | 1 + .../test_init.py} | 0 tests/components/introduction/__init__.py | 1 + .../test_init.py} | 0 .../{test_kira.py => kira/test_init.py} | 0 tests/components/light/test_rflink.py | 2 +- tests/components/litejet/__init__.py | 1 + .../{test_litejet.py => litejet/test_init.py} | 0 tests/components/logbook/__init__.py | 1 + .../{test_logbook.py => logbook/test_init.py} | 0 tests/components/logentries/__init__.py | 1 + .../test_init.py} | 0 tests/components/logger/__init__.py | 1 + .../{test_logger.py => logger/test_init.py} | 0 tests/components/melissa/__init__.py | 1 + .../{test_melissa.py => melissa/test_init.py} | 0 tests/components/microsoft_face/__init__.py | 1 + .../test_init.py} | 0 tests/components/mqtt_eventstream/__init__.py | 1 + .../test_init.py} | 0 tests/components/mqtt_statestream/__init__.py | 1 + .../test_init.py} | 0 tests/components/mythicbeastsdns/__init__.py | 1 + .../test_init.py} | 0 tests/components/namecheapdns/__init__.py | 1 + .../test_init.py} | 0 tests/components/ness_alarm/__init__.py | 1 + .../test_init.py} | 0 tests/components/no_ip/__init__.py | 1 + .../{test_no_ip.py => no_ip/test_init.py} | 0 tests/components/nuheat/__init__.py | 1 + .../{test_nuheat.py => nuheat/test_init.py} | 0 tests/components/panel_custom/__init__.py | 1 + .../test_init.py} | 0 tests/components/panel_iframe/__init__.py | 1 + .../test_init.py} | 0 tests/components/pilight/__init__.py | 1 + .../{test_pilight.py => pilight/test_init.py} | 0 tests/components/plant/__init__.py | 1 + .../{test_plant.py => plant/test_init.py} | 0 tests/components/prometheus/__init__.py | 1 + .../test_init.py} | 0 tests/components/proximity/__init__.py | 1 + .../test_init.py} | 0 tests/components/python_script/__init__.py | 1 + .../test_init.py} | 0 tests/components/qwikswitch/__init__.py | 1 + .../test_init.py} | 0 .../components/remember_the_milk/__init__.py | 1 + .../test_init.py} | 0 tests/components/rest_command/__init__.py | 1 + .../test_init.py} | 0 tests/components/rflink/__init__.py | 1 + .../{test_rflink.py => rflink/test_init.py} | 0 tests/components/rfxtrx/__init__.py | 1 + .../{test_rfxtrx.py => rfxtrx/test_init.py} | 0 tests/components/ring/__init__.py | 1 + .../{test_ring.py => ring/test_init.py} | 0 .../components/rss_feed_template/__init__.py | 1 + .../test_init.py} | 0 tests/components/script/__init__.py | 1 + .../{test_script.py => script/test_init.py} | 0 tests/components/sensor/test_canary.py | 2 +- tests/components/sensor/test_rflink.py | 2 +- tests/components/sensor/test_ring.py | 2 +- tests/components/sensor/test_sleepiq.py | 2 +- tests/components/sensor/test_vultr.py | 2 +- tests/components/shell_command/__init__.py | 1 + .../test_init.py} | 0 tests/components/shopping_list/__init__.py | 1 + .../test_init.py} | 0 tests/components/sleepiq/__init__.py | 1 + .../{test_sleepiq.py => sleepiq/test_init.py} | 0 tests/components/snips/__init__.py | 1 + .../{test_snips.py => snips/test_init.py} | 0 tests/components/spaceapi/__init__.py | 1 + .../test_init.py} | 0 tests/components/spc/__init__.py | 1 + .../{test_spc.py => spc/test_init.py} | 0 tests/components/splunk/__init__.py | 1 + .../{test_splunk.py => splunk/test_init.py} | 0 tests/components/statsd/__init__.py | 1 + .../{test_statsd.py => statsd/test_init.py} | 0 tests/components/sun/__init__.py | 1 + .../{test_sun.py => sun/test_init.py} | 0 tests/components/switch/test_rflink.py | 2 +- tests/components/switch/test_vultr.py | 2 +- tests/components/system_log/__init__.py | 1 + .../test_init.py} | 0 tests/components/updater/__init__.py | 1 + .../{test_updater.py => updater/test_init.py} | 0 tests/components/vultr/__init__.py | 1 + .../{test_vultr.py => vultr/test_init.py} | 0 tests/components/wake_on_lan/__init__.py | 1 + .../test_init.py} | 0 tests/components/webhook/__init__.py | 1 + .../{test_webhook.py => webhook/test_init.py} | 0 tests/components/weblink/__init__.py | 1 + .../{test_weblink.py => weblink/test_init.py} | 0 275 files changed, 512 insertions(+), 684 deletions(-) create mode 100644 homeassistant/components/abode/services.yaml rename homeassistant/components/{alert.py => alert/__init__.py} (100%) create mode 100644 homeassistant/components/alert/services.yaml rename homeassistant/components/{api.py => api/__init__.py} (100%) create mode 100644 homeassistant/components/apple_tv/services.yaml rename homeassistant/components/{asuswrt.py => asuswrt/__init__.py} (100%) rename homeassistant/components/{browser.py => browser/__init__.py} (100%) rename homeassistant/components/{canary.py => canary/__init__.py} (100%) rename homeassistant/components/{cloudflare.py => cloudflare/__init__.py} (100%) rename homeassistant/components/{coinbase.py => coinbase/__init__.py} (100%) rename homeassistant/components/{configurator.py => configurator/__init__.py} (100%) rename homeassistant/components/{datadog.py => datadog/__init__.py} (100%) rename homeassistant/components/{demo.py => demo/__init__.py} (100%) rename homeassistant/components/{device_sun_light_trigger.py => device_sun_light_trigger/__init__.py} (100%) rename homeassistant/components/{discovery.py => discovery/__init__.py} (100%) rename homeassistant/components/{dominos.py => dominos/__init__.py} (100%) rename homeassistant/components/{downloader.py => downloader/__init__.py} (100%) rename homeassistant/components/{duckdns.py => duckdns/__init__.py} (100%) rename homeassistant/components/{dyson.py => dyson/__init__.py} (100%) create mode 100644 homeassistant/components/eight_sleep/services.yaml rename homeassistant/components/{emoncms_history.py => emoncms_history/__init__.py} (100%) rename homeassistant/components/{feedreader.py => feedreader/__init__.py} (100%) rename homeassistant/components/{ffmpeg.py => ffmpeg/__init__.py} (100%) create mode 100644 homeassistant/components/ffmpeg/services.yaml rename homeassistant/components/{folder_watcher.py => folder_watcher/__init__.py} (100%) rename homeassistant/components/{foursquare.py => foursquare/__init__.py} (100%) create mode 100644 homeassistant/components/foursquare/services.yaml rename homeassistant/components/{freedns.py => freedns/__init__.py} (100%) rename homeassistant/components/{goalfeed.py => goalfeed/__init__.py} (100%) rename homeassistant/components/{google_domains.py => google_domains/__init__.py} (100%) rename homeassistant/components/{graphite.py => graphite/__init__.py} (100%) rename homeassistant/components/{greeneye_monitor.py => greeneye_monitor/__init__.py} (100%) create mode 100644 homeassistant/components/hassio/services.yaml create mode 100644 homeassistant/components/hdmi_cec/services.yaml rename homeassistant/components/{history.py => history/__init__.py} (100%) rename homeassistant/components/{history_graph.py => history_graph/__init__.py} (100%) rename homeassistant/components/{idteck_prox.py => idteck_prox/__init__.py} (100%) rename homeassistant/components/{influxdb.py => influxdb/__init__.py} (100%) rename homeassistant/components/{input_boolean.py => input_boolean/__init__.py} (100%) create mode 100644 homeassistant/components/input_boolean/services.yaml rename homeassistant/components/{input_datetime.py => input_datetime/__init__.py} (100%) rename homeassistant/components/{input_number.py => input_number/__init__.py} (100%) create mode 100644 homeassistant/components/input_number/services.yaml rename homeassistant/components/{input_select.py => input_select/__init__.py} (100%) create mode 100644 homeassistant/components/input_select/services.yaml rename homeassistant/components/{input_text.py => input_text/__init__.py} (100%) create mode 100644 homeassistant/components/input_text/services.yaml rename homeassistant/components/{insteon_local.py => insteon_local/__init__.py} (100%) rename homeassistant/components/{insteon_plm.py => insteon_plm/__init__.py} (100%) rename homeassistant/components/{intent_script.py => intent_script/__init__.py} (100%) rename homeassistant/components/{introduction.py => introduction/__init__.py} (100%) rename homeassistant/components/{keyboard.py => keyboard/__init__.py} (100%) rename homeassistant/components/{keyboard_remote.py => keyboard_remote/__init__.py} (100%) create mode 100644 homeassistant/components/knx/services.yaml rename homeassistant/components/{lirc.py => lirc/__init__.py} (98%) rename homeassistant/components/{litejet.py => litejet/__init__.py} (100%) rename homeassistant/components/{logbook.py => logbook/__init__.py} (100%) rename homeassistant/components/{logentries.py => logentries/__init__.py} (100%) rename homeassistant/components/{logger.py => logger/__init__.py} (100%) create mode 100644 homeassistant/components/logger/services.yaml rename homeassistant/components/{map.py => map/__init__.py} (100%) rename homeassistant/components/{media_extractor.py => media_extractor/__init__.py} (100%) rename homeassistant/components/{melissa.py => melissa/__init__.py} (100%) rename homeassistant/components/{microsoft_face.py => microsoft_face/__init__.py} (100%) create mode 100644 homeassistant/components/microsoft_face/services.yaml create mode 100644 homeassistant/components/modbus/services.yaml rename homeassistant/components/{mqtt_eventstream.py => mqtt_eventstream/__init__.py} (100%) rename homeassistant/components/{mqtt_statestream.py => mqtt_statestream/__init__.py} (100%) rename homeassistant/components/{mycroft.py => mycroft/__init__.py} (100%) rename homeassistant/components/{mythicbeastsdns.py => mythicbeastsdns/__init__.py} (100%) rename homeassistant/components/{namecheapdns.py => namecheapdns/__init__.py} (100%) rename homeassistant/components/{ness_alarm.py => ness_alarm/__init__.py} (100%) rename homeassistant/components/{no_ip.py => no_ip/__init__.py} (100%) rename homeassistant/components/{nuheat.py => nuheat/__init__.py} (100%) rename homeassistant/components/{nuimo_controller.py => nuimo_controller/__init__.py} (100%) rename homeassistant/components/{panel_custom.py => panel_custom/__init__.py} (100%) rename homeassistant/components/{panel_iframe.py => panel_iframe/__init__.py} (100%) rename homeassistant/components/{plant.py => plant/__init__.py} (100%) rename homeassistant/components/{prometheus.py => prometheus/__init__.py} (100%) rename homeassistant/components/{proximity.py => proximity/__init__.py} (100%) rename homeassistant/components/{python_script.py => python_script/__init__.py} (100%) rename homeassistant/components/{rainbird.py => rainbird/__init__.py} (100%) rename homeassistant/components/{rest_command.py => rest_command/__init__.py} (100%) rename homeassistant/components/{rflink.py => rflink/__init__.py} (100%) create mode 100644 homeassistant/components/rflink/services.yaml rename homeassistant/components/{ring.py => ring/__init__.py} (100%) rename homeassistant/components/{route53.py => route53/__init__.py} (100%) rename homeassistant/components/{rss_feed_template.py => rss_feed_template/__init__.py} (100%) rename homeassistant/components/{script.py => script/__init__.py} (100%) rename homeassistant/components/{shell_command.py => shell_command/__init__.py} (100%) rename homeassistant/components/{shiftr.py => shiftr/__init__.py} (100%) rename homeassistant/components/{shopping_list.py => shopping_list/__init__.py} (100%) create mode 100644 homeassistant/components/shopping_list/services.yaml rename homeassistant/components/{sleepiq.py => sleepiq/__init__.py} (100%) rename homeassistant/components/{snips.py => snips/__init__.py} (100%) create mode 100644 homeassistant/components/snips/services.yaml rename homeassistant/components/{spaceapi.py => spaceapi/__init__.py} (100%) rename homeassistant/components/{spc.py => spc/__init__.py} (100%) rename homeassistant/components/{splunk.py => splunk/__init__.py} (100%) rename homeassistant/components/{statsd.py => statsd/__init__.py} (100%) rename homeassistant/components/{sun.py => sun/__init__.py} (100%) rename homeassistant/components/{thingspeak.py => thingspeak/__init__.py} (100%) rename homeassistant/components/{updater.py => updater/__init__.py} (100%) create mode 100644 homeassistant/components/verisure/services.yaml rename homeassistant/components/{vultr.py => vultr/__init__.py} (100%) rename homeassistant/components/{wake_on_lan.py => wake_on_lan/__init__.py} (100%) create mode 100644 homeassistant/components/wake_on_lan/services.yaml rename homeassistant/components/{watson_iot.py => watson_iot/__init__.py} (100%) rename homeassistant/components/{webhook.py => webhook/__init__.py} (100%) rename homeassistant/components/{weblink.py => weblink/__init__.py} (100%) create mode 100644 homeassistant/components/xiaomi_aqara/services.yaml rename homeassistant/components/{zeroconf.py => zeroconf/__init__.py} (100%) create mode 100644 tests/components/alert/__init__.py rename tests/components/{test_alert.py => alert/test_init.py} (100%) create mode 100644 tests/components/api/__init__.py rename tests/components/{test_api.py => api/test_init.py} (100%) create mode 100644 tests/components/canary/__init__.py rename tests/components/{test_canary.py => canary/test_init.py} (100%) create mode 100644 tests/components/configurator/__init__.py rename tests/components/{test_configurator.py => configurator/test_init.py} (100%) create mode 100644 tests/components/conversation/__init__.py rename tests/components/{test_conversation.py => conversation/test_init.py} (100%) create mode 100644 tests/components/datadog/__init__.py rename tests/components/{test_datadog.py => datadog/test_init.py} (100%) create mode 100644 tests/components/demo/__init__.py rename tests/components/{test_demo.py => demo/test_init.py} (100%) create mode 100644 tests/components/device_sun_light_trigger/__init__.py rename tests/components/{test_device_sun_light_trigger.py => device_sun_light_trigger/test_init.py} (100%) create mode 100644 tests/components/discovery/__init__.py rename tests/components/{test_discovery.py => discovery/test_init.py} (100%) create mode 100644 tests/components/duckdns/__init__.py rename tests/components/{test_duckdns.py => duckdns/test_init.py} (100%) create mode 100644 tests/components/dyson/__init__.py rename tests/components/{test_dyson.py => dyson/test_init.py} (100%) create mode 100644 tests/components/feedreader/__init__.py rename tests/components/{test_feedreader.py => feedreader/test_init.py} (100%) create mode 100644 tests/components/ffmpeg/__init__.py rename tests/components/{test_ffmpeg.py => ffmpeg/test_init.py} (100%) create mode 100644 tests/components/folder_watcher/__init__.py rename tests/components/{test_folder_watcher.py => folder_watcher/test_init.py} (100%) create mode 100644 tests/components/freedns/__init__.py rename tests/components/{test_freedns.py => freedns/test_init.py} (100%) rename tests/components/{test_google.py => google/test_init.py} (100%) create mode 100644 tests/components/google_domains/__init__.py rename tests/components/{test_google_domains.py => google_domains/test_init.py} (100%) create mode 100644 tests/components/graphite/__init__.py rename tests/components/{test_graphite.py => graphite/test_init.py} (100%) create mode 100644 tests/components/history/__init__.py rename tests/components/{test_history.py => history/test_init.py} (100%) create mode 100644 tests/components/history_graph/__init__.py rename tests/components/{test_history_graph.py => history_graph/test_init.py} (100%) create mode 100644 tests/components/huawei_lte/__init__.py rename tests/components/{test_huawei_lte.py => huawei_lte/test_init.py} (100%) create mode 100644 tests/components/influxdb/__init__.py rename tests/components/{test_influxdb.py => influxdb/test_init.py} (100%) create mode 100644 tests/components/init/__init__.py rename tests/components/{ => init}/test_init.py (100%) create mode 100644 tests/components/input_boolean/__init__.py rename tests/components/{test_input_boolean.py => input_boolean/test_init.py} (100%) create mode 100644 tests/components/input_datetime/__init__.py rename tests/components/{test_input_datetime.py => input_datetime/test_init.py} (100%) create mode 100644 tests/components/input_number/__init__.py rename tests/components/{test_input_number.py => input_number/test_init.py} (100%) create mode 100644 tests/components/input_select/__init__.py rename tests/components/{test_input_select.py => input_select/test_init.py} (100%) create mode 100644 tests/components/input_text/__init__.py rename tests/components/{test_input_text.py => input_text/test_init.py} (100%) create mode 100644 tests/components/intent_script/__init__.py rename tests/components/{test_intent_script.py => intent_script/test_init.py} (100%) create mode 100644 tests/components/introduction/__init__.py rename tests/components/{test_introduction.py => introduction/test_init.py} (100%) rename tests/components/{test_kira.py => kira/test_init.py} (100%) create mode 100644 tests/components/litejet/__init__.py rename tests/components/{test_litejet.py => litejet/test_init.py} (100%) create mode 100644 tests/components/logbook/__init__.py rename tests/components/{test_logbook.py => logbook/test_init.py} (100%) create mode 100644 tests/components/logentries/__init__.py rename tests/components/{test_logentries.py => logentries/test_init.py} (100%) create mode 100644 tests/components/logger/__init__.py rename tests/components/{test_logger.py => logger/test_init.py} (100%) create mode 100644 tests/components/melissa/__init__.py rename tests/components/{test_melissa.py => melissa/test_init.py} (100%) create mode 100644 tests/components/microsoft_face/__init__.py rename tests/components/{test_microsoft_face.py => microsoft_face/test_init.py} (100%) create mode 100644 tests/components/mqtt_eventstream/__init__.py rename tests/components/{test_mqtt_eventstream.py => mqtt_eventstream/test_init.py} (100%) create mode 100644 tests/components/mqtt_statestream/__init__.py rename tests/components/{test_mqtt_statestream.py => mqtt_statestream/test_init.py} (100%) create mode 100644 tests/components/mythicbeastsdns/__init__.py rename tests/components/{test_mythicbeastsdns.py => mythicbeastsdns/test_init.py} (100%) create mode 100644 tests/components/namecheapdns/__init__.py rename tests/components/{test_namecheapdns.py => namecheapdns/test_init.py} (100%) create mode 100644 tests/components/ness_alarm/__init__.py rename tests/components/{test_ness_alarm.py => ness_alarm/test_init.py} (100%) create mode 100644 tests/components/no_ip/__init__.py rename tests/components/{test_no_ip.py => no_ip/test_init.py} (100%) create mode 100644 tests/components/nuheat/__init__.py rename tests/components/{test_nuheat.py => nuheat/test_init.py} (100%) create mode 100644 tests/components/panel_custom/__init__.py rename tests/components/{test_panel_custom.py => panel_custom/test_init.py} (100%) create mode 100644 tests/components/panel_iframe/__init__.py rename tests/components/{test_panel_iframe.py => panel_iframe/test_init.py} (100%) create mode 100644 tests/components/pilight/__init__.py rename tests/components/{test_pilight.py => pilight/test_init.py} (100%) create mode 100644 tests/components/plant/__init__.py rename tests/components/{test_plant.py => plant/test_init.py} (100%) create mode 100644 tests/components/prometheus/__init__.py rename tests/components/{test_prometheus.py => prometheus/test_init.py} (100%) create mode 100644 tests/components/proximity/__init__.py rename tests/components/{test_proximity.py => proximity/test_init.py} (100%) create mode 100644 tests/components/python_script/__init__.py rename tests/components/{test_python_script.py => python_script/test_init.py} (100%) create mode 100644 tests/components/qwikswitch/__init__.py rename tests/components/{test_qwikswitch.py => qwikswitch/test_init.py} (100%) create mode 100644 tests/components/remember_the_milk/__init__.py rename tests/components/{test_remember_the_milk.py => remember_the_milk/test_init.py} (100%) create mode 100644 tests/components/rest_command/__init__.py rename tests/components/{test_rest_command.py => rest_command/test_init.py} (100%) create mode 100644 tests/components/rflink/__init__.py rename tests/components/{test_rflink.py => rflink/test_init.py} (100%) create mode 100644 tests/components/rfxtrx/__init__.py rename tests/components/{test_rfxtrx.py => rfxtrx/test_init.py} (100%) create mode 100644 tests/components/ring/__init__.py rename tests/components/{test_ring.py => ring/test_init.py} (100%) create mode 100644 tests/components/rss_feed_template/__init__.py rename tests/components/{test_rss_feed_template.py => rss_feed_template/test_init.py} (100%) create mode 100644 tests/components/script/__init__.py rename tests/components/{test_script.py => script/test_init.py} (100%) create mode 100644 tests/components/shell_command/__init__.py rename tests/components/{test_shell_command.py => shell_command/test_init.py} (100%) create mode 100644 tests/components/shopping_list/__init__.py rename tests/components/{test_shopping_list.py => shopping_list/test_init.py} (100%) create mode 100644 tests/components/sleepiq/__init__.py rename tests/components/{test_sleepiq.py => sleepiq/test_init.py} (100%) create mode 100644 tests/components/snips/__init__.py rename tests/components/{test_snips.py => snips/test_init.py} (100%) create mode 100644 tests/components/spaceapi/__init__.py rename tests/components/{test_spaceapi.py => spaceapi/test_init.py} (100%) create mode 100644 tests/components/spc/__init__.py rename tests/components/{test_spc.py => spc/test_init.py} (100%) create mode 100644 tests/components/splunk/__init__.py rename tests/components/{test_splunk.py => splunk/test_init.py} (100%) create mode 100644 tests/components/statsd/__init__.py rename tests/components/{test_statsd.py => statsd/test_init.py} (100%) create mode 100644 tests/components/sun/__init__.py rename tests/components/{test_sun.py => sun/test_init.py} (100%) create mode 100644 tests/components/system_log/__init__.py rename tests/components/{test_system_log.py => system_log/test_init.py} (100%) create mode 100644 tests/components/updater/__init__.py rename tests/components/{test_updater.py => updater/test_init.py} (100%) create mode 100644 tests/components/vultr/__init__.py rename tests/components/{test_vultr.py => vultr/test_init.py} (100%) create mode 100644 tests/components/wake_on_lan/__init__.py rename tests/components/{test_wake_on_lan.py => wake_on_lan/test_init.py} (100%) create mode 100644 tests/components/webhook/__init__.py rename tests/components/{test_webhook.py => webhook/test_init.py} (100%) create mode 100644 tests/components/weblink/__init__.py rename tests/components/{test_weblink.py => weblink/test_init.py} (100%) diff --git a/.coveragerc b/.coveragerc index e7454ccfa9c..488ea50298a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -10,11 +10,8 @@ omit = homeassistant/helpers/signal.py # omit pieces of code that rely on external devices being present - homeassistant/components/ads/* - homeassistant/components/ihc/* - homeassistant/components/knx/* - homeassistant/components/lcn/* homeassistant/components/abode/* + homeassistant/components/ads/* homeassistant/components/air_quality/nilu.py homeassistant/components/air_quality/opensensemap.py homeassistant/components/alarm_control_panel/alarmdotcom.py @@ -30,7 +27,7 @@ omit = homeassistant/components/amcrest/* homeassistant/components/android_ip_webcam/* homeassistant/components/apcupsd/* - homeassistant/components/apiai.py + homeassistant/components/apiai/* homeassistant/components/apple_tv/* homeassistant/components/aqualogic/* homeassistant/components/arduino/* @@ -52,7 +49,7 @@ omit = homeassistant/components/blink/* homeassistant/components/bloomsky/* homeassistant/components/bmw_connected_drive/* - homeassistant/components/browser.py + homeassistant/components/browser/* homeassistant/components/calendar/caldav.py homeassistant/components/calendar/todoist.py homeassistant/components/camera/bloomsky.py @@ -85,8 +82,8 @@ omit = homeassistant/components/climate/touchline.py homeassistant/components/climate/venstar.py homeassistant/components/climate/zhong_hong.py - homeassistant/components/cloudflare.py - homeassistant/components/coinbase.py + homeassistant/components/cloudflare/* + homeassistant/components/coinbase/* homeassistant/components/comfoconnect/* homeassistant/components/cover/aladdin_connect.py homeassistant/components/cover/brunt.py @@ -139,10 +136,10 @@ omit = homeassistant/components/device_tracker/trackr.py homeassistant/components/device_tracker/ubus.py homeassistant/components/digital_ocean/* - homeassistant/components/dominos.py + homeassistant/components/dominos/* homeassistant/components/doorbird/* homeassistant/components/dovado/* - homeassistant/components/downloader.py + homeassistant/components/downloader/* homeassistant/components/dweet/* homeassistant/components/ecoal_boiler/* homeassistant/components/ecobee/* @@ -151,7 +148,7 @@ omit = homeassistant/components/egardia/* homeassistant/components/eight_sleep/* homeassistant/components/elkm1/* - homeassistant/components/emoncms_history.py + homeassistant/components/emoncms_history/* homeassistant/components/emulated_hue/upnp.py homeassistant/components/enocean/* homeassistant/components/envisalink/* @@ -167,15 +164,15 @@ omit = homeassistant/components/fan/wemo.py homeassistant/components/fastdotcom/* homeassistant/components/fibaro/* - homeassistant/components/folder_watcher.py - homeassistant/components/foursquare.py + homeassistant/components/folder_watcher/* + homeassistant/components/foursquare/* homeassistant/components/freebox/* homeassistant/components/fritzbox/* homeassistant/components/gc100/* - homeassistant/components/goalfeed.py + homeassistant/components/goalfeed/* homeassistant/components/google/* homeassistant/components/googlehome/* - homeassistant/components/greeneye_monitor.py + homeassistant/components/greeneye_monitor/* homeassistant/components/habitica/* homeassistant/components/hangouts/__init__.py homeassistant/components/hangouts/* @@ -191,26 +188,29 @@ omit = homeassistant/components/homeworks/* homeassistant/components/huawei_lte/* homeassistant/components/hydrawise/* - homeassistant/components/idteck_prox.py + homeassistant/components/idteck_prox/* homeassistant/components/ifttt/* + homeassistant/components/ihc/* homeassistant/components/image_processing/dlib_face_detect.py homeassistant/components/image_processing/dlib_face_identify.py homeassistant/components/image_processing/qrcode.py homeassistant/components/image_processing/seven_segments.py homeassistant/components/image_processing/tensorflow.py - homeassistant/components/insteon_local.py - homeassistant/components/insteon_plm.py + homeassistant/components/insteon_local/* + homeassistant/components/insteon_plm/* homeassistant/components/insteon/* homeassistant/components/ios/* homeassistant/components/iota/* homeassistant/components/isy994/* homeassistant/components/joaoapps_join/* homeassistant/components/juicenet/* - homeassistant/components/keyboard_remote.py - homeassistant/components/keyboard.py + homeassistant/components/keyboard_remote/* + homeassistant/components/keyboard/* homeassistant/components/kira/* + homeassistant/components/knx/* homeassistant/components/konnected/* homeassistant/components/lametric/* + homeassistant/components/lcn/* homeassistant/components/lifx/* homeassistant/components/light/avion.py homeassistant/components/light/blinksticklight.py @@ -244,7 +244,7 @@ omit = homeassistant/components/light/zengge.py homeassistant/components/lightwave/* homeassistant/components/linode/* - homeassistant/components/lirc.py + homeassistant/components/lirc/* homeassistant/components/lock/kiwi.py homeassistant/components/lock/lockitron.py homeassistant/components/lock/nello.py @@ -257,10 +257,10 @@ omit = homeassistant/components/lutron/* homeassistant/components/mailbox/asterisk_cdr.py homeassistant/components/mailgun/notify.py - homeassistant/components/map.py + homeassistant/components/map/* homeassistant/components/matrix/* homeassistant/components/maxcube/* - homeassistant/components/media_extractor.py + homeassistant/components/media_extractor/* homeassistant/components/media_player/anthemav.py homeassistant/components/media_player/aquostv.py homeassistant/components/media_player/bluesound.py @@ -317,7 +317,7 @@ omit = homeassistant/components/mochad/* homeassistant/components/modbus/* homeassistant/components/mychevy/* - homeassistant/components/mycroft.py + homeassistant/components/mycroft/* homeassistant/components/mysensors/* homeassistant/components/neato/* homeassistant/components/nest/* @@ -364,7 +364,7 @@ omit = homeassistant/components/notify/twilio_sms.py homeassistant/components/notify/twitter.py homeassistant/components/notify/xmpp.py - homeassistant/components/nuimo_controller.py + homeassistant/components/nuimo_controller/* homeassistant/components/octoprint/* homeassistant/components/opencv/* homeassistant/components/opentherm_gw/* @@ -374,10 +374,10 @@ omit = homeassistant/components/pilight/* homeassistant/components/plum_lightpad/* homeassistant/components/point/* - homeassistant/components/prometheus.py + homeassistant/components/prometheus/* homeassistant/components/qwikswitch/* homeassistant/components/rachio/* - homeassistant/components/rainbird.py + homeassistant/components/rainbird/* homeassistant/components/raincloud/* homeassistant/components/rainmachine/__init__.py homeassistant/components/rainmachine/binary_sensor.py @@ -390,7 +390,7 @@ omit = homeassistant/components/remote/itach.py homeassistant/components/rfxtrx/* homeassistant/components/roku/* - homeassistant/components/route53.py + homeassistant/components/route53/* homeassistant/components/rpi_gpio/* homeassistant/components/rpi_pfio/* homeassistant/components/sabnzbd/* @@ -572,15 +572,15 @@ omit = homeassistant/components/sensor/xbox_live.py homeassistant/components/sensor/zamg.py homeassistant/components/sensor/zestimate.py - homeassistant/components/shiftr.py + homeassistant/components/shiftr/* homeassistant/components/simplisafe/__init__.py homeassistant/components/simplisafe/alarm_control_panel.py homeassistant/components/sisyphus/* homeassistant/components/skybell/* homeassistant/components/smappee/* homeassistant/components/sonos/* + homeassistant/components/spc/* homeassistant/components/speedtestdotnet/* - homeassistant/components/spc.py homeassistant/components/spider/* homeassistant/components/switch/acer_projector.py homeassistant/components/switch/anel_pwrctrl.py @@ -616,7 +616,7 @@ omit = homeassistant/components/tellstick/* homeassistant/components/tesla/* homeassistant/components/thethingsnetwork/* - homeassistant/components/thingspeak.py + homeassistant/components/thingspeak/* homeassistant/components/thinkingcleaner/* homeassistant/components/tibber/* homeassistant/components/toon/* @@ -640,7 +640,7 @@ omit = homeassistant/components/w800rf32/* homeassistant/components/water_heater/econet.py homeassistant/components/waterfurnace/* - homeassistant/components/watson_iot.py + homeassistant/components/watson_iot/* homeassistant/components/weather/bom.py homeassistant/components/weather/buienradar.py homeassistant/components/weather/darksky.py @@ -655,7 +655,7 @@ omit = homeassistant/components/xiaomi_aqara/* homeassistant/components/xiaomi_miio/* homeassistant/components/zabbix/* - homeassistant/components/zeroconf.py + homeassistant/components/zeroconf/* homeassistant/components/zha/__init__.py homeassistant/components/zha/api.py homeassistant/components/zha/binary_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 4f0727e1101..b9f5cec0d64 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -7,34 +7,34 @@ setup.py @home-assistant/core homeassistant/*.py @home-assistant/core homeassistant/helpers/* @home-assistant/core homeassistant/util/* @home-assistant/core -homeassistant/components/api.py @home-assistant/core +homeassistant/components/api/* @home-assistant/core homeassistant/components/auth/* @home-assistant/core homeassistant/components/automation/* @home-assistant/core homeassistant/components/cloud/* @home-assistant/core homeassistant/components/config/* @home-assistant/core -homeassistant/components/configurator.py @home-assistant/core +homeassistant/components/configurator/* @home-assistant/core homeassistant/components/conversation/* @home-assistant/core homeassistant/components/frontend/* @home-assistant/core homeassistant/components/group/* @home-assistant/core -homeassistant/components/history.py @home-assistant/core +homeassistant/components/history/* @home-assistant/core homeassistant/components/http/* @home-assistant/core homeassistant/components/input_*.py @home-assistant/core -homeassistant/components/introduction.py @home-assistant/core -homeassistant/components/logger.py @home-assistant/core +homeassistant/components/introduction/* @home-assistant/core +homeassistant/components/logger/* @home-assistant/core homeassistant/components/lovelace/* @home-assistant/core homeassistant/components/mqtt/* @home-assistant/core -homeassistant/components/panel_custom.py @home-assistant/core -homeassistant/components/panel_iframe.py @home-assistant/core +homeassistant/components/panel_custom/* @home-assistant/core +homeassistant/components/panel_iframe/* @home-assistant/core homeassistant/components/onboarding/* @home-assistant/core homeassistant/components/persistent_notification/* @home-assistant/core homeassistant/components/scene/__init__.py @home-assistant/core homeassistant/components/scene/hass.py @home-assistant/core -homeassistant/components/script.py @home-assistant/core -homeassistant/components/shell_command.py @home-assistant/core -homeassistant/components/sun.py @home-assistant/core -homeassistant/components/updater.py @home-assistant/core +homeassistant/components/script/* @home-assistant/core +homeassistant/components/shell_command/* @home-assistant/core +homeassistant/components/sun/* @home-assistant/core +homeassistant/components/updater/* @home-assistant/core homeassistant/components/weblink/* @home-assistant/core -homeassistant/components/websocket_api.py @home-assistant/core +homeassistant/components/websocket_api/* @home-assistant/core homeassistant/components/zone/* @home-assistant/core # Home Assistant Developer Teams @@ -67,8 +67,8 @@ homeassistant/components/device_tracker/quantum_gateway.py @cisasteelersfan homeassistant/components/device_tracker/tile.py @bachya homeassistant/components/device_tracker/traccar.py @ludeeus homeassistant/components/device_tracker/bt_smarthub.py @jxwolstenholme -homeassistant/components/history_graph.py @andrey-git -homeassistant/components/influx.py @fabaff +homeassistant/components/history_graph/* @andrey-git +homeassistant/components/influx/* @fabaff homeassistant/components/light/lifx_legacy.py @amelchio homeassistant/components/light/tplink.py @rytilahti homeassistant/components/light/yeelight.py @rytilahti @@ -84,7 +84,7 @@ homeassistant/components/media_player/mpd.py @fabaff homeassistant/components/media_player/sonos.py @amelchio homeassistant/components/media_player/xiaomi_tv.py @fattdev homeassistant/components/media_player/yamaha_musiccast.py @jalmeroth -homeassistant/components/no_ip.py @fabaff +homeassistant/components/no_ip/* @fabaff homeassistant/components/notify/file.py @fabaff homeassistant/components/notify/flock.py @fabaff homeassistant/components/notify/instapush.py @fabaff @@ -93,7 +93,7 @@ homeassistant/components/notify/smtp.py @fabaff homeassistant/components/notify/syslog.py @fabaff homeassistant/components/notify/xmpp.py @fabaff homeassistant/components/notify/yessssms.py @flowolf -homeassistant/components/plant.py @ChristianKuehnel +homeassistant/components/plant/* @ChristianKuehnel homeassistant/components/remote/harmony.py @ehendrix23 homeassistant/components/scene/lifx_cloud.py @amelchio homeassistant/components/sensor/airvisual.py @bachya @@ -138,8 +138,8 @@ homeassistant/components/sensor/time_data.py @fabaff homeassistant/components/sensor/version.py @fabaff homeassistant/components/sensor/waqi.py @andrey-git homeassistant/components/sensor/worldclock.py @fabaff -homeassistant/components/shiftr.py @fabaff -homeassistant/components/spaceapi.py @fabaff +homeassistant/components/shiftr/* @fabaff +homeassistant/components/spaceapi/* @fabaff homeassistant/components/switch/switchbot.py @danielhiversen homeassistant/components/switch/switchmate.py @danielhiversen homeassistant/components/switch/tplink.py @rytilahti @@ -149,11 +149,11 @@ homeassistant/components/weather/darksky.py @fabaff homeassistant/components/weather/demo.py @fabaff homeassistant/components/weather/met.py @danielhiversen homeassistant/components/weather/openweathermap.py @fabaff -homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi +homeassistant/components/xiaomi_aqara/* @danielhiversen @syssi # A homeassistant/components/ambient_station/* @bachya -homeassistant/components/arduino.py @fabaff +homeassistant/components/arduino/* @fabaff homeassistant/components/*/arduino.py @fabaff homeassistant/components/*/arest.py @fabaff homeassistant/components/*/axis.py @kane610 @@ -161,64 +161,64 @@ homeassistant/components/*/axis.py @kane610 # B homeassistant/components/blink/* @fronzbot homeassistant/components/*/blink.py @fronzbot -homeassistant/components/bmw_connected_drive.py @ChristianKuehnel +homeassistant/components/bmw_connected_drive/* @ChristianKuehnel homeassistant/components/*/bmw_connected_drive.py @ChristianKuehnel homeassistant/components/*/broadlink.py @danielhiversen # C -homeassistant/components/cloudflare.py @ludeeus +homeassistant/components/cloudflare/* @ludeeus homeassistant/components/counter/* @fabaff # D -homeassistant/components/daikin.py @fredrike @rofrantz +homeassistant/components/daikin/* @fredrike @rofrantz homeassistant/components/*/daikin.py @fredrike @rofrantz homeassistant/components/*/deconz.py @kane610 -homeassistant/components/digital_ocean.py @fabaff +homeassistant/components/digital_ocean/* @fabaff homeassistant/components/*/digital_ocean.py @fabaff -homeassistant/components/dweet.py @fabaff +homeassistant/components/dweet/* @fabaff homeassistant/components/*/dweet.py @fabaff # E -homeassistant/components/ecovacs.py @OverloadUT +homeassistant/components/ecovacs/* @OverloadUT homeassistant/components/*/ecovacs.py @OverloadUT homeassistant/components/*/edp_redy.py @abmantis -homeassistant/components/edp_redy.py @abmantis -homeassistant/components/eight_sleep.py @mezz64 +homeassistant/components/edp_redy/* @abmantis +homeassistant/components/eight_sleep/* @mezz64 homeassistant/components/*/eight_sleep.py @mezz64 homeassistant/components/esphome/*.py @OttoWinter # G -homeassistant/components/googlehome.py @ludeeus +homeassistant/components/googlehome/* @ludeeus homeassistant/components/*/googlehome.py @ludeeus # H -homeassistant/components/hive.py @Rendili @KJonline +homeassistant/components/hive/* @Rendili @KJonline homeassistant/components/*/hive.py @Rendili @KJonline homeassistant/components/homekit/* @cdce8p -homeassistant/components/huawei_lte.py @scop +homeassistant/components/huawei_lte/* @scop homeassistant/components/*/huawei_lte.py @scop # K -homeassistant/components/knx.py @Julius2342 +homeassistant/components/knx/* @Julius2342 homeassistant/components/*/knx.py @Julius2342 -homeassistant/components/konnected.py @heythisisnate +homeassistant/components/konnected/* @heythisisnate homeassistant/components/*/konnected.py @heythisisnate # L -homeassistant/components/lifx.py @amelchio +homeassistant/components/lifx/* @amelchio homeassistant/components/*/lifx.py @amelchio homeassistant/components/luftdaten/* @fabaff homeassistant/components/*/luftdaten.py @fabaff # M -homeassistant/components/matrix.py @tinloaf +homeassistant/components/matrix/* @tinloaf homeassistant/components/*/matrix.py @tinloaf -homeassistant/components/melissa.py @kennedyshead +homeassistant/components/melissa/* @kennedyshead homeassistant/components/*/melissa.py @kennedyshead homeassistant/components/*/mystrom.py @fabaff # N -homeassistant/components/ness_alarm.py @nickw444 +homeassistant/components/ness_alarm/* @nickw444 homeassistant/components/*/ness_alarm.py @nickw444 # O @@ -229,7 +229,7 @@ homeassistant/components/point/* @fredrike homeassistant/components/*/point.py @fredrike # Q -homeassistant/components/qwikswitch.py @kellerza +homeassistant/components/qwikswitch/* @kellerza homeassistant/components/*/qwikswitch.py @kellerza # R @@ -243,13 +243,13 @@ homeassistant/components/smartthings/* @andrewsayre homeassistant/components/spider/* @peternijssen # T -homeassistant/components/tahoma.py @philklei +homeassistant/components/tahoma/* @philklei homeassistant/components/*/tahoma.py @philklei homeassistant/components/tellduslive/*.py @fredrike homeassistant/components/*/tellduslive.py @fredrike -homeassistant/components/tesla.py @zabuldon +homeassistant/components/tesla/* @zabuldon homeassistant/components/*/tesla.py @zabuldon -homeassistant/components/thethingsnetwork.py @fabaff +homeassistant/components/thethingsnetwork/* @fabaff homeassistant/components/*/thethingsnetwork.py @fabaff homeassistant/components/tibber/* @danielhiversen homeassistant/components/*/tibber.py @danielhiversen @@ -257,17 +257,17 @@ homeassistant/components/tradfri/* @ggravlingen homeassistant/components/*/tradfri.py @ggravlingen # U -homeassistant/components/unifi.py @kane610 +homeassistant/components/unifi/* @kane610 homeassistant/components/switch/unifi.py @kane610 -homeassistant/components/upcloud.py @scop +homeassistant/components/upcloud/* @scop homeassistant/components/*/upcloud.py @scop # V -homeassistant/components/velux.py @Julius2342 +homeassistant/components/velux/* @Julius2342 homeassistant/components/*/velux.py @Julius2342 # W -homeassistant/components/wemo.py @sqldiablo +homeassistant/components/wemo/* @sqldiablo homeassistant/components/*/wemo.py @sqldiablo # X diff --git a/homeassistant/components/abode/services.yaml b/homeassistant/components/abode/services.yaml new file mode 100644 index 00000000000..ad0bb076d90 --- /dev/null +++ b/homeassistant/components/abode/services.yaml @@ -0,0 +1,13 @@ +capture_image: + description: Request a new image capture from a camera device. + fields: + entity_id: {description: Entity id of the camera to request an image., example: camera.downstairs_motion_camera} +change_setting: + description: Change an Abode system setting. + fields: + setting: {description: Setting to change., example: beeper_mute} + value: {description: Value of the setting., example: '1'} +trigger_quick_action: + description: Trigger an Abode quick action. + fields: + entity_id: {description: Entity id of the quick action to trigger., example: binary_sensor.home_quick_action} diff --git a/homeassistant/components/alert.py b/homeassistant/components/alert/__init__.py similarity index 100% rename from homeassistant/components/alert.py rename to homeassistant/components/alert/__init__.py diff --git a/homeassistant/components/alert/services.yaml b/homeassistant/components/alert/services.yaml new file mode 100644 index 00000000000..1cdd1f02e7e --- /dev/null +++ b/homeassistant/components/alert/services.yaml @@ -0,0 +1,12 @@ +toggle: + description: Toggle alert's notifications. + fields: + entity_id: {description: Name of the alert to toggle., example: alert.garage_door_open} +turn_off: + description: Silence alert's notifications. + fields: + entity_id: {description: Name of the alert to silence., example: alert.garage_door_open} +turn_on: + description: Reset alert's notifications. + fields: + entity_id: {description: Name of the alert to reset., example: alert.garage_door_open} diff --git a/homeassistant/components/api.py b/homeassistant/components/api/__init__.py similarity index 100% rename from homeassistant/components/api.py rename to homeassistant/components/api/__init__.py diff --git a/homeassistant/components/apple_tv/services.yaml b/homeassistant/components/apple_tv/services.yaml new file mode 100644 index 00000000000..01e26a5630b --- /dev/null +++ b/homeassistant/components/apple_tv/services.yaml @@ -0,0 +1,5 @@ +apple_tv_authenticate: + description: Start AirPlay device authentication. + fields: + entity_id: {description: Name(s) of entities to authenticate with., example: media_player.apple_tv} +apple_tv_scan: {description: Scan for Apple TV devices.} diff --git a/homeassistant/components/asuswrt.py b/homeassistant/components/asuswrt/__init__.py similarity index 100% rename from homeassistant/components/asuswrt.py rename to homeassistant/components/asuswrt/__init__.py diff --git a/homeassistant/components/browser.py b/homeassistant/components/browser/__init__.py similarity index 100% rename from homeassistant/components/browser.py rename to homeassistant/components/browser/__init__.py diff --git a/homeassistant/components/canary.py b/homeassistant/components/canary/__init__.py similarity index 100% rename from homeassistant/components/canary.py rename to homeassistant/components/canary/__init__.py diff --git a/homeassistant/components/cloudflare.py b/homeassistant/components/cloudflare/__init__.py similarity index 100% rename from homeassistant/components/cloudflare.py rename to homeassistant/components/cloudflare/__init__.py diff --git a/homeassistant/components/coinbase.py b/homeassistant/components/coinbase/__init__.py similarity index 100% rename from homeassistant/components/coinbase.py rename to homeassistant/components/coinbase/__init__.py diff --git a/homeassistant/components/configurator.py b/homeassistant/components/configurator/__init__.py similarity index 100% rename from homeassistant/components/configurator.py rename to homeassistant/components/configurator/__init__.py diff --git a/homeassistant/components/datadog.py b/homeassistant/components/datadog/__init__.py similarity index 100% rename from homeassistant/components/datadog.py rename to homeassistant/components/datadog/__init__.py diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo/__init__.py similarity index 100% rename from homeassistant/components/demo.py rename to homeassistant/components/demo/__init__.py diff --git a/homeassistant/components/device_sun_light_trigger.py b/homeassistant/components/device_sun_light_trigger/__init__.py similarity index 100% rename from homeassistant/components/device_sun_light_trigger.py rename to homeassistant/components/device_sun_light_trigger/__init__.py diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery/__init__.py similarity index 100% rename from homeassistant/components/discovery.py rename to homeassistant/components/discovery/__init__.py diff --git a/homeassistant/components/dominos.py b/homeassistant/components/dominos/__init__.py similarity index 100% rename from homeassistant/components/dominos.py rename to homeassistant/components/dominos/__init__.py diff --git a/homeassistant/components/downloader.py b/homeassistant/components/downloader/__init__.py similarity index 100% rename from homeassistant/components/downloader.py rename to homeassistant/components/downloader/__init__.py diff --git a/homeassistant/components/duckdns.py b/homeassistant/components/duckdns/__init__.py similarity index 100% rename from homeassistant/components/duckdns.py rename to homeassistant/components/duckdns/__init__.py diff --git a/homeassistant/components/dyson.py b/homeassistant/components/dyson/__init__.py similarity index 100% rename from homeassistant/components/dyson.py rename to homeassistant/components/dyson/__init__.py diff --git a/homeassistant/components/eight_sleep/services.yaml b/homeassistant/components/eight_sleep/services.yaml new file mode 100644 index 00000000000..db7690730dd --- /dev/null +++ b/homeassistant/components/eight_sleep/services.yaml @@ -0,0 +1,6 @@ +heat_set: + description: Set heating level for eight sleep. + fields: + duration: {description: Duration to heat at the target level in seconds., example: 3600} + entity_id: {description: Entity id of the bed state to adjust., example: sensor.eight_left_bed_state} + target: {description: Target heating level from 0-100., example: 35} diff --git a/homeassistant/components/emoncms_history.py b/homeassistant/components/emoncms_history/__init__.py similarity index 100% rename from homeassistant/components/emoncms_history.py rename to homeassistant/components/emoncms_history/__init__.py diff --git a/homeassistant/components/feedreader.py b/homeassistant/components/feedreader/__init__.py similarity index 100% rename from homeassistant/components/feedreader.py rename to homeassistant/components/feedreader/__init__.py diff --git a/homeassistant/components/ffmpeg.py b/homeassistant/components/ffmpeg/__init__.py similarity index 100% rename from homeassistant/components/ffmpeg.py rename to homeassistant/components/ffmpeg/__init__.py diff --git a/homeassistant/components/ffmpeg/services.yaml b/homeassistant/components/ffmpeg/services.yaml new file mode 100644 index 00000000000..05c9c4fb180 --- /dev/null +++ b/homeassistant/components/ffmpeg/services.yaml @@ -0,0 +1,15 @@ +restart: + description: Send a restart command to a ffmpeg based sensor. + fields: + entity_id: {description: Name(s) of entities that will restart. Platform dependent., + example: binary_sensor.ffmpeg_noise} +start: + description: Send a start command to a ffmpeg based sensor. + fields: + entity_id: {description: Name(s) of entities that will start. Platform dependent., + example: binary_sensor.ffmpeg_noise} +stop: + description: Send a stop command to a ffmpeg based sensor. + fields: + entity_id: {description: Name(s) of entities that will stop. Platform dependent., + example: binary_sensor.ffmpeg_noise} diff --git a/homeassistant/components/folder_watcher.py b/homeassistant/components/folder_watcher/__init__.py similarity index 100% rename from homeassistant/components/folder_watcher.py rename to homeassistant/components/folder_watcher/__init__.py diff --git a/homeassistant/components/foursquare.py b/homeassistant/components/foursquare/__init__.py similarity index 100% rename from homeassistant/components/foursquare.py rename to homeassistant/components/foursquare/__init__.py diff --git a/homeassistant/components/foursquare/services.yaml b/homeassistant/components/foursquare/services.yaml new file mode 100644 index 00000000000..3d15a9583f6 --- /dev/null +++ b/homeassistant/components/foursquare/services.yaml @@ -0,0 +1,29 @@ +checkin: + description: Check a user into a Foursquare venue. + fields: + alt: {description: 'Altitude of the user''s location, in meters. [Optional]', + example: 0} + altAcc: {description: 'Vertical accuracy of the user''s location, in meters.', + example: 1} + broadcast: {description: 'Who to broadcast this check-in to. Accepts a comma-delimited + list of values: private (off the grid) or public (share with friends), facebook + share on facebook, twitter share on twitter, followers share with followers + (celebrity mode users only), If no valid value is found, the default is public. + [Optional]', example: 'public,twitter'} + eventId: {description: 'The event the user is checking in to. [Optional]', example: UHR8THISVNT} + ll: {description: 'Latitude and longitude of the user''s location. Only specify + this field if you have a GPS or other device reported location for the user + at the time of check-in. [Optional]', example: '33.7,44.2'} + llAcc: {description: 'Accuracy of the user''s latitude and longitude, in meters. + [Optional]', example: 1} + mentions: {description: 'Mentions in your check-in. This parameter is a semicolon-delimited + list of mentions. A single mention is of the form "start,end,userid", where + start is the index of the first character in the shout representing the mention, + end is the index of the first character in the shout after the mention, and + userid is the userid of the user being mentioned. If userid is prefixed with + "fbu-", this indicates a Facebook userid that is being mention. Character + indices in shouts are 0-based. [Optional]', example: '5,10,HZXXY3Y;15,20,GZYYZ3Z;25,30,fbu-GZXY13Y'} + shout: {description: 'A message about your check-in. The maximum length of this + field is 140 characters. [Optional]', example: There are crayons! Crayons!} + venueId: {description: 'The Foursquare venue where the user is checking in. [Required]', + example: IHR8THISVNU} diff --git a/homeassistant/components/freedns.py b/homeassistant/components/freedns/__init__.py similarity index 100% rename from homeassistant/components/freedns.py rename to homeassistant/components/freedns/__init__.py diff --git a/homeassistant/components/goalfeed.py b/homeassistant/components/goalfeed/__init__.py similarity index 100% rename from homeassistant/components/goalfeed.py rename to homeassistant/components/goalfeed/__init__.py diff --git a/homeassistant/components/google_domains.py b/homeassistant/components/google_domains/__init__.py similarity index 100% rename from homeassistant/components/google_domains.py rename to homeassistant/components/google_domains/__init__.py diff --git a/homeassistant/components/graphite.py b/homeassistant/components/graphite/__init__.py similarity index 100% rename from homeassistant/components/graphite.py rename to homeassistant/components/graphite/__init__.py diff --git a/homeassistant/components/greeneye_monitor.py b/homeassistant/components/greeneye_monitor/__init__.py similarity index 100% rename from homeassistant/components/greeneye_monitor.py rename to homeassistant/components/greeneye_monitor/__init__.py diff --git a/homeassistant/components/hassio/services.yaml b/homeassistant/components/hassio/services.yaml new file mode 100644 index 00000000000..33574c5dd71 --- /dev/null +++ b/homeassistant/components/hassio/services.yaml @@ -0,0 +1,37 @@ +addon_install: + description: Install a HassIO docker addon. + fields: + addon: {description: Name of addon., example: smb_config} + version: {description: Optional or it will be use the latest version., example: '0.2'} +addon_start: + description: Start a HassIO docker addon. + fields: + addon: {description: Name of addon., example: smb_config} +addon_stop: + description: Stop a HassIO docker addon. + fields: + addon: {description: Name of addon., example: smb_config} +addon_uninstall: + description: Uninstall a HassIO docker addon. + fields: + addon: {description: Name of addon., example: smb_config} +addon_update: + description: Update a HassIO docker addon. + fields: + addon: {description: Name of addon., example: smb_config} + version: {description: Optional or it will be use the latest version., example: '0.2'} +homeassistant_update: + description: Update HomeAssistant docker image. + fields: + version: {description: Optional or it will be use the latest version., example: 0.40.1} +host_reboot: {description: Reboot host computer.} +host_shutdown: {description: Poweroff host computer.} +host_update: + description: Update host computer. + fields: + version: {description: Optional or it will be use the latest version., example: '0.3'} +supervisor_reload: {description: Reload HassIO supervisor addons/updates/configs.} +supervisor_update: + description: Update HassIO supervisor. + fields: + version: {description: Optional or it will be use the latest version., example: '0.3'} diff --git a/homeassistant/components/hdmi_cec/services.yaml b/homeassistant/components/hdmi_cec/services.yaml new file mode 100644 index 00000000000..bb0f5f932ae --- /dev/null +++ b/homeassistant/components/hdmi_cec/services.yaml @@ -0,0 +1,32 @@ +power_on: {description: Power on all devices which supports it.} +select_device: + description: Select HDMI device. + fields: + device: {description: 'Address of device to select. Can be entity_id, physical + address or alias from confuguration.', example: '"switch.hdmi_1" or "1.1.0.0" + or "01:10"'} +send_command: + description: Sends CEC command into HDMI CEC capable adapter. + fields: + att: + description: Optional parameters. + example: [0, 2] + cmd: {description: 'Command itself. Could be decimal number or string with hexadeximal + notation: "0x10".', example: 144 or "0x90"} + dst: {description: 'Destination for command. Could be decimal number or string + with hexadeximal notation: "0x10".', example: 5 or "0x5"} + raw: {description: 'Raw CEC command in format "00:00:00:00" where first two digits + are source and destination, second byte is command and optional other bytes + are command parameters. If raw command specified, other params are ignored.', + example: '"10:36"'} + src: {desctiption: 'Source of command. Could be decimal number or string with + hexadeximal notation: "0x10".', example: 12 or "0xc"} +standby: {description: Standby all devices which supports it.} +update: {description: Update devices state from network.} +volume: + description: Increase or decrease volume of system. + fields: + down: {description: Decreases volume x levels., example: 3} + mute: {description: 'Mutes audio system. Value should be on, off or toggle.', + example: toggle} + up: {description: Increases volume x levels., example: 3} diff --git a/homeassistant/components/history.py b/homeassistant/components/history/__init__.py similarity index 100% rename from homeassistant/components/history.py rename to homeassistant/components/history/__init__.py diff --git a/homeassistant/components/history_graph.py b/homeassistant/components/history_graph/__init__.py similarity index 100% rename from homeassistant/components/history_graph.py rename to homeassistant/components/history_graph/__init__.py diff --git a/homeassistant/components/idteck_prox.py b/homeassistant/components/idteck_prox/__init__.py similarity index 100% rename from homeassistant/components/idteck_prox.py rename to homeassistant/components/idteck_prox/__init__.py diff --git a/homeassistant/components/influxdb.py b/homeassistant/components/influxdb/__init__.py similarity index 100% rename from homeassistant/components/influxdb.py rename to homeassistant/components/influxdb/__init__.py diff --git a/homeassistant/components/input_boolean.py b/homeassistant/components/input_boolean/__init__.py similarity index 100% rename from homeassistant/components/input_boolean.py rename to homeassistant/components/input_boolean/__init__.py diff --git a/homeassistant/components/input_boolean/services.yaml b/homeassistant/components/input_boolean/services.yaml new file mode 100644 index 00000000000..e49d46c9b86 --- /dev/null +++ b/homeassistant/components/input_boolean/services.yaml @@ -0,0 +1,12 @@ +toggle: + description: Toggles an input boolean. + fields: + entity_id: {description: Entity id of the input boolean to toggle., example: input_boolean.notify_alerts} +turn_off: + description: Turns off an input boolean + fields: + entity_id: {description: Entity id of the input boolean to turn off., example: input_boolean.notify_alerts} +turn_on: + description: Turns on an input boolean. + fields: + entity_id: {description: Entity id of the input boolean to turn on., example: input_boolean.notify_alerts} diff --git a/homeassistant/components/input_datetime.py b/homeassistant/components/input_datetime/__init__.py similarity index 100% rename from homeassistant/components/input_datetime.py rename to homeassistant/components/input_datetime/__init__.py diff --git a/homeassistant/components/input_number.py b/homeassistant/components/input_number/__init__.py similarity index 100% rename from homeassistant/components/input_number.py rename to homeassistant/components/input_number/__init__.py diff --git a/homeassistant/components/input_number/services.yaml b/homeassistant/components/input_number/services.yaml new file mode 100644 index 00000000000..650abc056a9 --- /dev/null +++ b/homeassistant/components/input_number/services.yaml @@ -0,0 +1,16 @@ +decrement: + description: Decrement the value of an input number entity by its stepping. + fields: + entity_id: {description: Entity id of the input number the should be decremented., + example: input_number.threshold} +increment: + description: Increment the value of an input number entity by its stepping. + fields: + entity_id: {description: Entity id of the input number the should be incremented., + example: input_number.threshold} +set_value: + description: Set the value of an input number entity. + fields: + entity_id: {description: Entity id of the input number to set the new value., + example: input_number.threshold} + value: {description: The target value the entity should be set to., example: 42} diff --git a/homeassistant/components/input_select.py b/homeassistant/components/input_select/__init__.py similarity index 100% rename from homeassistant/components/input_select.py rename to homeassistant/components/input_select/__init__.py diff --git a/homeassistant/components/input_select/services.yaml b/homeassistant/components/input_select/services.yaml new file mode 100644 index 00000000000..8084e56b731 --- /dev/null +++ b/homeassistant/components/input_select/services.yaml @@ -0,0 +1,22 @@ +select_next: + description: Select the next options of an input select entity. + fields: + entity_id: {description: Entity id of the input select to select the next value + for., example: input_select.my_select} +select_option: + description: Select an option of an input select entity. + fields: + entity_id: {description: Entity id of the input select to select the value., example: input_select.my_select} + option: {description: Option to be selected., example: '"Item A"'} +select_previous: + description: Select the previous options of an input select entity. + fields: + entity_id: {description: Entity id of the input select to select the previous + value for., example: input_select.my_select} +set_options: + description: Set the options of an input select entity. + fields: + entity_id: {description: Entity id of the input select to set the new options + for., example: input_select.my_select} + options: {description: Options for the input select entity., example: '["Item + A", "Item B", "Item C"]'} diff --git a/homeassistant/components/input_text.py b/homeassistant/components/input_text/__init__.py similarity index 100% rename from homeassistant/components/input_text.py rename to homeassistant/components/input_text/__init__.py diff --git a/homeassistant/components/input_text/services.yaml b/homeassistant/components/input_text/services.yaml new file mode 100644 index 00000000000..219eecf2fd6 --- /dev/null +++ b/homeassistant/components/input_text/services.yaml @@ -0,0 +1,6 @@ +set_value: + description: Set the value of an input text entity. + fields: + entity_id: {description: Entity id of the input text to set the new value., example: input_text.text1} + value: {description: The target value the entity should be set to., example: This + is an example text} diff --git a/homeassistant/components/insteon_local.py b/homeassistant/components/insteon_local/__init__.py similarity index 100% rename from homeassistant/components/insteon_local.py rename to homeassistant/components/insteon_local/__init__.py diff --git a/homeassistant/components/insteon_plm.py b/homeassistant/components/insteon_plm/__init__.py similarity index 100% rename from homeassistant/components/insteon_plm.py rename to homeassistant/components/insteon_plm/__init__.py diff --git a/homeassistant/components/intent_script.py b/homeassistant/components/intent_script/__init__.py similarity index 100% rename from homeassistant/components/intent_script.py rename to homeassistant/components/intent_script/__init__.py diff --git a/homeassistant/components/introduction.py b/homeassistant/components/introduction/__init__.py similarity index 100% rename from homeassistant/components/introduction.py rename to homeassistant/components/introduction/__init__.py diff --git a/homeassistant/components/keyboard.py b/homeassistant/components/keyboard/__init__.py similarity index 100% rename from homeassistant/components/keyboard.py rename to homeassistant/components/keyboard/__init__.py diff --git a/homeassistant/components/keyboard_remote.py b/homeassistant/components/keyboard_remote/__init__.py similarity index 100% rename from homeassistant/components/keyboard_remote.py rename to homeassistant/components/keyboard_remote/__init__.py diff --git a/homeassistant/components/knx/services.yaml b/homeassistant/components/knx/services.yaml new file mode 100644 index 00000000000..79b11c129af --- /dev/null +++ b/homeassistant/components/knx/services.yaml @@ -0,0 +1,5 @@ +group_write: + description: Turn a light on. + fields: + address: {description: Group address(es) to write to., example: 1/1/0} + data: {description: KNX data to send., example: 1} diff --git a/homeassistant/components/lirc.py b/homeassistant/components/lirc/__init__.py similarity index 98% rename from homeassistant/components/lirc.py rename to homeassistant/components/lirc/__init__.py index d7ec49e0096..f15020a5d72 100644 --- a/homeassistant/components/lirc.py +++ b/homeassistant/components/lirc/__init__.py @@ -4,7 +4,7 @@ LIRC interface to receive signals from an infrared remote control. For more details about this component, please refer to the documentation at https://home-assistant.io/components/lirc/ """ -# pylint: disable=no-member +# pylint: disable=no-member, import-error import threading import time import logging diff --git a/homeassistant/components/litejet.py b/homeassistant/components/litejet/__init__.py similarity index 100% rename from homeassistant/components/litejet.py rename to homeassistant/components/litejet/__init__.py diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook/__init__.py similarity index 100% rename from homeassistant/components/logbook.py rename to homeassistant/components/logbook/__init__.py diff --git a/homeassistant/components/logentries.py b/homeassistant/components/logentries/__init__.py similarity index 100% rename from homeassistant/components/logentries.py rename to homeassistant/components/logentries/__init__.py diff --git a/homeassistant/components/logger.py b/homeassistant/components/logger/__init__.py similarity index 100% rename from homeassistant/components/logger.py rename to homeassistant/components/logger/__init__.py diff --git a/homeassistant/components/logger/services.yaml b/homeassistant/components/logger/services.yaml new file mode 100644 index 00000000000..4d1ba649d36 --- /dev/null +++ b/homeassistant/components/logger/services.yaml @@ -0,0 +1,6 @@ +set_default_level: + description: Set the default log level for components. + fields: + level: {description: 'Default severity level. Possible values are notset, debug, + info, warn, warning, error, fatal, critical', example: debug} +set_level: {description: Set log level for components.} diff --git a/homeassistant/components/map.py b/homeassistant/components/map/__init__.py similarity index 100% rename from homeassistant/components/map.py rename to homeassistant/components/map/__init__.py diff --git a/homeassistant/components/media_extractor.py b/homeassistant/components/media_extractor/__init__.py similarity index 100% rename from homeassistant/components/media_extractor.py rename to homeassistant/components/media_extractor/__init__.py diff --git a/homeassistant/components/melissa.py b/homeassistant/components/melissa/__init__.py similarity index 100% rename from homeassistant/components/melissa.py rename to homeassistant/components/melissa/__init__.py diff --git a/homeassistant/components/microsoft_face.py b/homeassistant/components/microsoft_face/__init__.py similarity index 100% rename from homeassistant/components/microsoft_face.py rename to homeassistant/components/microsoft_face/__init__.py diff --git a/homeassistant/components/microsoft_face/services.yaml b/homeassistant/components/microsoft_face/services.yaml new file mode 100644 index 00000000000..386f7083f92 --- /dev/null +++ b/homeassistant/components/microsoft_face/services.yaml @@ -0,0 +1,28 @@ +create_group: + description: Create a new person group. + fields: + name: {description: Name of the group., example: family} +create_person: + description: Create a new person in the group. + fields: + group: {description: Name of the group, example: family} + name: {description: Name of the person, example: Hans} +delete_group: + description: Delete a new person group. + fields: + name: {description: Name of the group., example: family} +delete_person: + description: Delete a person in the group. + fields: + group: {description: Name of the group., example: family} + name: {description: Name of the person., example: Hans} +face_person: + description: Add a new picture to a person. + fields: + camera_entity: {description: Camera to take a picture., example: camera.door} + group: {description: Name of the group., example: family} + person: {description: Name of the person., example: Hans} +train_group: + description: Train a person group. + fields: + group: {description: Name of the group, example: family} diff --git a/homeassistant/components/modbus/services.yaml b/homeassistant/components/modbus/services.yaml new file mode 100644 index 00000000000..0fd9e5a49e7 --- /dev/null +++ b/homeassistant/components/modbus/services.yaml @@ -0,0 +1,12 @@ +write_coil: + description: Write to a modbus coil. + fields: + address: {description: Address of the register to read., example: 0} + state: {description: State to write., example: false} + unit: {description: Address of the modbus unit., example: 21} +write_register: + description: Write to a modbus holding register. + fields: + address: {description: Address of the holding register to write to., example: 0} + unit: {description: Address of the modbus unit., example: 21} + value: {description: Value to write., example: 0} diff --git a/homeassistant/components/mqtt_eventstream.py b/homeassistant/components/mqtt_eventstream/__init__.py similarity index 100% rename from homeassistant/components/mqtt_eventstream.py rename to homeassistant/components/mqtt_eventstream/__init__.py diff --git a/homeassistant/components/mqtt_statestream.py b/homeassistant/components/mqtt_statestream/__init__.py similarity index 100% rename from homeassistant/components/mqtt_statestream.py rename to homeassistant/components/mqtt_statestream/__init__.py diff --git a/homeassistant/components/mycroft.py b/homeassistant/components/mycroft/__init__.py similarity index 100% rename from homeassistant/components/mycroft.py rename to homeassistant/components/mycroft/__init__.py diff --git a/homeassistant/components/mythicbeastsdns.py b/homeassistant/components/mythicbeastsdns/__init__.py similarity index 100% rename from homeassistant/components/mythicbeastsdns.py rename to homeassistant/components/mythicbeastsdns/__init__.py diff --git a/homeassistant/components/namecheapdns.py b/homeassistant/components/namecheapdns/__init__.py similarity index 100% rename from homeassistant/components/namecheapdns.py rename to homeassistant/components/namecheapdns/__init__.py diff --git a/homeassistant/components/ness_alarm.py b/homeassistant/components/ness_alarm/__init__.py similarity index 100% rename from homeassistant/components/ness_alarm.py rename to homeassistant/components/ness_alarm/__init__.py diff --git a/homeassistant/components/nest/services.yaml b/homeassistant/components/nest/services.yaml index e10e6264643..0015c83342d 100644 --- a/homeassistant/components/nest/services.yaml +++ b/homeassistant/components/nest/services.yaml @@ -1,37 +1,16 @@ -# Describes the format for available Nest services +set_mode: + description: 'Set the home/away mode for a Nest structure. Set to away mode will + also set Estimated Arrival Time if provided. Set ETA will cause the thermostat + to begin warming or cooling the home before the user arrives. After ETA set other + Automation can read ETA sensor as a signal to prepare the home for the user''s + arrival. -set_away_mode: - description: Set the away mode for a Nest structure. + ' fields: - away_mode: - description: New mode to set. Valid modes are "away" or "home". - example: "away" - structure: - description: Name(s) of structure(s) to change. Defaults to all structures if not specified. - example: "Apartment" - -set_eta: - description: Set or update the estimated time of arrival window for a Nest structure. - fields: - eta: - description: Estimated time of arrival from now. - example: "00:10:30" - eta_window: - description: Estimated time of arrival window. Default is 1 minute. - example: "00:05" - trip_id: - description: Unique ID for the trip. Default is auto-generated using a timestamp. - example: "Leave Work" - structure: - description: Name(s) of structure(s) to change. Defaults to all structures if not specified. - example: "Apartment" - -cancel_eta: - description: Cancel an existing estimated time of arrival window for a Nest structure. - fields: - trip_id: - description: Unique ID for the trip. - example: "Leave Work" - structure: - description: Name(s) of structure(s) to change. Defaults to all structures if not specified. - example: "Apartment" + eta: {description: Optional Estimated Arrival Time from now., example: '0:10'} + eta_window: {description: Optional ETA window. Default is 1 minute., example: '0:5'} + home_mode: {description: home or away, example: home} + structure: {description: Optional structure name. Default set all structures managed + by Home Assistant., example: My Home} + trip_id: {description: Optional identity of a trip. Using the same trip_ID will + update the estimation., example: trip_back_home} diff --git a/homeassistant/components/no_ip.py b/homeassistant/components/no_ip/__init__.py similarity index 100% rename from homeassistant/components/no_ip.py rename to homeassistant/components/no_ip/__init__.py diff --git a/homeassistant/components/nuheat.py b/homeassistant/components/nuheat/__init__.py similarity index 100% rename from homeassistant/components/nuheat.py rename to homeassistant/components/nuheat/__init__.py diff --git a/homeassistant/components/nuimo_controller.py b/homeassistant/components/nuimo_controller/__init__.py similarity index 100% rename from homeassistant/components/nuimo_controller.py rename to homeassistant/components/nuimo_controller/__init__.py diff --git a/homeassistant/components/panel_custom.py b/homeassistant/components/panel_custom/__init__.py similarity index 100% rename from homeassistant/components/panel_custom.py rename to homeassistant/components/panel_custom/__init__.py diff --git a/homeassistant/components/panel_iframe.py b/homeassistant/components/panel_iframe/__init__.py similarity index 100% rename from homeassistant/components/panel_iframe.py rename to homeassistant/components/panel_iframe/__init__.py diff --git a/homeassistant/components/plant.py b/homeassistant/components/plant/__init__.py similarity index 100% rename from homeassistant/components/plant.py rename to homeassistant/components/plant/__init__.py diff --git a/homeassistant/components/prometheus.py b/homeassistant/components/prometheus/__init__.py similarity index 100% rename from homeassistant/components/prometheus.py rename to homeassistant/components/prometheus/__init__.py diff --git a/homeassistant/components/proximity.py b/homeassistant/components/proximity/__init__.py similarity index 100% rename from homeassistant/components/proximity.py rename to homeassistant/components/proximity/__init__.py diff --git a/homeassistant/components/python_script.py b/homeassistant/components/python_script/__init__.py similarity index 100% rename from homeassistant/components/python_script.py rename to homeassistant/components/python_script/__init__.py diff --git a/homeassistant/components/rainbird.py b/homeassistant/components/rainbird/__init__.py similarity index 100% rename from homeassistant/components/rainbird.py rename to homeassistant/components/rainbird/__init__.py diff --git a/homeassistant/components/rest_command.py b/homeassistant/components/rest_command/__init__.py similarity index 100% rename from homeassistant/components/rest_command.py rename to homeassistant/components/rest_command/__init__.py diff --git a/homeassistant/components/rflink.py b/homeassistant/components/rflink/__init__.py similarity index 100% rename from homeassistant/components/rflink.py rename to homeassistant/components/rflink/__init__.py diff --git a/homeassistant/components/rflink/services.yaml b/homeassistant/components/rflink/services.yaml new file mode 100644 index 00000000000..9269326ece6 --- /dev/null +++ b/homeassistant/components/rflink/services.yaml @@ -0,0 +1,5 @@ +send_command: + description: Send device command through RFLink. + fields: + command: {description: The command to be sent., example: 'on'} + device_id: {description: RFLink device ID., example: newkaku_0000c6c2_1} diff --git a/homeassistant/components/ring.py b/homeassistant/components/ring/__init__.py similarity index 100% rename from homeassistant/components/ring.py rename to homeassistant/components/ring/__init__.py diff --git a/homeassistant/components/route53.py b/homeassistant/components/route53/__init__.py similarity index 100% rename from homeassistant/components/route53.py rename to homeassistant/components/route53/__init__.py diff --git a/homeassistant/components/rss_feed_template.py b/homeassistant/components/rss_feed_template/__init__.py similarity index 100% rename from homeassistant/components/rss_feed_template.py rename to homeassistant/components/rss_feed_template/__init__.py diff --git a/homeassistant/components/script.py b/homeassistant/components/script/__init__.py similarity index 100% rename from homeassistant/components/script.py rename to homeassistant/components/script/__init__.py diff --git a/homeassistant/components/services.yaml b/homeassistant/components/services.yaml index 8988021a5b6..40c76376531 100644 --- a/homeassistant/components/services.yaml +++ b/homeassistant/components/services.yaml @@ -1,488 +1,5 @@ # Describes the format for available component services -foursquare: - checkin: - description: Check a user into a Foursquare venue. - fields: - venueId: - description: The Foursquare venue where the user is checking in. [Required] - example: IHR8THISVNU - eventId: - description: The event the user is checking in to. [Optional] - example: UHR8THISVNT - shout: - description: A message about your check-in. The maximum length of this field is 140 characters. [Optional] - example: There are crayons! Crayons! - mentions: - description: Mentions in your check-in. This parameter is a semicolon-delimited list of mentions. A single mention is of the form "start,end,userid", where start is the index of the first character in the shout representing the mention, end is the index of the first character in the shout after the mention, and userid is the userid of the user being mentioned. If userid is prefixed with "fbu-", this indicates a Facebook userid that is being mention. Character indices in shouts are 0-based. [Optional] - example: 5,10,HZXXY3Y;15,20,GZYYZ3Z;25,30,fbu-GZXY13Y - broadcast: - description: "Who to broadcast this check-in to. Accepts a comma-delimited list of values: private (off the grid) or public (share with friends), facebook share on facebook, twitter share on twitter, followers share with followers (celebrity mode users only), If no valid value is found, the default is public. [Optional]" - example: public,twitter - ll: - description: Latitude and longitude of the user's location. Only specify this field if you have a GPS or other device reported location for the user at the time of check-in. [Optional] - example: 33.7,44.2 - llAcc: - description: Accuracy of the user's latitude and longitude, in meters. [Optional] - example: 1 - alt: - description: Altitude of the user's location, in meters. [Optional] - example: 0 - altAcc: - description: Vertical accuracy of the user's location, in meters. - example: 1 - -microsoft_face: - create_group: - description: Create a new person group. - fields: - name: - description: Name of the group. - example: 'family' - delete_group: - description: Delete a new person group. - fields: - name: - description: Name of the group. - example: 'family' - train_group: - description: Train a person group. - fields: - group: - description: Name of the group - example: 'family' - create_person: - description: Create a new person in the group. - fields: - name: - description: Name of the person - example: 'Hans' - group: - description: Name of the group - example: 'family' - delete_person: - description: Delete a person in the group. - fields: - name: - description: Name of the person. - example: 'Hans' - group: - description: Name of the group. - example: 'family' - face_person: - description: Add a new picture to a person. - fields: - person: - description: Name of the person. - example: 'Hans' - group: - description: Name of the group. - example: 'family' - camera_entity: - description: Camera to take a picture. - example: camera.door - -verisure: - capture_smartcam: - description: Capture a new image from a smartcam. - fields: - device_serial: - description: The serial number of the smartcam you want to capture an image from. - example: '2DEU AT5Z' - -alert: - turn_off: - description: Silence alert's notifications. - fields: - entity_id: - description: Name of the alert to silence. - example: 'alert.garage_door_open' - turn_on: - description: Reset alert's notifications. - fields: - entity_id: - description: Name of the alert to reset. - example: 'alert.garage_door_open' - toggle: - description: Toggle alert's notifications. - fields: - entity_id: - description: Name of the alert to toggle. - example: 'alert.garage_door_open' - -hdmi_cec: - send_command: - description: Sends CEC command into HDMI CEC capable adapter. - fields: - raw: - description: 'Raw CEC command in format "00:00:00:00" where first two digits are source and destination, second byte is command and optional other bytes are command parameters. If raw command specified, other params are ignored.' - example: '"10:36"' - src: - desctiption: 'Source of command. Could be decimal number or string with hexadeximal notation: "0x10".' - example: '12 or "0xc"' - dst: - description: 'Destination for command. Could be decimal number or string with hexadeximal notation: "0x10".' - example: '5 or "0x5"' - cmd: - description: 'Command itself. Could be decimal number or string with hexadeximal notation: "0x10".' - example: '144 or "0x90"' - att: - description: Optional parameters. - example: [0, 2] - update: - description: Update devices state from network. - volume: - description: Increase or decrease volume of system. - fields: - up: - description: Increases volume x levels. - example: 3 - down: - description: Decreases volume x levels. - example: 3 - mute: - description: Mutes audio system. Value should be on, off or toggle. - example: "toggle" - select_device: - description: Select HDMI device. - fields: - device: - description: Address of device to select. Can be entity_id, physical address or alias from confuguration. - example: '"switch.hdmi_1" or "1.1.0.0" or "01:10"' - power_on: - description: Power on all devices which supports it. - standby: - description: Standby all devices which supports it. - -ffmpeg: - start: - description: Send a start command to a ffmpeg based sensor. - fields: - entity_id: - description: Name(s) of entities that will start. Platform dependent. - example: 'binary_sensor.ffmpeg_noise' - stop: - description: Send a stop command to a ffmpeg based sensor. - fields: - entity_id: - description: Name(s) of entities that will stop. Platform dependent. - example: 'binary_sensor.ffmpeg_noise' - restart: - description: Send a restart command to a ffmpeg based sensor. - fields: - entity_id: - description: Name(s) of entities that will restart. Platform dependent. - example: 'binary_sensor.ffmpeg_noise' - -logger: - set_default_level: - description: Set the default log level for components. - fields: - level: - description: Default severity level. Possible values are notset, debug, info, warn, warning, error, fatal, critical - example: 'debug' - set_level: - description: Set log level for components. - -hassio: - host_reboot: - description: Reboot host computer. - host_shutdown: - description: Poweroff host computer. - host_update: - description: Update host computer. - fields: - version: - description: Optional or it will be use the latest version. - example: '0.3' - supervisor_update: - description: Update HassIO supervisor. - fields: - version: - description: Optional or it will be use the latest version. - example: '0.3' - supervisor_reload: - description: Reload HassIO supervisor addons/updates/configs. - homeassistant_update: - description: Update HomeAssistant docker image. - fields: - version: - description: Optional or it will be use the latest version. - example: '0.40.1' - addon_install: - description: Install a HassIO docker addon. - fields: - addon: - description: Name of addon. - example: 'smb_config' - version: - description: Optional or it will be use the latest version. - example: '0.2' - addon_uninstall: - description: Uninstall a HassIO docker addon. - fields: - addon: - description: Name of addon. - example: 'smb_config' - addon_update: - description: Update a HassIO docker addon. - fields: - addon: - description: Name of addon. - example: 'smb_config' - version: - description: Optional or it will be use the latest version. - example: '0.2' - addon_start: - description: Start a HassIO docker addon. - fields: - addon: - description: Name of addon. - example: 'smb_config' - addon_stop: - description: Stop a HassIO docker addon. - fields: - addon: - description: Name of addon. - example: 'smb_config' - -eight_sleep: - heat_set: - description: Set heating level for eight sleep. - fields: - entity_id: - description: Entity id of the bed state to adjust. - example: 'sensor.eight_left_bed_state' - target: - description: Target heating level from 0-100. - example: 35 - duration: - description: Duration to heat at the target level in seconds. - example: 3600 - -apple_tv: - apple_tv_authenticate: - description: Start AirPlay device authentication. - fields: - entity_id: - description: Name(s) of entities to authenticate with. - example: 'media_player.apple_tv' - apple_tv_scan: - description: Scan for Apple TV devices. - -modbus: - write_register: - description: Write to a modbus holding register. - fields: - unit: - description: Address of the modbus unit. - example: 21 - address: - description: Address of the holding register to write to. - example: 0 - value: - description: Value to write. - example: 0 - write_coil: - description: Write to a modbus coil. - fields: - unit: - description: Address of the modbus unit. - example: 21 - address: - description: Address of the register to read. - example: 0 - state: - description: State to write. - example: false - -wake_on_lan: - send_magic_packet: - description: Send a 'magic packet' to wake up a device with 'Wake-On-LAN' capabilities. - fields: - mac: - description: MAC address of the device to wake up. - example: 'aa:bb:cc:dd:ee:ff' - broadcast_address: - description: Optional broadcast IP where to send the magic packet. - example: '192.168.255.255' - -knx: - group_write: - description: Turn a light on. - fields: - address: - description: Group address(es) to write to. - example: '1/1/0' - data: - description: KNX data to send. - example: 1 - -rflink: - send_command: - description: Send device command through RFLink. - fields: - device_id: - description: RFLink device ID. - example: 'newkaku_0000c6c2_1' - - command: - description: The command to be sent. - example: 'on' - -abode: - change_setting: - description: Change an Abode system setting. - fields: - setting: - description: Setting to change. - example: 'beeper_mute' - value: - description: Value of the setting. - example: '1' - capture_image: - description: Request a new image capture from a camera device. - fields: - entity_id: - description: Entity id of the camera to request an image. - example: 'camera.downstairs_motion_camera' - trigger_quick_action: - description: Trigger an Abode quick action. - fields: - entity_id: - description: Entity id of the quick action to trigger. - example: 'binary_sensor.home_quick_action' - -snips: - say: - description: Send a TTS message to Snips. - fields: - text: - description: Text to say. - example: My name is snips - site_id: - description: Site to use to start session, defaults to default (optional) - example: bedroom - custom_data: - description: custom data that will be included with all messages in this session - example: user=UserName - say_action: - description: Send a TTS message to Snips to listen for a response. - fields: - text: - description: Text to say - example: My name is snips - site_id: - description: Site to use to start session, defaults to default (optional) - example: bedroom - custom_data: - description: custom data that will be included with all messages in this session - example: user=UserName - can_be_enqueued: - description: If True, session waits for an open session to end, if False session is dropped if one is running - example: True - intent_filter: - description: Optional Array of Strings - A list of intents names to restrict the NLU resolution to on the first query. - example: turnOnLights, turnOffLights - feedback_on: - description: Turns feedback sounds on. - fields: - site_id: - description: Site to turn sounds on, defaults to all sites (optional) - example: bedroom - feedback_off: - description: Turns feedback sounds off. - fields: - site_id: - description: Site to turn sounds on, defaults to all sites (optional) - example: bedroom - -input_boolean: - toggle: - description: Toggles an input boolean. - fields: - entity_id: - description: Entity id of the input boolean to toggle. - example: 'input_boolean.notify_alerts' - turn_off: - description: Turns off an input boolean - fields: - entity_id: - description: Entity id of the input boolean to turn off. - example: 'input_boolean.notify_alerts' - turn_on: - description: Turns on an input boolean. - fields: - entity_id: - description: Entity id of the input boolean to turn on. - example: 'input_boolean.notify_alerts' - -input_text: - set_value: - description: Set the value of an input text entity. - fields: - entity_id: - description: Entity id of the input text to set the new value. - example: 'input_text.text1' - value: - description: The target value the entity should be set to. - example: This is an example text - -input_number: - set_value: - description: Set the value of an input number entity. - fields: - entity_id: - description: Entity id of the input number to set the new value. - example: 'input_number.threshold' - value: - description: The target value the entity should be set to. - example: 42 - increment: - description: Increment the value of an input number entity by its stepping. - fields: - entity_id: - description: Entity id of the input number the should be incremented. - example: 'input_number.threshold' - decrement: - description: Decrement the value of an input number entity by its stepping. - fields: - entity_id: - description: Entity id of the input number the should be decremented. - example: 'input_number.threshold' - -input_select: - select_option: - description: Select an option of an input select entity. - fields: - entity_id: - description: Entity id of the input select to select the value. - example: 'input_select.my_select' - option: - description: Option to be selected. - example: '"Item A"' - set_options: - description: Set the options of an input select entity. - fields: - entity_id: - description: Entity id of the input select to set the new options for. - example: 'input_select.my_select' - options: - description: Options for the input select entity. - example: '["Item A", "Item B", "Item C"]' - select_previous: - description: Select the previous options of an input select entity. - fields: - entity_id: - description: Entity id of the input select to select the previous value for. - example: 'input_select.my_select' - select_next: - description: Select the next options of an input select entity. - fields: - entity_id: - description: Entity id of the input select to select the next value for. - example: 'input_select.my_select' - homeassistant: check_config: description: Check the Home Assistant configuration files for errors. Errors will be displayed in the Home Assistant log. @@ -516,77 +33,3 @@ homeassistant: entity_id: description: One or multiple entity_ids to update. Can be a list. example: light.living_room - -xiaomi_aqara: - play_ringtone: - description: Play a specific ringtone. The version of the gateway firmware must be 1.4.1_145 at least. - fields: - gw_mac: - description: MAC address of the Xiaomi Aqara Gateway. - example: 34ce00880088 - ringtone_id: - description: One of the allowed ringtone ids. - example: 8 - ringtone_vol: - description: The volume in percent. - example: 30 - stop_ringtone: - description: Stops a playing ringtone immediately. - fields: - gw_mac: - description: MAC address of the Xiaomi Aqara Gateway. - example: 34ce00880088 - add_device: - description: Enables the join permission of the Xiaomi Aqara Gateway for 30 seconds. A new device can be added afterwards by pressing the pairing button once. - fields: - gw_mac: - description: MAC address of the Xiaomi Aqara Gateway. - example: 34ce00880088 - remove_device: - description: Removes a specific device. The removal is required if a device shall be paired with another gateway. - fields: - gw_mac: - description: MAC address of the Xiaomi Aqara Gateway. - example: 34ce00880088 - device_id: - description: Hardware address of the device to remove. - example: 158d0000000000 - -shopping_list: - add_item: - description: Adds an item to the shopping list. - fields: - name: - description: The name of the item to add. - example: Beer - complete_item: - description: Marks an item as completed in the shopping list. It does not remove the item. - fields: - name: - description: The name of the item to mark as completed. - example: Beer - -nest: - set_mode: - description: > - Set the home/away mode for a Nest structure. - Set to away mode will also set Estimated Arrival Time if provided. - Set ETA will cause the thermostat to begin warming or cooling the home before the user arrives. - After ETA set other Automation can read ETA sensor as a signal to prepare the home for - the user's arrival. - fields: - home_mode: - description: home or away - example: home - structure: - description: Optional structure name. Default set all structures managed by Home Assistant. - example: My Home - eta: - description: Optional Estimated Arrival Time from now. - example: 0:10 - eta_window: - description: Optional ETA window. Default is 1 minute. - example: 0:5 - trip_id: - description: Optional identity of a trip. Using the same trip_ID will update the estimation. - example: trip_back_home diff --git a/homeassistant/components/shell_command.py b/homeassistant/components/shell_command/__init__.py similarity index 100% rename from homeassistant/components/shell_command.py rename to homeassistant/components/shell_command/__init__.py diff --git a/homeassistant/components/shiftr.py b/homeassistant/components/shiftr/__init__.py similarity index 100% rename from homeassistant/components/shiftr.py rename to homeassistant/components/shiftr/__init__.py diff --git a/homeassistant/components/shopping_list.py b/homeassistant/components/shopping_list/__init__.py similarity index 100% rename from homeassistant/components/shopping_list.py rename to homeassistant/components/shopping_list/__init__.py diff --git a/homeassistant/components/shopping_list/services.yaml b/homeassistant/components/shopping_list/services.yaml new file mode 100644 index 00000000000..1d667e43fa6 --- /dev/null +++ b/homeassistant/components/shopping_list/services.yaml @@ -0,0 +1,9 @@ +add_item: + description: Adds an item to the shopping list. + fields: + name: {description: The name of the item to add., example: Beer} +complete_item: + description: Marks an item as completed in the shopping list. It does not remove + the item. + fields: + name: {description: The name of the item to mark as completed., example: Beer} diff --git a/homeassistant/components/sleepiq.py b/homeassistant/components/sleepiq/__init__.py similarity index 100% rename from homeassistant/components/sleepiq.py rename to homeassistant/components/sleepiq/__init__.py diff --git a/homeassistant/components/snips.py b/homeassistant/components/snips/__init__.py similarity index 100% rename from homeassistant/components/snips.py rename to homeassistant/components/snips/__init__.py diff --git a/homeassistant/components/snips/services.yaml b/homeassistant/components/snips/services.yaml new file mode 100644 index 00000000000..cca39062ce9 --- /dev/null +++ b/homeassistant/components/snips/services.yaml @@ -0,0 +1,31 @@ +feedback_off: + description: Turns feedback sounds off. + fields: + site_id: {description: 'Site to turn sounds on, defaults to all sites (optional)', + example: bedroom} +feedback_on: + description: Turns feedback sounds on. + fields: + site_id: {description: 'Site to turn sounds on, defaults to all sites (optional)', + example: bedroom} +say: + description: Send a TTS message to Snips. + fields: + custom_data: {description: custom data that will be included with all messages + in this session, example: user=UserName} + site_id: {description: 'Site to use to start session, defaults to default (optional)', + example: bedroom} + text: {description: Text to say., example: My name is snips} +say_action: + description: Send a TTS message to Snips to listen for a response. + fields: + can_be_enqueued: {description: 'If True, session waits for an open session to + end, if False session is dropped if one is running', example: true} + custom_data: {description: custom data that will be included with all messages + in this session, example: user=UserName} + intent_filter: {description: Optional Array of Strings - A list of intents names + to restrict the NLU resolution to on the first query., example: 'turnOnLights, + turnOffLights'} + site_id: {description: 'Site to use to start session, defaults to default (optional)', + example: bedroom} + text: {description: Text to say, example: My name is snips} diff --git a/homeassistant/components/spaceapi.py b/homeassistant/components/spaceapi/__init__.py similarity index 100% rename from homeassistant/components/spaceapi.py rename to homeassistant/components/spaceapi/__init__.py diff --git a/homeassistant/components/spc.py b/homeassistant/components/spc/__init__.py similarity index 100% rename from homeassistant/components/spc.py rename to homeassistant/components/spc/__init__.py diff --git a/homeassistant/components/splunk.py b/homeassistant/components/splunk/__init__.py similarity index 100% rename from homeassistant/components/splunk.py rename to homeassistant/components/splunk/__init__.py diff --git a/homeassistant/components/statsd.py b/homeassistant/components/statsd/__init__.py similarity index 100% rename from homeassistant/components/statsd.py rename to homeassistant/components/statsd/__init__.py diff --git a/homeassistant/components/sun.py b/homeassistant/components/sun/__init__.py similarity index 100% rename from homeassistant/components/sun.py rename to homeassistant/components/sun/__init__.py diff --git a/homeassistant/components/thingspeak.py b/homeassistant/components/thingspeak/__init__.py similarity index 100% rename from homeassistant/components/thingspeak.py rename to homeassistant/components/thingspeak/__init__.py diff --git a/homeassistant/components/updater.py b/homeassistant/components/updater/__init__.py similarity index 100% rename from homeassistant/components/updater.py rename to homeassistant/components/updater/__init__.py diff --git a/homeassistant/components/verisure/services.yaml b/homeassistant/components/verisure/services.yaml new file mode 100644 index 00000000000..405f0c5d57d --- /dev/null +++ b/homeassistant/components/verisure/services.yaml @@ -0,0 +1,5 @@ +capture_smartcam: + description: Capture a new image from a smartcam. + fields: + device_serial: {description: The serial number of the smartcam you want to capture + an image from., example: 2DEU AT5Z} diff --git a/homeassistant/components/vultr.py b/homeassistant/components/vultr/__init__.py similarity index 100% rename from homeassistant/components/vultr.py rename to homeassistant/components/vultr/__init__.py diff --git a/homeassistant/components/wake_on_lan.py b/homeassistant/components/wake_on_lan/__init__.py similarity index 100% rename from homeassistant/components/wake_on_lan.py rename to homeassistant/components/wake_on_lan/__init__.py diff --git a/homeassistant/components/wake_on_lan/services.yaml b/homeassistant/components/wake_on_lan/services.yaml new file mode 100644 index 00000000000..e20dd64396f --- /dev/null +++ b/homeassistant/components/wake_on_lan/services.yaml @@ -0,0 +1,6 @@ +send_magic_packet: + description: Send a 'magic packet' to wake up a device with 'Wake-On-LAN' capabilities. + fields: + broadcast_address: {description: Optional broadcast IP where to send the magic + packet., example: 192.168.255.255} + mac: {description: MAC address of the device to wake up., example: 'aa:bb:cc:dd:ee:ff'} diff --git a/homeassistant/components/watson_iot.py b/homeassistant/components/watson_iot/__init__.py similarity index 100% rename from homeassistant/components/watson_iot.py rename to homeassistant/components/watson_iot/__init__.py diff --git a/homeassistant/components/webhook.py b/homeassistant/components/webhook/__init__.py similarity index 100% rename from homeassistant/components/webhook.py rename to homeassistant/components/webhook/__init__.py diff --git a/homeassistant/components/weblink.py b/homeassistant/components/weblink/__init__.py similarity index 100% rename from homeassistant/components/weblink.py rename to homeassistant/components/weblink/__init__.py diff --git a/homeassistant/components/xiaomi_aqara/services.yaml b/homeassistant/components/xiaomi_aqara/services.yaml new file mode 100644 index 00000000000..0c5b89dc2cb --- /dev/null +++ b/homeassistant/components/xiaomi_aqara/services.yaml @@ -0,0 +1,22 @@ +add_device: + description: Enables the join permission of the Xiaomi Aqara Gateway for 30 seconds. + A new device can be added afterwards by pressing the pairing button once. + fields: + gw_mac: {description: MAC address of the Xiaomi Aqara Gateway., example: 34ce00880088} +play_ringtone: + description: Play a specific ringtone. The version of the gateway firmware must + be 1.4.1_145 at least. + fields: + gw_mac: {description: MAC address of the Xiaomi Aqara Gateway., example: 34ce00880088} + ringtone_id: {description: One of the allowed ringtone ids., example: 8} + ringtone_vol: {description: The volume in percent., example: 30} +remove_device: + description: Removes a specific device. The removal is required if a device shall + be paired with another gateway. + fields: + device_id: {description: Hardware address of the device to remove., example: 158d0000000000} + gw_mac: {description: MAC address of the Xiaomi Aqara Gateway., example: 34ce00880088} +stop_ringtone: + description: Stops a playing ringtone immediately. + fields: + gw_mac: {description: MAC address of the Xiaomi Aqara Gateway., example: 34ce00880088} diff --git a/homeassistant/components/zeroconf.py b/homeassistant/components/zeroconf/__init__.py similarity index 100% rename from homeassistant/components/zeroconf.py rename to homeassistant/components/zeroconf/__init__.py diff --git a/tests/components/alert/__init__.py b/tests/components/alert/__init__.py new file mode 100644 index 00000000000..80e51236d53 --- /dev/null +++ b/tests/components/alert/__init__.py @@ -0,0 +1 @@ +"""Tests for the alert component.""" diff --git a/tests/components/test_alert.py b/tests/components/alert/test_init.py similarity index 100% rename from tests/components/test_alert.py rename to tests/components/alert/test_init.py diff --git a/tests/components/api/__init__.py b/tests/components/api/__init__.py new file mode 100644 index 00000000000..c72fd03f7de --- /dev/null +++ b/tests/components/api/__init__.py @@ -0,0 +1 @@ +"""Tests for the api component.""" diff --git a/tests/components/test_api.py b/tests/components/api/test_init.py similarity index 100% rename from tests/components/test_api.py rename to tests/components/api/test_init.py diff --git a/tests/components/binary_sensor/test_rflink.py b/tests/components/binary_sensor/test_rflink.py index 98147ff7e6f..c14107438b3 100644 --- a/tests/components/binary_sensor/test_rflink.py +++ b/tests/components/binary_sensor/test_rflink.py @@ -16,7 +16,7 @@ from homeassistant.const import ( import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed -from ..test_rflink import mock_rflink +from tests.components.rflink.test_init import mock_rflink DOMAIN = 'binary_sensor' diff --git a/tests/components/binary_sensor/test_ring.py b/tests/components/binary_sensor/test_ring.py index a4b243e7420..924ed01f9e8 100644 --- a/tests/components/binary_sensor/test_ring.py +++ b/tests/components/binary_sensor/test_ring.py @@ -6,7 +6,7 @@ import requests_mock from homeassistant.components.binary_sensor import ring from homeassistant.components import ring as base_ring -from tests.components.test_ring import ATTRIBUTION, VALID_CONFIG +from tests.components.ring.test_init import ATTRIBUTION, VALID_CONFIG from tests.common import ( get_test_config_dir, get_test_home_assistant, load_fixture) diff --git a/tests/components/binary_sensor/test_sleepiq.py b/tests/components/binary_sensor/test_sleepiq.py index 3f3d69ad9d3..524093d76b3 100644 --- a/tests/components/binary_sensor/test_sleepiq.py +++ b/tests/components/binary_sensor/test_sleepiq.py @@ -7,7 +7,7 @@ import requests_mock from homeassistant.setup import setup_component from homeassistant.components.binary_sensor import sleepiq -from tests.components.test_sleepiq import mock_responses +from tests.components.sleepiq.test_init import mock_responses from tests.common import get_test_home_assistant diff --git a/tests/components/binary_sensor/test_vultr.py b/tests/components/binary_sensor/test_vultr.py index ee19cf941a3..3dcba033d73 100644 --- a/tests/components/binary_sensor/test_vultr.py +++ b/tests/components/binary_sensor/test_vultr.py @@ -16,7 +16,7 @@ from homeassistant.components.vultr import ( from homeassistant.const import ( CONF_PLATFORM, CONF_NAME) -from tests.components.test_vultr import VALID_CONFIG +from tests.components.vultr.test_init import VALID_CONFIG from tests.common import ( get_test_home_assistant, load_fixture) diff --git a/tests/components/canary/__init__.py b/tests/components/canary/__init__.py new file mode 100644 index 00000000000..cc85edd806a --- /dev/null +++ b/tests/components/canary/__init__.py @@ -0,0 +1 @@ +"""Tests for the canary component.""" diff --git a/tests/components/test_canary.py b/tests/components/canary/test_init.py similarity index 100% rename from tests/components/test_canary.py rename to tests/components/canary/test_init.py diff --git a/tests/components/configurator/__init__.py b/tests/components/configurator/__init__.py new file mode 100644 index 00000000000..a533a39a93c --- /dev/null +++ b/tests/components/configurator/__init__.py @@ -0,0 +1 @@ +"""Tests for the configurator component.""" diff --git a/tests/components/test_configurator.py b/tests/components/configurator/test_init.py similarity index 100% rename from tests/components/test_configurator.py rename to tests/components/configurator/test_init.py diff --git a/tests/components/conversation/__init__.py b/tests/components/conversation/__init__.py new file mode 100644 index 00000000000..ea244c00df8 --- /dev/null +++ b/tests/components/conversation/__init__.py @@ -0,0 +1 @@ +"""Tests for the conversation component.""" diff --git a/tests/components/test_conversation.py b/tests/components/conversation/test_init.py similarity index 100% rename from tests/components/test_conversation.py rename to tests/components/conversation/test_init.py diff --git a/tests/components/cover/test_rflink.py b/tests/components/cover/test_rflink.py index 9c6d41a26c0..d531574d34f 100644 --- a/tests/components/cover/test_rflink.py +++ b/tests/components/cover/test_rflink.py @@ -14,7 +14,7 @@ from homeassistant.const import ( from homeassistant.core import callback, State, CoreState from tests.common import mock_restore_cache -from ..test_rflink import mock_rflink +from tests.components.rflink.test_init import mock_rflink DOMAIN = 'cover' diff --git a/tests/components/datadog/__init__.py b/tests/components/datadog/__init__.py new file mode 100644 index 00000000000..4f29b839b85 --- /dev/null +++ b/tests/components/datadog/__init__.py @@ -0,0 +1 @@ +"""Tests for the datadog component.""" diff --git a/tests/components/test_datadog.py b/tests/components/datadog/test_init.py similarity index 100% rename from tests/components/test_datadog.py rename to tests/components/datadog/test_init.py diff --git a/tests/components/demo/__init__.py b/tests/components/demo/__init__.py new file mode 100644 index 00000000000..68d228076b9 --- /dev/null +++ b/tests/components/demo/__init__.py @@ -0,0 +1 @@ +"""Tests for the demo component.""" diff --git a/tests/components/test_demo.py b/tests/components/demo/test_init.py similarity index 100% rename from tests/components/test_demo.py rename to tests/components/demo/test_init.py diff --git a/tests/components/device_sun_light_trigger/__init__.py b/tests/components/device_sun_light_trigger/__init__.py new file mode 100644 index 00000000000..4400ace7cd8 --- /dev/null +++ b/tests/components/device_sun_light_trigger/__init__.py @@ -0,0 +1 @@ +"""Tests for the device_sun_light_trigger component.""" diff --git a/tests/components/test_device_sun_light_trigger.py b/tests/components/device_sun_light_trigger/test_init.py similarity index 100% rename from tests/components/test_device_sun_light_trigger.py rename to tests/components/device_sun_light_trigger/test_init.py diff --git a/tests/components/discovery/__init__.py b/tests/components/discovery/__init__.py new file mode 100644 index 00000000000..b5744b42d6b --- /dev/null +++ b/tests/components/discovery/__init__.py @@ -0,0 +1 @@ +"""Tests for the discovery component.""" diff --git a/tests/components/test_discovery.py b/tests/components/discovery/test_init.py similarity index 100% rename from tests/components/test_discovery.py rename to tests/components/discovery/test_init.py diff --git a/tests/components/duckdns/__init__.py b/tests/components/duckdns/__init__.py new file mode 100644 index 00000000000..d8304b7cf68 --- /dev/null +++ b/tests/components/duckdns/__init__.py @@ -0,0 +1 @@ +"""Tests for the duckdns component.""" diff --git a/tests/components/test_duckdns.py b/tests/components/duckdns/test_init.py similarity index 100% rename from tests/components/test_duckdns.py rename to tests/components/duckdns/test_init.py diff --git a/tests/components/dyson/__init__.py b/tests/components/dyson/__init__.py new file mode 100644 index 00000000000..d4c814a37db --- /dev/null +++ b/tests/components/dyson/__init__.py @@ -0,0 +1 @@ +"""Tests for the dyson component.""" diff --git a/tests/components/test_dyson.py b/tests/components/dyson/test_init.py similarity index 100% rename from tests/components/test_dyson.py rename to tests/components/dyson/test_init.py diff --git a/tests/components/feedreader/__init__.py b/tests/components/feedreader/__init__.py new file mode 100644 index 00000000000..3667f7c75ea --- /dev/null +++ b/tests/components/feedreader/__init__.py @@ -0,0 +1 @@ +"""Tests for the feedreader component.""" diff --git a/tests/components/test_feedreader.py b/tests/components/feedreader/test_init.py similarity index 100% rename from tests/components/test_feedreader.py rename to tests/components/feedreader/test_init.py diff --git a/tests/components/ffmpeg/__init__.py b/tests/components/ffmpeg/__init__.py new file mode 100644 index 00000000000..c69f1dac44a --- /dev/null +++ b/tests/components/ffmpeg/__init__.py @@ -0,0 +1 @@ +"""Tests for the ffmpeg component.""" diff --git a/tests/components/test_ffmpeg.py b/tests/components/ffmpeg/test_init.py similarity index 100% rename from tests/components/test_ffmpeg.py rename to tests/components/ffmpeg/test_init.py diff --git a/tests/components/folder_watcher/__init__.py b/tests/components/folder_watcher/__init__.py new file mode 100644 index 00000000000..8508216226f --- /dev/null +++ b/tests/components/folder_watcher/__init__.py @@ -0,0 +1 @@ +"""Tests for the folder_watcher component.""" diff --git a/tests/components/test_folder_watcher.py b/tests/components/folder_watcher/test_init.py similarity index 100% rename from tests/components/test_folder_watcher.py rename to tests/components/folder_watcher/test_init.py diff --git a/tests/components/freedns/__init__.py b/tests/components/freedns/__init__.py new file mode 100644 index 00000000000..ab0f8df6411 --- /dev/null +++ b/tests/components/freedns/__init__.py @@ -0,0 +1 @@ +"""Tests for the freedns component.""" diff --git a/tests/components/test_freedns.py b/tests/components/freedns/test_init.py similarity index 100% rename from tests/components/test_freedns.py rename to tests/components/freedns/test_init.py diff --git a/tests/components/test_google.py b/tests/components/google/test_init.py similarity index 100% rename from tests/components/test_google.py rename to tests/components/google/test_init.py diff --git a/tests/components/google_domains/__init__.py b/tests/components/google_domains/__init__.py new file mode 100644 index 00000000000..3466a3be489 --- /dev/null +++ b/tests/components/google_domains/__init__.py @@ -0,0 +1 @@ +"""Tests for the google_domains component.""" diff --git a/tests/components/test_google_domains.py b/tests/components/google_domains/test_init.py similarity index 100% rename from tests/components/test_google_domains.py rename to tests/components/google_domains/test_init.py diff --git a/tests/components/graphite/__init__.py b/tests/components/graphite/__init__.py new file mode 100644 index 00000000000..e62487ad79e --- /dev/null +++ b/tests/components/graphite/__init__.py @@ -0,0 +1 @@ +"""Tests for the graphite component.""" diff --git a/tests/components/test_graphite.py b/tests/components/graphite/test_init.py similarity index 100% rename from tests/components/test_graphite.py rename to tests/components/graphite/test_init.py diff --git a/tests/components/history/__init__.py b/tests/components/history/__init__.py new file mode 100644 index 00000000000..662e70a7bff --- /dev/null +++ b/tests/components/history/__init__.py @@ -0,0 +1 @@ +"""Tests for the history component.""" diff --git a/tests/components/test_history.py b/tests/components/history/test_init.py similarity index 100% rename from tests/components/test_history.py rename to tests/components/history/test_init.py diff --git a/tests/components/history_graph/__init__.py b/tests/components/history_graph/__init__.py new file mode 100644 index 00000000000..2cb34499938 --- /dev/null +++ b/tests/components/history_graph/__init__.py @@ -0,0 +1 @@ +"""Tests for the history_graph component.""" diff --git a/tests/components/test_history_graph.py b/tests/components/history_graph/test_init.py similarity index 100% rename from tests/components/test_history_graph.py rename to tests/components/history_graph/test_init.py diff --git a/tests/components/huawei_lte/__init__.py b/tests/components/huawei_lte/__init__.py new file mode 100644 index 00000000000..79602ecfb44 --- /dev/null +++ b/tests/components/huawei_lte/__init__.py @@ -0,0 +1 @@ +"""Tests for the huawei_lte component.""" diff --git a/tests/components/test_huawei_lte.py b/tests/components/huawei_lte/test_init.py similarity index 100% rename from tests/components/test_huawei_lte.py rename to tests/components/huawei_lte/test_init.py diff --git a/tests/components/influxdb/__init__.py b/tests/components/influxdb/__init__.py new file mode 100644 index 00000000000..7a215bea197 --- /dev/null +++ b/tests/components/influxdb/__init__.py @@ -0,0 +1 @@ +"""Tests for the influxdb component.""" diff --git a/tests/components/test_influxdb.py b/tests/components/influxdb/test_init.py similarity index 100% rename from tests/components/test_influxdb.py rename to tests/components/influxdb/test_init.py diff --git a/tests/components/init/__init__.py b/tests/components/init/__init__.py new file mode 100644 index 00000000000..b935cf060c8 --- /dev/null +++ b/tests/components/init/__init__.py @@ -0,0 +1 @@ +"""Tests for the init component.""" diff --git a/tests/components/test_init.py b/tests/components/init/test_init.py similarity index 100% rename from tests/components/test_init.py rename to tests/components/init/test_init.py diff --git a/tests/components/input_boolean/__init__.py b/tests/components/input_boolean/__init__.py new file mode 100644 index 00000000000..164d6a0ba5e --- /dev/null +++ b/tests/components/input_boolean/__init__.py @@ -0,0 +1 @@ +"""Tests for the input_boolean component.""" diff --git a/tests/components/test_input_boolean.py b/tests/components/input_boolean/test_init.py similarity index 100% rename from tests/components/test_input_boolean.py rename to tests/components/input_boolean/test_init.py diff --git a/tests/components/input_datetime/__init__.py b/tests/components/input_datetime/__init__.py new file mode 100644 index 00000000000..b408528a4ff --- /dev/null +++ b/tests/components/input_datetime/__init__.py @@ -0,0 +1 @@ +"""Tests for the input_datetime component.""" diff --git a/tests/components/test_input_datetime.py b/tests/components/input_datetime/test_init.py similarity index 100% rename from tests/components/test_input_datetime.py rename to tests/components/input_datetime/test_init.py diff --git a/tests/components/input_number/__init__.py b/tests/components/input_number/__init__.py new file mode 100644 index 00000000000..e40cd77455c --- /dev/null +++ b/tests/components/input_number/__init__.py @@ -0,0 +1 @@ +"""Tests for the input_number component.""" diff --git a/tests/components/test_input_number.py b/tests/components/input_number/test_init.py similarity index 100% rename from tests/components/test_input_number.py rename to tests/components/input_number/test_init.py diff --git a/tests/components/input_select/__init__.py b/tests/components/input_select/__init__.py new file mode 100644 index 00000000000..2d817e8a59c --- /dev/null +++ b/tests/components/input_select/__init__.py @@ -0,0 +1 @@ +"""Tests for the input_select component.""" diff --git a/tests/components/test_input_select.py b/tests/components/input_select/test_init.py similarity index 100% rename from tests/components/test_input_select.py rename to tests/components/input_select/test_init.py diff --git a/tests/components/input_text/__init__.py b/tests/components/input_text/__init__.py new file mode 100644 index 00000000000..b035eed0c9e --- /dev/null +++ b/tests/components/input_text/__init__.py @@ -0,0 +1 @@ +"""Tests for the input_text component.""" diff --git a/tests/components/test_input_text.py b/tests/components/input_text/test_init.py similarity index 100% rename from tests/components/test_input_text.py rename to tests/components/input_text/test_init.py diff --git a/tests/components/intent_script/__init__.py b/tests/components/intent_script/__init__.py new file mode 100644 index 00000000000..5328df0b0b1 --- /dev/null +++ b/tests/components/intent_script/__init__.py @@ -0,0 +1 @@ +"""Tests for the intent_script component.""" diff --git a/tests/components/test_intent_script.py b/tests/components/intent_script/test_init.py similarity index 100% rename from tests/components/test_intent_script.py rename to tests/components/intent_script/test_init.py diff --git a/tests/components/introduction/__init__.py b/tests/components/introduction/__init__.py new file mode 100644 index 00000000000..99cea29581c --- /dev/null +++ b/tests/components/introduction/__init__.py @@ -0,0 +1 @@ +"""Tests for the introduction component.""" diff --git a/tests/components/test_introduction.py b/tests/components/introduction/test_init.py similarity index 100% rename from tests/components/test_introduction.py rename to tests/components/introduction/test_init.py diff --git a/tests/components/test_kira.py b/tests/components/kira/test_init.py similarity index 100% rename from tests/components/test_kira.py rename to tests/components/kira/test_init.py diff --git a/tests/components/light/test_rflink.py b/tests/components/light/test_rflink.py index 17898bd2451..e2b80d12ce0 100644 --- a/tests/components/light/test_rflink.py +++ b/tests/components/light/test_rflink.py @@ -12,7 +12,7 @@ from homeassistant.const import ( from homeassistant.core import callback, State, CoreState from tests.common import mock_restore_cache -from ..test_rflink import mock_rflink +from tests.components.rflink.test_init import mock_rflink DOMAIN = 'light' diff --git a/tests/components/litejet/__init__.py b/tests/components/litejet/__init__.py new file mode 100644 index 00000000000..9a01fbe5114 --- /dev/null +++ b/tests/components/litejet/__init__.py @@ -0,0 +1 @@ +"""Tests for the litejet component.""" diff --git a/tests/components/test_litejet.py b/tests/components/litejet/test_init.py similarity index 100% rename from tests/components/test_litejet.py rename to tests/components/litejet/test_init.py diff --git a/tests/components/logbook/__init__.py b/tests/components/logbook/__init__.py new file mode 100644 index 00000000000..31f9305a213 --- /dev/null +++ b/tests/components/logbook/__init__.py @@ -0,0 +1 @@ +"""Tests for the logbook component.""" diff --git a/tests/components/test_logbook.py b/tests/components/logbook/test_init.py similarity index 100% rename from tests/components/test_logbook.py rename to tests/components/logbook/test_init.py diff --git a/tests/components/logentries/__init__.py b/tests/components/logentries/__init__.py new file mode 100644 index 00000000000..c01e58c9d40 --- /dev/null +++ b/tests/components/logentries/__init__.py @@ -0,0 +1 @@ +"""Tests for the logentries component.""" diff --git a/tests/components/test_logentries.py b/tests/components/logentries/test_init.py similarity index 100% rename from tests/components/test_logentries.py rename to tests/components/logentries/test_init.py diff --git a/tests/components/logger/__init__.py b/tests/components/logger/__init__.py new file mode 100644 index 00000000000..8fb7f0dab02 --- /dev/null +++ b/tests/components/logger/__init__.py @@ -0,0 +1 @@ +"""Tests for the logger component.""" diff --git a/tests/components/test_logger.py b/tests/components/logger/test_init.py similarity index 100% rename from tests/components/test_logger.py rename to tests/components/logger/test_init.py diff --git a/tests/components/melissa/__init__.py b/tests/components/melissa/__init__.py new file mode 100644 index 00000000000..c4caf0fe671 --- /dev/null +++ b/tests/components/melissa/__init__.py @@ -0,0 +1 @@ +"""Tests for the melissa component.""" diff --git a/tests/components/test_melissa.py b/tests/components/melissa/test_init.py similarity index 100% rename from tests/components/test_melissa.py rename to tests/components/melissa/test_init.py diff --git a/tests/components/microsoft_face/__init__.py b/tests/components/microsoft_face/__init__.py new file mode 100644 index 00000000000..0b07c35b515 --- /dev/null +++ b/tests/components/microsoft_face/__init__.py @@ -0,0 +1 @@ +"""Tests for the microsoft_face component.""" diff --git a/tests/components/test_microsoft_face.py b/tests/components/microsoft_face/test_init.py similarity index 100% rename from tests/components/test_microsoft_face.py rename to tests/components/microsoft_face/test_init.py diff --git a/tests/components/mqtt_eventstream/__init__.py b/tests/components/mqtt_eventstream/__init__.py new file mode 100644 index 00000000000..e5c1f19d094 --- /dev/null +++ b/tests/components/mqtt_eventstream/__init__.py @@ -0,0 +1 @@ +"""Tests for the mqtt_eventstream component.""" diff --git a/tests/components/test_mqtt_eventstream.py b/tests/components/mqtt_eventstream/test_init.py similarity index 100% rename from tests/components/test_mqtt_eventstream.py rename to tests/components/mqtt_eventstream/test_init.py diff --git a/tests/components/mqtt_statestream/__init__.py b/tests/components/mqtt_statestream/__init__.py new file mode 100644 index 00000000000..cc104a104c2 --- /dev/null +++ b/tests/components/mqtt_statestream/__init__.py @@ -0,0 +1 @@ +"""Tests for the mqtt_statestream component.""" diff --git a/tests/components/test_mqtt_statestream.py b/tests/components/mqtt_statestream/test_init.py similarity index 100% rename from tests/components/test_mqtt_statestream.py rename to tests/components/mqtt_statestream/test_init.py diff --git a/tests/components/mythicbeastsdns/__init__.py b/tests/components/mythicbeastsdns/__init__.py new file mode 100644 index 00000000000..b12d296455c --- /dev/null +++ b/tests/components/mythicbeastsdns/__init__.py @@ -0,0 +1 @@ +"""Tests for the mythicbeastsdns component.""" diff --git a/tests/components/test_mythicbeastsdns.py b/tests/components/mythicbeastsdns/test_init.py similarity index 100% rename from tests/components/test_mythicbeastsdns.py rename to tests/components/mythicbeastsdns/test_init.py diff --git a/tests/components/namecheapdns/__init__.py b/tests/components/namecheapdns/__init__.py new file mode 100644 index 00000000000..db064f4405d --- /dev/null +++ b/tests/components/namecheapdns/__init__.py @@ -0,0 +1 @@ +"""Tests for the namecheapdns component.""" diff --git a/tests/components/test_namecheapdns.py b/tests/components/namecheapdns/test_init.py similarity index 100% rename from tests/components/test_namecheapdns.py rename to tests/components/namecheapdns/test_init.py diff --git a/tests/components/ness_alarm/__init__.py b/tests/components/ness_alarm/__init__.py new file mode 100644 index 00000000000..ed901068c69 --- /dev/null +++ b/tests/components/ness_alarm/__init__.py @@ -0,0 +1 @@ +"""Tests for the ness_alarm component.""" diff --git a/tests/components/test_ness_alarm.py b/tests/components/ness_alarm/test_init.py similarity index 100% rename from tests/components/test_ness_alarm.py rename to tests/components/ness_alarm/test_init.py diff --git a/tests/components/no_ip/__init__.py b/tests/components/no_ip/__init__.py new file mode 100644 index 00000000000..d2c1c62b17e --- /dev/null +++ b/tests/components/no_ip/__init__.py @@ -0,0 +1 @@ +"""Tests for the no_ip component.""" diff --git a/tests/components/test_no_ip.py b/tests/components/no_ip/test_init.py similarity index 100% rename from tests/components/test_no_ip.py rename to tests/components/no_ip/test_init.py diff --git a/tests/components/nuheat/__init__.py b/tests/components/nuheat/__init__.py new file mode 100644 index 00000000000..c238d9b0c72 --- /dev/null +++ b/tests/components/nuheat/__init__.py @@ -0,0 +1 @@ +"""Tests for the nuheat component.""" diff --git a/tests/components/test_nuheat.py b/tests/components/nuheat/test_init.py similarity index 100% rename from tests/components/test_nuheat.py rename to tests/components/nuheat/test_init.py diff --git a/tests/components/panel_custom/__init__.py b/tests/components/panel_custom/__init__.py new file mode 100644 index 00000000000..543978d21fb --- /dev/null +++ b/tests/components/panel_custom/__init__.py @@ -0,0 +1 @@ +"""Tests for the panel_custom component.""" diff --git a/tests/components/test_panel_custom.py b/tests/components/panel_custom/test_init.py similarity index 100% rename from tests/components/test_panel_custom.py rename to tests/components/panel_custom/test_init.py diff --git a/tests/components/panel_iframe/__init__.py b/tests/components/panel_iframe/__init__.py new file mode 100644 index 00000000000..df7115d9e97 --- /dev/null +++ b/tests/components/panel_iframe/__init__.py @@ -0,0 +1 @@ +"""Tests for the panel_iframe component.""" diff --git a/tests/components/test_panel_iframe.py b/tests/components/panel_iframe/test_init.py similarity index 100% rename from tests/components/test_panel_iframe.py rename to tests/components/panel_iframe/test_init.py diff --git a/tests/components/pilight/__init__.py b/tests/components/pilight/__init__.py new file mode 100644 index 00000000000..02827305936 --- /dev/null +++ b/tests/components/pilight/__init__.py @@ -0,0 +1 @@ +"""Tests for the pilight component.""" diff --git a/tests/components/test_pilight.py b/tests/components/pilight/test_init.py similarity index 100% rename from tests/components/test_pilight.py rename to tests/components/pilight/test_init.py diff --git a/tests/components/plant/__init__.py b/tests/components/plant/__init__.py new file mode 100644 index 00000000000..43a00130db9 --- /dev/null +++ b/tests/components/plant/__init__.py @@ -0,0 +1 @@ +"""Tests for the plant component.""" diff --git a/tests/components/test_plant.py b/tests/components/plant/test_init.py similarity index 100% rename from tests/components/test_plant.py rename to tests/components/plant/test_init.py diff --git a/tests/components/prometheus/__init__.py b/tests/components/prometheus/__init__.py new file mode 100644 index 00000000000..d60de3cf49c --- /dev/null +++ b/tests/components/prometheus/__init__.py @@ -0,0 +1 @@ +"""Tests for the prometheus component.""" diff --git a/tests/components/test_prometheus.py b/tests/components/prometheus/test_init.py similarity index 100% rename from tests/components/test_prometheus.py rename to tests/components/prometheus/test_init.py diff --git a/tests/components/proximity/__init__.py b/tests/components/proximity/__init__.py new file mode 100644 index 00000000000..659d609edb6 --- /dev/null +++ b/tests/components/proximity/__init__.py @@ -0,0 +1 @@ +"""Tests for the proximity component.""" diff --git a/tests/components/test_proximity.py b/tests/components/proximity/test_init.py similarity index 100% rename from tests/components/test_proximity.py rename to tests/components/proximity/test_init.py diff --git a/tests/components/python_script/__init__.py b/tests/components/python_script/__init__.py new file mode 100644 index 00000000000..893f5a7eccd --- /dev/null +++ b/tests/components/python_script/__init__.py @@ -0,0 +1 @@ +"""Tests for the python_script component.""" diff --git a/tests/components/test_python_script.py b/tests/components/python_script/test_init.py similarity index 100% rename from tests/components/test_python_script.py rename to tests/components/python_script/test_init.py diff --git a/tests/components/qwikswitch/__init__.py b/tests/components/qwikswitch/__init__.py new file mode 100644 index 00000000000..e0617b4163b --- /dev/null +++ b/tests/components/qwikswitch/__init__.py @@ -0,0 +1 @@ +"""Tests for the qwikswitch component.""" diff --git a/tests/components/test_qwikswitch.py b/tests/components/qwikswitch/test_init.py similarity index 100% rename from tests/components/test_qwikswitch.py rename to tests/components/qwikswitch/test_init.py diff --git a/tests/components/remember_the_milk/__init__.py b/tests/components/remember_the_milk/__init__.py new file mode 100644 index 00000000000..c5cc359ab76 --- /dev/null +++ b/tests/components/remember_the_milk/__init__.py @@ -0,0 +1 @@ +"""Tests for the remember_the_milk component.""" diff --git a/tests/components/test_remember_the_milk.py b/tests/components/remember_the_milk/test_init.py similarity index 100% rename from tests/components/test_remember_the_milk.py rename to tests/components/remember_the_milk/test_init.py diff --git a/tests/components/rest_command/__init__.py b/tests/components/rest_command/__init__.py new file mode 100644 index 00000000000..7fbc4588ccb --- /dev/null +++ b/tests/components/rest_command/__init__.py @@ -0,0 +1 @@ +"""Tests for the rest_command component.""" diff --git a/tests/components/test_rest_command.py b/tests/components/rest_command/test_init.py similarity index 100% rename from tests/components/test_rest_command.py rename to tests/components/rest_command/test_init.py diff --git a/tests/components/rflink/__init__.py b/tests/components/rflink/__init__.py new file mode 100644 index 00000000000..fac6bf58dd8 --- /dev/null +++ b/tests/components/rflink/__init__.py @@ -0,0 +1 @@ +"""Tests for the rflink component.""" diff --git a/tests/components/test_rflink.py b/tests/components/rflink/test_init.py similarity index 100% rename from tests/components/test_rflink.py rename to tests/components/rflink/test_init.py diff --git a/tests/components/rfxtrx/__init__.py b/tests/components/rfxtrx/__init__.py new file mode 100644 index 00000000000..81b2db8f4df --- /dev/null +++ b/tests/components/rfxtrx/__init__.py @@ -0,0 +1 @@ +"""Tests for the rfxtrx component.""" diff --git a/tests/components/test_rfxtrx.py b/tests/components/rfxtrx/test_init.py similarity index 100% rename from tests/components/test_rfxtrx.py rename to tests/components/rfxtrx/test_init.py diff --git a/tests/components/ring/__init__.py b/tests/components/ring/__init__.py new file mode 100644 index 00000000000..b159d356d5b --- /dev/null +++ b/tests/components/ring/__init__.py @@ -0,0 +1 @@ +"""Tests for the ring component.""" diff --git a/tests/components/test_ring.py b/tests/components/ring/test_init.py similarity index 100% rename from tests/components/test_ring.py rename to tests/components/ring/test_init.py diff --git a/tests/components/rss_feed_template/__init__.py b/tests/components/rss_feed_template/__init__.py new file mode 100644 index 00000000000..4200aea1e32 --- /dev/null +++ b/tests/components/rss_feed_template/__init__.py @@ -0,0 +1 @@ +"""Tests for the rss_feed_template component.""" diff --git a/tests/components/test_rss_feed_template.py b/tests/components/rss_feed_template/test_init.py similarity index 100% rename from tests/components/test_rss_feed_template.py rename to tests/components/rss_feed_template/test_init.py diff --git a/tests/components/script/__init__.py b/tests/components/script/__init__.py new file mode 100644 index 00000000000..67b9b4e3670 --- /dev/null +++ b/tests/components/script/__init__.py @@ -0,0 +1 @@ +"""Tests for the script component.""" diff --git a/tests/components/test_script.py b/tests/components/script/test_init.py similarity index 100% rename from tests/components/test_script.py rename to tests/components/script/test_init.py diff --git a/tests/components/sensor/test_canary.py b/tests/components/sensor/test_canary.py index 7908e22e579..dde8f4e0f94 100644 --- a/tests/components/sensor/test_canary.py +++ b/tests/components/sensor/test_canary.py @@ -9,7 +9,7 @@ from homeassistant.components.sensor.canary import CanarySensor, \ SENSOR_TYPES, ATTR_AIR_QUALITY, STATE_AIR_QUALITY_NORMAL, \ STATE_AIR_QUALITY_ABNORMAL, STATE_AIR_QUALITY_VERY_ABNORMAL from tests.common import (get_test_home_assistant) -from tests.components.test_canary import mock_device, mock_location +from tests.components.canary.test_init import mock_device, mock_location VALID_CONFIG = { "canary": { diff --git a/tests/components/sensor/test_rflink.py b/tests/components/sensor/test_rflink.py index 8ab568905f9..4cf75857a9a 100644 --- a/tests/components/sensor/test_rflink.py +++ b/tests/components/sensor/test_rflink.py @@ -9,7 +9,7 @@ from homeassistant.components.rflink import ( CONF_RECONNECT_INTERVAL, TMP_ENTITY, DATA_ENTITY_LOOKUP, EVENT_KEY_COMMAND, EVENT_KEY_SENSOR) from homeassistant.const import STATE_UNKNOWN -from ..test_rflink import mock_rflink +from tests.components.rflink.test_init import mock_rflink DOMAIN = 'sensor' diff --git a/tests/components/sensor/test_ring.py b/tests/components/sensor/test_ring.py index 3844079172a..c54f22af8dc 100644 --- a/tests/components/sensor/test_ring.py +++ b/tests/components/sensor/test_ring.py @@ -6,7 +6,7 @@ import requests_mock from homeassistant.components.sensor import ring from homeassistant.components import ring as base_ring -from tests.components.test_ring import ATTRIBUTION, VALID_CONFIG +from tests.components.ring.test_init import ATTRIBUTION, VALID_CONFIG from tests.common import ( get_test_config_dir, get_test_home_assistant, load_fixture) diff --git a/tests/components/sensor/test_sleepiq.py b/tests/components/sensor/test_sleepiq.py index 96787473abf..c667a6a2cdf 100644 --- a/tests/components/sensor/test_sleepiq.py +++ b/tests/components/sensor/test_sleepiq.py @@ -7,7 +7,7 @@ import requests_mock from homeassistant.setup import setup_component from homeassistant.components.sensor import sleepiq -from tests.components.test_sleepiq import mock_responses +from tests.components.sleepiq.test_init import mock_responses from tests.common import get_test_home_assistant diff --git a/tests/components/sensor/test_vultr.py b/tests/components/sensor/test_vultr.py index 294657c22ec..333e4938ba4 100644 --- a/tests/components/sensor/test_vultr.py +++ b/tests/components/sensor/test_vultr.py @@ -13,7 +13,7 @@ from homeassistant.components.vultr import CONF_SUBSCRIPTION from homeassistant.const import ( CONF_NAME, CONF_MONITORED_CONDITIONS, CONF_PLATFORM) -from tests.components.test_vultr import VALID_CONFIG +from tests.components.vultr.test_init import VALID_CONFIG from tests.common import ( get_test_home_assistant, load_fixture) diff --git a/tests/components/shell_command/__init__.py b/tests/components/shell_command/__init__.py new file mode 100644 index 00000000000..5effcdb3cce --- /dev/null +++ b/tests/components/shell_command/__init__.py @@ -0,0 +1 @@ +"""Tests for the shell_command component.""" diff --git a/tests/components/test_shell_command.py b/tests/components/shell_command/test_init.py similarity index 100% rename from tests/components/test_shell_command.py rename to tests/components/shell_command/test_init.py diff --git a/tests/components/shopping_list/__init__.py b/tests/components/shopping_list/__init__.py new file mode 100644 index 00000000000..26be3c90736 --- /dev/null +++ b/tests/components/shopping_list/__init__.py @@ -0,0 +1 @@ +"""Tests for the shopping_list component.""" diff --git a/tests/components/test_shopping_list.py b/tests/components/shopping_list/test_init.py similarity index 100% rename from tests/components/test_shopping_list.py rename to tests/components/shopping_list/test_init.py diff --git a/tests/components/sleepiq/__init__.py b/tests/components/sleepiq/__init__.py new file mode 100644 index 00000000000..751f227a003 --- /dev/null +++ b/tests/components/sleepiq/__init__.py @@ -0,0 +1 @@ +"""Tests for the sleepiq component.""" diff --git a/tests/components/test_sleepiq.py b/tests/components/sleepiq/test_init.py similarity index 100% rename from tests/components/test_sleepiq.py rename to tests/components/sleepiq/test_init.py diff --git a/tests/components/snips/__init__.py b/tests/components/snips/__init__.py new file mode 100644 index 00000000000..d7ac8b5f822 --- /dev/null +++ b/tests/components/snips/__init__.py @@ -0,0 +1 @@ +"""Tests for the snips component.""" diff --git a/tests/components/test_snips.py b/tests/components/snips/test_init.py similarity index 100% rename from tests/components/test_snips.py rename to tests/components/snips/test_init.py diff --git a/tests/components/spaceapi/__init__.py b/tests/components/spaceapi/__init__.py new file mode 100644 index 00000000000..0b24e36acb2 --- /dev/null +++ b/tests/components/spaceapi/__init__.py @@ -0,0 +1 @@ +"""Tests for the spaceapi component.""" diff --git a/tests/components/test_spaceapi.py b/tests/components/spaceapi/test_init.py similarity index 100% rename from tests/components/test_spaceapi.py rename to tests/components/spaceapi/test_init.py diff --git a/tests/components/spc/__init__.py b/tests/components/spc/__init__.py new file mode 100644 index 00000000000..a86adf13be6 --- /dev/null +++ b/tests/components/spc/__init__.py @@ -0,0 +1 @@ +"""Tests for the spc component.""" diff --git a/tests/components/test_spc.py b/tests/components/spc/test_init.py similarity index 100% rename from tests/components/test_spc.py rename to tests/components/spc/test_init.py diff --git a/tests/components/splunk/__init__.py b/tests/components/splunk/__init__.py new file mode 100644 index 00000000000..709483291e3 --- /dev/null +++ b/tests/components/splunk/__init__.py @@ -0,0 +1 @@ +"""Tests for the splunk component.""" diff --git a/tests/components/test_splunk.py b/tests/components/splunk/test_init.py similarity index 100% rename from tests/components/test_splunk.py rename to tests/components/splunk/test_init.py diff --git a/tests/components/statsd/__init__.py b/tests/components/statsd/__init__.py new file mode 100644 index 00000000000..f72ec8d5be1 --- /dev/null +++ b/tests/components/statsd/__init__.py @@ -0,0 +1 @@ +"""Tests for the statsd component.""" diff --git a/tests/components/test_statsd.py b/tests/components/statsd/test_init.py similarity index 100% rename from tests/components/test_statsd.py rename to tests/components/statsd/test_init.py diff --git a/tests/components/sun/__init__.py b/tests/components/sun/__init__.py new file mode 100644 index 00000000000..11448700dcd --- /dev/null +++ b/tests/components/sun/__init__.py @@ -0,0 +1 @@ +"""Tests for the sun component.""" diff --git a/tests/components/test_sun.py b/tests/components/sun/test_init.py similarity index 100% rename from tests/components/test_sun.py rename to tests/components/sun/test_init.py diff --git a/tests/components/switch/test_rflink.py b/tests/components/switch/test_rflink.py index b50a223fe8b..a91d18ce19e 100644 --- a/tests/components/switch/test_rflink.py +++ b/tests/components/switch/test_rflink.py @@ -11,7 +11,7 @@ from homeassistant.const import ( from homeassistant.core import callback, State, CoreState from tests.common import mock_restore_cache -from ..test_rflink import mock_rflink +from tests.components.rflink.test_init import mock_rflink DOMAIN = 'switch' diff --git a/tests/components/switch/test_vultr.py b/tests/components/switch/test_vultr.py index 699da34319a..f5e94e3e1b1 100644 --- a/tests/components/switch/test_vultr.py +++ b/tests/components/switch/test_vultr.py @@ -16,7 +16,7 @@ from homeassistant.components.vultr import ( from homeassistant.const import ( CONF_PLATFORM, CONF_NAME) -from tests.components.test_vultr import VALID_CONFIG +from tests.components.vultr.test_init import VALID_CONFIG from tests.common import ( get_test_home_assistant, load_fixture) diff --git a/tests/components/system_log/__init__.py b/tests/components/system_log/__init__.py new file mode 100644 index 00000000000..691a4f221ca --- /dev/null +++ b/tests/components/system_log/__init__.py @@ -0,0 +1 @@ +"""Tests for the system_log component.""" diff --git a/tests/components/test_system_log.py b/tests/components/system_log/test_init.py similarity index 100% rename from tests/components/test_system_log.py rename to tests/components/system_log/test_init.py diff --git a/tests/components/updater/__init__.py b/tests/components/updater/__init__.py new file mode 100644 index 00000000000..31a19cb3bf7 --- /dev/null +++ b/tests/components/updater/__init__.py @@ -0,0 +1 @@ +"""Tests for the updater component.""" diff --git a/tests/components/test_updater.py b/tests/components/updater/test_init.py similarity index 100% rename from tests/components/test_updater.py rename to tests/components/updater/test_init.py diff --git a/tests/components/vultr/__init__.py b/tests/components/vultr/__init__.py new file mode 100644 index 00000000000..fb25b7e145e --- /dev/null +++ b/tests/components/vultr/__init__.py @@ -0,0 +1 @@ +"""Tests for the vultr component.""" diff --git a/tests/components/test_vultr.py b/tests/components/vultr/test_init.py similarity index 100% rename from tests/components/test_vultr.py rename to tests/components/vultr/test_init.py diff --git a/tests/components/wake_on_lan/__init__.py b/tests/components/wake_on_lan/__init__.py new file mode 100644 index 00000000000..f691e3973f3 --- /dev/null +++ b/tests/components/wake_on_lan/__init__.py @@ -0,0 +1 @@ +"""Tests for the wake_on_lan component.""" diff --git a/tests/components/test_wake_on_lan.py b/tests/components/wake_on_lan/test_init.py similarity index 100% rename from tests/components/test_wake_on_lan.py rename to tests/components/wake_on_lan/test_init.py diff --git a/tests/components/webhook/__init__.py b/tests/components/webhook/__init__.py new file mode 100644 index 00000000000..7064c578b1c --- /dev/null +++ b/tests/components/webhook/__init__.py @@ -0,0 +1 @@ +"""Tests for the webhook component.""" diff --git a/tests/components/test_webhook.py b/tests/components/webhook/test_init.py similarity index 100% rename from tests/components/test_webhook.py rename to tests/components/webhook/test_init.py diff --git a/tests/components/weblink/__init__.py b/tests/components/weblink/__init__.py new file mode 100644 index 00000000000..1d58e9c24d6 --- /dev/null +++ b/tests/components/weblink/__init__.py @@ -0,0 +1 @@ +"""Tests for the weblink component.""" diff --git a/tests/components/test_weblink.py b/tests/components/weblink/test_init.py similarity index 100% rename from tests/components/test_weblink.py rename to tests/components/weblink/test_init.py From a1477fa15681d03a60e4fd7538b79406b4c1aa28 Mon Sep 17 00:00:00 2001 From: OleksandrBerchenko Date: Wed, 6 Feb 2019 12:39:56 +0200 Subject: [PATCH 073/242] Fix error handling in switch.broadlink module (#20772) * Fix error handling in switch.broadlink module * Improve error messages --- homeassistant/components/switch/broadlink.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/switch/broadlink.py b/homeassistant/components/switch/broadlink.py index 9c17767f033..2237a0a2977 100644 --- a/homeassistant/components/switch/broadlink.py +++ b/homeassistant/components/switch/broadlink.py @@ -235,7 +235,7 @@ class BroadlinkRMSwitch(SwitchDevice): self._device.send_data(packet) except (socket.timeout, ValueError) as error: if retry < 1: - _LOGGER.error(error) + _LOGGER.error("Error during sending a packet: %s", error) return False if not self._auth(): return False @@ -247,6 +247,8 @@ class BroadlinkRMSwitch(SwitchDevice): auth = self._device.auth() except socket.timeout: auth = False + if retry < 1: + _LOGGER.error("Timeout during authorization") if not auth and retry > 0: return self._auth(retry-1) return auth @@ -268,7 +270,7 @@ class BroadlinkSP1Switch(BroadlinkRMSwitch): self._device.set_power(packet) except (socket.timeout, ValueError) as error: if retry < 1: - _LOGGER.error(error) + _LOGGER.error("Error during sending a packet: %s", error) return False if not self._auth(): return False @@ -308,7 +310,7 @@ class BroadlinkSP2Switch(BroadlinkSP1Switch): load_power = self._device.get_energy() except (socket.timeout, ValueError) as error: if retry < 1: - _LOGGER.error(error) + _LOGGER.error("Error during updating the state: %s", error) return if not self._auth(): return @@ -341,7 +343,7 @@ class BroadlinkMP1Slot(BroadlinkRMSwitch): self._device.set_power(self._slot, packet) except (socket.timeout, ValueError) as error: if retry < 1: - _LOGGER.error(error) + _LOGGER.error("Error during sending a packet: %s", error) return False if not self._auth(): return False @@ -382,7 +384,7 @@ class BroadlinkMP1Switch: states = self._device.check_power() except (socket.timeout, ValueError) as error: if retry < 1: - _LOGGER.error(error) + _LOGGER.error("Error during updating the state: %s", error) return if not self._auth(): return From 574823fcbb93af567d4cba1909b796f8fa689820 Mon Sep 17 00:00:00 2001 From: Oleksii Serdiuk Date: Wed, 6 Feb 2019 11:40:57 +0100 Subject: [PATCH 074/242] Flux Led: Add support for defining custom effect (#19072) Flux Led controllers support defining a custom effect. User may define up to 16 colors, speed of switching between them, and transition type. Additional changes: - add support for reporting currently running effect on the controller. - make effects list sorted, so it's easier to find specific effect in the list. --- homeassistant/components/light/flux_led.py | 56 ++++++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/light/flux_led.py b/homeassistant/components/light/flux_led.py index cab6957c265..088fc871fc1 100644 --- a/homeassistant/components/light/flux_led.py +++ b/homeassistant/components/light/flux_led.py @@ -23,6 +23,10 @@ REQUIREMENTS = ['flux_led==0.22'] _LOGGER = logging.getLogger(__name__) CONF_AUTOMATIC_ADD = 'automatic_add' +CONF_CUSTOM_EFFECT = 'custom_effect' +CONF_COLORS = 'colors' +CONF_SPEED_PCT = 'speed_pct' +CONF_TRANSITION = 'transition' ATTR_MODE = 'mode' DOMAIN = 'flux_led' @@ -57,6 +61,7 @@ EFFECT_CYAN_STROBE = 'cyan_strobe' EFFECT_PURPLE_STROBE = 'purple_strobe' EFFECT_WHITE_STROBE = 'white_strobe' EFFECT_COLORJUMP = 'colorjump' +EFFECT_CUSTOM = 'custom' EFFECT_MAP = { EFFECT_COLORLOOP: 0x25, @@ -73,17 +78,32 @@ EFFECT_MAP = { EFFECT_COLORSTROBE: 0x30, EFFECT_RED_STROBE: 0x31, EFFECT_GREEN_STROBE: 0x32, - EFFECT_BLUE_STROBE: 0x33, + EFFECT_BLUE_STROBE: 0x33, EFFECT_YELLOW_STROBE: 0x34, EFFECT_CYAN_STROBE: 0x35, EFFECT_PURPLE_STROBE: 0x36, EFFECT_WHITE_STROBE: 0x37, EFFECT_COLORJUMP: 0x38 } +EFFECT_CUSTOM_CODE = 0x60 -FLUX_EFFECT_LIST = [ - EFFECT_RANDOM, - ] + list(EFFECT_MAP) +TRANSITION_GRADUAL = 'gradual' +TRANSITION_JUMP = 'jump' +TRANSITION_STROBE = 'strobe' + +FLUX_EFFECT_LIST = sorted(list(EFFECT_MAP)) + [EFFECT_RANDOM] + +CUSTOM_EFFECT_SCHEMA = vol.Schema({ + vol.Required(CONF_COLORS): + vol.All(cv.ensure_list, vol.Length(min=1, max=16), + [vol.All(vol.ExactSequence((cv.byte, cv.byte, cv.byte)), + vol.Coerce(tuple))]), + vol.Optional(CONF_SPEED_PCT, default=50): + vol.All(vol.Range(min=0, max=100), vol.Coerce(int)), + vol.Optional(CONF_TRANSITION, default=TRANSITION_GRADUAL): + vol.All(cv.string, vol.In( + [TRANSITION_GRADUAL, TRANSITION_JUMP, TRANSITION_STROBE])), +}) DEVICE_SCHEMA = vol.Schema({ vol.Optional(CONF_NAME): cv.string, @@ -91,6 +111,7 @@ DEVICE_SCHEMA = vol.Schema({ vol.All(cv.string, vol.In([MODE_RGBW, MODE_RGB, MODE_WHITE])), vol.Optional(CONF_PROTOCOL): vol.All(cv.string, vol.In(['ledenet'])), + vol.Optional(CONF_CUSTOM_EFFECT): CUSTOM_EFFECT_SCHEMA, }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -111,6 +132,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): device['ipaddr'] = ipaddr device[CONF_PROTOCOL] = device_config.get(CONF_PROTOCOL) device[ATTR_MODE] = device_config[ATTR_MODE] + device[CONF_CUSTOM_EFFECT] = device_config[CONF_CUSTOM_EFFECT] light = FluxLight(device) lights.append(light) light_ips.append(ipaddr) @@ -144,6 +166,7 @@ class FluxLight(Light): self._ipaddr = device['ipaddr'] self._protocol = device[CONF_PROTOCOL] self._mode = device[ATTR_MODE] + self._custom_effect = device[CONF_CUSTOM_EFFECT] self._bulb = None self._error_reported = False @@ -214,8 +237,25 @@ class FluxLight(Light): @property def effect_list(self): """Return the list of supported effects.""" + if self._custom_effect: + return FLUX_EFFECT_LIST + [EFFECT_CUSTOM] + return FLUX_EFFECT_LIST + @property + def effect(self): + """Return the current effect.""" + current_mode = self._bulb.raw_state[3] + + if current_mode == EFFECT_CUSTOM_CODE: + return EFFECT_CUSTOM + + for effect, code in EFFECT_MAP.items(): + if current_mode == code: + return effect + + return None + def turn_on(self, **kwargs): """Turn the specified or all lights on.""" if not self.is_on: @@ -244,6 +284,14 @@ class FluxLight(Light): random.randint(0, 255)) return + if effect == EFFECT_CUSTOM: + if self._custom_effect: + self._bulb.setCustomPattern( + self._custom_effect[CONF_COLORS], + self._custom_effect[CONF_SPEED_PCT], + self._custom_effect[CONF_TRANSITION]) + return + # Effect selection if effect in EFFECT_MAP: self._bulb.setPresetPattern(EFFECT_MAP[effect], 50) From 3de21d3fda3c32743cd23e9c83941e46fb47d2ec Mon Sep 17 00:00:00 2001 From: Eliran Turgeman Date: Wed, 6 Feb 2019 12:42:11 +0200 Subject: [PATCH 075/242] Fix waze_travel_time component ERROR on startup (#20316) * Fix waze_travel_time component ERROR on startup Fix the unhandled exception with Waze Travel Time sensor upon startup, by adding Throttle before update_interval are starting. * add missing whitespace after ',' * fix line too long (80 > 79 characters) * lint * fix interval to use const * Change to Throttle as a decorator to update Change to Throttle as a decorator to update instead of self.update = Throttle(interval)(self.update) remove unnecessary code. * fix indentations * Update waze_travel_time.py * Update waze_travel_time.py * Update waze_travel_time.py --- homeassistant/components/sensor/waze_travel_time.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/waze_travel_time.py b/homeassistant/components/sensor/waze_travel_time.py index c55c229f549..ae38c529fe2 100644 --- a/homeassistant/components/sensor/waze_travel_time.py +++ b/homeassistant/components/sensor/waze_travel_time.py @@ -16,6 +16,7 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv from homeassistant.helpers import location from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle REQUIREMENTS = ['WazeRouteCalculator==0.6'] @@ -40,6 +41,7 @@ ICON = 'mdi:car' REGIONS = ['US', 'NA', 'EU', 'IL', 'AU'] SCAN_INTERVAL = timedelta(minutes=5) +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5) TRACKABLE_DOMAINS = ['device_tracker', 'sensor', 'zone'] @@ -67,7 +69,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): sensor = WazeTravelTime(name, origin, destination, region, incl_filter, excl_filter, realtime) - add_entities([sensor]) + add_entities([sensor], True) # Wait until start event is sent to load this component. hass.bus.listen_once( @@ -182,6 +184,7 @@ class WazeTravelTime(Entity): return friendly_name + @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Fetch new state data for the sensor.""" import WazeRouteCalculator From 208f1a4a47687de242565ac1764fadf904561a87 Mon Sep 17 00:00:00 2001 From: Pawel Date: Wed, 6 Feb 2019 13:04:01 +0100 Subject: [PATCH 076/242] Allow pausing xiaomi vacuum in all states (#20620) * fix state update when no cleaning is yet performed allow pause vacuum when returning to base * revert checking of atttribute updates. Will be fixed in upstream lib. * remove unnecesarry if on pause_commadn --- homeassistant/components/xiaomi_miio/vacuum.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index 943b487857f..a6613f7c3c3 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -287,9 +287,8 @@ class MiroboVacuum(StateVacuumDevice): async def async_pause(self): """Pause the cleaning task.""" - if self.state == STATE_CLEANING: - await self._try_command( - "Unable to set start/pause: %s", self._vacuum.pause) + await self._try_command( + "Unable to set start/pause: %s", self._vacuum.pause) async def async_stop(self, **kwargs): """Stop the vacuum cleaner.""" From 65a225da7587c689bae1b4f8394478f3e1741be1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 6 Feb 2019 09:50:48 -0800 Subject: [PATCH 077/242] Make sure Locative doesn't submit invalid device IDs (#20784) --- homeassistant/components/locative/device_tracker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/locative/device_tracker.py b/homeassistant/components/locative/device_tracker.py index 20808c773f0..78090914b2c 100644 --- a/homeassistant/components/locative/device_tracker.py +++ b/homeassistant/components/locative/device_tracker.py @@ -11,6 +11,7 @@ from homeassistant.components.device_tracker import \ from homeassistant.components.locative import DOMAIN as LOCATIVE_DOMAIN from homeassistant.components.locative import TRACKER_UPDATE from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util import slugify _LOGGER = logging.getLogger(__name__) @@ -24,7 +25,7 @@ async def async_setup_entry(hass, entry, async_see): async def _set_location(device, gps_location, location_name): """Fire HA event to set location.""" await async_see( - dev_id=device, + dev_id=slugify(device), gps=gps_location, location_name=location_name ) From e6cd04d7115858198b40cb1c4ac37c08f02aee56 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Wed, 6 Feb 2019 13:33:21 -0500 Subject: [PATCH 078/242] ZHA component rewrite (#20434) * rebase reorg * update coveragerc for now * sensor cleanup * remove availability tracking for entities * finish removing changes from tests * review comments pass 1 * use asyncio.gather - review comments * review comments * cleanup - review comments * review comments * review comments * cleanup * cleanup - review comments * review comments * review comments * use signal for removal * correct comment * remove entities from gateway * remove dead module * remove accidently committed file * use named tuple - review comments * squash bugs * squash bugs * add light and sensor back to coveragerc until % is higher --- .coveragerc | 4 - homeassistant/components/zha/__init__.py | 45 +- homeassistant/components/zha/api.py | 310 +++++---- homeassistant/components/zha/binary_sensor.py | 318 +++------ homeassistant/components/zha/core/__init__.py | 7 + homeassistant/components/zha/core/const.py | 31 +- homeassistant/components/zha/core/device.py | 31 +- homeassistant/components/zha/core/gateway.py | 621 ++++++++++++------ .../components/zha/core/listeners.py | 554 +++++++++++++++- homeassistant/components/zha/device_entity.py | 134 ++-- homeassistant/components/zha/entity.py | 388 ++++------- homeassistant/components/zha/event.py | 99 --- homeassistant/components/zha/fan.py | 65 +- homeassistant/components/zha/light.py | 291 +++----- homeassistant/components/zha/sensor.py | 373 ++++------- homeassistant/components/zha/switch.py | 75 +-- tests/components/zha/conftest.py | 11 +- tests/components/zha/test_binary_sensor.py | 1 + tests/components/zha/test_fan.py | 1 + tests/components/zha/test_light.py | 1 + tests/components/zha/test_sensor.py | 1 + tests/components/zha/test_switch.py | 4 + 22 files changed, 1774 insertions(+), 1591 deletions(-) delete mode 100644 homeassistant/components/zha/event.py diff --git a/.coveragerc b/.coveragerc index 488ea50298a..1f467c93c2d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -658,7 +658,6 @@ omit = homeassistant/components/zeroconf/* homeassistant/components/zha/__init__.py homeassistant/components/zha/api.py - homeassistant/components/zha/binary_sensor.py homeassistant/components/zha/const.py homeassistant/components/zha/core/const.py homeassistant/components/zha/core/device.py @@ -667,11 +666,8 @@ omit = homeassistant/components/zha/core/listeners.py homeassistant/components/zha/device_entity.py homeassistant/components/zha/entity.py - homeassistant/components/zha/event.py - homeassistant/components/zha/fan.py homeassistant/components/zha/light.py homeassistant/components/zha/sensor.py - homeassistant/components/zha/switch.py homeassistant/components/zigbee/* homeassistant/components/zoneminder/* homeassistant/components/zwave/util.py diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index 4f9b5b04362..2e693907769 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -4,6 +4,7 @@ Support for Zigbee Home Automation devices. For more details about this component, please refer to the documentation at https://home-assistant.io/components/zha/ """ +import asyncio import logging import os import types @@ -17,14 +18,15 @@ from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE # Loading the config flow file will register the flow from . import config_flow # noqa # pylint: disable=unused-import from . import api -from .core.gateway import ZHAGateway -from .const import ( +from .core import ZHAGateway +from .core.const import ( COMPONENTS, CONF_BAUDRATE, CONF_DATABASE, CONF_DEVICE_CONFIG, CONF_RADIO_TYPE, CONF_USB_PATH, DATA_ZHA, DATA_ZHA_BRIDGE_ID, DATA_ZHA_CONFIG, DATA_ZHA_CORE_COMPONENT, DATA_ZHA_DISPATCHERS, DATA_ZHA_RADIO, DEFAULT_BAUDRATE, DEFAULT_DATABASE_NAME, - DEFAULT_RADIO_TYPE, DOMAIN, RadioType, DATA_ZHA_CORE_EVENTS, - ENABLE_QUIRKS) + DEFAULT_RADIO_TYPE, DOMAIN, RadioType, DATA_ZHA_CORE_EVENTS, ENABLE_QUIRKS) +from .core.gateway import establish_device_mappings +from .core.listeners import populate_listener_registry REQUIREMENTS = [ 'bellows==0.7.0', @@ -87,9 +89,16 @@ async def async_setup_entry(hass, config_entry): Will automatically load components to support devices found on the network. """ + establish_device_mappings() + populate_listener_registry() + + for component in COMPONENTS: + hass.data[DATA_ZHA][component] = ( + hass.data[DATA_ZHA].get(component, {}) + ) + hass.data[DATA_ZHA] = hass.data.get(DATA_ZHA, {}) hass.data[DATA_ZHA][DATA_ZHA_DISPATCHERS] = [] - config = hass.data[DATA_ZHA].get(DATA_ZHA_CONFIG, {}) if config.get(ENABLE_QUIRKS, True): @@ -137,14 +146,32 @@ async def async_setup_entry(hass, config_entry): ClusterPersistingListener ) - application_controller = ControllerApplication(radio, database) zha_gateway = ZHAGateway(hass, config) + hass.bus.async_listen_once( + ha_const.EVENT_HOMEASSISTANT_START, zha_gateway.accept_zigbee_messages) + + # Patch handle_message until zigpy can provide an event here + def handle_message(sender, is_reply, profile, cluster, + src_ep, dst_ep, tsn, command_id, args): + """Handle message from a device.""" + if sender.last_seen is None and not sender.initializing: + if sender.ieee in zha_gateway.devices: + device = zha_gateway.devices[sender.ieee] + device.update_available(True) + return sender.handle_message( + is_reply, profile, cluster, src_ep, dst_ep, tsn, command_id, args) + + application_controller = ControllerApplication(radio, database) + application_controller.handle_message = handle_message application_controller.add_listener(zha_gateway) await application_controller.startup(auto_form=True) + hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = str(application_controller.ieee) + + init_tasks = [] for device in application_controller.devices.values(): - hass.async_create_task( - zha_gateway.async_device_initialized(device, False)) + init_tasks.append(zha_gateway.async_device_initialized(device, False)) + await asyncio.gather(*init_tasks) device_registry = await \ hass.helpers.device_registry.async_get_registry() @@ -157,8 +184,6 @@ async def async_setup_entry(hass, config_entry): model=radio_description, ) - hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = str(application_controller.ieee) - for component in COMPONENTS: hass.async_create_task( hass.config_entries.async_forward_entry_setup( diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index 0312a40967f..c412cb9fef0 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -11,8 +11,7 @@ import voluptuous as vol from homeassistant.components import websocket_api from homeassistant.const import ATTR_ENTITY_ID import homeassistant.helpers.config_validation as cv -from .device_entity import ZhaDeviceEntity -from .const import ( +from .core.const import ( DOMAIN, ATTR_CLUSTER_ID, ATTR_CLUSTER_TYPE, ATTR_ATTRIBUTE, ATTR_VALUE, ATTR_MANUFACTURER, ATTR_COMMAND, ATTR_COMMAND_TYPE, ATTR_ARGS, IN, OUT, CLIENT_COMMANDS, SERVER_COMMANDS, SERVER) @@ -118,115 +117,7 @@ SCHEMA_WS_CLUSTER_COMMANDS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ }) -@websocket_api.async_response -async def websocket_entity_cluster_attributes(hass, connection, msg): - """Return a list of cluster attributes.""" - entity_id = msg[ATTR_ENTITY_ID] - cluster_id = msg[ATTR_CLUSTER_ID] - cluster_type = msg[ATTR_CLUSTER_TYPE] - component = hass.data.get(entity_id.split('.')[0]) - entity = component.get_entity(entity_id) - cluster_attributes = [] - if entity is not None: - res = await entity.get_cluster_attributes(cluster_id, cluster_type) - if res is not None: - for attr_id in res: - cluster_attributes.append( - { - ID: attr_id, - NAME: res[attr_id][0] - } - ) - _LOGGER.debug("Requested attributes for: %s %s %s %s", - "{}: [{}]".format(ATTR_CLUSTER_ID, cluster_id), - "{}: [{}]".format(ATTR_CLUSTER_TYPE, cluster_type), - "{}: [{}]".format(ATTR_ENTITY_ID, entity_id), - "{}: [{}]".format(RESPONSE, cluster_attributes) - ) - - connection.send_message(websocket_api.result_message( - msg[ID], - cluster_attributes - )) - - -@websocket_api.async_response -async def websocket_entity_cluster_commands(hass, connection, msg): - """Return a list of cluster commands.""" - entity_id = msg[ATTR_ENTITY_ID] - cluster_id = msg[ATTR_CLUSTER_ID] - cluster_type = msg[ATTR_CLUSTER_TYPE] - component = hass.data.get(entity_id.split('.')[0]) - entity = component.get_entity(entity_id) - cluster_commands = [] - if entity is not None: - res = await entity.get_cluster_commands(cluster_id, cluster_type) - if res is not None: - for cmd_id in res[CLIENT_COMMANDS]: - cluster_commands.append( - { - TYPE: CLIENT, - ID: cmd_id, - NAME: res[CLIENT_COMMANDS][cmd_id][0] - } - ) - for cmd_id in res[SERVER_COMMANDS]: - cluster_commands.append( - { - TYPE: SERVER, - ID: cmd_id, - NAME: res[SERVER_COMMANDS][cmd_id][0] - } - ) - _LOGGER.debug("Requested commands for: %s %s %s %s", - "{}: [{}]".format(ATTR_CLUSTER_ID, cluster_id), - "{}: [{}]".format(ATTR_CLUSTER_TYPE, cluster_type), - "{}: [{}]".format(ATTR_ENTITY_ID, entity_id), - "{}: [{}]".format(RESPONSE, cluster_commands) - ) - - connection.send_message(websocket_api.result_message( - msg[ID], - cluster_commands - )) - - -@websocket_api.async_response -async def websocket_read_zigbee_cluster_attributes(hass, connection, msg): - """Read zigbee attribute for cluster on zha entity.""" - entity_id = msg[ATTR_ENTITY_ID] - cluster_id = msg[ATTR_CLUSTER_ID] - cluster_type = msg[ATTR_CLUSTER_TYPE] - attribute = msg[ATTR_ATTRIBUTE] - component = hass.data.get(entity_id.split('.')[0]) - entity = component.get_entity(entity_id) - clusters = await entity.get_clusters() - cluster = clusters[cluster_type][cluster_id] - manufacturer = msg.get(ATTR_MANUFACTURER) or None - success = failure = None - if entity is not None: - success, failure = await cluster.read_attributes( - [attribute], - allow_cache=False, - only_cache=False, - manufacturer=manufacturer - ) - _LOGGER.debug("Read attribute for: %s %s %s %s %s %s %s", - "{}: [{}]".format(ATTR_CLUSTER_ID, cluster_id), - "{}: [{}]".format(ATTR_CLUSTER_TYPE, cluster_type), - "{}: [{}]".format(ATTR_ENTITY_ID, entity_id), - "{}: [{}]".format(ATTR_ATTRIBUTE, attribute), - "{}: [{}]".format(ATTR_MANUFACTURER, manufacturer), - "{}: [{}]".format(RESPONSE, str(success.get(attribute))), - "{}: [{}]".format('failure', failure) - ) - connection.send_message(websocket_api.result_message( - msg[ID], - str(success.get(attribute)) - )) - - -def async_load_api(hass, application_controller, listener): +def async_load_api(hass, application_controller, zha_gateway): """Set up the web socket API.""" async def permit(service): """Allow devices to join this network.""" @@ -256,11 +147,12 @@ def async_load_api(hass, application_controller, listener): attribute = service.data.get(ATTR_ATTRIBUTE) value = service.data.get(ATTR_VALUE) manufacturer = service.data.get(ATTR_MANUFACTURER) or None - component = hass.data.get(entity_id.split('.')[0]) - entity = component.get_entity(entity_id) + entity_ref = zha_gateway.get_entity_reference(entity_id) response = None - if entity is not None: - response = await entity.write_zigbe_attribute( + if entity_ref is not None: + response = await entity_ref.zha_device.write_zigbee_attribute( + list(entity_ref.cluster_listeners.values())[ + 0].cluster.endpoint.endpoint_id, cluster_id, attribute, value, @@ -292,11 +184,13 @@ def async_load_api(hass, application_controller, listener): command_type = service.data.get(ATTR_COMMAND_TYPE) args = service.data.get(ATTR_ARGS) manufacturer = service.data.get(ATTR_MANUFACTURER) or None - component = hass.data.get(entity_id.split('.')[0]) - entity = component.get_entity(entity_id) + entity_ref = zha_gateway.get_entity_reference(entity_id) + zha_device = entity_ref.zha_device response = None - if entity is not None: - response = await entity.issue_cluster_command( + if entity_ref is not None: + response = await zha_device.issue_cluster_command( + list(entity_ref.cluster_listeners.values())[ + 0].cluster.endpoint.endpoint_id, cluster_id, command, command_type, @@ -325,11 +219,9 @@ def async_load_api(hass, application_controller, listener): async def websocket_reconfigure_node(hass, connection, msg): """Reconfigure a ZHA nodes entities by its ieee address.""" ieee = msg[ATTR_IEEE] - entities = listener.get_entities_for_ieee(ieee) + device = zha_gateway.get_device(ieee) _LOGGER.debug("Reconfiguring node with ieee_address: %s", ieee) - for entity in entities: - if hasattr(entity, 'async_configure'): - hass.async_create_task(entity.async_configure()) + hass.async_create_task(device.async_configure()) hass.components.websocket_api.async_register_command( WS_RECONFIGURE_NODE, websocket_reconfigure_node, @@ -340,15 +232,15 @@ def async_load_api(hass, application_controller, listener): async def websocket_entities_by_ieee(hass, connection, msg): """Return a dict of all zha entities grouped by ieee.""" entities_by_ieee = {} - for ieee, entities in listener.device_registry.items(): + for ieee, entities in zha_gateway.device_registry.items(): ieee_string = str(ieee) entities_by_ieee[ieee_string] = [] for entity in entities: - if not isinstance(entity, ZhaDeviceEntity): - entities_by_ieee[ieee_string].append({ - ATTR_ENTITY_ID: entity.entity_id, - DEVICE_INFO: entity.device_info - }) + entities_by_ieee[ieee_string].append({ + ATTR_ENTITY_ID: entity.reference_id, + DEVICE_INFO: entity.device_info + }) + connection.send_message(websocket_api.result_message( msg[ID], entities_by_ieee @@ -363,24 +255,25 @@ def async_load_api(hass, application_controller, listener): async def websocket_entity_clusters(hass, connection, msg): """Return a list of entity clusters.""" entity_id = msg[ATTR_ENTITY_ID] - entities = listener.get_entities_for_ieee(msg[ATTR_IEEE]) - entity = next( - ent for ent in entities if ent.entity_id == entity_id) - entity_clusters = await entity.get_clusters() + entity_ref = zha_gateway.get_entity_reference(entity_id) clusters = [] - - for cluster_id, cluster in entity_clusters[IN].items(): - clusters.append({ - TYPE: IN, - ID: cluster_id, - NAME: cluster.__class__.__name__ - }) - for cluster_id, cluster in entity_clusters[OUT].items(): - clusters.append({ - TYPE: OUT, - ID: cluster_id, - NAME: cluster.__class__.__name__ - }) + if entity_ref is not None: + for listener in entity_ref.cluster_listeners.values(): + cluster = listener.cluster + in_clusters = cluster.endpoint.in_clusters.values() + out_clusters = cluster.endpoint.out_clusters.values() + if cluster in in_clusters: + clusters.append({ + TYPE: IN, + ID: cluster.cluster_id, + NAME: cluster.__class__.__name__ + }) + elif cluster in out_clusters: + clusters.append({ + TYPE: OUT, + ID: cluster.cluster_id, + NAME: cluster.__class__.__name__ + }) connection.send_message(websocket_api.result_message( msg[ID], @@ -392,16 +285,141 @@ def async_load_api(hass, application_controller, listener): SCHEMA_WS_CLUSTERS ) + @websocket_api.async_response + async def websocket_entity_cluster_attributes(hass, connection, msg): + """Return a list of cluster attributes.""" + entity_id = msg[ATTR_ENTITY_ID] + cluster_id = msg[ATTR_CLUSTER_ID] + cluster_type = msg[ATTR_CLUSTER_TYPE] + ieee = msg[ATTR_IEEE] + cluster_attributes = [] + entity_ref = zha_gateway.get_entity_reference(entity_id) + device = zha_gateway.get_device(ieee) + attributes = None + if entity_ref is not None: + attributes = await device.get_cluster_attributes( + list(entity_ref.cluster_listeners.values())[ + 0].cluster.endpoint.endpoint_id, + cluster_id, + cluster_type) + if attributes is not None: + for attr_id in attributes: + cluster_attributes.append( + { + ID: attr_id, + NAME: attributes[attr_id][0] + } + ) + _LOGGER.debug("Requested attributes for: %s %s %s %s", + "{}: [{}]".format(ATTR_CLUSTER_ID, cluster_id), + "{}: [{}]".format(ATTR_CLUSTER_TYPE, cluster_type), + "{}: [{}]".format(ATTR_ENTITY_ID, entity_id), + "{}: [{}]".format(RESPONSE, cluster_attributes) + ) + + connection.send_message(websocket_api.result_message( + msg[ID], + cluster_attributes + )) + hass.components.websocket_api.async_register_command( WS_ENTITY_CLUSTER_ATTRIBUTES, websocket_entity_cluster_attributes, SCHEMA_WS_CLUSTER_ATTRIBUTES ) + @websocket_api.async_response + async def websocket_entity_cluster_commands(hass, connection, msg): + """Return a list of cluster commands.""" + entity_id = msg[ATTR_ENTITY_ID] + cluster_id = msg[ATTR_CLUSTER_ID] + cluster_type = msg[ATTR_CLUSTER_TYPE] + ieee = msg[ATTR_IEEE] + entity_ref = zha_gateway.get_entity_reference(entity_id) + device = zha_gateway.get_device(ieee) + cluster_commands = [] + commands = None + if entity_ref is not None: + commands = await device.get_cluster_commands( + list(entity_ref.cluster_listeners.values())[ + 0].cluster.endpoint.endpoint_id, + cluster_id, + cluster_type) + + if commands is not None: + for cmd_id in commands[CLIENT_COMMANDS]: + cluster_commands.append( + { + TYPE: CLIENT, + ID: cmd_id, + NAME: commands[CLIENT_COMMANDS][cmd_id][0] + } + ) + for cmd_id in commands[SERVER_COMMANDS]: + cluster_commands.append( + { + TYPE: SERVER, + ID: cmd_id, + NAME: commands[SERVER_COMMANDS][cmd_id][0] + } + ) + _LOGGER.debug("Requested commands for: %s %s %s %s", + "{}: [{}]".format(ATTR_CLUSTER_ID, cluster_id), + "{}: [{}]".format(ATTR_CLUSTER_TYPE, cluster_type), + "{}: [{}]".format(ATTR_ENTITY_ID, entity_id), + "{}: [{}]".format(RESPONSE, cluster_commands) + ) + + connection.send_message(websocket_api.result_message( + msg[ID], + cluster_commands + )) + hass.components.websocket_api.async_register_command( WS_ENTITY_CLUSTER_COMMANDS, websocket_entity_cluster_commands, SCHEMA_WS_CLUSTER_COMMANDS ) + @websocket_api.async_response + async def websocket_read_zigbee_cluster_attributes(hass, connection, msg): + """Read zigbee attribute for cluster on zha entity.""" + entity_id = msg[ATTR_ENTITY_ID] + cluster_id = msg[ATTR_CLUSTER_ID] + cluster_type = msg[ATTR_CLUSTER_TYPE] + attribute = msg[ATTR_ATTRIBUTE] + entity_ref = zha_gateway.get_entity_reference(entity_id) + manufacturer = msg.get(ATTR_MANUFACTURER) or None + success = failure = None + clusters = [] + if cluster_type == IN: + clusters = \ + list(entity_ref.cluster_listeners.values())[ + 0].cluster.endpoint.in_clusters + else: + clusters = \ + list(entity_ref.cluster_listeners.values())[ + 0].cluster.endpoint.out_clusters + cluster = clusters[cluster_id] + if entity_ref is not None: + success, failure = await cluster.read_attributes( + [attribute], + allow_cache=False, + only_cache=False, + manufacturer=manufacturer + ) + _LOGGER.debug("Read attribute for: %s %s %s %s %s %s %s", + "{}: [{}]".format(ATTR_CLUSTER_ID, cluster_id), + "{}: [{}]".format(ATTR_CLUSTER_TYPE, cluster_type), + "{}: [{}]".format(ATTR_ENTITY_ID, entity_id), + "{}: [{}]".format(ATTR_ATTRIBUTE, attribute), + "{}: [{}]".format(ATTR_MANUFACTURER, manufacturer), + "{}: [{}]".format(RESPONSE, str(success.get(attribute))), + "{}: [{}]".format('failure', failure) + ) + connection.send_message(websocket_api.result_message( + msg[ID], + str(success.get(attribute)) + )) + hass.components.websocket_api.async_register_command( WS_READ_CLUSTER_ATTRIBUTE, websocket_read_zigbee_cluster_attributes, SCHEMA_WS_READ_CLUSTER_ATTRIBUTE diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index d0f23ff3dd2..1f85373eecc 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -7,16 +7,13 @@ at https://home-assistant.io/components/binary_sensor.zha/ import logging from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice -from homeassistant.const import STATE_ON from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.restore_state import RestoreEntity -from .core import helpers from .core.const import ( - DATA_ZHA, DATA_ZHA_DISPATCHERS, REPORT_CONFIG_IMMEDIATE, ZHA_DISCOVERY_NEW) + DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW, LISTENER_ON_OFF, + LISTENER_LEVEL, LISTENER_ZONE, SIGNAL_ATTR_UPDATED, SIGNAL_MOVE_LEVEL, + SIGNAL_SET_LEVEL, LISTENER_ATTRIBUTE, UNKNOWN, OPENING, ZONE, OCCUPANCY, + ATTR_LEVEL, SENSOR_TYPE) from .entity import ZhaEntity -from .core.listeners import ( - OnOffListener, LevelListener -) _LOGGER = logging.getLogger(__name__) @@ -31,7 +28,20 @@ CLASS_MAPPING = { 0x002b: 'gas', 0x002d: 'vibration', } -DEVICE_CLASS_OCCUPANCY = 'occupancy' + + +async def get_ias_device_class(listener): + """Get the HA device class from the listener.""" + zone_type = await listener.get_attribute_value('zone_type') + return CLASS_MAPPING.get(zone_type) + + +DEVICE_CLASS_REGISTRY = { + UNKNOWN: None, + OPENING: OPENING, + ZONE: get_ias_device_class, + OCCUPANCY: OCCUPANCY, +} async def async_setup_platform(hass, config, async_add_entities, @@ -60,249 +70,60 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async def _async_setup_entities(hass, config_entry, async_add_entities, discovery_infos): """Set up the ZHA binary sensors.""" - from zigpy.zcl.clusters.general import OnOff - from zigpy.zcl.clusters.measurement import OccupancySensing - from zigpy.zcl.clusters.security import IasZone - entities = [] for discovery_info in discovery_infos: - if IasZone.cluster_id in discovery_info['in_clusters']: - entities.append(await _async_setup_iaszone(discovery_info)) - elif OccupancySensing.cluster_id in discovery_info['in_clusters']: - entities.append( - BinarySensor(DEVICE_CLASS_OCCUPANCY, **discovery_info)) - elif OnOff.cluster_id in discovery_info['out_clusters']: - entities.append(Remote(**discovery_info)) + entities.append(BinarySensor(**discovery_info)) async_add_entities(entities, update_before_add=True) -async def _async_setup_iaszone(discovery_info): - device_class = None - from zigpy.zcl.clusters.security import IasZone - cluster = discovery_info['in_clusters'][IasZone.cluster_id] - - try: - zone_type = await cluster['zone_type'] - device_class = CLASS_MAPPING.get(zone_type, None) - except Exception: # pylint: disable=broad-except - # If we fail to read from the device, use a non-specific class - pass - - return IasZoneSensor(device_class, **discovery_info) - - -class IasZoneSensor(RestoreEntity, ZhaEntity, BinarySensorDevice): - """The IasZoneSensor Binary Sensor.""" - - _domain = DOMAIN - - def __init__(self, device_class, **kwargs): - """Initialize the ZHA binary sensor.""" - super().__init__(**kwargs) - self._device_class = device_class - from zigpy.zcl.clusters.security import IasZone - self._ias_zone_cluster = self._in_clusters[IasZone.cluster_id] - - @property - def is_on(self) -> bool: - """Return True if entity is on.""" - if self._state is None: - return False - return bool(self._state) - - @property - def device_class(self): - """Return the class of this device, from component DEVICE_CLASSES.""" - return self._device_class - - def cluster_command(self, tsn, command_id, args): - """Handle commands received to this cluster.""" - if command_id == 0: - self._state = args[0] & 3 - _LOGGER.debug("Updated alarm state: %s", self._state) - self.async_schedule_update_ha_state() - elif command_id == 1: - _LOGGER.debug("Enroll requested") - res = self._ias_zone_cluster.enroll_response(0, 0) - self.hass.async_add_job(res) - - async def async_added_to_hass(self): - """Run when about to be added to hass.""" - await super().async_added_to_hass() - old_state = await self.async_get_last_state() - if self._state is not None or old_state is None: - return - - _LOGGER.debug("%s restoring old state: %s", self.entity_id, old_state) - if old_state.state == STATE_ON: - self._state = 3 - else: - self._state = 0 - - async def async_configure(self): - """Configure IAS device.""" - await self._ias_zone_cluster.bind() - ieee = self._ias_zone_cluster.endpoint.device.application.ieee - await self._ias_zone_cluster.write_attributes({'cie_addr': ieee}) - _LOGGER.debug("%s: finished configuration", self.entity_id) - - async def async_update(self): - """Retrieve latest state.""" - from zigpy.types.basic import uint16_t - - result = await helpers.safe_read(self._endpoint.ias_zone, - ['zone_status'], - allow_cache=False, - only_cache=(not self._initialized)) - state = result.get('zone_status', self._state) - if isinstance(state, (int, uint16_t)): - self._state = result.get('zone_status', self._state) & 3 - - -class Remote(RestoreEntity, ZhaEntity, BinarySensorDevice): - """ZHA switch/remote controller/button.""" - - _domain = DOMAIN - - def __init__(self, **kwargs): - """Initialize Switch.""" - super().__init__(**kwargs) - self._level = 0 - from zigpy.zcl.clusters import general - self._out_listeners = { - general.OnOff.cluster_id: OnOffListener( - self, - self._out_clusters[general.OnOff.cluster_id] - ) - } - - out_clusters = kwargs.get('out_clusters') - self._zcl_reporting = {} - - if general.LevelControl.cluster_id in out_clusters: - self._out_listeners.update({ - general.LevelControl.cluster_id: LevelListener( - self, - out_clusters[general.LevelControl.cluster_id] - ) - }) - - @property - def is_on(self) -> bool: - """Return true if the binary sensor is on.""" - return self._state - - @property - def device_state_attributes(self): - """Return the device state attributes.""" - self._device_state_attributes.update({ - 'level': self._state and self._level or 0 - }) - return self._device_state_attributes - - @property - def zcl_reporting_config(self): - """Return ZCL attribute reporting configuration.""" - return self._zcl_reporting - - def move_level(self, change): - """Increment the level, setting state if appropriate.""" - if not self._state and change > 0: - self._level = 0 - self._level = min(255, max(0, self._level + change)) - self._state = bool(self._level) - self.async_schedule_update_ha_state() - - def set_level(self, level): - """Set the level, setting state if appropriate.""" - self._level = level - self._state = bool(self._level) - self.async_schedule_update_ha_state() - - def set_state(self, state): - """Set the state.""" - self._state = state - if self._level == 0: - self._level = 255 - self.async_schedule_update_ha_state() - - async def async_configure(self): - """Bind clusters.""" - from zigpy.zcl.clusters import general - await helpers.bind_cluster( - self.entity_id, - self._out_clusters[general.OnOff.cluster_id] - ) - if general.LevelControl.cluster_id in self._out_clusters: - await helpers.bind_cluster( - self.entity_id, - self._out_clusters[general.LevelControl.cluster_id] - ) - - async def async_added_to_hass(self): - """Run when about to be added to hass.""" - await super().async_added_to_hass() - old_state = await self.async_get_last_state() - if self._state is not None or old_state is None: - return - - _LOGGER.debug("%s restoring old state: %s", self.entity_id, old_state) - if 'level' in old_state.attributes: - self._level = old_state.attributes['level'] - self._state = old_state.state == STATE_ON - - async def async_update(self): - """Retrieve latest state.""" - from zigpy.zcl.clusters.general import OnOff - result = await helpers.safe_read( - self._endpoint.out_clusters[OnOff.cluster_id], - ['on_off'], - allow_cache=False, - only_cache=(not self._initialized) - ) - self._state = result.get('on_off', self._state) - - -class BinarySensor(RestoreEntity, ZhaEntity, BinarySensorDevice): - """ZHA switch.""" +class BinarySensor(ZhaEntity, BinarySensorDevice): + """ZHA BinarySensor.""" _domain = DOMAIN _device_class = None - value_attribute = 0 - def __init__(self, device_class, **kwargs): + def __init__(self, **kwargs): """Initialize the ZHA binary sensor.""" super().__init__(**kwargs) - self._device_class = device_class - self._cluster = list(kwargs['in_clusters'].values())[0] + self._device_state_attributes = {} + self._zone_listener = self.cluster_listeners.get(LISTENER_ZONE) + self._on_off_listener = self.cluster_listeners.get(LISTENER_ON_OFF) + self._level_listener = self.cluster_listeners.get(LISTENER_LEVEL) + self._attr_listener = self.cluster_listeners.get(LISTENER_ATTRIBUTE) + self._zha_sensor_type = kwargs[SENSOR_TYPE] + self._level = None - def attribute_updated(self, attribute, value): - """Handle attribute update from device.""" - _LOGGER.debug("Attribute updated: %s %s %s", self, attribute, value) - if attribute == self.value_attribute: - self._state = bool(value) - self.async_schedule_update_ha_state() + async def _determine_device_class(self): + """Determine the device class for this binary sensor.""" + device_class_supplier = DEVICE_CLASS_REGISTRY.get( + self._zha_sensor_type) + if callable(device_class_supplier): + listener = self.cluster_listeners.get(self._zha_sensor_type) + if listener is None: + return None + return await device_class_supplier(listener) + return device_class_supplier async def async_added_to_hass(self): """Run when about to be added to hass.""" + self._device_class = await self._determine_device_class() await super().async_added_to_hass() - old_state = await self.async_get_last_state() - if self._state is not None or old_state is None: - return - - _LOGGER.debug("%s restoring old state: %s", self.entity_id, old_state) - self._state = old_state.state == STATE_ON - - @property - def cluster(self): - """Zigbee cluster for this entity.""" - return self._cluster - - @property - def zcl_reporting_config(self): - """ZHA reporting configuration.""" - return {self.cluster: {self.value_attribute: REPORT_CONFIG_IMMEDIATE}} + if self._level_listener: + await self.async_accept_signal( + self._level_listener, SIGNAL_SET_LEVEL, self.set_level) + await self.async_accept_signal( + self._level_listener, SIGNAL_MOVE_LEVEL, self.move_level) + if self._on_off_listener: + await self.async_accept_signal( + self._on_off_listener, SIGNAL_ATTR_UPDATED, + self.async_set_state) + if self._zone_listener: + await self.async_accept_signal( + self._zone_listener, SIGNAL_ATTR_UPDATED, self.async_set_state) + if self._attr_listener: + await self.async_accept_signal( + self._attr_listener, SIGNAL_ATTR_UPDATED, self.async_set_state) @property def is_on(self) -> bool: @@ -315,3 +136,32 @@ class BinarySensor(RestoreEntity, ZhaEntity, BinarySensorDevice): def device_class(self) -> str: """Return device class from component DEVICE_CLASSES.""" return self._device_class + + def async_set_state(self, state): + """Set the state.""" + self._state = bool(state) + self.async_schedule_update_ha_state() + + def move_level(self, change): + """Increment the level, setting state if appropriate.""" + level = self._level or 0 + if not self._state and change > 0: + level = 0 + self._level = min(254, max(0, level + change)) + self._state = bool(self._level) + self.async_schedule_update_ha_state() + + def set_level(self, level): + """Set the level, setting state if appropriate.""" + self._level = level + self._state = bool(level) + self.async_schedule_update_ha_state() + + @property + def device_state_attributes(self): + """Return the device state attributes.""" + if self._level_listener is not None: + self._device_state_attributes.update({ + ATTR_LEVEL: self._state and self._level or 0 + }) + return self._device_state_attributes diff --git a/homeassistant/components/zha/core/__init__.py b/homeassistant/components/zha/core/__init__.py index 47e6ed2b0ee..e7443e7e0b7 100644 --- a/homeassistant/components/zha/core/__init__.py +++ b/homeassistant/components/zha/core/__init__.py @@ -4,3 +4,10 @@ Core module for Zigbee Home Automation. For more details about this component, please refer to the documentation at https://home-assistant.io/components/zha/ """ + +# flake8: noqa +from .device import ZHADevice +from .gateway import ZHAGateway +from .listeners import ( + ClusterListener, AttributeListener, OnOffListener, LevelListener, + IASZoneListener, ActivePowerListener, BatteryListener, EventRelayListener) diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 3069ebf02db..cb3a311c985 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -55,10 +55,38 @@ IEEE = 'ieee' MODEL = 'model' NAME = 'name' +SENSOR_TYPE = 'sensor_type' +HUMIDITY = 'humidity' +TEMPERATURE = 'temperature' +ILLUMINANCE = 'illuminance' +PRESSURE = 'pressure' +METERING = 'metering' +ELECTRICAL_MEASUREMENT = 'electrical_measurement' +POWER_CONFIGURATION = 'power_configuration' +GENERIC = 'generic' +UNKNOWN = 'unknown' +OPENING = 'opening' +ZONE = 'zone' +OCCUPANCY = 'occupancy' + +ATTR_LEVEL = 'level' + +LISTENER_ON_OFF = 'on_off' +LISTENER_ATTRIBUTE = 'attribute' +LISTENER_COLOR = 'color' +LISTENER_FAN = 'fan' +LISTENER_LEVEL = ATTR_LEVEL +LISTENER_ZONE = 'zone' +LISTENER_ACTIVE_POWER = 'active_power' LISTENER_BATTERY = 'battery' +LISTENER_EVENT_RELAY = 'event_relay' SIGNAL_ATTR_UPDATED = 'attribute_updated' +SIGNAL_MOVE_LEVEL = "move_level" +SIGNAL_SET_LEVEL = "set_level" +SIGNAL_STATE_ATTR = "update_state_attribute" SIGNAL_AVAILABLE = 'available' +SIGNAL_REMOVE = 'remove' class RadioType(enum.Enum): @@ -78,9 +106,10 @@ DISCOVERY_KEY = 'zha_discovery_info' DEVICE_CLASS = {} SINGLE_INPUT_CLUSTER_DEVICE_CLASS = {} SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS = {} +CLUSTER_REPORT_CONFIGS = {} CUSTOM_CLUSTER_MAPPINGS = {} COMPONENT_CLUSTERS = {} -EVENTABLE_CLUSTERS = [] +EVENT_RELAY_CLUSTERS = [] REPORT_CONFIG_MAX_INT = 900 REPORT_CONFIG_MAX_INT_BATTERY_SAVE = 10800 diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index c7dabced24b..292f9817671 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -14,7 +14,7 @@ from .const import ( ATTR_MANUFACTURER, LISTENER_BATTERY, SIGNAL_AVAILABLE, IN, OUT, ATTR_CLUSTER_ID, ATTR_ATTRIBUTE, ATTR_VALUE, ATTR_COMMAND, SERVER, ATTR_COMMAND_TYPE, ATTR_ARGS, CLIENT_COMMANDS, SERVER_COMMANDS, - ATTR_ENDPOINT_ID, IEEE, MODEL, NAME + ATTR_ENDPOINT_ID, IEEE, MODEL, NAME, UNKNOWN ) from .listeners import EventRelayListener @@ -30,11 +30,14 @@ class ZHADevice: self._zigpy_device = zigpy_device # Get first non ZDO endpoint id to use to get manufacturer and model endpoint_ids = zigpy_device.endpoints.keys() - ept_id = next(ept_id for ept_id in endpoint_ids if ept_id != 0) - self._manufacturer = zigpy_device.endpoints[ept_id].manufacturer - self._model = zigpy_device.endpoints[ept_id].model + self._manufacturer = UNKNOWN + self._model = UNKNOWN + ept_id = next((ept_id for ept_id in endpoint_ids if ept_id != 0), None) + if ept_id is not None: + self._manufacturer = zigpy_device.endpoints[ept_id].manufacturer + self._model = zigpy_device.endpoints[ept_id].model self._zha_gateway = zha_gateway - self._cluster_listeners = {} + self.cluster_listeners = {} self._relay_listeners = [] self._all_listeners = [] self._name = "{} {}".format( @@ -101,21 +104,11 @@ class ZHADevice: """Return the gateway for this device.""" return self._zha_gateway - @property - def cluster_listeners(self): - """Return cluster listeners for device.""" - return self._cluster_listeners.values() - @property def all_listeners(self): """Return cluster listeners and relay listeners for device.""" return self._all_listeners - @property - def cluster_listener_keys(self): - """Return cluster listeners for device.""" - return self._cluster_listeners.keys() - @property def available_signal(self): """Signal to use to subscribe to device availability changes.""" @@ -157,17 +150,13 @@ class ZHADevice: """Add cluster listener to device.""" # only keep 1 power listener if cluster_listener.name is LISTENER_BATTERY and \ - LISTENER_BATTERY in self._cluster_listeners: + LISTENER_BATTERY in self.cluster_listeners: return self._all_listeners.append(cluster_listener) if isinstance(cluster_listener, EventRelayListener): self._relay_listeners.append(cluster_listener) else: - self._cluster_listeners[cluster_listener.name] = cluster_listener - - def get_cluster_listener(self, name): - """Get cluster listener by name.""" - return self._cluster_listeners.get(name, None) + self.cluster_listeners[cluster_listener.name] = cluster_listener async def async_configure(self): """Configure the device.""" diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 479b2f79b26..2722f6720ce 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -5,7 +5,9 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/zha/ """ +import asyncio import collections +import itertools import logging from homeassistant import const as ha_const from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -13,15 +15,27 @@ from homeassistant.helpers.entity_component import EntityComponent from . import const as zha_const from .const import ( COMPONENTS, CONF_DEVICE_CONFIG, DATA_ZHA, DATA_ZHA_CORE_COMPONENT, DOMAIN, - ZHA_DISCOVERY_NEW, EVENTABLE_CLUSTERS, DATA_ZHA_CORE_EVENTS, DEVICE_CLASS, - SINGLE_INPUT_CLUSTER_DEVICE_CLASS, SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS, - CUSTOM_CLUSTER_MAPPINGS, COMPONENT_CLUSTERS) + ZHA_DISCOVERY_NEW, DEVICE_CLASS, SINGLE_INPUT_CLUSTER_DEVICE_CLASS, + SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS, COMPONENT_CLUSTERS, HUMIDITY, + TEMPERATURE, ILLUMINANCE, PRESSURE, METERING, ELECTRICAL_MEASUREMENT, + POWER_CONFIGURATION, GENERIC, SENSOR_TYPE, EVENT_RELAY_CLUSTERS, + LISTENER_BATTERY, UNKNOWN, OPENING, ZONE, OCCUPANCY, + CLUSTER_REPORT_CONFIGS, REPORT_CONFIG_IMMEDIATE, REPORT_CONFIG_ASAP, + REPORT_CONFIG_DEFAULT, REPORT_CONFIG_MIN_INT, REPORT_CONFIG_MAX_INT, + REPORT_CONFIG_OP, SIGNAL_REMOVE) +from .device import ZHADevice from ..device_entity import ZhaDeviceEntity -from ..event import ZhaEvent, ZhaRelayEvent +from .listeners import ( + LISTENER_REGISTRY, AttributeListener, EventRelayListener, ZDOListener) from .helpers import convert_ieee _LOGGER = logging.getLogger(__name__) +SENSOR_TYPES = {} +BINARY_SENSOR_TYPES = {} +EntityReference = collections.namedtuple( + 'EntityReference', 'reference_id zha_device cluster_listeners device_info') + class ZHAGateway: """Gateway that handles events that happen on the ZHA Zigbee network.""" @@ -31,16 +45,9 @@ class ZHAGateway: self._hass = hass self._config = config self._component = EntityComponent(_LOGGER, DOMAIN, hass) + self._devices = {} self._device_registry = collections.defaultdict(list) - self._events = {} - establish_device_mappings() - - for component in COMPONENTS: - hass.data[DATA_ZHA][component] = ( - hass.data[DATA_ZHA].get(component, {}) - ) hass.data[DATA_ZHA][DATA_ZHA_CORE_COMPONENT] = self._component - hass.data[DATA_ZHA][DATA_ZHA_CORE_EVENTS] = self._events def device_joined(self, device): """Handle device joined. @@ -67,197 +74,310 @@ class ZHAGateway: def device_removed(self, device): """Handle device being removed from the network.""" - for device_entity in self._device_registry[device.ieee]: - self._hass.async_create_task(device_entity.async_remove()) - if device.ieee in self._events: - self._events.pop(device.ieee) - - def get_device_entity(self, ieee_str): - """Return ZHADeviceEntity for given ieee.""" - ieee = convert_ieee(ieee_str) - if ieee in self._device_registry: - entities = self._device_registry[ieee] - entity = next( - ent for ent in entities if isinstance(ent, ZhaDeviceEntity)) - return entity - return None - - def get_entities_for_ieee(self, ieee_str): - """Return list of entities for given ieee.""" - ieee = convert_ieee(ieee_str) - if ieee in self._device_registry: - return self._device_registry[ieee] - return [] - - @property - def device_registry(self) -> str: - """Return devices.""" - return self._device_registry - - async def async_device_initialized(self, device, join): - """Handle device joined and basic information discovered (async).""" - import zigpy.profiles - - device_manufacturer = device_model = None - - for endpoint_id, endpoint in device.endpoints.items(): - if endpoint_id == 0: # ZDO - continue - - if endpoint.manufacturer is not None: - device_manufacturer = endpoint.manufacturer - if endpoint.model is not None: - device_model = endpoint.model - - component = None - profile_clusters = ([], []) - device_key = "{}-{}".format(device.ieee, endpoint_id) - node_config = {} - if CONF_DEVICE_CONFIG in self._config: - node_config = self._config[CONF_DEVICE_CONFIG].get( - device_key, {} - ) - - if endpoint.profile_id in zigpy.profiles.PROFILES: - profile = zigpy.profiles.PROFILES[endpoint.profile_id] - if zha_const.DEVICE_CLASS.get(endpoint.profile_id, - {}).get(endpoint.device_type, - None): - profile_clusters = profile.CLUSTERS[endpoint.device_type] - profile_info = zha_const.DEVICE_CLASS[endpoint.profile_id] - component = profile_info[endpoint.device_type] - - if ha_const.CONF_TYPE in node_config: - component = node_config[ha_const.CONF_TYPE] - profile_clusters = zha_const.COMPONENT_CLUSTERS[component] - - if component: - in_clusters = [endpoint.in_clusters[c] - for c in profile_clusters[0] - if c in endpoint.in_clusters] - out_clusters = [endpoint.out_clusters[c] - for c in profile_clusters[1] - if c in endpoint.out_clusters] - discovery_info = { - 'application_listener': self, - 'endpoint': endpoint, - 'in_clusters': {c.cluster_id: c for c in in_clusters}, - 'out_clusters': {c.cluster_id: c for c in out_clusters}, - 'manufacturer': endpoint.manufacturer, - 'model': endpoint.model, - 'new_join': join, - 'unique_id': device_key, - } - - if join: - async_dispatcher_send( - self._hass, - ZHA_DISCOVERY_NEW.format(component), - discovery_info - ) - else: - self._hass.data[DATA_ZHA][component][device_key] = ( - discovery_info - ) - - for cluster in endpoint.in_clusters.values(): - await self._attempt_single_cluster_device( - endpoint, - cluster, - profile_clusters[0], - device_key, - zha_const.SINGLE_INPUT_CLUSTER_DEVICE_CLASS, - 'in_clusters', - join, - ) - - for cluster in endpoint.out_clusters.values(): - await self._attempt_single_cluster_device( - endpoint, - cluster, - profile_clusters[1], - device_key, - zha_const.SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS, - 'out_clusters', - join, - ) - - endpoint_entity = ZhaDeviceEntity( - device, - device_manufacturer, - device_model, - self, - ) - await self._component.async_add_entities([endpoint_entity]) - - def register_entity(self, ieee, entity_obj): - """Record the creation of a hass entity associated with ieee.""" - self._device_registry[ieee].append(entity_obj) - - async def _attempt_single_cluster_device(self, endpoint, cluster, - profile_clusters, device_key, - device_classes, discovery_attr, - is_new_join): - """Try to set up an entity from a "bare" cluster.""" - if cluster.cluster_id in EVENTABLE_CLUSTERS: - if cluster.endpoint.device.ieee not in self._events: - self._events.update({cluster.endpoint.device.ieee: []}) - from zigpy.zcl.clusters.general import OnOff, LevelControl - if discovery_attr == 'out_clusters' and \ - (cluster.cluster_id == OnOff.cluster_id or - cluster.cluster_id == LevelControl.cluster_id): - self._events[cluster.endpoint.device.ieee].append( - ZhaRelayEvent(self._hass, cluster) - ) - else: - self._events[cluster.endpoint.device.ieee].append(ZhaEvent( - self._hass, - cluster - )) - - if cluster.cluster_id in profile_clusters: - return - - component = sub_component = None - for cluster_type, candidate_component in device_classes.items(): - if isinstance(cluster, cluster_type): - component = candidate_component - break - - for signature, comp in zha_const.CUSTOM_CLUSTER_MAPPINGS.items(): - if (isinstance(endpoint.device, signature[0]) and - cluster.cluster_id == signature[1]): - component = comp[0] - sub_component = comp[1] - break - - if component is None: - return - - cluster_key = "{}-{}".format(device_key, cluster.cluster_id) - discovery_info = { - 'application_listener': self, - 'endpoint': endpoint, - 'in_clusters': {}, - 'out_clusters': {}, - 'manufacturer': endpoint.manufacturer, - 'model': endpoint.model, - 'new_join': is_new_join, - 'unique_id': cluster_key, - 'entity_suffix': '_{}'.format(cluster.cluster_id), - } - discovery_info[discovery_attr] = {cluster.cluster_id: cluster} - if sub_component: - discovery_info.update({'sub_component': sub_component}) - - if is_new_join: + device = self._devices.pop(device.ieee, None) + self._device_registry.pop(device.ieee, None) + if device is not None: + self._hass.async_create_task(device.async_unsub_dispatcher()) async_dispatcher_send( self._hass, - ZHA_DISCOVERY_NEW.format(component), - discovery_info + "{}_{}".format(SIGNAL_REMOVE, str(device.ieee)) ) - else: - self._hass.data[DATA_ZHA][component][cluster_key] = discovery_info + + def get_device(self, ieee_str): + """Return ZHADevice for given ieee.""" + ieee = convert_ieee(ieee_str) + return self._devices.get(ieee) + + def get_entity_reference(self, entity_id): + """Return entity reference for given entity_id if found.""" + for entity_reference in itertools.chain.from_iterable( + self.device_registry.values()): + if entity_id == entity_reference.reference_id: + return entity_reference + + @property + def devices(self): + """Return devices.""" + return self._devices + + @property + def device_registry(self): + """Return entities by ieee.""" + return self._device_registry + + def register_entity_reference( + self, ieee, reference_id, zha_device, cluster_listeners, + device_info): + """Record the creation of a hass entity associated with ieee.""" + self._device_registry[ieee].append( + EntityReference( + reference_id=reference_id, + zha_device=zha_device, + cluster_listeners=cluster_listeners, + device_info=device_info + ) + ) + + async def _get_or_create_device(self, zigpy_device): + """Get or create a ZHA device.""" + zha_device = self._devices.get(zigpy_device.ieee) + if zha_device is None: + zha_device = ZHADevice(self._hass, zigpy_device, self) + self._devices[zigpy_device.ieee] = zha_device + return zha_device + + async def accept_zigbee_messages(self, _service_or_event): + """Allow devices to accept zigbee messages.""" + accept_messages_calls = [] + for device in self.devices.values(): + accept_messages_calls.append(device.async_accept_messages()) + await asyncio.gather(*accept_messages_calls) + + async def async_device_initialized(self, device, is_new_join): + """Handle device joined and basic information discovered (async).""" + zha_device = await self._get_or_create_device(device) + discovery_infos = [] + endpoint_tasks = [] + for endpoint_id, endpoint in device.endpoints.items(): + endpoint_tasks.append(self._async_process_endpoint( + endpoint_id, endpoint, discovery_infos, device, zha_device, + is_new_join + )) + await asyncio.gather(*endpoint_tasks) + + await zha_device.async_initialize(not is_new_join) + + discovery_tasks = [] + for discovery_info in discovery_infos: + discovery_tasks.append(_dispatch_discovery_info( + self._hass, + is_new_join, + discovery_info + )) + await asyncio.gather(*discovery_tasks) + + device_entity = _create_device_entity(zha_device) + await self._component.async_add_entities([device_entity]) + + async def _async_process_endpoint( + self, endpoint_id, endpoint, discovery_infos, device, zha_device, + is_new_join): + """Process an endpoint on a zigpy device.""" + import zigpy.profiles + + if endpoint_id == 0: # ZDO + await _create_cluster_listener( + endpoint, + zha_device, + is_new_join, + listener_class=ZDOListener + ) + return + + component = None + profile_clusters = ([], []) + device_key = "{}-{}".format(device.ieee, endpoint_id) + node_config = {} + if CONF_DEVICE_CONFIG in self._config: + node_config = self._config[CONF_DEVICE_CONFIG].get( + device_key, {} + ) + + if endpoint.profile_id in zigpy.profiles.PROFILES: + profile = zigpy.profiles.PROFILES[endpoint.profile_id] + if zha_const.DEVICE_CLASS.get(endpoint.profile_id, + {}).get(endpoint.device_type, + None): + profile_clusters = profile.CLUSTERS[endpoint.device_type] + profile_info = zha_const.DEVICE_CLASS[endpoint.profile_id] + component = profile_info[endpoint.device_type] + + if ha_const.CONF_TYPE in node_config: + component = node_config[ha_const.CONF_TYPE] + profile_clusters = zha_const.COMPONENT_CLUSTERS[component] + + if component and component in COMPONENTS: + profile_match = await _handle_profile_match( + self._hass, endpoint, profile_clusters, zha_device, + component, device_key, is_new_join) + discovery_infos.append(profile_match) + + discovery_infos.extend(await _handle_single_cluster_matches( + self._hass, + endpoint, + zha_device, + profile_clusters, + device_key, + is_new_join + )) + + +async def _create_cluster_listener(cluster, zha_device, is_new_join, + listeners=None, listener_class=None): + """Create a cluster listener and attach it to a device.""" + if listener_class is None: + listener_class = LISTENER_REGISTRY.get(cluster.cluster_id, + AttributeListener) + listener = listener_class(cluster, zha_device) + if is_new_join: + await listener.async_configure() + zha_device.add_cluster_listener(listener) + if listeners is not None: + listeners.append(listener) + + +async def _dispatch_discovery_info(hass, is_new_join, discovery_info): + """Dispatch or store discovery information.""" + component = discovery_info['component'] + if is_new_join: + async_dispatcher_send( + hass, + ZHA_DISCOVERY_NEW.format(component), + discovery_info + ) + else: + hass.data[DATA_ZHA][component][discovery_info['unique_id']] = \ + discovery_info + + +async def _handle_profile_match(hass, endpoint, profile_clusters, zha_device, + component, device_key, is_new_join): + """Dispatch a profile match to the appropriate HA component.""" + in_clusters = [endpoint.in_clusters[c] + for c in profile_clusters[0] + if c in endpoint.in_clusters] + out_clusters = [endpoint.out_clusters[c] + for c in profile_clusters[1] + if c in endpoint.out_clusters] + + listeners = [] + cluster_tasks = [] + + for cluster in in_clusters: + cluster_tasks.append(_create_cluster_listener( + cluster, zha_device, is_new_join, listeners=listeners)) + + for cluster in out_clusters: + cluster_tasks.append(_create_cluster_listener( + cluster, zha_device, is_new_join, listeners=listeners)) + + await asyncio.gather(*cluster_tasks) + + discovery_info = { + 'unique_id': device_key, + 'zha_device': zha_device, + 'listeners': listeners, + 'component': component + } + + if component == 'binary_sensor': + discovery_info.update({SENSOR_TYPE: UNKNOWN}) + cluster_ids = [] + cluster_ids.extend(profile_clusters[0]) + cluster_ids.extend(profile_clusters[1]) + for cluster_id in cluster_ids: + if cluster_id in BINARY_SENSOR_TYPES: + discovery_info.update({ + SENSOR_TYPE: BINARY_SENSOR_TYPES.get( + cluster_id, UNKNOWN) + }) + break + + return discovery_info + + +async def _handle_single_cluster_matches(hass, endpoint, zha_device, + profile_clusters, device_key, + is_new_join): + """Dispatch single cluster matches to HA components.""" + cluster_matches = [] + cluster_match_tasks = [] + event_listener_tasks = [] + for cluster in endpoint.in_clusters.values(): + if cluster.cluster_id not in profile_clusters[0]: + cluster_match_tasks.append(_handle_single_cluster_match( + hass, + zha_device, + cluster, + device_key, + zha_const.SINGLE_INPUT_CLUSTER_DEVICE_CLASS, + is_new_join, + )) + + for cluster in endpoint.out_clusters.values(): + if cluster.cluster_id not in profile_clusters[1]: + cluster_match_tasks.append(_handle_single_cluster_match( + hass, + zha_device, + cluster, + device_key, + zha_const.SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS, + is_new_join, + )) + + if cluster.cluster_id in EVENT_RELAY_CLUSTERS: + event_listener_tasks.append(_create_cluster_listener( + cluster, + zha_device, + is_new_join, + listener_class=EventRelayListener + )) + await asyncio.gather(*event_listener_tasks) + cluster_match_results = await asyncio.gather(*cluster_match_tasks) + for cluster_match in cluster_match_results: + if cluster_match is not None: + cluster_matches.append(cluster_match) + return cluster_matches + + +async def _handle_single_cluster_match(hass, zha_device, cluster, device_key, + device_classes, is_new_join): + """Dispatch a single cluster match to a HA component.""" + component = None # sub_component = None + for cluster_type, candidate_component in device_classes.items(): + if isinstance(cluster, cluster_type): + component = candidate_component + break + + if component is None or component not in COMPONENTS: + return + listeners = [] + await _create_cluster_listener(cluster, zha_device, is_new_join, + listeners=listeners) + # don't actually create entities for PowerConfiguration + # find a better way to do this without abusing single cluster reg + from zigpy.zcl.clusters.general import PowerConfiguration + if cluster.cluster_id == PowerConfiguration.cluster_id: + return + + cluster_key = "{}-{}".format(device_key, cluster.cluster_id) + discovery_info = { + 'unique_id': cluster_key, + 'zha_device': zha_device, + 'listeners': listeners, + 'entity_suffix': '_{}'.format(cluster.cluster_id), + 'component': component + } + + if component == 'sensor': + discovery_info.update({ + SENSOR_TYPE: SENSOR_TYPES.get(cluster.cluster_id, GENERIC) + }) + if component == 'binary_sensor': + discovery_info.update({ + SENSOR_TYPE: BINARY_SENSOR_TYPES.get(cluster.cluster_id, UNKNOWN) + }) + + return discovery_info + + +def _create_device_entity(zha_device): + """Create ZHADeviceEntity.""" + device_entity_listeners = [] + if LISTENER_BATTERY in zha_device.cluster_listeners: + listener = zha_device.cluster_listeners.get(LISTENER_BATTERY) + device_entity_listeners.append(listener) + return ZhaDeviceEntity(zha_device, device_entity_listeners) def establish_device_mappings(): @@ -266,19 +386,16 @@ def establish_device_mappings(): These cannot be module level, as importing bellows must be done in a in a function. """ - from zigpy import zcl, quirks + from zigpy import zcl from zigpy.profiles import PROFILES, zha, zll - from ..sensor import RelativeHumiditySensor if zha.PROFILE_ID not in DEVICE_CLASS: DEVICE_CLASS[zha.PROFILE_ID] = {} if zll.PROFILE_ID not in DEVICE_CLASS: DEVICE_CLASS[zll.PROFILE_ID] = {} - EVENTABLE_CLUSTERS.append(zcl.clusters.general.AnalogInput.cluster_id) - EVENTABLE_CLUSTERS.append(zcl.clusters.general.LevelControl.cluster_id) - EVENTABLE_CLUSTERS.append(zcl.clusters.general.MultistateInput.cluster_id) - EVENTABLE_CLUSTERS.append(zcl.clusters.general.OnOff.cluster_id) + EVENT_RELAY_CLUSTERS.append(zcl.clusters.general.LevelControl.cluster_id) + EVENT_RELAY_CLUSTERS.append(zcl.clusters.general.OnOff.cluster_id) DEVICE_CLASS[zha.PROFILE_ID].update({ zha.DeviceType.ON_OFF_SWITCH: 'binary_sensor', @@ -293,6 +410,7 @@ def establish_device_mappings(): zha.DeviceType.DIMMER_SWITCH: 'binary_sensor', zha.DeviceType.COLOR_DIMMER_SWITCH: 'binary_sensor', }) + DEVICE_CLASS[zll.PROFILE_ID].update({ zll.DeviceType.ON_OFF_LIGHT: 'light', zll.DeviceType.ON_OFF_PLUGIN_UNIT: 'switch', @@ -321,14 +439,97 @@ def establish_device_mappings(): zcl.clusters.measurement.OccupancySensing: 'binary_sensor', zcl.clusters.hvac.Fan: 'fan', }) + SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS.update({ zcl.clusters.general.OnOff: 'binary_sensor', }) - # A map of device/cluster to component/sub-component - CUSTOM_CLUSTER_MAPPINGS.update({ - (quirks.smartthings.SmartthingsTemperatureHumiditySensor, 64581): - ('sensor', RelativeHumiditySensor) + SENSOR_TYPES.update({ + zcl.clusters.measurement.RelativeHumidity.cluster_id: HUMIDITY, + zcl.clusters.measurement.TemperatureMeasurement.cluster_id: + TEMPERATURE, + zcl.clusters.measurement.PressureMeasurement.cluster_id: PRESSURE, + zcl.clusters.measurement.IlluminanceMeasurement.cluster_id: + ILLUMINANCE, + zcl.clusters.smartenergy.Metering.cluster_id: METERING, + zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id: + ELECTRICAL_MEASUREMENT, + zcl.clusters.general.PowerConfiguration.cluster_id: + POWER_CONFIGURATION, + }) + + BINARY_SENSOR_TYPES.update({ + zcl.clusters.measurement.OccupancySensing.cluster_id: OCCUPANCY, + zcl.clusters.security.IasZone.cluster_id: ZONE, + zcl.clusters.general.OnOff.cluster_id: OPENING + }) + + CLUSTER_REPORT_CONFIGS.update({ + zcl.clusters.general.OnOff.cluster_id: [{ + 'attr': 'on_off', + 'config': REPORT_CONFIG_IMMEDIATE + }], + zcl.clusters.general.LevelControl.cluster_id: [{ + 'attr': 'current_level', + 'config': REPORT_CONFIG_ASAP + }], + zcl.clusters.lighting.Color.cluster_id: [{ + 'attr': 'current_x', + 'config': REPORT_CONFIG_DEFAULT + }, { + 'attr': 'current_y', + 'config': REPORT_CONFIG_DEFAULT + }, { + 'attr': 'color_temperature', + 'config': REPORT_CONFIG_DEFAULT + }], + zcl.clusters.measurement.RelativeHumidity.cluster_id: [{ + 'attr': 'measured_value', + 'config': ( + REPORT_CONFIG_MIN_INT, + REPORT_CONFIG_MAX_INT, + 50 + ) + }], + zcl.clusters.measurement.TemperatureMeasurement.cluster_id: [{ + 'attr': 'measured_value', + 'config': ( + REPORT_CONFIG_MIN_INT, + REPORT_CONFIG_MAX_INT, + 50 + ) + }], + zcl.clusters.measurement.PressureMeasurement.cluster_id: [{ + 'attr': 'measured_value', + 'config': REPORT_CONFIG_DEFAULT + }], + zcl.clusters.measurement.IlluminanceMeasurement.cluster_id: [{ + 'attr': 'measured_value', + 'config': REPORT_CONFIG_DEFAULT + }], + zcl.clusters.smartenergy.Metering.cluster_id: [{ + 'attr': 'instantaneous_demand', + 'config': REPORT_CONFIG_DEFAULT + }], + zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id: [{ + 'attr': 'active_power', + 'config': REPORT_CONFIG_DEFAULT + }], + zcl.clusters.general.PowerConfiguration.cluster_id: [{ + 'attr': 'battery_voltage', + 'config': REPORT_CONFIG_DEFAULT + }, { + 'attr': 'battery_percentage_remaining', + 'config': REPORT_CONFIG_DEFAULT + }], + zcl.clusters.measurement.OccupancySensing.cluster_id: [{ + 'attr': 'occupancy', + 'config': REPORT_CONFIG_IMMEDIATE + }], + zcl.clusters.hvac.Fan.cluster_id: [{ + 'attr': 'fan_mode', + 'config': REPORT_CONFIG_OP + }], }) # A map of hass components to all Zigbee clusters it could use diff --git a/homeassistant/components/zha/core/listeners.py b/homeassistant/components/zha/core/listeners.py index 4f60ea83d6f..916319b2d98 100644 --- a/homeassistant/components/zha/core/listeners.py +++ b/homeassistant/components/zha/core/listeners.py @@ -5,20 +5,48 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/zha/ """ +import asyncio +from enum import Enum +from functools import wraps import logging +from random import uniform from homeassistant.core import callback -from .const import SIGNAL_ATTR_UPDATED +from homeassistant.helpers.dispatcher import async_dispatcher_send +from .helpers import ( + bind_configure_reporting, construct_unique_id, + safe_read, get_attr_id_by_name) +from .const import ( + CLUSTER_REPORT_CONFIGS, REPORT_CONFIG_DEFAULT, SIGNAL_ATTR_UPDATED, + SIGNAL_MOVE_LEVEL, SIGNAL_SET_LEVEL, SIGNAL_STATE_ATTR, ATTR_LEVEL +) + +LISTENER_REGISTRY = {} _LOGGER = logging.getLogger(__name__) -def parse_and_log_command(entity_id, cluster, tsn, command_id, args): +def populate_listener_registry(): + """Populate the listener registry.""" + from zigpy import zcl + LISTENER_REGISTRY.update({ + zcl.clusters.general.OnOff.cluster_id: OnOffListener, + zcl.clusters.general.LevelControl.cluster_id: LevelListener, + zcl.clusters.lighting.Color.cluster_id: ColorListener, + zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id: + ActivePowerListener, + zcl.clusters.general.PowerConfiguration.cluster_id: BatteryListener, + zcl.clusters.security.IasZone.cluster_id: IASZoneListener, + zcl.clusters.hvac.Fan.cluster_id: FanListener, + }) + + +def parse_and_log_command(unique_id, cluster, tsn, command_id, args): """Parse and log a zigbee cluster command.""" cmd = cluster.server_commands.get(command_id, [command_id])[0] _LOGGER.debug( "%s: received '%s' command with %s args on cluster_id '%s' tsn '%s'", - entity_id, + unique_id, cmd, args, cluster.cluster_id, @@ -27,40 +55,214 @@ def parse_and_log_command(entity_id, cluster, tsn, command_id, args): return cmd +def decorate_command(listener, command): + """Wrap a cluster command to make it safe.""" + @wraps(command) + async def wrapper(*args, **kwds): + from zigpy.zcl.foundation import Status + from zigpy.exceptions import DeliveryError + try: + result = await command(*args, **kwds) + _LOGGER.debug("%s: executed command: %s %s %s %s", + listener.unique_id, + command.__name__, + "{}: {}".format("with args", args), + "{}: {}".format("with kwargs", kwds), + "{}: {}".format("and result", result)) + return result[1] is Status.SUCCESS + except DeliveryError: + _LOGGER.debug("%s: command failed: %s", listener.unique_id, + command.__name__) + return False + return wrapper + + +class ListenerStatus(Enum): + """Status of a listener.""" + + CREATED = 1 + CONFIGURED = 2 + INITIALIZED = 3 + LISTENING = 4 + + class ClusterListener: """Listener for a Zigbee cluster.""" - def __init__(self, entity, cluster): + def __init__(self, cluster, device): """Initialize ClusterListener.""" - self._entity = entity self._cluster = cluster + self._zha_device = device + self._unique_id = construct_unique_id(cluster) + self._report_config = CLUSTER_REPORT_CONFIGS.get( + self._cluster.cluster_id, + [{'attr': 0, 'config': REPORT_CONFIG_DEFAULT}] + ) + self._status = ListenerStatus.CREATED + @property + def unique_id(self): + """Return the unique id for this listener.""" + return self._unique_id + + @property + def cluster(self): + """Return the zigpy cluster for this listener.""" + return self._cluster + + @property + def device(self): + """Return the device this listener is linked to.""" + return self._zha_device + + @property + def status(self): + """Return the status of the listener.""" + return self._status + + def set_report_config(self, report_config): + """Set the reporting configuration.""" + self._report_config = report_config + + async def async_configure(self): + """Set cluster binding and attribute reporting.""" + manufacturer = None + manufacturer_code = self._zha_device.manufacturer_code + if self.cluster.cluster_id >= 0xfc00 and manufacturer_code: + manufacturer = manufacturer_code + + skip_bind = False # bind cluster only for the 1st configured attr + for report_config in self._report_config: + attr = report_config.get('attr') + min_report_interval, max_report_interval, change = \ + report_config.get('config') + await bind_configure_reporting( + self._unique_id, self.cluster, attr, + min_report=min_report_interval, + max_report=max_report_interval, + reportable_change=change, + skip_bind=skip_bind, + manufacturer=manufacturer + ) + skip_bind = True + await asyncio.sleep(uniform(0.1, 0.5)) + _LOGGER.debug( + "%s: finished listener configuration", + self._unique_id + ) + self._status = ListenerStatus.CONFIGURED + + async def async_initialize(self, from_cache): + """Initialize listener.""" + self._status = ListenerStatus.INITIALIZED + + async def accept_messages(self): + """Attach to the cluster so we can receive messages.""" + self._cluster.add_listener(self) + self._status = ListenerStatus.LISTENING + + @callback def cluster_command(self, tsn, command_id, args): """Handle commands received to this cluster.""" pass + @callback def attribute_updated(self, attrid, value): """Handle attribute updates on this cluster.""" pass + @callback def zdo_command(self, *args, **kwargs): """Handle ZDO commands on this cluster.""" pass + @callback def zha_send_event(self, cluster, command, args): - """Relay entity events to hass.""" - pass # don't let entities fire events + """Relay events to hass.""" + self._zha_device.hass.bus.async_fire( + 'zha_event', + { + 'unique_id': self._unique_id, + 'command': command, + 'args': args + } + ) + + async def async_update(self): + """Retrieve latest state from cluster.""" + pass + + async def get_attribute_value(self, attribute, from_cache=True): + """Get the value for an attribute.""" + result = await safe_read( + self._cluster, + [attribute], + allow_cache=from_cache, + only_cache=from_cache + ) + return result.get(attribute) + + def __getattr__(self, name): + """Get attribute or a decorated cluster command.""" + if hasattr(self._cluster, name) and callable( + getattr(self._cluster, name)): + command = getattr(self._cluster, name) + command.__name__ = name + return decorate_command( + self, + command + ) + return self.__getattribute__(name) + + +class AttributeListener(ClusterListener): + """Listener for the attribute reports cluster.""" + + name = 'attribute' + + def __init__(self, cluster, device): + """Initialize AttributeListener.""" + super().__init__(cluster, device) + attr = self._report_config[0].get('attr') + if isinstance(attr, str): + self._value_attribute = get_attr_id_by_name(self.cluster, attr) + else: + self._value_attribute = attr + + @callback + def attribute_updated(self, attrid, value): + """Handle attribute updates on this cluster.""" + if attrid == self._value_attribute: + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), + value + ) + + async def async_initialize(self, from_cache): + """Initialize listener.""" + await self.get_attribute_value( + self._report_config[0].get('attr'), from_cache=from_cache) + await super().async_initialize(from_cache) class OnOffListener(ClusterListener): """Listener for the OnOff Zigbee cluster.""" + name = 'on_off' + ON_OFF = 0 + def __init__(self, cluster, device): + """Initialize ClusterListener.""" + super().__init__(cluster, device) + self._state = None + + @callback def cluster_command(self, tsn, command_id, args): """Handle commands received to this cluster.""" cmd = parse_and_log_command( - self._entity.entity_id, + self.unique_id, self._cluster, tsn, command_id, @@ -68,27 +270,42 @@ class OnOffListener(ClusterListener): ) if cmd in ('off', 'off_with_effect'): - self._entity.set_state(False) + self.attribute_updated(self.ON_OFF, False) elif cmd in ('on', 'on_with_recall_global_scene', 'on_with_timed_off'): - self._entity.set_state(True) + self.attribute_updated(self.ON_OFF, True) elif cmd == 'toggle': - self._entity.set_state(not self._entity.is_on) + self.attribute_updated(self.ON_OFF, not bool(self._state)) + @callback def attribute_updated(self, attrid, value): """Handle attribute updates on this cluster.""" if attrid == self.ON_OFF: - self._entity.set_state(bool(value)) + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), + value + ) + self._state = bool(value) + + async def async_initialize(self, from_cache): + """Initialize listener.""" + self._state = bool( + await self.get_attribute_value(self.ON_OFF, from_cache=from_cache)) + await super().async_initialize(from_cache) class LevelListener(ClusterListener): """Listener for the LevelControl Zigbee cluster.""" + name = ATTR_LEVEL + CURRENT_LEVEL = 0 + @callback def cluster_command(self, tsn, command_id, args): """Handle commands received to this cluster.""" cmd = parse_and_log_command( - self._entity.entity_id, + self.unique_id, self._cluster, tsn, command_id, @@ -96,21 +313,190 @@ class LevelListener(ClusterListener): ) if cmd in ('move_to_level', 'move_to_level_with_on_off'): - self._entity.set_level(args[0]) + self.dispatch_level_change(SIGNAL_SET_LEVEL, args[0]) elif cmd in ('move', 'move_with_on_off'): # We should dim slowly -- for now, just step once rate = args[1] if args[0] == 0xff: rate = 10 # Should read default move rate - self._entity.move_level(-rate if args[0] else rate) + self.dispatch_level_change( + SIGNAL_MOVE_LEVEL, -rate if args[0] else rate) elif cmd in ('step', 'step_with_on_off'): # Step (technically may change on/off) - self._entity.move_level(-args[1] if args[0] else args[1]) + self.dispatch_level_change( + SIGNAL_MOVE_LEVEL, -args[1] if args[0] else args[1]) + @callback def attribute_updated(self, attrid, value): """Handle attribute updates on this cluster.""" + _LOGGER.debug("%s: received attribute: %s update with value: %i", + self.unique_id, attrid, value) if attrid == self.CURRENT_LEVEL: - self._entity.set_level(value) + self.dispatch_level_change(SIGNAL_SET_LEVEL, value) + + def dispatch_level_change(self, command, level): + """Dispatch level change.""" + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, command), + level + ) + + async def async_initialize(self, from_cache): + """Initialize listener.""" + await self.get_attribute_value( + self.CURRENT_LEVEL, from_cache=from_cache) + await super().async_initialize(from_cache) + + +class IASZoneListener(ClusterListener): + """Listener for the IASZone Zigbee cluster.""" + + name = 'zone' + + def __init__(self, cluster, device): + """Initialize IASZoneListener.""" + super().__init__(cluster, device) + self._cluster.add_listener(self) + self._status = ListenerStatus.LISTENING + + @callback + def cluster_command(self, tsn, command_id, args): + """Handle commands received to this cluster.""" + if command_id == 0: + state = args[0] & 3 + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), + state + ) + _LOGGER.debug("Updated alarm state: %s", state) + elif command_id == 1: + _LOGGER.debug("Enroll requested") + res = self._cluster.enroll_response(0, 0) + self._zha_device.hass.async_create_task(res) + + async def async_configure(self): + """Configure IAS device.""" + from zigpy.exceptions import DeliveryError + _LOGGER.debug("%s: started IASZoneListener configuration", + self._unique_id) + try: + res = await self._cluster.bind() + _LOGGER.debug( + "%s: bound '%s' cluster: %s", + self.unique_id, self._cluster.ep_attribute, res[0] + ) + except DeliveryError as ex: + _LOGGER.debug( + "%s: Failed to bind '%s' cluster: %s", + self.unique_id, self._cluster.ep_attribute, str(ex) + ) + + ieee = self._cluster.endpoint.device.application.ieee + + try: + res = await self._cluster.write_attributes({'cie_addr': ieee}) + _LOGGER.debug( + "%s: wrote cie_addr: %s to '%s' cluster: %s", + self.unique_id, str(ieee), self._cluster.ep_attribute, + res[0] + ) + except DeliveryError as ex: + _LOGGER.debug( + "%s: Failed to write cie_addr: %s to '%s' cluster: %s", + self.unique_id, str(ieee), self._cluster.ep_attribute, str(ex) + ) + _LOGGER.debug("%s: finished IASZoneListener configuration", + self._unique_id) + + await self.get_attribute_value('zone_type', from_cache=False) + + @callback + def attribute_updated(self, attrid, value): + """Handle attribute updates on this cluster.""" + if attrid == 2: + value = value & 3 + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), + value + ) + + async def async_initialize(self, from_cache): + """Initialize listener.""" + await self.get_attribute_value('zone_status', from_cache=from_cache) + await self.get_attribute_value('zone_state', from_cache=from_cache) + await super().async_initialize(from_cache) + + async def accept_messages(self): + """Attach to the cluster so we can receive messages.""" + self._status = ListenerStatus.LISTENING + + +class ActivePowerListener(AttributeListener): + """Listener that polls active power level.""" + + name = 'active_power' + + async def async_update(self): + """Retrieve latest state.""" + _LOGGER.debug("%s async_update", self.unique_id) + + # This is a polling listener. Don't allow cache. + result = await self.get_attribute_value( + 'active_power', from_cache=False) + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), + result + ) + + async def async_initialize(self, from_cache): + """Initialize listener.""" + await self.get_attribute_value( + 'active_power', from_cache=from_cache) + await super().async_initialize(from_cache) + + +class BatteryListener(ClusterListener): + """Listener that polls active power level.""" + + name = 'battery' + + @callback + def attribute_updated(self, attrid, value): + """Handle attribute updates on this cluster.""" + attr = self._report_config[1].get('attr') + if isinstance(attr, str): + attr_id = get_attr_id_by_name(self.cluster, attr) + else: + attr_id = attr + if attrid == attr_id: + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, SIGNAL_STATE_ATTR), + 'battery_level', + value + ) + + async def async_initialize(self, from_cache): + """Initialize listener.""" + await self.async_read_state(from_cache) + await super().async_initialize(from_cache) + + async def async_update(self): + """Retrieve latest state.""" + await self.async_read_state(True) + + async def async_read_state(self, from_cache): + """Read data from the cluster.""" + await self.get_attribute_value( + 'battery_size', from_cache=from_cache) + await self.get_attribute_value( + 'battery_percentage_remaining', from_cache=from_cache) + await self.get_attribute_value( + 'active_power', from_cache=from_cache) class EventRelayListener(ClusterListener): @@ -143,3 +529,137 @@ class EventRelayListener(ClusterListener): self._cluster.server_commands.get(command_id)[0], args ) + + +class ColorListener(ClusterListener): + """Color listener.""" + + name = 'color' + + CAPABILITIES_COLOR_XY = 0x08 + CAPABILITIES_COLOR_TEMP = 0x10 + UNSUPPORTED_ATTRIBUTE = 0x86 + + def __init__(self, cluster, device): + """Initialize ClusterListener.""" + super().__init__(cluster, device) + self._color_capabilities = None + + def get_color_capabilities(self): + """Return the color capabilities.""" + return self._color_capabilities + + async def async_initialize(self, from_cache): + """Initialize listener.""" + capabilities = await self.get_attribute_value( + 'color_capabilities', from_cache=from_cache) + + if capabilities is None: + # ZCL Version 4 devices don't support the color_capabilities + # attribute. In this version XY support is mandatory, but we + # need to probe to determine if the device supports color + # temperature. + capabilities = self.CAPABILITIES_COLOR_XY + result = await self.get_attribute_value( + 'color_temperature', from_cache=from_cache) + + if result is not self.UNSUPPORTED_ATTRIBUTE: + capabilities |= self.CAPABILITIES_COLOR_TEMP + self._color_capabilities = capabilities + await super().async_initialize(from_cache) + + +class FanListener(ClusterListener): + """Fan listener.""" + + name = 'fan' + + _value_attribute = 0 + + async def async_set_speed(self, value) -> None: + """Set the speed of the fan.""" + from zigpy.exceptions import DeliveryError + try: + await self.cluster.write_attributes({'fan_mode': value}) + except DeliveryError as ex: + _LOGGER.error("%s: Could not set speed: %s", self.unique_id, ex) + return + + async def async_update(self): + """Retrieve latest state.""" + result = await self.get_attribute_value('fan_mode', from_cache=True) + + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), + result + ) + + def attribute_updated(self, attrid, value): + """Handle attribute update from fan cluster.""" + attr_name = self.cluster.attributes.get(attrid, [attrid])[0] + _LOGGER.debug("%s: Attribute report '%s'[%s] = %s", + self.unique_id, self.cluster.name, attr_name, value) + if attrid == self._value_attribute: + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), + value + ) + + async def async_initialize(self, from_cache): + """Initialize listener.""" + await self.get_attribute_value( + self._value_attribute, from_cache=from_cache) + await super().async_initialize(from_cache) + + +class ZDOListener: + """Listener for ZDO events.""" + + name = 'zdo' + + def __init__(self, cluster, device): + """Initialize ClusterListener.""" + self._cluster = cluster + self._zha_device = device + self._status = ListenerStatus.CREATED + self._unique_id = "{}_ZDO".format(device.name) + + @property + def unique_id(self): + """Return the unique id for this listener.""" + return self._unique_id + + @property + def cluster(self): + """Return the aigpy cluster for this listener.""" + return self._cluster + + @property + def status(self): + """Return the status of the listener.""" + return self._status + + @callback + def device_announce(self, zigpy_device): + """Device announce handler.""" + pass + + @callback + def permit_duration(self, duration): + """Permit handler.""" + pass + + async def accept_messages(self): + """Attach to the cluster so we can receive messages.""" + self._cluster.add_listener(self) + self._status = ListenerStatus.LISTENING + + async def async_initialize(self, from_cache): + """Initialize listener.""" + self._status = ListenerStatus.INITIALIZED + + async def async_configure(self): + """Configure listener.""" + self._status = ListenerStatus.CONFIGURED diff --git a/homeassistant/components/zha/device_entity.py b/homeassistant/components/zha/device_entity.py index 2d2a5d76b81..cf2156b76c3 100644 --- a/homeassistant/components/zha/device_entity.py +++ b/homeassistant/components/zha/device_entity.py @@ -5,78 +5,134 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/zha/ """ +import logging import time -from homeassistant.helpers import entity from homeassistant.util import slugify +from .entity import ZhaEntity +from .const import LISTENER_BATTERY, SIGNAL_STATE_ATTR + +_LOGGER = logging.getLogger(__name__) + +BATTERY_SIZES = { + 0: 'No battery', + 1: 'Built in', + 2: 'Other', + 3: 'AA', + 4: 'AAA', + 5: 'C', + 6: 'D', + 7: 'CR2', + 8: 'CR123A', + 9: 'CR2450', + 10: 'CR2032', + 11: 'CR1632', + 255: 'Unknown' +} -class ZhaDeviceEntity(entity.Entity): +class ZhaDeviceEntity(ZhaEntity): """A base class for ZHA devices.""" - def __init__(self, device, manufacturer, model, application_listener, - keepalive_interval=7200, **kwargs): + def __init__(self, zha_device, listeners, keepalive_interval=7200, + **kwargs): """Init ZHA endpoint entity.""" - self._device_state_attributes = { - 'nwk': '0x{0:04x}'.format(device.nwk), - 'ieee': str(device.ieee), - 'lqi': device.lqi, - 'rssi': device.rssi, - } - - ieee = device.ieee + ieee = zha_device.ieee ieeetail = ''.join(['%02x' % (o, ) for o in ieee[-4:]]) - if manufacturer is not None and model is not None: - self._unique_id = "{}_{}_{}".format( - slugify(manufacturer), - slugify(model), + unique_id = None + if zha_device.manufacturer is not None and \ + zha_device.model is not None: + unique_id = "{}_{}_{}".format( + slugify(zha_device.manufacturer), + slugify(zha_device.model), ieeetail, ) - self._device_state_attributes['friendly_name'] = "{} {}".format( - manufacturer, - model, - ) else: - self._unique_id = str(ieeetail) + unique_id = str(ieeetail) + + kwargs['component'] = 'zha' + super().__init__(unique_id, zha_device, listeners, skip_entity_id=True, + **kwargs) - self._device = device - self._state = 'offline' self._keepalive_interval = keepalive_interval - - application_listener.register_entity(ieee, self) - - @property - def unique_id(self) -> str: - """Return a unique ID.""" - return self._unique_id + self._device_state_attributes.update({ + 'nwk': '0x{0:04x}'.format(zha_device.nwk), + 'ieee': str(zha_device.ieee), + 'lqi': zha_device.lqi, + 'rssi': zha_device.rssi, + }) + self._should_poll = True + self._battery_listener = self.cluster_listeners.get(LISTENER_BATTERY) @property def state(self) -> str: """Return the state of the entity.""" return self._state + @property + def available(self): + """Return True if device is available.""" + return self._zha_device.available + @property def device_state_attributes(self): """Return device specific state attributes.""" update_time = None - if self._device.last_seen is not None and self._state == 'offline': - time_struct = time.localtime(self._device.last_seen) + device = self._zha_device + if device.last_seen is not None and not self.available: + time_struct = time.localtime(device.last_seen) update_time = time.strftime("%Y-%m-%dT%H:%M:%S", time_struct) self._device_state_attributes['last_seen'] = update_time if ('last_seen' in self._device_state_attributes and - self._state != 'offline'): + self.available): del self._device_state_attributes['last_seen'] - self._device_state_attributes['lqi'] = self._device.lqi - self._device_state_attributes['rssi'] = self._device.rssi + self._device_state_attributes['lqi'] = device.lqi + self._device_state_attributes['rssi'] = device.rssi return self._device_state_attributes + async def async_added_to_hass(self): + """Run when about to be added to hass.""" + await super().async_added_to_hass() + if self._battery_listener: + await self.async_accept_signal( + self._battery_listener, SIGNAL_STATE_ATTR, + self.async_update_state_attribute) + # only do this on add to HA because it is static + await self._async_init_battery_values() + async def async_update(self): """Handle polling.""" - if self._device.last_seen is None: - self._state = 'offline' + if self._zha_device.last_seen is None: + self._zha_device.update_available(False) else: - difference = time.time() - self._device.last_seen + difference = time.time() - self._zha_device.last_seen if difference > self._keepalive_interval: - self._state = 'offline' + self._zha_device.update_available(False) + self._state = None else: + self._zha_device.update_available(True) self._state = 'online' + if self._battery_listener: + await self.async_get_latest_battery_reading() + + async def _async_init_battery_values(self): + """Get initial battery level and battery info from listener cache.""" + battery_size = await self._battery_listener.get_attribute_value( + 'battery_size') + if battery_size is not None: + self._device_state_attributes['battery_size'] = BATTERY_SIZES.get( + battery_size, 'Unknown') + + battery_quantity = await self._battery_listener.get_attribute_value( + 'battery_quantity') + if battery_quantity is not None: + self._device_state_attributes['battery_quantity'] = \ + battery_quantity + await self.async_get_latest_battery_reading() + + async def async_get_latest_battery_reading(self): + """Get the latest battery reading from listeners cache.""" + battery = await self._battery_listener.get_attribute_value( + 'battery_percentage_remaining') + if battery is not None: + self._device_state_attributes['battery_level'] = battery diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index e112e32d592..5a78d91553f 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -4,20 +4,18 @@ Entity for Zigbee Home Automation. For more details about this component, please refer to the documentation at https://home-assistant.io/components/zha/ """ -import asyncio -import logging -from random import uniform -from homeassistant.const import ATTR_ENTITY_ID -from homeassistant.core import callback +import logging + from homeassistant.helpers import entity from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util import slugify + from .core.const import ( - DATA_ZHA, DATA_ZHA_BRIDGE_ID, DOMAIN, ATTR_CLUSTER_ID, ATTR_ATTRIBUTE, - ATTR_VALUE, ATTR_MANUFACTURER, ATTR_COMMAND, SERVER, ATTR_COMMAND_TYPE, - ATTR_ARGS, IN, OUT, CLIENT_COMMANDS, SERVER_COMMANDS) -from .core.helpers import bind_configure_reporting + DOMAIN, ATTR_MANUFACTURER, DATA_ZHA, DATA_ZHA_BRIDGE_ID, MODEL, NAME, + SIGNAL_REMOVE +) _LOGGER = logging.getLogger(__name__) @@ -29,287 +27,155 @@ class ZhaEntity(entity.Entity): _domain = None # Must be overridden by subclasses - def __init__(self, endpoint, in_clusters, out_clusters, manufacturer, - model, application_listener, unique_id, new_join=False, - **kwargs): + def __init__(self, unique_id, zha_device, listeners, + skip_entity_id=False, **kwargs): """Init ZHA entity.""" - self._device_state_attributes = {} - self._name = None - ieee = endpoint.device.ieee - ieeetail = ''.join(['%02x' % (o, ) for o in ieee[-4:]]) - if manufacturer and model is not None: - self.entity_id = "{}.{}_{}_{}_{}{}".format( - self._domain, - slugify(manufacturer), - slugify(model), - ieeetail, - endpoint.endpoint_id, - kwargs.get(ENTITY_SUFFIX, ''), - ) - self._name = "{} {}".format(manufacturer, model) - else: - self.entity_id = "{}.zha_{}_{}{}".format( - self._domain, - ieeetail, - endpoint.endpoint_id, - kwargs.get(ENTITY_SUFFIX, ''), - ) - - self._endpoint = endpoint - self._in_clusters = in_clusters - self._out_clusters = out_clusters - self._new_join = new_join - self._state = None + self._force_update = False + self._should_poll = False self._unique_id = unique_id - - # Normally the entity itself is the listener. Sub-classes may set this - # to a dict of cluster ID -> listener to receive messages for specific - # clusters separately - self._in_listeners = {} - self._out_listeners = {} - - self._initialized = False - self.manufacturer_code = None - application_listener.register_entity(ieee, self) - - async def get_clusters(self): - """Get zigbee clusters from this entity.""" - return { - IN: self._in_clusters, - OUT: self._out_clusters - } - - async def _get_cluster(self, cluster_id, cluster_type=IN): - """Get zigbee cluster from this entity.""" - if cluster_type == IN: - cluster = self._in_clusters[cluster_id] - else: - cluster = self._out_clusters[cluster_id] - if cluster is None: - _LOGGER.warning('in_cluster with id: %s not found on entity: %s', - cluster_id, self.entity_id) - return cluster - - async def get_cluster_attributes(self, cluster_id, cluster_type=IN): - """Get zigbee attributes for specified cluster.""" - cluster = await self._get_cluster(cluster_id, cluster_type) - if cluster is None: - return - return cluster.attributes - - async def write_zigbe_attribute(self, cluster_id, attribute, value, - cluster_type=IN, manufacturer=None): - """Write a value to a zigbee attribute for a cluster in this entity.""" - cluster = await self._get_cluster(cluster_id, cluster_type) - if cluster is None: - return - - from zigpy.exceptions import DeliveryError - try: - response = await cluster.write_attributes( - {attribute: value}, - manufacturer=manufacturer + self._name = None + if zha_device.manufacturer and zha_device.model is not None: + self._name = "{} {}".format( + zha_device.manufacturer, + zha_device.model ) - _LOGGER.debug( - 'set: %s for attr: %s to cluster: %s for entity: %s - res: %s', - value, - attribute, - cluster_id, - self.entity_id, - response - ) - return response - except DeliveryError as exc: - _LOGGER.debug( - 'failed to set attribute: %s %s %s %s %s', - '{}: {}'.format(ATTR_VALUE, value), - '{}: {}'.format(ATTR_ATTRIBUTE, attribute), - '{}: {}'.format(ATTR_CLUSTER_ID, cluster_id), - '{}: {}'.format(ATTR_ENTITY_ID, self.entity_id), - exc - ) - - async def get_cluster_commands(self, cluster_id, cluster_type=IN): - """Get zigbee commands for specified cluster.""" - cluster = await self._get_cluster(cluster_id, cluster_type) - if cluster is None: - return - return { - CLIENT_COMMANDS: cluster.client_commands, - SERVER_COMMANDS: cluster.server_commands, - } - - async def issue_cluster_command(self, cluster_id, command, command_type, - args, cluster_type=IN, - manufacturer=None): - """Issue a command against specified zigbee cluster on this entity.""" - cluster = await self._get_cluster(cluster_id, cluster_type) - if cluster is None: - return - response = None - if command_type == SERVER: - response = await cluster.command(command, *args, - manufacturer=manufacturer, - expect_reply=True) - else: - response = await cluster.client_command(command, *args) - - _LOGGER.debug( - 'Issued cluster command: %s %s %s %s %s %s %s', - '{}: {}'.format(ATTR_CLUSTER_ID, cluster_id), - '{}: {}'.format(ATTR_COMMAND, command), - '{}: {}'.format(ATTR_COMMAND_TYPE, command_type), - '{}: {}'.format(ATTR_ARGS, args), - '{}: {}'.format(ATTR_CLUSTER_ID, cluster_type), - '{}: {}'.format(ATTR_MANUFACTURER, manufacturer), - '{}: {}'.format(ATTR_ENTITY_ID, self.entity_id) - ) - return response - - async def async_added_to_hass(self): - """Handle entity addition to hass. - - It is now safe to update the entity state - """ - for cluster_id, cluster in self._in_clusters.items(): - cluster.add_listener(self._in_listeners.get(cluster_id, self)) - for cluster_id, cluster in self._out_clusters.items(): - cluster.add_listener(self._out_listeners.get(cluster_id, self)) - - self._endpoint.device.zdo.add_listener(self) - - if self._new_join: - self.hass.async_create_task(self.async_configure()) - - self._initialized = True - - async def async_configure(self): - """Set cluster binding and attribute reporting.""" - for cluster_key, attrs in self.zcl_reporting_config.items(): - cluster = self._get_cluster_from_report_config(cluster_key) - if cluster is None: - continue - - manufacturer = None - if cluster.cluster_id >= 0xfc00 and self.manufacturer_code: - manufacturer = self.manufacturer_code - - skip_bind = False # bind cluster only for the 1st configured attr - for attr, details in attrs.items(): - min_report_interval, max_report_interval, change = details - await bind_configure_reporting( - self.entity_id, cluster, attr, - min_report=min_report_interval, - max_report=max_report_interval, - reportable_change=change, - skip_bind=skip_bind, - manufacturer=manufacturer + if not skip_entity_id: + ieee = zha_device.ieee + ieeetail = ''.join(['%02x' % (o, ) for o in ieee[-4:]]) + if zha_device.manufacturer and zha_device.model is not None: + self.entity_id = "{}.{}_{}_{}_{}{}".format( + self._domain, + slugify(zha_device.manufacturer), + slugify(zha_device.model), + ieeetail, + listeners[0].cluster.endpoint.endpoint_id, + kwargs.get(ENTITY_SUFFIX, ''), ) - skip_bind = True - await asyncio.sleep(uniform(0.1, 0.5)) - _LOGGER.debug("%s: finished configuration", self.entity_id) - - def _get_cluster_from_report_config(self, cluster_key): - """Parse an entry from zcl_reporting_config dict.""" - from zigpy.zcl import Cluster as Zcl_Cluster - - cluster = None - if isinstance(cluster_key, Zcl_Cluster): - cluster = cluster_key - elif isinstance(cluster_key, str): - cluster = getattr(self._endpoint, cluster_key, None) - elif isinstance(cluster_key, int): - if cluster_key in self._in_clusters: - cluster = self._in_clusters[cluster_key] - elif cluster_key in self._out_clusters: - cluster = self._out_clusters[cluster_key] - elif issubclass(cluster_key, Zcl_Cluster): - cluster_id = cluster_key.cluster_id - if cluster_id in self._in_clusters: - cluster = self._in_clusters[cluster_id] - elif cluster_id in self._out_clusters: - cluster = self._out_clusters[cluster_id] - return cluster + else: + self.entity_id = "{}.zha_{}_{}{}".format( + self._domain, + ieeetail, + listeners[0].cluster.endpoint.endpoint_id, + kwargs.get(ENTITY_SUFFIX, ''), + ) + self._state = None + self._device_state_attributes = {} + self._zha_device = zha_device + self.cluster_listeners = {} + # this will get flipped to false once we enable the feature after the + # reorg is merged + self._available = True + self._component = kwargs['component'] + self._unsubs = [] + for listener in listeners: + self.cluster_listeners[listener.name] = listener @property def name(self): """Return Entity's default name.""" return self._name - @property - def zcl_reporting_config(self): - """Return a dict of ZCL attribute reporting configuration. - - { - Cluster_Class: { - attr_id: (min_report_interval, max_report_interval, change), - attr_name: (min_rep_interval, max_rep_interval, change) - } - Cluster_Instance: { - attr_id: (min_report_interval, max_report_interval, change), - attr_name: (min_rep_interval, max_rep_interval, change) - } - cluster_id: { - attr_id: (min_report_interval, max_report_interval, change), - attr_name: (min_rep_interval, max_rep_interval, change) - } - 'cluster_name': { - attr_id: (min_report_interval, max_report_interval, change), - attr_name: (min_rep_interval, max_rep_interval, change) - } - } - """ - return {} - @property def unique_id(self) -> str: """Return a unique ID.""" return self._unique_id + @property + def zha_device(self): + """Return the zha device this entity is attached to.""" + return self._zha_device + @property def device_state_attributes(self): """Return device specific state attributes.""" return self._device_state_attributes + @property + def force_update(self) -> bool: + """Force update this entity.""" + return self._force_update + @property def should_poll(self) -> bool: - """Let ZHA handle polling.""" - return False - - @callback - def attribute_updated(self, attribute, value): - """Handle an attribute updated on this cluster.""" - pass - - @callback - def zdo_command(self, tsn, command_id, args): - """Handle a ZDO command received on this cluster.""" - pass - - @callback - def device_announce(self, device): - """Handle device_announce zdo event.""" - self.async_schedule_update_ha_state(force_refresh=True) - - @callback - def permit_duration(self, permit_duration): - """Handle permit_duration zdo event.""" - pass + """Poll state from device.""" + return self._should_poll @property def device_info(self): """Return a device description for device registry.""" - ieee = str(self._endpoint.device.ieee) + zha_device_info = self._zha_device.device_info + ieee = zha_device_info['ieee'] return { 'connections': {(CONNECTION_ZIGBEE, ieee)}, 'identifiers': {(DOMAIN, ieee)}, - ATTR_MANUFACTURER: self._endpoint.manufacturer, - 'model': self._endpoint.model, - 'name': self.name or ieee, + ATTR_MANUFACTURER: zha_device_info[ATTR_MANUFACTURER], + MODEL: zha_device_info[MODEL], + NAME: zha_device_info[NAME], 'via_hub': (DOMAIN, self.hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID]), } - @callback - def zha_send_event(self, cluster, command, args): - """Relay entity events to hass.""" - pass # don't relay events from entities + @property + def available(self): + """Return entity availability.""" + return self._available + + def async_set_available(self, available): + """Set entity availability.""" + self._available = available + self.async_schedule_update_ha_state() + + def async_update_state_attribute(self, key, value): + """Update a single device state attribute.""" + self._device_state_attributes.update({ + key: value + }) + self.async_schedule_update_ha_state() + + def async_set_state(self, state): + """Set the entity state.""" + pass + + async def async_added_to_hass(self): + """Run when about to be added to hass.""" + await super().async_added_to_hass() + await self.async_accept_signal( + None, "{}_{}".format(self.zha_device.available_signal, 'entity'), + self.async_set_available, + signal_override=True) + await self.async_accept_signal( + None, "{}_{}".format(SIGNAL_REMOVE, str(self.zha_device.ieee)), + self.async_remove, + signal_override=True + ) + self._zha_device.gateway.register_entity_reference( + self._zha_device.ieee, self.entity_id, self._zha_device, + self.cluster_listeners, self.device_info) + + async def async_will_remove_from_hass(self) -> None: + """Disconnect entity object when removed.""" + for unsub in self._unsubs: + unsub() + + async def async_update(self): + """Retrieve latest state.""" + for listener in self.cluster_listeners: + if hasattr(listener, 'async_update'): + await listener.async_update() + + async def async_accept_signal(self, listener, signal, func, + signal_override=False): + """Accept a signal from a listener.""" + unsub = None + if signal_override: + unsub = async_dispatcher_connect( + self.hass, + signal, + func + ) + else: + unsub = async_dispatcher_connect( + self.hass, + "{}_{}".format(listener.unique_id, signal), + func + ) + self._unsubs.append(unsub) diff --git a/homeassistant/components/zha/event.py b/homeassistant/components/zha/event.py deleted file mode 100644 index 7828a695a7b..00000000000 --- a/homeassistant/components/zha/event.py +++ /dev/null @@ -1,99 +0,0 @@ -""" -Event for Zigbee Home Automation. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/zha/ -""" -import logging - -from homeassistant.core import EventOrigin, callback -from homeassistant.util import slugify - -_LOGGER = logging.getLogger(__name__) - - -class ZhaEvent(): - """A base class for ZHA events.""" - - def __init__(self, hass, cluster, **kwargs): - """Init ZHA event.""" - self._hass = hass - self._cluster = cluster - cluster.add_listener(self) - ieee = cluster.endpoint.device.ieee - ieeetail = ''.join(['%02x' % (o, ) for o in ieee[-4:]]) - endpoint = cluster.endpoint - if endpoint.manufacturer and endpoint.model is not None: - self._unique_id = "{}.{}_{}_{}_{}{}".format( - 'zha_event', - slugify(endpoint.manufacturer), - slugify(endpoint.model), - ieeetail, - cluster.endpoint.endpoint_id, - kwargs.get('entity_suffix', ''), - ) - else: - self._unique_id = "{}.zha_{}_{}{}".format( - 'zha_event', - ieeetail, - cluster.endpoint.endpoint_id, - kwargs.get('entity_suffix', ''), - ) - - @callback - def attribute_updated(self, attribute, value): - """Handle an attribute updated on this cluster.""" - pass - - @callback - def zdo_command(self, tsn, command_id, args): - """Handle a ZDO command received on this cluster.""" - pass - - @callback - def cluster_command(self, tsn, command_id, args): - """Handle a cluster command received on this cluster.""" - pass - - @callback - def zha_send_event(self, cluster, command, args): - """Relay entity events to hass.""" - self._hass.bus.async_fire( - 'zha_event', - { - 'unique_id': self._unique_id, - 'command': command, - 'args': args - }, - EventOrigin.remote - ) - - -class ZhaRelayEvent(ZhaEvent): - """Event relay that can be attached to zigbee clusters.""" - - @callback - def attribute_updated(self, attribute, value): - """Handle an attribute updated on this cluster.""" - self.zha_send_event( - self._cluster, - 'attribute_updated', - { - 'attribute_id': attribute, - 'attribute_name': self._cluster.attributes.get( - attribute, - ['Unknown'])[0], - 'value': value - } - ) - - @callback - def cluster_command(self, tsn, command_id, args): - """Handle a cluster command received on this cluster.""" - if self._cluster.server_commands is not None and\ - self._cluster.server_commands.get(command_id) is not None: - self.zha_send_event( - self._cluster, - self._cluster.server_commands.get(command_id)[0], - args - ) diff --git a/homeassistant/components/zha/fan.py b/homeassistant/components/zha/fan.py index f6dbef50923..dfe3c8cdd23 100644 --- a/homeassistant/components/zha/fan.py +++ b/homeassistant/components/zha/fan.py @@ -10,9 +10,10 @@ from homeassistant.components.fan import ( DOMAIN, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, SUPPORT_SET_SPEED, FanEntity) from homeassistant.helpers.dispatcher import async_dispatcher_connect -from .core import helpers from .core.const import ( - DATA_ZHA, DATA_ZHA_DISPATCHERS, REPORT_CONFIG_OP, ZHA_DISCOVERY_NEW) + DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW, LISTENER_FAN, + SIGNAL_ATTR_UPDATED +) from .entity import ZhaEntity DEPENDENCIES = ['zha'] @@ -79,19 +80,17 @@ class ZhaFan(ZhaEntity, FanEntity): """Representation of a ZHA fan.""" _domain = DOMAIN - value_attribute = 0 # fan_mode - @property - def zcl_reporting_config(self) -> dict: - """Return a dict of attribute reporting configuration.""" - return { - self.cluster: {self.value_attribute: REPORT_CONFIG_OP} - } + def __init__(self, unique_id, zha_device, listeners, **kwargs): + """Init this sensor.""" + super().__init__(unique_id, zha_device, listeners, **kwargs) + self._fan_listener = self.cluster_listeners.get(LISTENER_FAN) - @property - def cluster(self): - """Fan ZCL Cluster.""" - return self._endpoint.fan + async def async_added_to_hass(self): + """Run when about to be added to hass.""" + await super().async_added_to_hass() + await self.async_accept_signal( + self._fan_listener, SIGNAL_ATTR_UPDATED, self.async_set_state) @property def supported_features(self) -> int: @@ -115,6 +114,16 @@ class ZhaFan(ZhaEntity, FanEntity): return False return self._state != SPEED_OFF + @property + def device_state_attributes(self): + """Return state attributes.""" + return self.state_attributes + + def async_set_state(self, state): + """Handle state update from listener.""" + self._state = VALUE_TO_SPEED.get(state, self._state) + self.async_schedule_update_ha_state() + async def async_turn_on(self, speed: str = None, **kwargs) -> None: """Turn the entity on.""" if speed is None: @@ -128,31 +137,5 @@ class ZhaFan(ZhaEntity, FanEntity): async def async_set_speed(self, speed: str) -> None: """Set the speed of the fan.""" - from zigpy.exceptions import DeliveryError - try: - await self._endpoint.fan.write_attributes( - {'fan_mode': SPEED_TO_VALUE[speed]} - ) - except DeliveryError as ex: - _LOGGER.error("%s: Could not set speed: %s", self.entity_id, ex) - return - - self._state = speed - self.async_schedule_update_ha_state() - - async def async_update(self): - """Retrieve latest state.""" - result = await helpers.safe_read(self.cluster, ['fan_mode'], - allow_cache=False, - only_cache=(not self._initialized)) - new_value = result.get('fan_mode', None) - self._state = VALUE_TO_SPEED.get(new_value, None) - - def attribute_updated(self, attribute, value): - """Handle attribute update from device.""" - attr_name = self.cluster.attributes.get(attribute, [attribute])[0] - _LOGGER.debug("%s: Attribute report '%s'[%s] = %s", - self.entity_id, self.cluster.name, attr_name, value) - if attribute == self.value_attribute: - self._state = VALUE_TO_SPEED.get(value, self._state) - self.async_schedule_update_ha_state() + await self._fan_listener.async_set_speed(SPEED_TO_VALUE[speed]) + self.async_set_state(speed) diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index 49a09112b31..1d1b4c5f921 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -9,14 +9,12 @@ import logging from homeassistant.components import light from homeassistant.helpers.dispatcher import async_dispatcher_connect import homeassistant.util.color as color_util -from .core import helpers -from .core.const import ( - DATA_ZHA, DATA_ZHA_DISPATCHERS, REPORT_CONFIG_ASAP, REPORT_CONFIG_DEFAULT, - REPORT_CONFIG_IMMEDIATE, ZHA_DISCOVERY_NEW) +from .const import ( + DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW, LISTENER_COLOR, + LISTENER_ON_OFF, LISTENER_LEVEL, SIGNAL_ATTR_UPDATED, SIGNAL_SET_LEVEL + ) from .entity import ZhaEntity -from .core.listeners import ( - OnOffListener, LevelListener -) + _LOGGER = logging.getLogger(__name__) @@ -58,26 +56,6 @@ async def _async_setup_entities(hass, config_entry, async_add_entities, """Set up the ZHA lights.""" entities = [] for discovery_info in discovery_infos: - endpoint = discovery_info['endpoint'] - if hasattr(endpoint, 'light_color'): - caps = await helpers.safe_read( - endpoint.light_color, ['color_capabilities']) - discovery_info['color_capabilities'] = caps.get( - 'color_capabilities') - if discovery_info['color_capabilities'] is None: - # ZCL Version 4 devices don't support the color_capabilities - # attribute. In this version XY support is mandatory, but we - # need to probe to determine if the device supports color - # temperature. - discovery_info['color_capabilities'] = \ - CAPABILITIES_COLOR_XY - result = await helpers.safe_read( - endpoint.light_color, ['color_temperature']) - if (result.get('color_temperature') is not - UNSUPPORTED_ATTRIBUTE): - discovery_info['color_capabilities'] |= \ - CAPABILITIES_COLOR_TEMP - zha_light = Light(**discovery_info) entities.append(zha_light) @@ -89,34 +67,24 @@ class Light(ZhaEntity, light.Light): _domain = light.DOMAIN - def __init__(self, **kwargs): + def __init__(self, unique_id, zha_device, listeners, **kwargs): """Initialize the ZHA light.""" - super().__init__(**kwargs) + super().__init__(unique_id, zha_device, listeners, **kwargs) self._supported_features = 0 self._color_temp = None self._hs_color = None self._brightness = None - from zigpy.zcl.clusters.general import OnOff, LevelControl - self._in_listeners = { - OnOff.cluster_id: OnOffListener( - self, - self._in_clusters[OnOff.cluster_id] - ), - } + self._on_off_listener = self.cluster_listeners.get(LISTENER_ON_OFF) + self._level_listener = self.cluster_listeners.get(LISTENER_LEVEL) + self._color_listener = self.cluster_listeners.get(LISTENER_COLOR) - if LevelControl.cluster_id in self._in_clusters: + if self._level_listener: self._supported_features |= light.SUPPORT_BRIGHTNESS self._supported_features |= light.SUPPORT_TRANSITION self._brightness = 0 - self._in_listeners.update({ - LevelControl.cluster_id: LevelListener( - self, - self._in_clusters[LevelControl.cluster_id] - ) - }) - import zigpy.zcl.clusters as zcl_clusters - if zcl_clusters.lighting.Color.cluster_id in self._in_clusters: - color_capabilities = kwargs['color_capabilities'] + + if self._color_listener: + color_capabilities = self._color_listener.get_color_capabilities() if color_capabilities & CAPABILITIES_COLOR_TEMP: self._supported_features |= light.SUPPORT_COLOR_TEMP @@ -124,131 +92,28 @@ class Light(ZhaEntity, light.Light): self._supported_features |= light.SUPPORT_COLOR self._hs_color = (0, 0) - @property - def zcl_reporting_config(self) -> dict: - """Return attribute reporting configuration.""" - return { - 'on_off': {'on_off': REPORT_CONFIG_IMMEDIATE}, - 'level': {'current_level': REPORT_CONFIG_ASAP}, - 'light_color': { - 'current_x': REPORT_CONFIG_DEFAULT, - 'current_y': REPORT_CONFIG_DEFAULT, - 'color_temperature': REPORT_CONFIG_DEFAULT, - } - } - @property def is_on(self) -> bool: """Return true if entity is on.""" if self._state is None: return False - return bool(self._state) - - def set_state(self, state): - """Set the state.""" - self._state = state - self.async_schedule_update_ha_state() - - async def async_turn_on(self, **kwargs): - """Turn the entity on.""" - from zigpy.exceptions import DeliveryError - - duration = kwargs.get(light.ATTR_TRANSITION, DEFAULT_DURATION) - duration = duration * 10 # tenths of s - if light.ATTR_COLOR_TEMP in kwargs and \ - self.supported_features & light.SUPPORT_COLOR_TEMP: - temperature = kwargs[light.ATTR_COLOR_TEMP] - try: - res = await self._endpoint.light_color.move_to_color_temp( - temperature, duration) - _LOGGER.debug("%s: moved to %i color temp: %s", - self.entity_id, temperature, res) - except DeliveryError as ex: - _LOGGER.error("%s: Couldn't change color temp: %s", - self.entity_id, ex) - return - self._color_temp = temperature - - if light.ATTR_HS_COLOR in kwargs and \ - self.supported_features & light.SUPPORT_COLOR: - self._hs_color = kwargs[light.ATTR_HS_COLOR] - xy_color = color_util.color_hs_to_xy(*self._hs_color) - try: - res = await self._endpoint.light_color.move_to_color( - int(xy_color[0] * 65535), - int(xy_color[1] * 65535), - duration, - ) - _LOGGER.debug("%s: moved XY color to (%1.2f, %1.2f): %s", - self.entity_id, xy_color[0], xy_color[1], res) - except DeliveryError as ex: - _LOGGER.error("%s: Couldn't change color temp: %s", - self.entity_id, ex) - return - - if self._brightness is not None: - brightness = kwargs.get( - light.ATTR_BRIGHTNESS, self._brightness or 255) - # Move to level with on/off: - try: - res = await self._endpoint.level.move_to_level_with_on_off( - brightness, - duration - ) - _LOGGER.debug("%s: moved to %i level with on/off: %s", - self.entity_id, brightness, res) - except DeliveryError as ex: - _LOGGER.error("%s: Couldn't change brightness level: %s", - self.entity_id, ex) - return - self._state = 1 - self._brightness = brightness - self.async_schedule_update_ha_state() - return - - try: - res = await self._endpoint.on_off.on() - _LOGGER.debug("%s was turned on: %s", self.entity_id, res) - except DeliveryError as ex: - _LOGGER.error("%s: Unable to turn the light on: %s", - self.entity_id, ex) - return - - self._state = 1 - self.async_schedule_update_ha_state() - - async def async_turn_off(self, **kwargs): - """Turn the entity off.""" - from zigpy.exceptions import DeliveryError - duration = kwargs.get(light.ATTR_TRANSITION) - try: - supports_level = self.supported_features & light.SUPPORT_BRIGHTNESS - if duration and supports_level: - res = await self._endpoint.level.move_to_level_with_on_off( - 0, duration*10 - ) - else: - res = await self._endpoint.on_off.off() - _LOGGER.debug("%s was turned off: %s", self.entity_id, res) - except DeliveryError as ex: - _LOGGER.error("%s: Unable to turn the light off: %s", - self.entity_id, ex) - return - - self._state = 0 - self.async_schedule_update_ha_state() + return self._state @property def brightness(self): - """Return the brightness of this light between 0..255.""" + """Return the brightness of this light.""" return self._brightness + @property + def device_state_attributes(self): + """Return state attributes.""" + return self.state_attributes + def set_level(self, value): """Set the brightness of this light between 0..255.""" - if value < 0 or value > 255: - return + value = max(0, min(255, value)) self._brightness = value - self.async_schedule_update_ha_state() + self.async_set_state(value) @property def hs_color(self): @@ -265,40 +130,82 @@ class Light(ZhaEntity, light.Light): """Flag supported features.""" return self._supported_features - async def async_update(self): - """Retrieve latest state.""" - result = await helpers.safe_read(self._endpoint.on_off, ['on_off'], - allow_cache=False, - only_cache=(not self._initialized)) - self._state = result.get('on_off', self._state) + def async_set_state(self, state): + """Set the state.""" + self._state = bool(state) + self.async_schedule_update_ha_state() - if self._supported_features & light.SUPPORT_BRIGHTNESS: - result = await helpers.safe_read(self._endpoint.level, - ['current_level'], - allow_cache=False, - only_cache=( - not self._initialized - )) - self._brightness = result.get('current_level', self._brightness) + async def async_added_to_hass(self): + """Run when about to be added to hass.""" + await super().async_added_to_hass() + await self.async_accept_signal( + self._on_off_listener, SIGNAL_ATTR_UPDATED, self.async_set_state) + if self._level_listener: + await self.async_accept_signal( + self._level_listener, SIGNAL_SET_LEVEL, self.set_level) - if self._supported_features & light.SUPPORT_COLOR_TEMP: - result = await helpers.safe_read(self._endpoint.light_color, - ['color_temperature'], - allow_cache=False, - only_cache=( - not self._initialized - )) - self._color_temp = result.get('color_temperature', - self._color_temp) + async def async_turn_on(self, **kwargs): + """Turn the entity on.""" + duration = kwargs.get(light.ATTR_TRANSITION, DEFAULT_DURATION) + duration = duration * 10 # tenths of s - if self._supported_features & light.SUPPORT_COLOR: - result = await helpers.safe_read(self._endpoint.light_color, - ['current_x', 'current_y'], - allow_cache=False, - only_cache=( - not self._initialized - )) - if 'current_x' in result and 'current_y' in result: - xy_color = (round(result['current_x']/65535, 3), - round(result['current_y']/65535, 3)) - self._hs_color = color_util.color_xy_to_hs(*xy_color) + if light.ATTR_COLOR_TEMP in kwargs and \ + self.supported_features & light.SUPPORT_COLOR_TEMP: + temperature = kwargs[light.ATTR_COLOR_TEMP] + success = await self._color_listener.move_to_color_temp( + temperature, duration) + if not success: + return + self._color_temp = temperature + + if light.ATTR_HS_COLOR in kwargs and \ + self.supported_features & light.SUPPORT_COLOR: + hs_color = kwargs[light.ATTR_HS_COLOR] + xy_color = color_util.color_hs_to_xy(*hs_color) + success = await self._color_listener.move_to_color( + int(xy_color[0] * 65535), + int(xy_color[1] * 65535), + duration, + ) + if not success: + return + self._hs_color = hs_color + + if self._brightness is not None: + brightness = kwargs.get( + light.ATTR_BRIGHTNESS, self._brightness or 255) + success = await self._level_listener.move_to_level_with_on_off( + brightness, + duration + ) + if not success: + return + self._state = True + self._brightness = brightness + self.async_schedule_update_ha_state() + return + + success = await self._on_off_listener.on() + if not success: + return + + self._state = True + self.async_schedule_update_ha_state() + + async def async_turn_off(self, **kwargs): + """Turn the entity off.""" + duration = kwargs.get(light.ATTR_TRANSITION) + supports_level = self.supported_features & light.SUPPORT_BRIGHTNESS + success = None + if duration and supports_level: + success = await self._level_listener.move_to_level_with_on_off( + 0, + duration*10 + ) + else: + success = await self._on_off_listener.off() + _LOGGER.debug("%s was turned off: %s", self.entity_id, success) + if not success: + return + self._state = False + self.async_schedule_update_ha_state() diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index ae45fad0826..ad566df00f4 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -9,11 +9,11 @@ import logging from homeassistant.components.sensor import DOMAIN from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.util.temperature import convert as convert_temperature -from .core import helpers from .core.const import ( - DATA_ZHA, DATA_ZHA_DISPATCHERS, REPORT_CONFIG_MAX_INT, - REPORT_CONFIG_MIN_INT, REPORT_CONFIG_RPT_CHANGE, ZHA_DISCOVERY_NEW) + DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW, HUMIDITY, TEMPERATURE, + ILLUMINANCE, PRESSURE, METERING, ELECTRICAL_MEASUREMENT, + POWER_CONFIGURATION, GENERIC, SENSOR_TYPE, LISTENER_ATTRIBUTE, + LISTENER_ACTIVE_POWER, SIGNAL_ATTR_UPDATED, SIGNAL_STATE_ATTR) from .entity import ZhaEntity _LOGGER = logging.getLogger(__name__) @@ -21,6 +21,73 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['zha'] +# Formatter functions +def pass_through_formatter(value): + """No op update function.""" + return value + + +def temperature_formatter(value): + """Convert temperature data.""" + if value is None: + return None + return round(value / 100, 1) + + +def humidity_formatter(value): + """Return the state of the entity.""" + if value is None: + return None + return round(float(value) / 100, 1) + + +def active_power_formatter(value): + """Return the state of the entity.""" + if value is None: + return None + return round(float(value) / 10, 1) + + +def pressure_formatter(value): + """Return the state of the entity.""" + if value is None: + return None + + return round(float(value)) + + +FORMATTER_FUNC_REGISTRY = { + HUMIDITY: humidity_formatter, + TEMPERATURE: temperature_formatter, + PRESSURE: pressure_formatter, + ELECTRICAL_MEASUREMENT: active_power_formatter, + GENERIC: pass_through_formatter, +} + +UNIT_REGISTRY = { + HUMIDITY: '%', + TEMPERATURE: TEMP_CELSIUS, + PRESSURE: 'hPa', + ILLUMINANCE: 'lx', + METERING: 'W', + ELECTRICAL_MEASUREMENT: 'W', + POWER_CONFIGURATION: '%', + GENERIC: None +} + +LISTENER_REGISTRY = { + ELECTRICAL_MEASUREMENT: LISTENER_ACTIVE_POWER, +} + +POLLING_REGISTRY = { + ELECTRICAL_MEASUREMENT: True +} + +FORCE_UPDATE_REGISTRY = { + ELECTRICAL_MEASUREMENT: True +} + + async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Old way of setting up Zigbee Home Automation sensors.""" @@ -56,279 +123,59 @@ async def _async_setup_entities(hass, config_entry, async_add_entities, async def make_sensor(discovery_info): """Create ZHA sensors factory.""" - from zigpy.zcl.clusters.measurement import ( - RelativeHumidity, TemperatureMeasurement, PressureMeasurement, - IlluminanceMeasurement - ) - from zigpy.zcl.clusters.smartenergy import Metering - from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement - from zigpy.zcl.clusters.general import PowerConfiguration - in_clusters = discovery_info['in_clusters'] - if 'sub_component' in discovery_info: - sensor = discovery_info['sub_component'](**discovery_info) - elif RelativeHumidity.cluster_id in in_clusters: - sensor = RelativeHumiditySensor(**discovery_info) - elif PowerConfiguration.cluster_id in in_clusters: - sensor = GenericBatterySensor(**discovery_info) - elif TemperatureMeasurement.cluster_id in in_clusters: - sensor = TemperatureSensor(**discovery_info) - elif PressureMeasurement.cluster_id in in_clusters: - sensor = PressureSensor(**discovery_info) - elif IlluminanceMeasurement.cluster_id in in_clusters: - sensor = IlluminanceMeasurementSensor(**discovery_info) - elif Metering.cluster_id in in_clusters: - sensor = MeteringSensor(**discovery_info) - elif ElectricalMeasurement.cluster_id in in_clusters: - sensor = ElectricalMeasurementSensor(**discovery_info) - return sensor - else: - sensor = Sensor(**discovery_info) - - return sensor + return Sensor(**discovery_info) class Sensor(ZhaEntity): """Base ZHA sensor.""" _domain = DOMAIN - value_attribute = 0 - min_report_interval = REPORT_CONFIG_MIN_INT - max_report_interval = REPORT_CONFIG_MAX_INT - min_reportable_change = REPORT_CONFIG_RPT_CHANGE - report_config = (min_report_interval, max_report_interval, - min_reportable_change) - def __init__(self, **kwargs): - """Init ZHA Sensor instance.""" - super().__init__(**kwargs) - self._cluster = list(kwargs['in_clusters'].values())[0] + def __init__(self, unique_id, zha_device, listeners, **kwargs): + """Init this sensor.""" + super().__init__(unique_id, zha_device, listeners, **kwargs) + sensor_type = kwargs.get(SENSOR_TYPE, GENERIC) + self._unit = UNIT_REGISTRY.get(sensor_type) + self._formatter_function = FORMATTER_FUNC_REGISTRY.get( + sensor_type, + pass_through_formatter + ) + self._force_update = FORCE_UPDATE_REGISTRY.get( + sensor_type, + False + ) + self._should_poll = POLLING_REGISTRY.get( + sensor_type, + False + ) + self._listener = self.cluster_listeners.get( + LISTENER_REGISTRY.get(sensor_type, LISTENER_ATTRIBUTE) + ) + + async def async_added_to_hass(self): + """Run when about to be added to hass.""" + await super().async_added_to_hass() + await self.async_accept_signal( + self._listener, SIGNAL_ATTR_UPDATED, self.async_set_state) + await self.async_accept_signal( + self._listener, SIGNAL_STATE_ATTR, + self.async_update_state_attribute) @property - def zcl_reporting_config(self) -> dict: - """Return a dict of attribute reporting configuration.""" - return { - self.cluster: {self.value_attribute: self.report_config} - } - - @property - def cluster(self): - """Return Sensor's cluster.""" - return self._cluster + def unit_of_measurement(self): + """Return the unit of measurement of this entity.""" + return self._unit @property def state(self) -> str: """Return the state of the entity.""" + if self._state is None: + return None if isinstance(self._state, float): return str(round(self._state, 2)) return self._state - def attribute_updated(self, attribute, value): - """Handle attribute update from device.""" - _LOGGER.debug("Attribute updated: %s %s %s", self, attribute, value) - if attribute == self.value_attribute: - self._state = value - self.async_schedule_update_ha_state() - - async def async_update(self): - """Retrieve latest state.""" - result = await helpers.safe_read( - self.cluster, - [self.value_attribute], - allow_cache=False, - only_cache=(not self._initialized) - ) - self._state = result.get(self.value_attribute, self._state) - - -class GenericBatterySensor(Sensor): - """ZHA generic battery sensor.""" - - report_attribute = 32 - value_attribute = 33 - battery_sizes = { - 0: 'No battery', - 1: 'Built in', - 2: 'Other', - 3: 'AA', - 4: 'AAA', - 5: 'C', - 6: 'D', - 7: 'CR2', - 8: 'CR123A', - 9: 'CR2450', - 10: 'CR2032', - 11: 'CR1632', - 255: 'Unknown' - } - - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity.""" - return '%' - - @property - def zcl_reporting_config(self) -> dict: - """Return a dict of attribute reporting configuration.""" - return { - self.cluster: { - self.value_attribute: self.report_config, - self.report_attribute: self.report_config - } - } - - async def async_update(self): - """Retrieve latest state.""" - _LOGGER.debug("%s async_update", self.entity_id) - - result = await helpers.safe_read( - self._endpoint.power, - [ - 'battery_size', - 'battery_quantity', - 'battery_percentage_remaining' - ], - allow_cache=False, - only_cache=(not self._initialized) - ) - self._device_state_attributes['battery_size'] = self.battery_sizes.get( - result.get('battery_size', 255), 'Unknown') - self._device_state_attributes['battery_quantity'] = result.get( - 'battery_quantity', 'Unknown') - self._state = result.get('battery_percentage_remaining', self._state) - - @property - def state(self): - """Return the state of the entity.""" - if self._state == 'unknown' or self._state is None: - return None - - return self._state - - -class TemperatureSensor(Sensor): - """ZHA temperature sensor.""" - - min_reportable_change = 50 # 0.5'C - - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity.""" - return self.hass.config.units.temperature_unit - - @property - def state(self): - """Return the state of the entity.""" - if self._state is None: - return None - celsius = self._state / 100 - return round(convert_temperature(celsius, - TEMP_CELSIUS, - self.unit_of_measurement), - 1) - - -class RelativeHumiditySensor(Sensor): - """ZHA relative humidity sensor.""" - - min_reportable_change = 50 # 0.5% - - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity.""" - return '%' - - @property - def state(self): - """Return the state of the entity.""" - if self._state is None: - return None - - return round(float(self._state) / 100, 1) - - -class PressureSensor(Sensor): - """ZHA pressure sensor.""" - - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity.""" - return 'hPa' - - @property - def state(self): - """Return the state of the entity.""" - if self._state is None: - return None - - return round(float(self._state)) - - -class IlluminanceMeasurementSensor(Sensor): - """ZHA lux sensor.""" - - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity.""" - return 'lx' - - @property - def state(self): - """Return the state of the entity.""" - return self._state - - -class MeteringSensor(Sensor): - """ZHA Metering sensor.""" - - value_attribute = 1024 - - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity.""" - return 'W' - - @property - def state(self): - """Return the state of the entity.""" - if self._state is None: - return None - - return round(float(self._state)) - - -class ElectricalMeasurementSensor(Sensor): - """ZHA Electrical Measurement sensor.""" - - value_attribute = 1291 - - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity.""" - return 'W' - - @property - def force_update(self) -> bool: - """Force update this entity.""" - return True - - @property - def state(self): - """Return the state of the entity.""" - if self._state is None: - return None - - return round(float(self._state) / 10, 1) - - @property - def should_poll(self) -> bool: - """Poll state from device.""" - return True - - async def async_update(self): - """Retrieve latest state.""" - _LOGGER.debug("%s async_update", self.entity_id) - - result = await helpers.safe_read( - self.cluster, ['active_power'], - allow_cache=False, only_cache=(not self._initialized)) - self._state = result.get('active_power', self._state) + def async_set_state(self, state): + """Handle state update from listener.""" + self._state = self._formatter_function(state) + self.async_schedule_update_ha_state() diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py index 09c20acd088..4eee3d5da35 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -8,9 +8,10 @@ import logging from homeassistant.components.switch import DOMAIN, SwitchDevice from homeassistant.helpers.dispatcher import async_dispatcher_connect -from .core import helpers from .core.const import ( - DATA_ZHA, DATA_ZHA_DISPATCHERS, REPORT_CONFIG_IMMEDIATE, ZHA_DISCOVERY_NEW) + DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW, LISTENER_ON_OFF, + SIGNAL_ATTR_UPDATED +) from .entity import ZhaEntity _LOGGER = logging.getLogger(__name__) @@ -55,69 +56,39 @@ class Switch(ZhaEntity, SwitchDevice): """ZHA switch.""" _domain = DOMAIN - value_attribute = 0 - def attribute_updated(self, attribute, value): - """Handle attribute update from device.""" - cluster = self._endpoint.on_off - attr_name = cluster.attributes.get(attribute, [attribute])[0] - _LOGGER.debug("%s: Attribute '%s' on cluster '%s' updated to %s", - self.entity_id, attr_name, cluster.ep_attribute, value) - if attribute == self.value_attribute: - self._state = value - self.async_schedule_update_ha_state() - - @property - def zcl_reporting_config(self) -> dict: - """Retrun a dict of attribute reporting configuration.""" - return { - self.cluster: {'on_off': REPORT_CONFIG_IMMEDIATE} - } - - @property - def cluster(self): - """Entity's cluster.""" - return self._endpoint.on_off + def __init__(self, **kwargs): + """Initialize the ZHA switch.""" + super().__init__(**kwargs) + self._on_off_listener = self.cluster_listeners.get(LISTENER_ON_OFF) @property def is_on(self) -> bool: """Return if the switch is on based on the statemachine.""" if self._state is None: return False - return bool(self._state) + return self._state async def async_turn_on(self, **kwargs): """Turn the entity on.""" - from zigpy.exceptions import DeliveryError - try: - res = await self._endpoint.on_off.on() - _LOGGER.debug("%s: turned 'on': %s", self.entity_id, res[1]) - except DeliveryError as ex: - _LOGGER.error("%s: Unable to turn the switch on: %s", - self.entity_id, ex) - return - - self._state = 1 - self.async_schedule_update_ha_state() + await self._on_off_listener.on() async def async_turn_off(self, **kwargs): """Turn the entity off.""" - from zigpy.exceptions import DeliveryError - try: - res = await self._endpoint.on_off.off() - _LOGGER.debug("%s: turned 'off': %s", self.entity_id, res[1]) - except DeliveryError as ex: - _LOGGER.error("%s: Unable to turn the switch off: %s", - self.entity_id, ex) - return + await self._on_off_listener.off() - self._state = 0 + def async_set_state(self, state): + """Handle state update from listener.""" + self._state = bool(state) self.async_schedule_update_ha_state() - async def async_update(self): - """Retrieve latest state.""" - result = await helpers.safe_read(self.cluster, - ['on_off'], - allow_cache=False, - only_cache=(not self._initialized)) - self._state = result.get('on_off', self._state) + @property + def device_state_attributes(self): + """Return state attributes.""" + return self.state_attributes + + async def async_added_to_hass(self): + """Run when about to be added to hass.""" + await super().async_added_to_hass() + await self.async_accept_signal( + self._on_off_listener, SIGNAL_ATTR_UPDATED, self.async_set_state) diff --git a/tests/components/zha/conftest.py b/tests/components/zha/conftest.py index 624c6a02964..c806b1a2217 100644 --- a/tests/components/zha/conftest.py +++ b/tests/components/zha/conftest.py @@ -3,9 +3,12 @@ from unittest.mock import patch import pytest from homeassistant import config_entries from homeassistant.components.zha.core.const import ( - DOMAIN, DATA_ZHA + DOMAIN, DATA_ZHA, COMPONENTS ) from homeassistant.components.zha.core.gateway import ZHAGateway +from homeassistant.components.zha.core.gateway import establish_device_mappings +from homeassistant.components.zha.core.listeners \ + import populate_listener_registry from .common import async_setup_entry @@ -25,6 +28,12 @@ def zha_gateway_fixture(hass): Create a ZHAGateway object that can be used to interact with as if we had a real zigbee network running. """ + populate_listener_registry() + establish_device_mappings() + for component in COMPONENTS: + hass.data[DATA_ZHA][component] = ( + hass.data[DATA_ZHA].get(component, {}) + ) return ZHAGateway(hass, {}) diff --git a/tests/components/zha/test_binary_sensor.py b/tests/components/zha/test_binary_sensor.py index 7c0c8b5350f..ba723987042 100644 --- a/tests/components/zha/test_binary_sensor.py +++ b/tests/components/zha/test_binary_sensor.py @@ -48,6 +48,7 @@ async def test_binary_sensor(hass, config_entry, zha_gateway): # load up binary_sensor domain await hass.config_entries.async_forward_entry_setup( config_entry, DOMAIN) + await zha_gateway.accept_zigbee_messages({}) await hass.async_block_till_done() # on off binary_sensor diff --git a/tests/components/zha/test_fan.py b/tests/components/zha/test_fan.py index c19225bf310..14da94bdf52 100644 --- a/tests/components/zha/test_fan.py +++ b/tests/components/zha/test_fan.py @@ -26,6 +26,7 @@ async def test_fan(hass, config_entry, zha_gateway): # load up fan domain await hass.config_entries.async_forward_entry_setup( config_entry, DOMAIN) + await zha_gateway.accept_zigbee_messages({}) await hass.async_block_till_done() cluster = zigpy_device.endpoints.get(1).fan diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index d9063b4885a..e94e53c293d 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -40,6 +40,7 @@ async def test_light(hass, config_entry, zha_gateway): # load up light domain await hass.config_entries.async_forward_entry_setup( config_entry, DOMAIN) + await zha_gateway.accept_zigbee_messages({}) await hass.async_block_till_done() # on off light diff --git a/tests/components/zha/test_sensor.py b/tests/components/zha/test_sensor.py index 3933f416e3d..18d2e152beb 100644 --- a/tests/components/zha/test_sensor.py +++ b/tests/components/zha/test_sensor.py @@ -92,6 +92,7 @@ async def async_build_devices(hass, zha_gateway, config_entry, cluster_ids): # load up sensor domain await hass.config_entries.async_forward_entry_setup( config_entry, DOMAIN) + await zha_gateway.accept_zigbee_messages({}) await hass.async_block_till_done() # put the other relevant info in the device info dict diff --git a/tests/components/zha/test_switch.py b/tests/components/zha/test_switch.py index d3415bde59b..4e6ff6da6ba 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -24,6 +24,7 @@ async def test_switch(hass, config_entry, zha_gateway): # load up switch domain await hass.config_entries.async_forward_entry_setup( config_entry, DOMAIN) + await zha_gateway.accept_zigbee_messages({}) await hass.async_block_till_done() cluster = zigpy_device.endpoints.get(1).on_off @@ -44,6 +45,7 @@ async def test_switch(hass, config_entry, zha_gateway): await hass.async_block_till_done() assert hass.states.get(entity_id).state == STATE_OFF + # turn on from HA with patch( 'zigpy.zcl.Cluster.request', return_value=mock_coro([Status.SUCCESS, Status.SUCCESS])): @@ -55,6 +57,7 @@ async def test_switch(hass, config_entry, zha_gateway): assert cluster.request.call_args == call( False, ON, (), expect_reply=True, manufacturer=None) + # turn off from HA with patch( 'zigpy.zcl.Cluster.request', return_value=mock_coro([Status.SUCCESS, Status.SUCCESS])): @@ -66,5 +69,6 @@ async def test_switch(hass, config_entry, zha_gateway): assert cluster.request.call_args == call( False, OFF, (), expect_reply=True, manufacturer=None) + # test joining a new switch to the network and HA await async_test_device_join( hass, zha_gateway, OnOff.cluster_id, DOMAIN, expected_state=STATE_OFF) From 59393ab085a48c977d07dced1faf1d4be2a3da12 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 6 Feb 2019 11:15:27 -0800 Subject: [PATCH 079/242] Prevent template changing options (#20775) * Prevent complex template validation changing input value * Remove deprecation warnings --- homeassistant/helpers/config_validation.py | 14 ++++++++------ tests/helpers/test_config_validation.py | 12 ++++++++++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 36dca2bbcaf..3a4b9ced0ab 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -436,13 +436,15 @@ def template(value): def template_complex(value): """Validate a complex jinja2 template.""" if isinstance(value, list): - for idx, element in enumerate(value): - value[idx] = template_complex(element) - return value + return_value = value.copy() + for idx, element in enumerate(return_value): + return_value[idx] = template_complex(element) + return return_value if isinstance(value, dict): - for key, element in value.items(): - value[key] = template_complex(element) - return value + return_value = value.copy() + for key, element in return_value.items(): + return_value[key] = template_complex(element) + return return_value return template(value) diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 53ed8004f7d..119725b06dd 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -402,8 +402,7 @@ def test_template(): schema = vol.Schema(cv.template) for value in (None, '{{ partial_print }', '{% if True %}Hello', ['test']): - with pytest.raises(vol.Invalid, - message='{} not considered invalid'.format(value)): + with pytest.raises(vol.Invalid): schema(value) options = ( @@ -433,6 +432,15 @@ def test_template_complex(): for value in options: schema(value) + # ensure the validator didn't mutate the input + assert options == ( + 1, 'Hello', + '{{ beer }}', + '{% if 1 == 1 %}Hello{% else %}World{% endif %}', + {'test': 1, 'test2': '{{ beer }}'}, + ['{{ beer }}', 1] + ) + def test_time_zone(): """Test time zone validation.""" From fb1da53568bb243abc8780734b6082ec9e17cd02 Mon Sep 17 00:00:00 2001 From: Greg Johnson Date: Wed, 6 Feb 2019 11:16:21 -0800 Subject: [PATCH 080/242] Allow both VOLUME_STEP and VOLUME_SET (#20732) * Allow both VOLUME_STEP and VOLUME_SET Seems like it should be possible to support both at the same time. * Update test to allow VOLUME_SET and VOLUME_STEP --- homeassistant/components/media_player/universal.py | 3 +-- tests/components/media_player/test_universal.py | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/media_player/universal.py b/homeassistant/components/media_player/universal.py index 18b953a0372..a0c99dbec45 100644 --- a/homeassistant/components/media_player/universal.py +++ b/homeassistant/components/media_player/universal.py @@ -333,8 +333,7 @@ class UniversalMediaPlayer(MediaPlayerDevice): if any([cmd in self._cmds for cmd in [SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN]]): flags |= SUPPORT_VOLUME_STEP - flags &= ~SUPPORT_VOLUME_SET - elif SERVICE_VOLUME_SET in self._cmds: + if SERVICE_VOLUME_SET in self._cmds: flags |= SUPPORT_VOLUME_SET if SERVICE_VOLUME_MUTE in self._cmds and \ diff --git a/tests/components/media_player/test_universal.py b/tests/components/media_player/test_universal.py index 58911776836..ee86735a063 100644 --- a/tests/components/media_player/test_universal.py +++ b/tests/components/media_player/test_universal.py @@ -584,7 +584,8 @@ class TestMediaPlayer(unittest.TestCase): check_flags = universal.SUPPORT_TURN_ON | universal.SUPPORT_TURN_OFF \ | universal.SUPPORT_VOLUME_STEP | universal.SUPPORT_VOLUME_MUTE \ - | universal.SUPPORT_SELECT_SOURCE | universal.SUPPORT_SHUFFLE_SET + | universal.SUPPORT_SELECT_SOURCE | universal.SUPPORT_SHUFFLE_SET \ + | universal.SUPPORT_VOLUME_SET assert check_flags == ump.supported_features From 06f3e8137af1895c599f2bf73c3ea74edee88dc4 Mon Sep 17 00:00:00 2001 From: Robert Schindler Date: Thu, 7 Feb 2019 01:36:41 +0100 Subject: [PATCH 081/242] Added command_line auth provider that validates credentials by calling a command (#19985) * Added external auth provider that calls a configurable program Closes #19975 * Raise proper InvalidAuth exception on OSError during program execution * Changed name of external auth provider to command_line * Renamed program config option to command in command_line auth provider * Made meta variable parsing in command_line auth provider optional * Added tests for command_line auth provider * Fixed indentation * Suppressed wrong pylint warning * Fixed linting * Added test for command line auth provider login flow * Log error when user fails authentication * Use %r formatter instead of explicit repr() * Mix all used names of typing module into module namespace I consider this nasty and bad coding style, but was requested by @awarecan for consistency with the remaining codebase. * Small code style change * Strip usernames with command_line auth provider --- homeassistant/auth/providers/command_line.py | 164 ++++++++++++++++++ tests/auth/providers/test_command_line.py | 148 ++++++++++++++++ tests/auth/providers/test_command_line_cmd.sh | 12 ++ 3 files changed, 324 insertions(+) create mode 100644 homeassistant/auth/providers/command_line.py create mode 100644 tests/auth/providers/test_command_line.py create mode 100755 tests/auth/providers/test_command_line_cmd.sh diff --git a/homeassistant/auth/providers/command_line.py b/homeassistant/auth/providers/command_line.py new file mode 100644 index 00000000000..9cec34c1340 --- /dev/null +++ b/homeassistant/auth/providers/command_line.py @@ -0,0 +1,164 @@ +"""Auth provider that validates credentials via an external command.""" + +from typing import Any, Dict, Optional, cast + +import asyncio.subprocess +import collections +import logging +import os + +import voluptuous as vol + +from homeassistant.exceptions import HomeAssistantError + +from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow +from ..models import Credentials, UserMeta + + +CONF_COMMAND = "command" +CONF_ARGS = "args" +CONF_META = "meta" + +CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({ + vol.Required(CONF_COMMAND): vol.All( + str, + os.path.normpath, + msg="must be an absolute path" + ), + vol.Optional(CONF_ARGS, default=None): vol.Any(vol.DefaultTo(list), [str]), + vol.Optional(CONF_META, default=False): bool, +}, extra=vol.PREVENT_EXTRA) + +_LOGGER = logging.getLogger(__name__) + + +class InvalidAuthError(HomeAssistantError): + """Raised when authentication with given credentials fails.""" + + +@AUTH_PROVIDERS.register("command_line") +class CommandLineAuthProvider(AuthProvider): + """Auth provider validating credentials by calling a command.""" + + DEFAULT_TITLE = "Command Line Authentication" + + # which keys to accept from a program's stdout + ALLOWED_META_KEYS = ("name",) + + def __init__(self, *args: Any, **kwargs: Any) -> None: + """Extend parent's __init__. + + Adds self._user_meta dictionary to hold the user-specific + attributes provided by external programs. + """ + super().__init__(*args, **kwargs) + self._user_meta = {} # type: Dict[str, Dict[str, Any]] + + async def async_login_flow(self, context: Optional[dict]) -> LoginFlow: + """Return a flow to login.""" + return CommandLineLoginFlow(self) + + async def async_validate_login(self, username: str, password: str) -> None: + """Validate a username and password.""" + env = { + "username": username, + "password": password, + } + try: + # pylint: disable=no-member + process = await asyncio.subprocess.create_subprocess_exec( + self.config[CONF_COMMAND], *self.config[CONF_ARGS], + env=env, + stdout=asyncio.subprocess.PIPE + if self.config[CONF_META] else None, + ) + stdout, _ = (await process.communicate()) + except OSError as err: + # happens when command doesn't exist or permission is denied + _LOGGER.error("Error while authenticating %r: %s", + username, err) + raise InvalidAuthError + + if process.returncode != 0: + _LOGGER.error("User %r failed to authenticate, command exited " + "with code %d.", + username, process.returncode) + raise InvalidAuthError + + if self.config[CONF_META]: + meta = {} # type: Dict[str, str] + for _line in stdout.splitlines(): + try: + line = _line.decode().lstrip() + if line.startswith("#"): + continue + key, value = line.split("=", 1) + except ValueError: + # malformed line + continue + key = key.strip() + value = value.strip() + if key in self.ALLOWED_META_KEYS: + meta[key] = value + self._user_meta[username] = meta + + async def async_get_or_create_credentials( + self, flow_result: Dict[str, str] + ) -> Credentials: + """Get credentials based on the flow result.""" + username = flow_result["username"] + for credential in await self.async_credentials(): + if credential.data["username"] == username: + return credential + + # Create new credentials. + return self.async_create_credentials({ + "username": username, + }) + + async def async_user_meta_for_credentials( + self, credentials: Credentials + ) -> UserMeta: + """Return extra user metadata for credentials. + + Currently, only name is supported. + """ + meta = self._user_meta.get(credentials.data["username"], {}) + return UserMeta( + name=meta.get("name"), + is_active=True, + ) + + +class CommandLineLoginFlow(LoginFlow): + """Handler for the login flow.""" + + async def async_step_init( + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: + """Handle the step of the form.""" + errors = {} + + if user_input is not None: + user_input["username"] = user_input["username"].strip() + try: + await cast(CommandLineAuthProvider, self._auth_provider) \ + .async_validate_login( + user_input["username"], user_input["password"] + ) + except InvalidAuthError: + errors["base"] = "invalid_auth" + + if not errors: + user_input.pop("password") + return await self.async_finish(user_input) + + schema = collections.OrderedDict() # type: Dict[str, type] + schema["username"] = str + schema["password"] = str + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema(schema), + errors=errors, + ) diff --git a/tests/auth/providers/test_command_line.py b/tests/auth/providers/test_command_line.py new file mode 100644 index 00000000000..f22958e7e38 --- /dev/null +++ b/tests/auth/providers/test_command_line.py @@ -0,0 +1,148 @@ +"""Tests for the command_line auth provider.""" + +from unittest.mock import Mock +import os +import uuid + +import pytest + +from homeassistant import data_entry_flow +from homeassistant.auth import auth_store, models as auth_models, AuthManager +from homeassistant.auth.providers import command_line +from homeassistant.const import CONF_TYPE + +from tests.common import mock_coro + + +@pytest.fixture +def store(hass): + """Mock store.""" + return auth_store.AuthStore(hass) + + +@pytest.fixture +def provider(hass, store): + """Mock provider.""" + return command_line.CommandLineAuthProvider(hass, store, { + CONF_TYPE: "command_line", + command_line.CONF_COMMAND: os.path.join( + os.path.dirname(__file__), "test_command_line_cmd.sh" + ), + command_line.CONF_ARGS: [], + command_line.CONF_META: False, + }) + + +@pytest.fixture +def manager(hass, store, provider): + """Mock manager.""" + return AuthManager(hass, store, { + (provider.type, provider.id): provider + }, {}) + + +async def test_create_new_credential(manager, provider): + """Test that we create a new credential.""" + credentials = await provider.async_get_or_create_credentials({ + "username": "good-user", + "password": "good-pass", + }) + assert credentials.is_new is True + + user = await manager.async_get_or_create_user(credentials) + assert user.is_active + + +async def test_match_existing_credentials(store, provider): + """See if we match existing users.""" + existing = auth_models.Credentials( + id=uuid.uuid4(), + auth_provider_type="command_line", + auth_provider_id=None, + data={ + "username": "good-user" + }, + is_new=False, + ) + provider.async_credentials = Mock(return_value=mock_coro([existing])) + credentials = await provider.async_get_or_create_credentials({ + "username": "good-user", + "password": "irrelevant", + }) + assert credentials is existing + + +async def test_invalid_username(provider): + """Test we raise if incorrect user specified.""" + with pytest.raises(command_line.InvalidAuthError): + await provider.async_validate_login("bad-user", "good-pass") + + +async def test_invalid_password(provider): + """Test we raise if incorrect password specified.""" + with pytest.raises(command_line.InvalidAuthError): + await provider.async_validate_login("good-user", "bad-pass") + + +async def test_good_auth(provider): + """Test nothing is raised with good credentials.""" + await provider.async_validate_login("good-user", "good-pass") + + +async def test_good_auth_with_meta(manager, provider): + """Test metadata is added upon successful authentication.""" + provider.config[command_line.CONF_ARGS] = ["--with-meta"] + provider.config[command_line.CONF_META] = True + + await provider.async_validate_login("good-user", "good-pass") + + credentials = await provider.async_get_or_create_credentials({ + "username": "good-user", + "password": "good-pass", + }) + assert credentials.is_new is True + + user = await manager.async_get_or_create_user(credentials) + assert user.name == "Bob" + assert user.is_active + + +async def test_utf_8_username_password(provider): + """Test that we create a new credential.""" + credentials = await provider.async_get_or_create_credentials({ + "username": "ßßß", + "password": "äöü", + }) + assert credentials.is_new is True + + +async def test_login_flow_validates(provider): + """Test login flow.""" + flow = await provider.async_login_flow({}) + result = await flow.async_step_init() + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + + result = await flow.async_step_init({ + "username": "bad-user", + "password": "bad-pass", + }) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result['errors']["base"] == "invalid_auth" + + result = await flow.async_step_init({ + "username": "good-user", + "password": "good-pass", + }) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"]["username"] == "good-user" + + +async def test_strip_username(provider): + """Test authentication works with username with whitespace around.""" + flow = await provider.async_login_flow({}) + result = await flow.async_step_init({ + "username": "\t\ngood-user ", + "password": "good-pass", + }) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"]["username"] == "good-user" diff --git a/tests/auth/providers/test_command_line_cmd.sh b/tests/auth/providers/test_command_line_cmd.sh new file mode 100755 index 00000000000..0e689e338f1 --- /dev/null +++ b/tests/auth/providers/test_command_line_cmd.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +if [ "$username" = "good-user" ] && [ "$password" = "good-pass" ]; then + echo "Auth should succeed." >&2 + if [ "$1" = "--with-meta" ]; then + echo "name=Bob" + fi + exit 0 +fi + +echo "Auth should fail." >&2 +exit 1 From a611fb1664b00a26ef1c65a5feed7be5a6606e5a Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 7 Feb 2019 03:40:38 +0100 Subject: [PATCH 082/242] Upgrade distro to 1.4.0 (#20797) --- homeassistant/components/updater/__init__.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index daa85a2425e..28c88bf5c29 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -23,7 +23,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util -REQUIREMENTS = ['distro==1.3.0'] +REQUIREMENTS = ['distro==1.4.0'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index ed107f224c8..d52ef878b19 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -327,7 +327,7 @@ discogs_client==2.2.1 discord.py==0.16.12 # homeassistant.components.updater -distro==1.3.0 +distro==1.4.0 # homeassistant.components.switch.digitalloggers dlipower==0.7.165 From 850556d6c301ad862169704bcc09bc5526dadf18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Thu, 7 Feb 2019 03:41:38 +0100 Subject: [PATCH 083/242] upgrade switchmate lib (#20792) --- homeassistant/components/switch/switchmate.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/switch/switchmate.py b/homeassistant/components/switch/switchmate.py index 23794abeba4..be80ef19169 100644 --- a/homeassistant/components/switch/switchmate.py +++ b/homeassistant/components/switch/switchmate.py @@ -13,7 +13,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA from homeassistant.const import CONF_NAME, CONF_MAC -REQUIREMENTS = ['pySwitchmate==0.4.4'] +REQUIREMENTS = ['pySwitchmate==0.4.5'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index d52ef878b19..577de8e5de8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -891,7 +891,7 @@ pyMetno==0.3.0 pyRFXtrx==0.23 # homeassistant.components.switch.switchmate -pySwitchmate==0.4.4 +pySwitchmate==0.4.5 # homeassistant.components.tibber pyTibber==0.9.4 From ff84c01d416ffca8d2eb21b9761e55e9c215388e Mon Sep 17 00:00:00 2001 From: William Scanlon Date: Thu, 7 Feb 2019 00:22:44 -0500 Subject: [PATCH 084/242] Remove wink sensor log calls (#20798) * Removed log calls * pass during exception --- homeassistant/components/wink/sensor.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wink/sensor.py b/homeassistant/components/wink/sensor.py index 62f4638820f..3e228c4b40b 100644 --- a/homeassistant/components/wink/sensor.py +++ b/homeassistant/components/wink/sensor.py @@ -91,10 +91,9 @@ class WinkSensorDevice(WinkDevice): def device_state_attributes(self): """Return the state attributes.""" super_attrs = super().device_state_attributes - _LOGGER.debug("Adding in eggs if egg minder") try: super_attrs['egg_times'] = self.wink.eggs() - _LOGGER.debug("Its an egg minder") except AttributeError: - _LOGGER.debug("Not an eggtray") + # Ignore error, this sensor isn't an eggminder + pass return super_attrs From 1715a2070b75d74eee4aa19a51e02963fa02ff2a Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 7 Feb 2019 07:00:39 +0100 Subject: [PATCH 085/242] Upgrade astral to 1.9.2 (#20796) --- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index b9443c287a1..1f0fb9d1a3b 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -1,5 +1,5 @@ aiohttp==3.5.4 -astral==1.8 +astral==1.9.2 async_timeout==3.0.1 attrs==18.2.0 bcrypt==3.1.5 diff --git a/requirements_all.txt b/requirements_all.txt index 577de8e5de8..46d71c6c4e9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1,6 +1,6 @@ # Home Assistant core aiohttp==3.5.4 -astral==1.8 +astral==1.9.2 async_timeout==3.0.1 attrs==18.2.0 bcrypt==3.1.5 diff --git a/setup.py b/setup.py index 0ceaa7d55b3..ef4c010d42f 100755 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ PACKAGES = find_packages(exclude=['tests', 'tests.*']) REQUIRES = [ 'aiohttp==3.5.4', - 'astral==1.8', + 'astral==1.9.2', 'async_timeout==3.0.1', 'attrs==18.2.0', 'bcrypt==3.1.5', From 03ab152c824bb11cc5aff18b073c0b5045cca293 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Thu, 7 Feb 2019 03:14:19 -0500 Subject: [PATCH 086/242] Enable the available property for zha entities (#20788) --- homeassistant/components/zha/entity.py | 4 +--- tests/components/zha/common.py | 7 +++---- tests/components/zha/test_binary_sensor.py | 21 ++++++++++++++++----- tests/components/zha/test_fan.py | 16 +++++++++++----- tests/components/zha/test_light.py | 16 +++++++++++++--- tests/components/zha/test_sensor.py | 17 +++++++++++++++-- tests/components/zha/test_switch.py | 13 ++++++++++--- 7 files changed, 69 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index 5a78d91553f..d914a76c4ce 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -62,9 +62,7 @@ class ZhaEntity(entity.Entity): self._device_state_attributes = {} self._zha_device = zha_device self.cluster_listeners = {} - # this will get flipped to false once we enable the feature after the - # reorg is merged - self._available = True + self._available = False self._component = kwargs['component'] self._unsubs = [] for listener in listeners: diff --git a/tests/components/zha/common.py b/tests/components/zha/common.py index c7a9c786054..f0b03a4b40b 100644 --- a/tests/components/zha/common.py +++ b/tests/components/zha/common.py @@ -1,7 +1,7 @@ """Common test objects.""" import time from unittest.mock import patch, Mock -from homeassistant.const import STATE_UNKNOWN +from homeassistant.const import STATE_UNAVAILABLE from homeassistant.components.zha.core.helpers import convert_ieee from homeassistant.components.zha.core.const import ( DATA_ZHA, DATA_ZHA_CONFIG, DATA_ZHA_DISPATCHERS, DATA_ZHA_BRIDGE_ID @@ -168,8 +168,7 @@ async def async_enable_traffic(hass, zha_gateway, zha_devices): async def async_test_device_join( - hass, zha_gateway, cluster_id, domain, device_type=None, - expected_state=STATE_UNKNOWN): + hass, zha_gateway, cluster_id, domain, device_type=None): """Test a newly joining device. This creates a new fake device and adds it to the network. It is meant to @@ -193,4 +192,4 @@ async def async_test_device_join( cluster = zigpy_device.endpoints.get(1).in_clusters[cluster_id] entity_id = make_entity_id( domain, zigpy_device, cluster, use_suffix=device_type is None) - assert hass.states.get(entity_id).state == expected_state + assert hass.states.get(entity_id).state == STATE_UNAVAILABLE diff --git a/tests/components/zha/test_binary_sensor.py b/tests/components/zha/test_binary_sensor.py index ba723987042..c81f96468ce 100644 --- a/tests/components/zha/test_binary_sensor.py +++ b/tests/components/zha/test_binary_sensor.py @@ -1,9 +1,9 @@ """Test zha binary sensor.""" from homeassistant.components.binary_sensor import DOMAIN -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.const import STATE_ON, STATE_OFF, STATE_UNAVAILABLE from .common import ( async_init_zigpy_device, make_attribute, make_entity_id, - async_test_device_join + async_test_device_join, async_enable_traffic ) @@ -48,19 +48,21 @@ async def test_binary_sensor(hass, config_entry, zha_gateway): # load up binary_sensor domain await hass.config_entries.async_forward_entry_setup( config_entry, DOMAIN) - await zha_gateway.accept_zigbee_messages({}) await hass.async_block_till_done() # on off binary_sensor zone_cluster = zigpy_device_zone.endpoints.get( 1).ias_zone zone_entity_id = make_entity_id(DOMAIN, zigpy_device_zone, zone_cluster) + zone_zha_device = zha_gateway.get_device(str(zigpy_device_zone.ieee)) # occupancy binary_sensor occupancy_cluster = zigpy_device_occupancy.endpoints.get( 1).occupancy occupancy_entity_id = make_entity_id( DOMAIN, zigpy_device_occupancy, occupancy_cluster) + occupancy_zha_device = zha_gateway.get_device( + str(zigpy_device_occupancy.ieee)) # dimmable binary_sensor remote_on_off_cluster = zigpy_device_remote.endpoints.get( @@ -70,6 +72,16 @@ async def test_binary_sensor(hass, config_entry, zha_gateway): remote_entity_id = make_entity_id(DOMAIN, zigpy_device_remote, remote_on_off_cluster, use_suffix=False) + remote_zha_device = zha_gateway.get_device(str(zigpy_device_remote.ieee)) + + # test that the sensors exist and are in the unavailable state + assert hass.states.get(zone_entity_id).state == STATE_UNAVAILABLE + assert hass.states.get(remote_entity_id).state == STATE_UNAVAILABLE + assert hass.states.get(occupancy_entity_id).state == STATE_UNAVAILABLE + + await async_enable_traffic(hass, zha_gateway, + [zone_zha_device, remote_zha_device, + occupancy_zha_device]) # test that the sensors exist and are in the off state assert hass.states.get(zone_entity_id).state == STATE_OFF @@ -98,8 +110,7 @@ async def test_binary_sensor(hass, config_entry, zha_gateway): # test new sensor join await async_test_device_join( - hass, zha_gateway, OccupancySensing.cluster_id, DOMAIN, - expected_state=STATE_OFF) + hass, zha_gateway, OccupancySensing.cluster_id, DOMAIN) async def async_test_binary_sensor_on_off(hass, cluster, entity_id): diff --git a/tests/components/zha/test_fan.py b/tests/components/zha/test_fan.py index 14da94bdf52..a67c8572072 100644 --- a/tests/components/zha/test_fan.py +++ b/tests/components/zha/test_fan.py @@ -1,7 +1,7 @@ """Test zha fan.""" from unittest.mock import call, patch from homeassistant.components import fan -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.const import STATE_ON, STATE_OFF, STATE_UNAVAILABLE from homeassistant.components.fan import ( ATTR_SPEED, DOMAIN, SERVICE_SET_SPEED ) @@ -10,7 +10,7 @@ from homeassistant.const import ( from tests.common import mock_coro from .common import ( async_init_zigpy_device, make_attribute, make_entity_id, - async_test_device_join + async_test_device_join, async_enable_traffic ) @@ -31,8 +31,15 @@ async def test_fan(hass, config_entry, zha_gateway): cluster = zigpy_device.endpoints.get(1).fan entity_id = make_entity_id(DOMAIN, zigpy_device, cluster) + zha_device = zha_gateway.get_device(str(zigpy_device.ieee)) - # test that the fan was created and that it is off + # test that the fan was created and that it is unavailable + assert hass.states.get(entity_id).state == STATE_UNAVAILABLE + + # allow traffic to flow through the gateway and device + await async_enable_traffic(hass, zha_gateway, [zha_device]) + + # test that the state has changed from unavailable to off assert hass.states.get(entity_id).state == STATE_OFF # turn on at fan @@ -78,8 +85,7 @@ async def test_fan(hass, config_entry, zha_gateway): {'fan_mode': 3}) # test adding new fan to the network and HA - await async_test_device_join(hass, zha_gateway, Fan.cluster_id, DOMAIN, - expected_state=STATE_OFF) + await async_test_device_join(hass, zha_gateway, Fan.cluster_id, DOMAIN) async def async_turn_on(hass, entity_id, speed=None): diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index e94e53c293d..4712cac28d0 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -1,11 +1,11 @@ """Test zha light.""" from unittest.mock import call, patch from homeassistant.components.light import DOMAIN -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.const import STATE_ON, STATE_OFF, STATE_UNAVAILABLE from tests.common import mock_coro from .common import ( async_init_zigpy_device, make_attribute, make_entity_id, - async_test_device_join + async_test_device_join, async_enable_traffic ) ON = 1 @@ -48,6 +48,7 @@ async def test_light(hass, config_entry, zha_gateway): on_off_entity_id = make_entity_id(DOMAIN, zigpy_device_on_off, on_off_device_on_off_cluster, use_suffix=False) + on_off_zha_device = zha_gateway.get_device(str(zigpy_device_on_off.ieee)) # dimmable light level_device_on_off_cluster = zigpy_device_level.endpoints.get(1).on_off @@ -55,6 +56,15 @@ async def test_light(hass, config_entry, zha_gateway): level_entity_id = make_entity_id(DOMAIN, zigpy_device_level, level_device_on_off_cluster, use_suffix=False) + level_zha_device = zha_gateway.get_device(str(zigpy_device_level.ieee)) + + # test that the lights were created and that they are unavailable + assert hass.states.get(on_off_entity_id).state == STATE_UNAVAILABLE + assert hass.states.get(level_entity_id).state == STATE_UNAVAILABLE + + # allow traffic to flow through the gateway and device + await async_enable_traffic(hass, zha_gateway, + [on_off_zha_device, level_zha_device]) # test that the lights were created and are off assert hass.states.get(on_off_entity_id).state == STATE_OFF @@ -85,7 +95,7 @@ async def test_light(hass, config_entry, zha_gateway): # test adding a new light to the network and HA await async_test_device_join( hass, zha_gateway, OnOff.cluster_id, - DOMAIN, device_type=DeviceType.ON_OFF_LIGHT, expected_state=STATE_OFF) + DOMAIN, device_type=DeviceType.ON_OFF_LIGHT) async def async_test_on_off_from_light(hass, cluster, entity_id): diff --git a/tests/components/zha/test_sensor.py b/tests/components/zha/test_sensor.py index 18d2e152beb..bc367e15533 100644 --- a/tests/components/zha/test_sensor.py +++ b/tests/components/zha/test_sensor.py @@ -1,9 +1,9 @@ """Test zha sensor.""" from homeassistant.components.sensor import DOMAIN -from homeassistant.const import STATE_UNKNOWN +from homeassistant.const import STATE_UNKNOWN, STATE_UNAVAILABLE from .common import ( async_init_zigpy_device, make_attribute, make_entity_id, - async_test_device_join + async_test_device_join, async_enable_traffic ) @@ -31,6 +31,17 @@ async def test_sensor(hass, config_entry, zha_gateway): hass, zha_gateway, config_entry, cluster_ids) # ensure the sensor entity was created for each id in cluster_ids + for cluster_id in cluster_ids: + zigpy_device_info = zigpy_device_infos[cluster_id] + entity_id = zigpy_device_info["entity_id"] + assert hass.states.get(entity_id).state == STATE_UNAVAILABLE + + # allow traffic to flow through the gateway and devices + await async_enable_traffic(hass, zha_gateway, [ + zigpy_device_info["zha_device"] for zigpy_device_info in + zigpy_device_infos.values()]) + + # test that the sensors now have a state of unknown for cluster_id in cluster_ids: zigpy_device_info = zigpy_device_infos[cluster_id] entity_id = zigpy_device_info["entity_id"] @@ -103,6 +114,8 @@ async def async_build_devices(hass, zha_gateway, config_entry, cluster_ids): 1).in_clusters[cluster_id] device_info["entity_id"] = make_entity_id( DOMAIN, zigpy_device, device_info["cluster"]) + device_info["zha_device"] = zha_gateway.get_device( + str(zigpy_device.ieee)) return device_infos diff --git a/tests/components/zha/test_switch.py b/tests/components/zha/test_switch.py index 4e6ff6da6ba..8b2553aae7a 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -1,11 +1,11 @@ """Test zha switch.""" from unittest.mock import call, patch from homeassistant.components.switch import DOMAIN -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.const import STATE_ON, STATE_OFF, STATE_UNAVAILABLE from tests.common import mock_coro from .common import ( async_init_zigpy_device, make_attribute, make_entity_id, - async_test_device_join + async_test_device_join, async_enable_traffic ) ON = 1 @@ -29,6 +29,13 @@ async def test_switch(hass, config_entry, zha_gateway): cluster = zigpy_device.endpoints.get(1).on_off entity_id = make_entity_id(DOMAIN, zigpy_device, cluster) + zha_device = zha_gateway.get_device(str(zigpy_device.ieee)) + + # test that the switch was created and that its state is unavailable + assert hass.states.get(entity_id).state == STATE_UNAVAILABLE + + # allow traffic to flow through the gateway and device + await async_enable_traffic(hass, zha_gateway, [zha_device]) # test that the state has changed from unavailable to off assert hass.states.get(entity_id).state == STATE_OFF @@ -71,4 +78,4 @@ async def test_switch(hass, config_entry, zha_gateway): # test joining a new switch to the network and HA await async_test_device_join( - hass, zha_gateway, OnOff.cluster_id, DOMAIN, expected_state=STATE_OFF) + hass, zha_gateway, OnOff.cluster_id, DOMAIN) From d4c34c6b0298eef13a545d0a67f5697eaa25c84d Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Thu, 7 Feb 2019 03:23:01 -0500 Subject: [PATCH 087/242] Cleanup zha listener lifecycle (#20789) --- homeassistant/components/zha/__init__.py | 2 -- homeassistant/components/zha/core/device.py | 4 ---- homeassistant/components/zha/core/gateway.py | 7 ------ .../components/zha/core/listeners.py | 23 ++----------------- tests/components/zha/common.py | 1 - tests/components/zha/test_fan.py | 1 - tests/components/zha/test_light.py | 1 - tests/components/zha/test_sensor.py | 1 - tests/components/zha/test_switch.py | 1 - 9 files changed, 2 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index 2e693907769..ae08b2cac40 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -147,8 +147,6 @@ async def async_setup_entry(hass, config_entry): ) zha_gateway = ZHAGateway(hass, config) - hass.bus.async_listen_once( - ha_const.EVENT_HOMEASSISTANT_START, zha_gateway.accept_zigbee_messages) # Patch handle_message until zigpy can provide an event here def handle_message(sender, is_reply, profile, cluster, diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 292f9817671..2322df5452c 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -170,10 +170,6 @@ class ZHADevice: await self._execute_listener_tasks('async_initialize', from_cache) _LOGGER.debug('%s: completed initialization', self.name) - async def async_accept_messages(self): - """Start accepting messages from the zigbee network.""" - await self._execute_listener_tasks('accept_messages') - async def _execute_listener_tasks(self, task_name, *args): """Gather and execute a set of listener tasks.""" listener_tasks = [] diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 2722f6720ce..dca6b60ccc5 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -126,13 +126,6 @@ class ZHAGateway: self._devices[zigpy_device.ieee] = zha_device return zha_device - async def accept_zigbee_messages(self, _service_or_event): - """Allow devices to accept zigbee messages.""" - accept_messages_calls = [] - for device in self.devices.values(): - accept_messages_calls.append(device.async_accept_messages()) - await asyncio.gather(*accept_messages_calls) - async def async_device_initialized(self, device, is_new_join): """Handle device joined and basic information discovered (async).""" zha_device = await self._get_or_create_device(device) diff --git a/homeassistant/components/zha/core/listeners.py b/homeassistant/components/zha/core/listeners.py index 916319b2d98..d7c46bdfb3a 100644 --- a/homeassistant/components/zha/core/listeners.py +++ b/homeassistant/components/zha/core/listeners.py @@ -83,7 +83,6 @@ class ListenerStatus(Enum): CREATED = 1 CONFIGURED = 2 INITIALIZED = 3 - LISTENING = 4 class ClusterListener: @@ -99,6 +98,7 @@ class ClusterListener: [{'attr': 0, 'config': REPORT_CONFIG_DEFAULT}] ) self._status = ListenerStatus.CREATED + self._cluster.add_listener(self) @property def unique_id(self): @@ -156,11 +156,6 @@ class ClusterListener: """Initialize listener.""" self._status = ListenerStatus.INITIALIZED - async def accept_messages(self): - """Attach to the cluster so we can receive messages.""" - self._cluster.add_listener(self) - self._status = ListenerStatus.LISTENING - @callback def cluster_command(self, tsn, command_id, args): """Handle commands received to this cluster.""" @@ -354,12 +349,6 @@ class IASZoneListener(ClusterListener): name = 'zone' - def __init__(self, cluster, device): - """Initialize IASZoneListener.""" - super().__init__(cluster, device) - self._cluster.add_listener(self) - self._status = ListenerStatus.LISTENING - @callback def cluster_command(self, tsn, command_id, args): """Handle commands received to this cluster.""" @@ -429,10 +418,6 @@ class IASZoneListener(ClusterListener): await self.get_attribute_value('zone_state', from_cache=from_cache) await super().async_initialize(from_cache) - async def accept_messages(self): - """Attach to the cluster so we can receive messages.""" - self._status = ListenerStatus.LISTENING - class ActivePowerListener(AttributeListener): """Listener that polls active power level.""" @@ -625,6 +610,7 @@ class ZDOListener: self._zha_device = device self._status = ListenerStatus.CREATED self._unique_id = "{}_ZDO".format(device.name) + self._cluster.add_listener(self) @property def unique_id(self): @@ -651,11 +637,6 @@ class ZDOListener: """Permit handler.""" pass - async def accept_messages(self): - """Attach to the cluster so we can receive messages.""" - self._cluster.add_listener(self) - self._status = ListenerStatus.LISTENING - async def async_initialize(self, from_cache): """Initialize listener.""" self._status = ListenerStatus.INITIALIZED diff --git a/tests/components/zha/common.py b/tests/components/zha/common.py index f0b03a4b40b..1a923849ce5 100644 --- a/tests/components/zha/common.py +++ b/tests/components/zha/common.py @@ -161,7 +161,6 @@ def make_entity_id(domain, device, cluster, use_suffix=True): async def async_enable_traffic(hass, zha_gateway, zha_devices): """Allow traffic to flow through the gateway and the zha device.""" - await zha_gateway.accept_zigbee_messages({}) for zha_device in zha_devices: zha_device.update_available(True) await hass.async_block_till_done() diff --git a/tests/components/zha/test_fan.py b/tests/components/zha/test_fan.py index a67c8572072..6beafc6ca8e 100644 --- a/tests/components/zha/test_fan.py +++ b/tests/components/zha/test_fan.py @@ -26,7 +26,6 @@ async def test_fan(hass, config_entry, zha_gateway): # load up fan domain await hass.config_entries.async_forward_entry_setup( config_entry, DOMAIN) - await zha_gateway.accept_zigbee_messages({}) await hass.async_block_till_done() cluster = zigpy_device.endpoints.get(1).fan diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index 4712cac28d0..9c5e69d1347 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -40,7 +40,6 @@ async def test_light(hass, config_entry, zha_gateway): # load up light domain await hass.config_entries.async_forward_entry_setup( config_entry, DOMAIN) - await zha_gateway.accept_zigbee_messages({}) await hass.async_block_till_done() # on off light diff --git a/tests/components/zha/test_sensor.py b/tests/components/zha/test_sensor.py index bc367e15533..d16cafb7df8 100644 --- a/tests/components/zha/test_sensor.py +++ b/tests/components/zha/test_sensor.py @@ -103,7 +103,6 @@ async def async_build_devices(hass, zha_gateway, config_entry, cluster_ids): # load up sensor domain await hass.config_entries.async_forward_entry_setup( config_entry, DOMAIN) - await zha_gateway.accept_zigbee_messages({}) await hass.async_block_till_done() # put the other relevant info in the device info dict diff --git a/tests/components/zha/test_switch.py b/tests/components/zha/test_switch.py index 8b2553aae7a..32c8ee64e67 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -24,7 +24,6 @@ async def test_switch(hass, config_entry, zha_gateway): # load up switch domain await hass.config_entries.async_forward_entry_setup( config_entry, DOMAIN) - await zha_gateway.accept_zigbee_messages({}) await hass.async_block_till_done() cluster = zigpy_device.endpoints.get(1).on_off From d177e1324c3a8aacacbb62ad493a9f6f151cb93a Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Thu, 7 Feb 2019 10:31:24 -0500 Subject: [PATCH 088/242] Add device ieee to zha events (#20791) --- homeassistant/components/zha/core/listeners.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/zha/core/listeners.py b/homeassistant/components/zha/core/listeners.py index d7c46bdfb3a..035c752c42b 100644 --- a/homeassistant/components/zha/core/listeners.py +++ b/homeassistant/components/zha/core/listeners.py @@ -178,6 +178,7 @@ class ClusterListener: 'zha_event', { 'unique_id': self._unique_id, + 'device_ieee': str(self._zha_device.ieee), 'command': command, 'args': args } From 968f98706eb7d350fbb04dc1db77735dbb2dd866 Mon Sep 17 00:00:00 2001 From: Timmo Date: Thu, 7 Feb 2019 17:34:27 +0000 Subject: [PATCH 089/242] GitHub Sensor (#19561) * :sparkles: Add GitHub sensor * :shirt: fix tox lint warning * :hammer: Add GitHub to .coveragerc * :shirt: Fix pylint warning * :hammer: Use config.get * :fire: Tighten validation * :shirt: fix linter error * :hammer: Add path for context in errors * :sparkles: Add releases * :sparkles: Add GitHub Enterprise server support * :hammer: remove unused constant * :hammer: Requested changes * :hammer: Reorder imports * :hammer: Change to CONF_URL * :hammer: Add docstring * :hammer: Add validation for repo list * :arrow_up: Update PyGithub to 1.43.5 * :hammer: Sort attributes * :fire: Fix validation * :shirt: Fix linting issue * :hammer: Fail platform setup when data init fails with bad credentials etc * :shirt: Fix whitespace lint error * :hammer: Fix requirements_all version * :shirt: Linter fix attempt * :fire: Missing bracket * :fire: Another attempt to at a linter fix * :fire: Fix indentation * :hammer: Reduce exception down to main one * :fire: Remove update throttle logic * :hammer: Reduce calls * :shirt: Remove unused imports * :fire: :hammer: Reduce attribute data * :shirt: Remove unused json import * :hammer: Remove username and password * :fire: Fix counts * :hammer: Update attrs and add any missing * :hammer: Add unique_id * :fire: Convert uuid to string * :fire: Replace UUID with repository path * :hammer: Cleanup * :hammer: Cleanup * :fire: Remove unused variable * :hammer: Change to update instead of _update * :hammer: Improved consistency * :hammer: Improve consistency * :shirt: Fix line lengths * :hammer: Fix length * :hammer: Fix syntax --- .coveragerc | 1 + homeassistant/components/sensor/github.py | 215 ++++++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 219 insertions(+) create mode 100644 homeassistant/components/sensor/github.py diff --git a/.coveragerc b/.coveragerc index 1f467c93c2d..557567e7aaf 100644 --- a/.coveragerc +++ b/.coveragerc @@ -451,6 +451,7 @@ omit = homeassistant/components/sensor/fritzbox_netmonitor.py homeassistant/components/sensor/gearbest.py homeassistant/components/sensor/geizhals.py + homeassistant/components/sensor/github.py homeassistant/components/sensor/gitlab_ci.py homeassistant/components/sensor/gitter.py homeassistant/components/sensor/glances.py diff --git a/homeassistant/components/sensor/github.py b/homeassistant/components/sensor/github.py new file mode 100644 index 00000000000..335dbc668d9 --- /dev/null +++ b/homeassistant/components/sensor/github.py @@ -0,0 +1,215 @@ +""" +Support for GitHub. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.github/ +""" +from datetime import timedelta +import logging +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + ATTR_NAME, CONF_ACCESS_TOKEN, CONF_NAME, CONF_PATH, CONF_URL) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity + +REQUIREMENTS = ['PyGithub==1.43.5'] + +_LOGGER = logging.getLogger(__name__) + +CONF_REPOS = 'repositories' + +ATTR_LATEST_COMMIT_MESSAGE = 'latest_commit_message' +ATTR_LATEST_COMMIT_SHA = 'latest_commit_sha' +ATTR_LATEST_RELEASE_URL = 'latest_release_url' +ATTR_LATEST_OPEN_ISSUE_URL = 'latest_open_issue_url' +ATTR_OPEN_ISSUES = 'open_issues' +ATTR_LATEST_OPEN_PULL_REQUEST_URL = 'latest_open_pull_request_url' +ATTR_OPEN_PULL_REQUESTS = 'open_pull_requests' +ATTR_PATH = 'path' +ATTR_STARGAZERS = 'stargazers' + +DEFAULT_NAME = 'GitHub' + +SCAN_INTERVAL = timedelta(seconds=300) + +REPO_SCHEMA = vol.Schema({ + vol.Required(CONF_PATH): cv.string, + vol.Optional(CONF_NAME): cv.string +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_ACCESS_TOKEN): cv.string, + vol.Optional(CONF_URL): cv.url, + vol.Required(CONF_REPOS): + vol.All(cv.ensure_list, [REPO_SCHEMA]) +}) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the GitHub sensor platform.""" + sensors = [] + for repository in config[CONF_REPOS]: + data = GitHubData( + repository=repository, + access_token=config.get(CONF_ACCESS_TOKEN), + server_url=config.get(CONF_URL) + ) + if data.setup_error is True: + _LOGGER.error("Error setting up GitHub platform. %s", + "Check previous errors for details") + return + sensors.append(GitHubSensor(data)) + add_entities(sensors, True) + + +class GitHubSensor(Entity): + """Representation of a GitHub sensor.""" + + def __init__(self, github_data): + """Initialize the GitHub sensor.""" + self._unique_id = github_data.repository_path + self._name = None + self._state = None + self._available = False + self._repository_path = None + self._latest_commit_message = None + self._latest_commit_sha = None + self._latest_release_url = None + self._open_issue_count = None + self._latest_open_issue_url = None + self._pull_request_count = None + self._latest_open_pr_url = None + self._stargazers = None + self._github_data = github_data + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def unique_id(self): + """Return unique ID for the sensor.""" + return self._unique_id + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def available(self): + """Return True if entity is available.""" + return self._available + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return { + ATTR_PATH: self._repository_path, + ATTR_NAME: self._name, + ATTR_LATEST_COMMIT_MESSAGE: self._latest_commit_message, + ATTR_LATEST_COMMIT_SHA: self._latest_commit_sha, + ATTR_LATEST_RELEASE_URL: self._latest_release_url, + ATTR_LATEST_OPEN_ISSUE_URL: self._latest_open_issue_url, + ATTR_OPEN_ISSUES: self._open_issue_count, + ATTR_LATEST_OPEN_PULL_REQUEST_URL: self._latest_open_pr_url, + ATTR_OPEN_PULL_REQUESTS: self._pull_request_count, + ATTR_STARGAZERS: self._stargazers + } + + @property + def icon(self): + """Return the icon to use in the frontend.""" + return 'mdi:github-circle' + + def update(self): + """Collect updated data from GitHub API.""" + self._github_data.update() + + self._name = self._github_data.name + self._state = self._github_data.latest_commit_sha + self._repository_path = self._github_data.repository_path + self._available = self._github_data.available + self._latest_commit_message = self._github_data.latest_commit_message + self._latest_commit_sha = self._github_data.latest_commit_sha + self._latest_release_url = self._github_data.latest_release_url + self._open_issue_count = self._github_data.open_issue_count + self._latest_open_issue_url = self._github_data.latest_open_issue_url + self._pull_request_count = self._github_data.pull_request_count + self._latest_open_pr_url = self._github_data.latest_open_pr_url + self._stargazers = self._github_data.stargazers + + +class GitHubData(): + """GitHub Data object.""" + + def __init__(self, repository, access_token=None, server_url=None): + """Set up GitHub.""" + import github + + self._github = github + + self.setup_error = False + + try: + if server_url is not None: + server_url += "/api/v3" + self._github_obj = github.Github( + access_token, base_url=server_url) + else: + self._github_obj = github.Github(access_token) + + self.repository_path = repository[CONF_PATH] + + repo = self._github_obj.get_repo(self.repository_path) + except self._github.GithubException as err: + _LOGGER.error("GitHub error for %s: %s", self.repository_path, err) + self.setup_error = True + return + + self.name = repository.get(CONF_NAME, repo.name) + + self.available = False + self.latest_commit_message = None + self.latest_commit_sha = None + self.latest_release_url = None + self.open_issue_count = None + self.latest_open_issue_url = None + self.pull_request_count = None + self.latest_open_pr_url = None + self.stargazers = None + + def update(self): + """Update GitHub Sensor.""" + try: + repo = self._github_obj.get_repo(self.repository_path) + + self.stargazers = repo.stargazers_count + + open_issues = repo.get_issues(state='open', sort='created') + if open_issues is not None: + self.open_issue_count = open_issues.totalCount + if open_issues.totalCount > 0: + self.latest_open_issue_url = open_issues[0].html_url + + open_pull_requests = repo.get_pulls(state='open', sort='created') + if open_pull_requests is not None: + self.pull_request_count = open_pull_requests.totalCount + if open_pull_requests.totalCount > 0: + self.latest_open_pr_url = open_pull_requests[0].html_url + + latest_commit = repo.get_commits()[0] + self.latest_commit_sha = latest_commit.sha + self.latest_commit_message = latest_commit.commit.message + + releases = repo.get_releases() + if releases and releases.totalCount > 0: + self.latest_release_url = releases[0].html_url + + self.available = True + except self._github.GithubException as err: + _LOGGER.error("GitHub error for %s: %s", self.repository_path, err) + self.available = False diff --git a/requirements_all.txt b/requirements_all.txt index 46d71c6c4e9..7f618e0920a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -38,6 +38,9 @@ HAP-python==2.4.2 # homeassistant.components.notify.mastodon Mastodon.py==1.3.1 +# homeassistant.components.sensor.github +PyGithub==1.43.5 + # homeassistant.components.isy994 PyISY==1.1.1 From e0f63132e80585b2f54c79121330ac4bbe46809c Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Thu, 7 Feb 2019 21:32:37 +0000 Subject: [PATCH 090/242] Deduplication of log entries in system_log (#20493) * Deduplication of log entries * fix --- .../components/system_log/__init__.py | 83 ++++++++++++++----- tests/components/system_log/test_init.py | 13 +++ 2 files changed, 73 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/system_log/__init__.py b/homeassistant/components/system_log/__init__.py index 8ab6bd752ef..c59aee56a51 100644 --- a/homeassistant/components/system_log/__init__.py +++ b/homeassistant/components/system_log/__init__.py @@ -4,8 +4,7 @@ Support for system log. For more details about this component, please refer to the documentation at https://home-assistant.io/components/system_log/ """ -from collections import deque -from io import StringIO +from collections import OrderedDict import logging import re import traceback @@ -89,11 +88,59 @@ def _figure_out_source(record, call_stack, hass): return record.pathname -def _exception_as_string(exc_info): - buf = StringIO() - if exc_info: - traceback.print_exception(*exc_info, file=buf) - return buf.getvalue() +class LogEntry: + """Store HA log entries.""" + + def __init__(self, record, stack, source): + """Initialize a log entry.""" + self.timestamp = record.created + self.level = record.levelname + self.message = record.getMessage() + if record.exc_info: + self.exception = ''.join( + traceback.format_exception(*record.exc_info)) + _, _, tb = record.exc_info # pylint: disable=invalid-name + # Last line of traceback contains the root cause of the exception + self.root_cause = str(traceback.extract_tb(tb)[-1]) + else: + self.exception = '' + self.root_cause = None + self.source = source + self.count = 1 + + def hash(self): + """Calculate a key for DedupStore.""" + return frozenset([self.message, self.root_cause]) + + def to_dict(self): + """Convert object into dict to maintain backward compatability.""" + return vars(self) + + +class DedupStore(OrderedDict): + """Data store to hold max amount of deduped entries.""" + + def __init__(self, maxlen=50): + """Initialize a new DedupStore.""" + super().__init__() + self.maxlen = maxlen + + def add_entry(self, entry): + """Add a new entry.""" + key = str(entry.hash()) + + if key in self: + entry.count = self[key].count + 1 + + self[key] = entry + + if len(self) > self.maxlen: + # Removes the first record which should also be the oldest + self.popitem(last=False) + + def to_list(self): + """Return reversed list of log entries - LIFO.""" + return [value.to_dict() for value in reversed(self.values())] class LogErrorHandler(logging.Handler): @@ -103,18 +150,9 @@ class LogErrorHandler(logging.Handler): """Initialize a new LogErrorHandler.""" super().__init__() self.hass = hass - self.records = deque(maxlen=maxlen) + self.records = DedupStore(maxlen=maxlen) self.fire_event = fire_event - def _create_entry(self, record, call_stack): - return { - 'timestamp': record.created, - 'level': record.levelname, - 'message': record.getMessage(), - 'exception': _exception_as_string(record.exc_info), - 'source': _figure_out_source(record, call_stack, self.hass), - } - def emit(self, record): """Save error and warning logs. @@ -127,10 +165,11 @@ class LogErrorHandler(logging.Handler): if not record.exc_info: stack = [f for f, _, _, _ in traceback.extract_stack()] - entry = self._create_entry(record, stack) - self.records.appendleft(entry) + entry = LogEntry(record, stack, + _figure_out_source(record, stack, self.hass)) + self.records.add_entry(entry) if self.fire_event: - self.hass.bus.fire(EVENT_SYSTEM_LOG, entry) + self.hass.bus.fire(EVENT_SYSTEM_LOG, entry.to_dict()) async def async_setup(hass, config): @@ -186,6 +225,4 @@ class AllErrorsView(HomeAssistantView): async def get(self, request): """Get all errors and warnings.""" - # deque is not serializable (it's just "list-like") so it must be - # converted to a list before it can be serialized to json - return self.json(list(self.handler.records)) + return self.json(self.handler.records.to_list()) diff --git a/tests/components/system_log/test_init.py b/tests/components/system_log/test_init.py index 6afd792be9c..c1d79c9f33f 100644 --- a/tests/components/system_log/test_init.py +++ b/tests/components/system_log/test_init.py @@ -140,6 +140,19 @@ async def test_remove_older_logs(hass, hass_client): assert_log(log[1], '', 'error message 2', 'ERROR') +async def test_dedup_logs(hass, hass_client): + """Test that duplicate log entries are dedup.""" + await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) + _LOGGER.error('error message 1') + _LOGGER.error('error message 2') + _LOGGER.error('error message 2') + _LOGGER.error('error message 3') + log = await get_error_log(hass, hass_client, 2) + assert_log(log[0], '', 'error message 3', 'ERROR') + assert log[1]["count"] == 2 + assert_log(log[1], '', 'error message 2', 'ERROR') + + async def test_clear_logs(hass, hass_client): """Test that the log can be cleared via a service call.""" await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) From 16159cc3d0d2adcbff201246df5f46ad3c063769 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 7 Feb 2019 13:33:12 -0800 Subject: [PATCH 091/242] Update platform loading path (#20807) * Warn when platform loaded from an entity component folder * Fix tests --- homeassistant/const.py | 4 ++-- homeassistant/loader.py | 21 ++++++++++++------- tests/common.py | 2 ++ tests/test_loader.py | 14 +++++++++++++ .../custom_components/switch/test_embedded.py | 1 + 5 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 tests/testing_config/custom_components/switch/test_embedded.py diff --git a/homeassistant/const.py b/homeassistant/const.py index ba9d32e0daf..1a3d4e2e455 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,8 +7,8 @@ __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) -# Format for platforms -PLATFORM_FORMAT = '{domain}.{platform}' +# Format for platform files +PLATFORM_FORMAT = '{platform}.{domain}' # Can be used to specify a catch all when registering state or event listeners. MATCH_ALL = '*' diff --git a/homeassistant/loader.py b/homeassistant/loader.py index cd22a69dab1..d02d22cc8d2 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -45,9 +45,7 @@ def set_component(hass, # type: HomeAssistant Async friendly. """ - cache = hass.data.get(DATA_KEY) - if cache is None: - cache = hass.data[DATA_KEY] = {} + cache = hass.data.setdefault(DATA_KEY, {}) cache[comp_name] = component @@ -60,13 +58,22 @@ def get_platform(hass, # type: HomeAssistant platform = _load_file(hass, PLATFORM_FORMAT.format( domain=domain, platform=platform_name)) - if platform is None: - # Turn it around for legacy purposes - platform = _load_file(hass, PLATFORM_FORMAT.format( - domain=platform_name, platform=domain)) + if platform is not None: + return platform + + # Legacy platform check: light/hue.py + platform = _load_file(hass, PLATFORM_FORMAT.format( + domain=platform_name, platform=domain)) if platform is None: _LOGGER.error("Unable to find platform %s", platform_name) + return None + + if platform.__name__.startswith(PATH_CUSTOM_COMPONENTS): + _LOGGER.warning( + "Integrations need to be in their own folder. Change %s/%s.py to " + "%s/%s.py. This will stop working soon.", + domain, platform_name, platform_name, domain) return platform diff --git a/tests/common.py b/tests/common.py index 3642c5da6ec..409b020f728 100644 --- a/tests/common.py +++ b/tests/common.py @@ -486,6 +486,8 @@ class MockModule: class MockPlatform: """Provide a fake platform.""" + __name__ = "homeassistant.components.light.bla" + # pylint: disable=invalid-name def __init__(self, setup_platform=None, dependencies=None, platform_schema=None, async_setup_platform=None, diff --git a/tests/test_loader.py b/tests/test_loader.py index 5bd273ea16a..6fecd5086b1 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -132,3 +132,17 @@ async def test_log_warning_custom_component(hass, caplog): loader.get_component(hass, 'light.test') assert 'You are using a custom component for light.test' in caplog.text + + +async def test_get_platform(hass, caplog): + """Test get_platform.""" + # Test we prefer embedded over normal platforms.""" + embedded_platform = loader.get_platform(hass, 'switch', 'test_embedded') + assert embedded_platform.__name__ == \ + 'custom_components.test_embedded.switch' + + caplog.clear() + + legacy_platform = loader.get_platform(hass, 'switch', 'test') + assert legacy_platform.__name__ == 'custom_components.switch.test' + assert 'Integrations need to be in their own folder.' in caplog.text diff --git a/tests/testing_config/custom_components/switch/test_embedded.py b/tests/testing_config/custom_components/switch/test_embedded.py new file mode 100644 index 00000000000..0023aa8a1f2 --- /dev/null +++ b/tests/testing_config/custom_components/switch/test_embedded.py @@ -0,0 +1 @@ +"""Test switch platform for test_embedded component.""" From d45f25ce2c7d5a4a8ac67d6e14e18b935f514ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 7 Feb 2019 23:34:14 +0200 Subject: [PATCH 092/242] Add more type hints to helpers (#20811) * Add type hints to helpers.aiohttp_client * Add type hints to helpers.area_registry --- homeassistant/helpers/aiohttp_client.py | 48 ++++++++++++++++--------- homeassistant/helpers/area_registry.py | 22 ++++++------ tox.ini | 2 +- 3 files changed, 45 insertions(+), 27 deletions(-) diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index b2669312e38..6b1dd10bd5b 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -1,6 +1,9 @@ """Helper for aiohttp webclient stuff.""" import asyncio import sys +from ssl import SSLContext # noqa: F401 +from typing import Any, Awaitable, Optional, cast +from typing import Union # noqa: F401 import aiohttp from aiohttp.hdrs import USER_AGENT, CONTENT_TYPE @@ -8,8 +11,9 @@ from aiohttp import web from aiohttp.web_exceptions import HTTPGatewayTimeout, HTTPBadGateway import async_timeout -from homeassistant.core import callback +from homeassistant.core import callback, Event from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE, __version__ +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.loader import bind_hass from homeassistant.util import ssl as ssl_util @@ -23,7 +27,8 @@ SERVER_SOFTWARE = 'HomeAssistant/{0} aiohttp/{1} Python/{2[0]}.{2[1]}'.format( @callback @bind_hass -def async_get_clientsession(hass, verify_ssl=True): +def async_get_clientsession(hass: HomeAssistantType, + verify_ssl: bool = True) -> aiohttp.ClientSession: """Return default aiohttp ClientSession. This method must be run in the event loop. @@ -36,13 +41,15 @@ def async_get_clientsession(hass, verify_ssl=True): if key not in hass.data: hass.data[key] = async_create_clientsession(hass, verify_ssl) - return hass.data[key] + return cast(aiohttp.ClientSession, hass.data[key]) @callback @bind_hass -def async_create_clientsession(hass, verify_ssl=True, auto_cleanup=True, - **kwargs): +def async_create_clientsession(hass: HomeAssistantType, + verify_ssl: bool = True, + auto_cleanup: bool = True, + **kwargs: Any) -> aiohttp.ClientSession: """Create a new ClientSession with kwargs, i.e. for cookies. If auto_cleanup is False, you need to call detach() after the session @@ -67,8 +74,10 @@ def async_create_clientsession(hass, verify_ssl=True, auto_cleanup=True, @bind_hass -async def async_aiohttp_proxy_web(hass, request, web_coro, - buffer_size=102400, timeout=10): +async def async_aiohttp_proxy_web( + hass: HomeAssistantType, request: web.BaseRequest, + web_coro: Awaitable[aiohttp.ClientResponse], buffer_size: int = 102400, + timeout: int = 10) -> Optional[web.StreamResponse]: """Stream websession request to aiohttp web response.""" try: with async_timeout.timeout(timeout, loop=hass.loop): @@ -76,7 +85,7 @@ async def async_aiohttp_proxy_web(hass, request, web_coro, except asyncio.CancelledError: # The user cancelled the request - return + return None except asyncio.TimeoutError as err: # Timeout trying to start the web request @@ -98,8 +107,12 @@ async def async_aiohttp_proxy_web(hass, request, web_coro, @bind_hass -async def async_aiohttp_proxy_stream(hass, request, stream, content_type, - buffer_size=102400, timeout=10): +async def async_aiohttp_proxy_stream(hass: HomeAssistantType, + request: web.BaseRequest, + stream: aiohttp.StreamReader, + content_type: str, + buffer_size: int = 102400, + timeout: int = 10) -> web.StreamResponse: """Stream a stream to aiohttp web response.""" response = web.StreamResponse() response.content_type = content_type @@ -122,13 +135,14 @@ async def async_aiohttp_proxy_stream(hass, request, stream, content_type, @callback -def _async_register_clientsession_shutdown(hass, clientsession): +def _async_register_clientsession_shutdown( + hass: HomeAssistantType, clientsession: aiohttp.ClientSession) -> None: """Register ClientSession close on Home Assistant shutdown. This method must be run in the event loop. """ @callback - def _async_close_websession(event): + def _async_close_websession(event: Event) -> None: """Close websession.""" clientsession.detach() @@ -137,7 +151,8 @@ def _async_register_clientsession_shutdown(hass, clientsession): @callback -def _async_get_connector(hass, verify_ssl=True): +def _async_get_connector(hass: HomeAssistantType, + verify_ssl: bool = True) -> aiohttp.BaseConnector: """Return the connector pool for aiohttp. This method must be run in the event loop. @@ -145,17 +160,18 @@ def _async_get_connector(hass, verify_ssl=True): key = DATA_CONNECTOR if verify_ssl else DATA_CONNECTOR_NOTVERIFY if key in hass.data: - return hass.data[key] + return cast(aiohttp.BaseConnector, hass.data[key]) if verify_ssl: - ssl_context = ssl_util.client_context() + ssl_context = \ + ssl_util.client_context() # type: Union[bool, SSLContext] else: ssl_context = False connector = aiohttp.TCPConnector(loop=hass.loop, ssl=ssl_context) hass.data[key] = connector - async def _async_close_connector(event): + async def _async_close_connector(event: Event) -> None: """Close connector pool.""" await connector.close() diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index 19ad52534cb..bc8c05ed0a6 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -2,12 +2,14 @@ import logging import uuid from collections import OrderedDict -from typing import List, Optional +from typing import MutableMapping # noqa: F401 +from typing import Iterable, Optional, cast import attr from homeassistant.core import callback from homeassistant.loader import bind_hass +from .typing import HomeAssistantType _LOGGER = logging.getLogger(__name__) @@ -29,14 +31,14 @@ class AreaEntry: class AreaRegistry: """Class to hold a registry of areas.""" - def __init__(self, hass) -> None: + def __init__(self, hass: HomeAssistantType) -> None: """Initialize the area registry.""" self.hass = hass - self.areas = None + self.areas = {} # type: MutableMapping[str, AreaEntry] self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) @callback - def async_list_areas(self) -> List[AreaEntry]: + def async_list_areas(self) -> Iterable[AreaEntry]: """Get all areas.""" return self.areas.values() @@ -81,18 +83,18 @@ class AreaRegistry: return new @callback - def _async_is_registered(self, name) -> Optional[AreaEntry]: + def _async_is_registered(self, name: str) -> Optional[AreaEntry]: """Check if a name is currently registered.""" for area in self.areas.values(): if name == area.name: return area - return False + return None async def async_load(self) -> None: """Load the area registry.""" data = await self._store.async_load() - areas = OrderedDict() + areas = OrderedDict() # type: OrderedDict[str, AreaEntry] if data is not None: for area in data['areas']: @@ -124,16 +126,16 @@ class AreaRegistry: @bind_hass -async def async_get_registry(hass) -> AreaRegistry: +async def async_get_registry(hass: HomeAssistantType) -> AreaRegistry: """Return area registry instance.""" task = hass.data.get(DATA_REGISTRY) if task is None: - async def _load_reg(): + async def _load_reg() -> AreaRegistry: registry = AreaRegistry(hass) await registry.async_load() return registry task = hass.data[DATA_REGISTRY] = hass.async_create_task(_load_reg()) - return await task + return cast(AreaRegistry, await task) diff --git a/tox.ini b/tox.ini index d240149cff8..1dfa77c14f1 100644 --- a/tox.ini +++ b/tox.ini @@ -60,4 +60,4 @@ whitelist_externals=/bin/bash deps = -r{toxinidir}/requirements_test.txt commands = - /bin/bash -c 'mypy homeassistant/*.py homeassistant/{auth,util}/ homeassistant/helpers/{__init__,condition,deprecation,dispatcher,entity_values,entityfilter,icon,intent,json,location,signal,state,sun,temperature,translation,typing}.py' + /bin/bash -c 'mypy homeassistant/*.py homeassistant/{auth,util}/ homeassistant/helpers/{__init__,aiohttp_client,area_registry,condition,deprecation,dispatcher,entity_values,entityfilter,icon,intent,json,location,signal,state,sun,temperature,translation,typing}.py' From d24ccbd1e6d9f0ee900e759c2111cf73152bb898 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 7 Feb 2019 14:35:23 -0700 Subject: [PATCH 093/242] Fix binary sensor in Ambient PWS (#20801) * Fix binary sensor in Ambient PWS * Correctly load entities * Corrected what on and off means for existing sensor * Make sure to return a boolean * Member comments * Binary sensor doesn't need state property --- .../components/ambient_station/__init__.py | 158 +++++++++++++----- .../ambient_station/binary_sensor.py | 71 ++++++++ .../components/ambient_station/const.py | 3 + .../components/ambient_station/sensor.py | 67 ++------ 4 files changed, 209 insertions(+), 90 deletions(-) create mode 100644 homeassistant/components/ambient_station/binary_sensor.py diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index 0991336f42a..c5ddd2734cb 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -12,51 +12,85 @@ from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_NAME, ATTR_LOCATION, CONF_API_KEY, CONF_MONITORED_CONDITIONS, EVENT_HOMEASSISTANT_STOP) +from homeassistant.core import callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, config_validation as cv -from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, async_dispatcher_send) +from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_call_later from .config_flow import configured_instances from .const import ( - ATTR_LAST_DATA, CONF_APP_KEY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE) + ATTR_LAST_DATA, CONF_APP_KEY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE, + TYPE_BINARY_SENSOR, TYPE_SENSOR) REQUIREMENTS = ['aioambient==0.1.0'] _LOGGER = logging.getLogger(__name__) DEFAULT_SOCKET_MIN_RETRY = 15 +TYPE_24HOURRAININ = '24hourrainin' +TYPE_BAROMABSIN = 'baromabsin' +TYPE_BAROMRELIN = 'baromrelin' +TYPE_BATTOUT = 'battout' +TYPE_CO2 = 'co2' +TYPE_DAILYRAININ = 'dailyrainin' +TYPE_DEWPOINT = 'dewPoint' +TYPE_EVENTRAININ = 'eventrainin' +TYPE_FEELSLIKE = 'feelsLike' +TYPE_HOURLYRAININ = 'hourlyrainin' +TYPE_HUMIDITY = 'humidity' +TYPE_HUMIDITYIN = 'humidityin' +TYPE_LASTRAIN = 'lastRain' +TYPE_MAXDAILYGUST = 'maxdailygust' +TYPE_MONTHLYRAININ = 'monthlyrainin' +TYPE_SOLARRADIATION = 'solarradiation' +TYPE_TEMPF = 'tempf' +TYPE_TEMPINF = 'tempinf' +TYPE_TOTALRAININ = 'totalrainin' +TYPE_UV = 'uv' +TYPE_WEEKLYRAININ = 'weeklyrainin' +TYPE_WINDDIR = 'winddir' +TYPE_WINDDIR_AVG10M = 'winddir_avg10m' +TYPE_WINDDIR_AVG2M = 'winddir_avg2m' +TYPE_WINDGUSTDIR = 'windgustdir' +TYPE_WINDGUSTMPH = 'windgustmph' +TYPE_WINDSPDMPH_AVG10M = 'windspdmph_avg10m' +TYPE_WINDSPDMPH_AVG2M = 'windspdmph_avg2m' +TYPE_WINDSPEEDMPH = 'windspeedmph' +TYPE_YEARLYRAININ = 'yearlyrainin' SENSOR_TYPES = { - '24hourrainin': ('24 Hr Rain', 'in'), - 'baromabsin': ('Abs Pressure', 'inHg'), - 'baromrelin': ('Rel Pressure', 'inHg'), - 'battout': ('Battery', ''), - 'co2': ('co2', 'ppm'), - 'dailyrainin': ('Daily Rain', 'in'), - 'dewPoint': ('Dew Point', '°F'), - 'eventrainin': ('Event Rain', 'in'), - 'feelsLike': ('Feels Like', '°F'), - 'hourlyrainin': ('Hourly Rain Rate', 'in/hr'), - 'humidity': ('Humidity', '%'), - 'humidityin': ('Humidity In', '%'), - 'lastRain': ('Last Rain', ''), - 'maxdailygust': ('Max Gust', 'mph'), - 'monthlyrainin': ('Monthly Rain', 'in'), - 'solarradiation': ('Solar Rad', 'W/m^2'), - 'tempf': ('Temp', '°F'), - 'tempinf': ('Inside Temp', '°F'), - 'totalrainin': ('Lifetime Rain', 'in'), - 'uv': ('uv', 'Index'), - 'weeklyrainin': ('Weekly Rain', 'in'), - 'winddir': ('Wind Dir', '°'), - 'winddir_avg10m': ('Wind Dir Avg 10m', '°'), - 'winddir_avg2m': ('Wind Dir Avg 2m', 'mph'), - 'windgustdir': ('Gust Dir', '°'), - 'windgustmph': ('Wind Gust', 'mph'), - 'windspdmph_avg10m': ('Wind Avg 10m', 'mph'), - 'windspdmph_avg2m': ('Wind Avg 2m', 'mph'), - 'windspeedmph': ('Wind Speed', 'mph'), - 'yearlyrainin': ('Yearly Rain', 'in'), + TYPE_24HOURRAININ: ('24 Hr Rain', 'in', TYPE_SENSOR, None), + TYPE_BAROMABSIN: ('Abs Pressure', 'inHg', TYPE_SENSOR, None), + TYPE_BAROMRELIN: ('Rel Pressure', 'inHg', TYPE_SENSOR, None), + TYPE_BATTOUT: ('Battery', None, TYPE_BINARY_SENSOR, 'battery'), + TYPE_CO2: ('co2', 'ppm', TYPE_SENSOR, None), + TYPE_DAILYRAININ: ('Daily Rain', 'in', TYPE_SENSOR, None), + TYPE_DEWPOINT: ('Dew Point', '°F', TYPE_SENSOR, None), + TYPE_EVENTRAININ: ('Event Rain', 'in', TYPE_SENSOR, None), + TYPE_FEELSLIKE: ('Feels Like', '°F', TYPE_SENSOR, None), + TYPE_HOURLYRAININ: ('Hourly Rain Rate', 'in/hr', TYPE_SENSOR, None), + TYPE_HUMIDITY: ('Humidity', '%', TYPE_SENSOR, None), + TYPE_HUMIDITYIN: ('Humidity In', '%', TYPE_SENSOR, None), + TYPE_LASTRAIN: ('Last Rain', None, TYPE_SENSOR, None), + TYPE_MAXDAILYGUST: ('Max Gust', 'mph', TYPE_SENSOR, None), + TYPE_MONTHLYRAININ: ('Monthly Rain', 'in', TYPE_SENSOR, None), + TYPE_SOLARRADIATION: ('Solar Rad', 'W/m^2', TYPE_SENSOR, None), + TYPE_TEMPF: ('Temp', '°F', TYPE_SENSOR, None), + TYPE_TEMPINF: ('Inside Temp', '°F', TYPE_SENSOR, None), + TYPE_TOTALRAININ: ('Lifetime Rain', 'in', TYPE_SENSOR, None), + TYPE_UV: ('uv', 'Index', TYPE_SENSOR, None), + TYPE_WEEKLYRAININ: ('Weekly Rain', 'in', TYPE_SENSOR, None), + TYPE_WINDDIR: ('Wind Dir', '°', TYPE_SENSOR, None), + TYPE_WINDDIR_AVG10M: ('Wind Dir Avg 10m', '°', TYPE_SENSOR, None), + TYPE_WINDDIR_AVG2M: ('Wind Dir Avg 2m', 'mph', TYPE_SENSOR, None), + TYPE_WINDGUSTDIR: ('Gust Dir', '°', TYPE_SENSOR, None), + TYPE_WINDGUSTMPH: ('Wind Gust', 'mph', TYPE_SENSOR, None), + TYPE_WINDSPDMPH_AVG10M: ('Wind Avg 10m', 'mph', TYPE_SENSOR, None), + TYPE_WINDSPDMPH_AVG2M: ('Wind Avg 2m', 'mph', TYPE_SENSOR, None), + TYPE_WINDSPEEDMPH: ('Wind Speed', 'mph', TYPE_SENSOR, None), + TYPE_YEARLYRAININ: ('Yearly Rain', 'in', TYPE_SENSOR, None), } CONFIG_SCHEMA = vol.Schema({ @@ -102,8 +136,7 @@ async def async_setup_entry(hass, config_entry): try: ambient = AmbientStation( - hass, - config_entry, + hass, config_entry, Client( config_entry.data[CONF_API_KEY], config_entry.data[CONF_APP_KEY], session), @@ -126,8 +159,9 @@ async def async_unload_entry(hass, config_entry): ambient = hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id) hass.async_create_task(ambient.ws_disconnect()) - await hass.config_entries.async_forward_entry_unload( - config_entry, 'sensor') + for component in ('binary_sensor', 'sensor'): + await hass.config_entries.async_forward_entry_unload( + config_entry, component) return True @@ -178,9 +212,10 @@ class AmbientStation: ATTR_NAME: station['info']['name'], } + for component in ('binary_sensor', 'sensor'): self._hass.async_create_task( self._hass.config_entries.async_forward_entry_setup( - self._config_entry, 'sensor')) + self._config_entry, component)) self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY @@ -194,8 +229,7 @@ class AmbientStation: except WebsocketError as err: _LOGGER.error("Error with the websocket connection: %s", err) - self._ws_reconnect_delay = min( - 2 * self._ws_reconnect_delay, 480) + self._ws_reconnect_delay = min(2 * self._ws_reconnect_delay, 480) async_call_later( self._hass, self._ws_reconnect_delay, self.ws_connect) @@ -203,3 +237,49 @@ class AmbientStation: async def ws_disconnect(self): """Disconnect from the websocket.""" await self.client.websocket.disconnect() + + +class AmbientWeatherEntity(Entity): + """Define a base Ambient PWS entity.""" + + def __init__( + self, ambient, mac_address, station_name, sensor_type, + sensor_name): + """Initialize the sensor.""" + self._ambient = ambient + self._async_unsub_dispatcher_connect = None + self._mac_address = mac_address + self._sensor_name = sensor_name + self._sensor_type = sensor_type + self._state = None + self._station_name = station_name + + @property + def name(self): + """Return the name of the sensor.""" + return '{0}_{1}'.format(self._station_name, self._sensor_name) + + @property + def should_poll(self): + """Disable polling.""" + return False + + @property + def unique_id(self): + """Return a unique, unchanging string that represents this sensor.""" + return '{0}_{1}'.format(self._mac_address, self._sensor_name) + + async def async_added_to_hass(self): + """Register callbacks.""" + @callback + def update(): + """Update the state.""" + self.async_schedule_update_ha_state(True) + + self._async_unsub_dispatcher_connect = async_dispatcher_connect( + self.hass, TOPIC_UPDATE, update) + + async def async_will_remove_from_hass(self): + """Disconnect dispatcher listener when removed.""" + if self._async_unsub_dispatcher_connect: + self._async_unsub_dispatcher_connect() diff --git a/homeassistant/components/ambient_station/binary_sensor.py b/homeassistant/components/ambient_station/binary_sensor.py new file mode 100644 index 00000000000..c9c0160cf7c --- /dev/null +++ b/homeassistant/components/ambient_station/binary_sensor.py @@ -0,0 +1,71 @@ +""" +Support for Ambient Weather Station binary sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.ambient_station/ +""" +import logging + +from homeassistant.components.ambient_station import ( + SENSOR_TYPES, TYPE_BATTOUT, AmbientWeatherEntity) +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.const import ATTR_NAME + +from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TYPE_BINARY_SENSOR + +DEPENDENCIES = ['ambient_station'] +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): + """Set up Ambient PWS binary sensors based on the old way.""" + pass + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Ambient PWS binary sensors based on a config entry.""" + ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] + + binary_sensor_list = [] + for mac_address, station in ambient.stations.items(): + for condition in ambient.monitored_conditions: + name, _, kind, device_class = SENSOR_TYPES[condition] + if kind == TYPE_BINARY_SENSOR: + binary_sensor_list.append( + AmbientWeatherBinarySensor( + ambient, mac_address, station[ATTR_NAME], condition, + name, device_class)) + + async_add_entities(binary_sensor_list, True) + + +class AmbientWeatherBinarySensor(AmbientWeatherEntity, BinarySensorDevice): + """Define an Ambient binary sensor.""" + + def __init__( + self, ambient, mac_address, station_name, sensor_type, sensor_name, + device_class): + """Initialize the sensor.""" + super().__init__( + ambient, mac_address, station_name, sensor_type, sensor_name) + + self._device_class = device_class + + @property + def device_class(self): + """Return the device class.""" + return self._device_class + + @property + def is_on(self): + """Return the status of the sensor.""" + if self._sensor_type == TYPE_BATTOUT: + return self._state == 0 + + return self._state == 1 + + async def async_update(self): + """Fetch new state data for the entity.""" + self._state = self._ambient.stations[ + self._mac_address][ATTR_LAST_DATA].get(self._sensor_type) diff --git a/homeassistant/components/ambient_station/const.py b/homeassistant/components/ambient_station/const.py index 75606a1c699..27ec7afefaa 100644 --- a/homeassistant/components/ambient_station/const.py +++ b/homeassistant/components/ambient_station/const.py @@ -8,3 +8,6 @@ CONF_APP_KEY = 'app_key' DATA_CLIENT = 'data_client' TOPIC_UPDATE = 'update' + +TYPE_BINARY_SENSOR = 'binary_sensor' +TYPE_SENSOR = 'sensor' diff --git a/homeassistant/components/ambient_station/sensor.py b/homeassistant/components/ambient_station/sensor.py index 9e0833e3441..2699975cfb5 100644 --- a/homeassistant/components/ambient_station/sensor.py +++ b/homeassistant/components/ambient_station/sensor.py @@ -1,18 +1,16 @@ """ -Support for Ambient Weather Station Service. +Support for Ambient Weather Station sensors. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.ambient_station/ """ import logging -from homeassistant.components.ambient_station import SENSOR_TYPES -from homeassistant.helpers.entity import Entity +from homeassistant.components.ambient_station import ( + SENSOR_TYPES, AmbientWeatherEntity) from homeassistant.const import ATTR_NAME -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TOPIC_UPDATE +from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TYPE_SENSOR DEPENDENCIES = ['ambient_station'] _LOGGER = logging.getLogger(__name__) @@ -20,52 +18,39 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_platform( hass, config, async_add_entities, discovery_info=None): - """Set up an Ambient PWS sensor based on existing config.""" + """Set up Ambient PWS sensors based on existing config.""" pass async def async_setup_entry(hass, entry, async_add_entities): - """Set up an Ambient PWS sensor based on a config entry.""" + """Set up Ambient PWS sensors based on a config entry.""" ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] sensor_list = [] for mac_address, station in ambient.stations.items(): for condition in ambient.monitored_conditions: - name, unit = SENSOR_TYPES[condition] - sensor_list.append( - AmbientWeatherSensor( - ambient, mac_address, station[ATTR_NAME], condition, name, - unit)) + name, unit, kind, _ = SENSOR_TYPES[condition] + if kind == TYPE_SENSOR: + sensor_list.append( + AmbientWeatherSensor( + ambient, mac_address, station[ATTR_NAME], condition, + name, unit)) async_add_entities(sensor_list, True) -class AmbientWeatherSensor(Entity): +class AmbientWeatherSensor(AmbientWeatherEntity): """Define an Ambient sensor.""" def __init__( self, ambient, mac_address, station_name, sensor_type, sensor_name, unit): """Initialize the sensor.""" - self._ambient = ambient - self._async_unsub_dispatcher_connect = None - self._mac_address = mac_address - self._sensor_name = sensor_name - self._sensor_type = sensor_type - self._state = None - self._station_name = station_name + super().__init__( + ambient, mac_address, station_name, sensor_type, sensor_name) + self._unit = unit - @property - def name(self): - """Return the name of the sensor.""" - return '{0}_{1}'.format(self._station_name, self._sensor_name) - - @property - def should_poll(self): - """Disable polling.""" - return False - @property def state(self): """Return the state of the sensor.""" @@ -76,26 +61,6 @@ class AmbientWeatherSensor(Entity): """Return the unit of measurement.""" return self._unit - @property - def unique_id(self): - """Return a unique, unchanging string that represents this sensor.""" - return '{0}_{1}'.format(self._mac_address, self._sensor_name) - - async def async_added_to_hass(self): - """Register callbacks.""" - @callback - def update(): - """Update the state.""" - self.async_schedule_update_ha_state(True) - - self._async_unsub_dispatcher_connect = async_dispatcher_connect( - self.hass, TOPIC_UPDATE, update) - - async def async_will_remove_from_hass(self): - """Disconnect dispatcher listener when removed.""" - if self._async_unsub_dispatcher_connect: - self._async_unsub_dispatcher_connect() - async def async_update(self): """Fetch new state data for the sensor.""" self._state = self._ambient.stations[ From f3b20d138e6e1412cf0547ba6fda6222ae0829ec Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 7 Feb 2019 13:50:59 -0800 Subject: [PATCH 094/242] Embed Z-Wave platforms (#20810) --- .../zwave.py => zwave/binary_sensor.py} | 0 .../{climate/zwave.py => zwave/climate.py} | 0 .../{cover/zwave.py => zwave/cover.py} | 0 .../components/{fan/zwave.py => zwave/fan.py} | 0 .../{light/zwave.py => zwave/light.py} | 0 .../{lock/zwave.py => zwave/lock.py} | 0 .../{sensor/zwave.py => zwave/sensor.py} | 0 .../{switch/zwave.py => zwave/switch.py} | 0 tests/components/zwave/conftest.py | 24 +++++++++++++++++++ .../test_binary_sensor.py} | 2 +- .../test_zwave.py => zwave/test_climate.py} | 3 ++- .../test_zwave.py => zwave/test_cover.py} | 3 ++- .../{fan/test_zwave.py => zwave/test_fan.py} | 3 ++- tests/components/zwave/test_init.py | 2 +- .../test_zwave.py => zwave/test_light.py} | 3 ++- .../test_zwave.py => zwave/test_lock.py} | 2 +- .../test_zwave.py => zwave/test_sensor.py} | 2 +- .../test_zwave.py => zwave/test_switch.py} | 2 +- tests/conftest.py | 21 +--------------- 19 files changed, 38 insertions(+), 29 deletions(-) rename homeassistant/components/{binary_sensor/zwave.py => zwave/binary_sensor.py} (100%) rename homeassistant/components/{climate/zwave.py => zwave/climate.py} (100%) rename homeassistant/components/{cover/zwave.py => zwave/cover.py} (100%) rename homeassistant/components/{fan/zwave.py => zwave/fan.py} (100%) rename homeassistant/components/{light/zwave.py => zwave/light.py} (100%) rename homeassistant/components/{lock/zwave.py => zwave/lock.py} (100%) rename homeassistant/components/{sensor/zwave.py => zwave/sensor.py} (100%) rename homeassistant/components/{switch/zwave.py => zwave/switch.py} (100%) create mode 100644 tests/components/zwave/conftest.py rename tests/components/{binary_sensor/test_zwave.py => zwave/test_binary_sensor.py} (98%) rename tests/components/{climate/test_zwave.py => zwave/test_climate.py} (98%) rename tests/components/{cover/test_zwave.py => zwave/test_cover.py} (98%) rename tests/components/{fan/test_zwave.py => zwave/test_fan.py} (96%) rename tests/components/{light/test_zwave.py => zwave/test_light.py} (99%) rename tests/components/{lock/test_zwave.py => zwave/test_lock.py} (99%) rename tests/components/{sensor/test_zwave.py => zwave/test_sensor.py} (98%) rename tests/components/{switch/test_zwave.py => zwave/test_switch.py} (97%) diff --git a/homeassistant/components/binary_sensor/zwave.py b/homeassistant/components/zwave/binary_sensor.py similarity index 100% rename from homeassistant/components/binary_sensor/zwave.py rename to homeassistant/components/zwave/binary_sensor.py diff --git a/homeassistant/components/climate/zwave.py b/homeassistant/components/zwave/climate.py similarity index 100% rename from homeassistant/components/climate/zwave.py rename to homeassistant/components/zwave/climate.py diff --git a/homeassistant/components/cover/zwave.py b/homeassistant/components/zwave/cover.py similarity index 100% rename from homeassistant/components/cover/zwave.py rename to homeassistant/components/zwave/cover.py diff --git a/homeassistant/components/fan/zwave.py b/homeassistant/components/zwave/fan.py similarity index 100% rename from homeassistant/components/fan/zwave.py rename to homeassistant/components/zwave/fan.py diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/zwave/light.py similarity index 100% rename from homeassistant/components/light/zwave.py rename to homeassistant/components/zwave/light.py diff --git a/homeassistant/components/lock/zwave.py b/homeassistant/components/zwave/lock.py similarity index 100% rename from homeassistant/components/lock/zwave.py rename to homeassistant/components/zwave/lock.py diff --git a/homeassistant/components/sensor/zwave.py b/homeassistant/components/zwave/sensor.py similarity index 100% rename from homeassistant/components/sensor/zwave.py rename to homeassistant/components/zwave/sensor.py diff --git a/homeassistant/components/switch/zwave.py b/homeassistant/components/zwave/switch.py similarity index 100% rename from homeassistant/components/switch/zwave.py rename to homeassistant/components/zwave/switch.py diff --git a/tests/components/zwave/conftest.py b/tests/components/zwave/conftest.py new file mode 100644 index 00000000000..7a1aae357ad --- /dev/null +++ b/tests/components/zwave/conftest.py @@ -0,0 +1,24 @@ +"""Fixtures for Z-Wave tests.""" +from unittest.mock import patch, MagicMock + +import pytest + +from tests.mock.zwave import MockNetwork, MockOption + + +@pytest.fixture +def mock_openzwave(): + """Mock out Open Z-Wave.""" + base_mock = MagicMock() + libopenzwave = base_mock.libopenzwave + libopenzwave.__file__ = 'test' + base_mock.network.ZWaveNetwork = MockNetwork + base_mock.option.ZWaveOption = MockOption + + with patch.dict('sys.modules', { + 'libopenzwave': libopenzwave, + 'openzwave.option': base_mock.option, + 'openzwave.network': base_mock.network, + 'openzwave.group': base_mock.group, + }): + yield base_mock diff --git a/tests/components/binary_sensor/test_zwave.py b/tests/components/zwave/test_binary_sensor.py similarity index 98% rename from tests/components/binary_sensor/test_zwave.py rename to tests/components/zwave/test_binary_sensor.py index f33e8a83e1e..786afb1b9ce 100644 --- a/tests/components/binary_sensor/test_zwave.py +++ b/tests/components/zwave/test_binary_sensor.py @@ -4,7 +4,7 @@ import datetime from unittest.mock import patch from homeassistant.components.zwave import const -from homeassistant.components.binary_sensor import zwave +import homeassistant.components.zwave.binary_sensor as zwave from tests.mock.zwave import ( MockNode, MockValue, MockEntityValues, value_changed) diff --git a/tests/components/climate/test_zwave.py b/tests/components/zwave/test_climate.py similarity index 98% rename from tests/components/climate/test_zwave.py rename to tests/components/zwave/test_climate.py index 39a85ab493f..e8be6d7382b 100644 --- a/tests/components/climate/test_zwave.py +++ b/tests/components/zwave/test_climate.py @@ -1,7 +1,8 @@ """Test Z-Wave climate devices.""" import pytest -from homeassistant.components.climate import zwave, STATE_COOL, STATE_HEAT +from homeassistant.components.climate import STATE_COOL, STATE_HEAT +import homeassistant.components.zwave.climate as zwave from homeassistant.const import ( STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE) diff --git a/tests/components/cover/test_zwave.py b/tests/components/zwave/test_cover.py similarity index 98% rename from tests/components/cover/test_zwave.py rename to tests/components/zwave/test_cover.py index b870075d39f..cf3bcdf993b 100644 --- a/tests/components/cover/test_zwave.py +++ b/tests/components/zwave/test_cover.py @@ -1,7 +1,8 @@ """Test Z-Wave cover devices.""" from unittest.mock import MagicMock -from homeassistant.components.cover import zwave, SUPPORT_OPEN, SUPPORT_CLOSE +from homeassistant.components.cover import SUPPORT_OPEN, SUPPORT_CLOSE +import homeassistant.components.zwave.cover as zwave from homeassistant.components.zwave import const from tests.mock.zwave import ( diff --git a/tests/components/fan/test_zwave.py b/tests/components/zwave/test_fan.py similarity index 96% rename from tests/components/fan/test_zwave.py rename to tests/components/zwave/test_fan.py index b7d7e497c03..af3d16f6288 100644 --- a/tests/components/fan/test_zwave.py +++ b/tests/components/zwave/test_fan.py @@ -1,6 +1,7 @@ """Test Z-Wave fans.""" +import homeassistant.components.zwave.fan as zwave from homeassistant.components.fan import ( - zwave, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, SUPPORT_SET_SPEED) + SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, SUPPORT_SET_SPEED) from tests.mock.zwave import ( MockNode, MockValue, MockEntityValues, value_changed) diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index 212d3e02802..66011f3e6ee 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -10,7 +10,7 @@ from unittest.mock import patch, MagicMock from homeassistant.bootstrap import async_setup_component from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START from homeassistant.components import zwave -from homeassistant.components.binary_sensor.zwave import get_device +from homeassistant.components.zwave.binary_sensor import get_device from homeassistant.components.zwave import ( const, CONFIG_SCHEMA, CONF_DEVICE_CONFIG_GLOB, DATA_NETWORK) from homeassistant.setup import setup_component diff --git a/tests/components/light/test_zwave.py b/tests/components/zwave/test_light.py similarity index 99% rename from tests/components/light/test_zwave.py rename to tests/components/zwave/test_light.py index 5805c8eb2fb..5e85f28da39 100644 --- a/tests/components/light/test_zwave.py +++ b/tests/components/zwave/test_light.py @@ -3,8 +3,9 @@ from unittest.mock import patch, MagicMock import homeassistant.components.zwave from homeassistant.components.zwave import const +import homeassistant.components.zwave.light as zwave from homeassistant.components.light import ( - zwave, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_TRANSITION, + ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_TRANSITION, SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION, SUPPORT_COLOR, ATTR_WHITE_VALUE, SUPPORT_COLOR_TEMP, SUPPORT_WHITE_VALUE) diff --git a/tests/components/lock/test_zwave.py b/tests/components/zwave/test_lock.py similarity index 99% rename from tests/components/lock/test_zwave.py rename to tests/components/zwave/test_lock.py index 07095e4fe3e..98734db8d7c 100644 --- a/tests/components/lock/test_zwave.py +++ b/tests/components/zwave/test_lock.py @@ -2,7 +2,7 @@ from unittest.mock import patch, MagicMock from homeassistant import config_entries -from homeassistant.components.lock import zwave +import homeassistant.components.zwave.lock as zwave from homeassistant.components.zwave import const from tests.mock.zwave import ( diff --git a/tests/components/sensor/test_zwave.py b/tests/components/zwave/test_sensor.py similarity index 98% rename from tests/components/sensor/test_zwave.py rename to tests/components/zwave/test_sensor.py index e3792c27d77..cce6bf9deaa 100644 --- a/tests/components/sensor/test_zwave.py +++ b/tests/components/zwave/test_sensor.py @@ -1,5 +1,5 @@ """Test Z-Wave sensor.""" -from homeassistant.components.sensor import zwave +import homeassistant.components.zwave.sensor as zwave from homeassistant.components.zwave import const import homeassistant.const diff --git a/tests/components/switch/test_zwave.py b/tests/components/zwave/test_switch.py similarity index 97% rename from tests/components/switch/test_zwave.py rename to tests/components/zwave/test_switch.py index 3769eef828b..943be9fc4ea 100644 --- a/tests/components/switch/test_zwave.py +++ b/tests/components/zwave/test_switch.py @@ -1,7 +1,7 @@ """Test Z-Wave switches.""" from unittest.mock import patch -from homeassistant.components.switch import zwave +import homeassistant.components.zwave.switch as zwave from tests.mock.zwave import ( MockNode, MockValue, MockEntityValues, value_changed) diff --git a/tests/conftest.py b/tests/conftest.py index c2e8eb1eb28..1dc5733cf40 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,7 @@ import asyncio import functools import logging import os -from unittest.mock import patch, MagicMock +from unittest.mock import patch import pytest import requests_mock as _requests_mock @@ -17,7 +17,6 @@ from tests.common import ( async_test_home_assistant, INSTANCES, mock_coro, mock_storage as mock_storage, MockUser, CLIENT_ID) from tests.test_util.aiohttp import mock_aiohttp_client -from tests.mock.zwave import MockNetwork, MockOption if os.environ.get('UVLOOP') == '1': import uvloop @@ -92,24 +91,6 @@ def aioclient_mock(): yield mock_session -@pytest.fixture -def mock_openzwave(): - """Mock out Open Z-Wave.""" - base_mock = MagicMock() - libopenzwave = base_mock.libopenzwave - libopenzwave.__file__ = 'test' - base_mock.network.ZWaveNetwork = MockNetwork - base_mock.option.ZWaveOption = MockOption - - with patch.dict('sys.modules', { - 'libopenzwave': libopenzwave, - 'openzwave.option': base_mock.option, - 'openzwave.network': base_mock.network, - 'openzwave.group': base_mock.group, - }): - yield base_mock - - @pytest.fixture def mock_device_tracker_conf(): """Prevent device tracker from reading/writing data.""" From a9672b0d522f76d212b5ec254e0ed76619eae3df Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 7 Feb 2019 13:56:40 -0800 Subject: [PATCH 095/242] Load as many components in parallel as possible (#20806) * Load as many components in parallel as possible * Lint --- homeassistant/bootstrap.py | 12 ++++- homeassistant/loader.py | 70 +++++++++++++++----------- homeassistant/setup.py | 18 ++++--- homeassistant/util/__init__.py | 91 ---------------------------------- tests/test_loader.py | 67 +++++++++++-------------- tests/util/test_init.py | 61 ----------------------- 6 files changed, 92 insertions(+), 227 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 5dd62005609..90a74f23598 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -10,7 +10,8 @@ from typing import Any, Optional, Dict import voluptuous as vol from homeassistant import ( - core, config as conf_util, config_entries, components as core_components) + core, config as conf_util, config_entries, components as core_components, + loader) from homeassistant.components import persistent_notification from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE from homeassistant.setup import async_setup_component @@ -124,6 +125,15 @@ async def async_from_config_dict(config: Dict[str, Any], if key != core.DOMAIN) components.update(hass.config_entries.async_domains()) + # Resolve all dependencies of all components. + for component in list(components): + try: + components.update(loader.component_dependencies(hass, component)) + except loader.LoaderError: + # Ignore it, or we'll break startup + # It will be properly handled during setup. + pass + # setup components res = await core_components.async_setup(hass, config) if not res: diff --git a/homeassistant/loader.py b/homeassistant/loader.py index d02d22cc8d2..962b168aa97 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -18,7 +18,6 @@ from types import ModuleType from typing import Optional, Set, TYPE_CHECKING, Callable, Any, TypeVar # noqa pylint: disable=unused-import from homeassistant.const import PLATFORM_FORMAT -from homeassistant.util import OrderedSet # Typing imports that create a circular dependency # pylint: disable=using-constant-test,unused-import @@ -39,6 +38,30 @@ PATH_CUSTOM_COMPONENTS = 'custom_components' PACKAGE_COMPONENTS = 'homeassistant.components' +class LoaderError(Exception): + """Loader base error.""" + + +class ComponentNotFound(LoaderError): + """Raised when a component is not found.""" + + def __init__(self, domain: str) -> None: + """Initialize a component not found error.""" + super().__init__("Component {} not found.".format(domain)) + self.domain = domain + + +class CircularDependency(LoaderError): + """Raised when a circular dependency is found when resolving components.""" + + def __init__(self, from_domain: str, to_domain: str) -> None: + """Initialize circular dependency error.""" + super().__init__("Circular dependency detected: {} -> {}.".format( + from_domain, to_domain)) + self.from_domain = from_domain + self.to_domain = to_domain + + def set_component(hass, # type: HomeAssistant comp_name: str, component: Optional[ModuleType]) -> None: """Set a component in the cache. @@ -235,57 +258,46 @@ def bind_hass(func: CALLABLE_T) -> CALLABLE_T: return func -def load_order_component(hass, # type: HomeAssistant - comp_name: str) -> OrderedSet: - """Return an OrderedSet of components in the correct order of loading. +def component_dependencies(hass, # type: HomeAssistant + comp_name: str) -> Set[str]: + """Return all dependencies and subdependencies of components. - Returns an empty list if a circular dependency is detected - or the component could not be loaded. In both cases, the error is - logged. + Raises CircularDependency if a circular dependency is found. Async friendly. """ - return _load_order_component(hass, comp_name, OrderedSet(), set()) + return _component_dependencies(hass, comp_name, set(), set()) -def _load_order_component(hass, # type: HomeAssistant - comp_name: str, load_order: OrderedSet, - loading: Set) -> OrderedSet: - """Recursive function to get load order of components. +def _component_dependencies(hass, # type: HomeAssistant + comp_name: str, loaded: Set[str], + loading: Set) -> Set[str]: + """Recursive function to get component dependencies. Async friendly. """ component = get_component(hass, comp_name) - # If None it does not exist, error already thrown by get_component. if component is None: - return OrderedSet() + raise ComponentNotFound(comp_name) loading.add(comp_name) for dependency in getattr(component, 'DEPENDENCIES', []): # Check not already loaded - if dependency in load_order: + if dependency in loaded: continue # If we are already loading it, we have a circular dependency. if dependency in loading: - _LOGGER.error("Circular dependency detected: %s -> %s", - comp_name, dependency) - return OrderedSet() + raise CircularDependency(comp_name, dependency) - dep_load_order = _load_order_component( - hass, dependency, load_order, loading) + dep_loaded = _component_dependencies( + hass, dependency, loaded, loading) - # length == 0 means error loading dependency or children - if not dep_load_order: - _LOGGER.error("Error loading %s dependency: %s", - comp_name, dependency) - return OrderedSet() + loaded.update(dep_loaded) - load_order.update(dep_load_order) - - load_order.add(comp_name) + loaded.add(comp_name) loading.remove(comp_name) - return load_order + return loaded diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 49aae2178fc..33c5d5311b1 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -106,12 +106,18 @@ async def _async_setup_component(hass: core.HomeAssistant, log_error("Component not found.", False) return False - # Validate no circular dependencies - components = loader.load_order_component(hass, domain) - - # OrderedSet is empty if component or dependencies could not be resolved - if not components: - log_error("Unable to resolve component or dependencies.") + # Validate all dependencies exist and there are no circular dependencies + try: + loader.component_dependencies(hass, domain) + except loader.ComponentNotFound as err: + _LOGGER.error( + "Not setting up %s because we are unable to resolve " + "(sub)dependency %s", domain, err.domain) + return False + except loader.CircularDependency as err: + _LOGGER.error( + "Not setting up %s because it contains a circular dependency: " + "%s -> %s", domain, err.from_domain, err.to_domain) return False processed_config = \ diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index b4d45b48079..12cd543a872 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -1,7 +1,6 @@ """Helper methods for various modules.""" import asyncio from datetime import datetime, timedelta -from itertools import chain import threading import re import enum @@ -141,96 +140,6 @@ class OrderedEnum(enum.Enum): return NotImplemented -class OrderedSet(MutableSet[T]): - """Ordered set taken from http://code.activestate.com/recipes/576694/.""" - - def __init__(self, iterable: Optional[Iterable[T]] = None) -> None: - """Initialize the set.""" - self.end = end = [] # type: List[Any] - end += [None, end, end] # sentinel node for doubly linked list - self.map = {} # type: Dict[T, List] # key --> [key, prev, next] - if iterable is not None: - self |= iterable # type: ignore - - def __len__(self) -> int: - """Return the length of the set.""" - return len(self.map) - - def __contains__(self, key: T) -> bool: # type: ignore - """Check if key is in set.""" - return key in self.map - - # pylint: disable=arguments-differ - def add(self, key: T) -> None: - """Add an element to the end of the set.""" - if key not in self.map: - end = self.end - curr = end[1] - curr[2] = end[1] = self.map[key] = [key, curr, end] - - def promote(self, key: T) -> None: - """Promote element to beginning of the set, add if not there.""" - if key in self.map: - self.discard(key) - - begin = self.end[2] - curr = begin[1] - curr[2] = begin[1] = self.map[key] = [key, curr, begin] - - # pylint: disable=arguments-differ - def discard(self, key: T) -> None: - """Discard an element from the set.""" - if key in self.map: - key, prev_item, next_item = self.map.pop(key) - prev_item[2] = next_item - next_item[1] = prev_item - - def __iter__(self) -> Iterator[T]: - """Iterate of the set.""" - end = self.end - curr = end[2] - while curr is not end: - yield curr[0] - curr = curr[2] - - def __reversed__(self) -> Iterator[T]: - """Reverse the ordering.""" - end = self.end - curr = end[1] - while curr is not end: - yield curr[0] - curr = curr[1] - - # pylint: disable=arguments-differ - def pop(self, last: bool = True) -> T: - """Pop element of the end of the set. - - Set last=False to pop from the beginning. - """ - if not self: - raise KeyError('set is empty') - key = self.end[1][0] if last else self.end[2][0] - self.discard(key) - return key # type: ignore - - def update(self, *args: Any) -> None: - """Add elements from args to the set.""" - for item in chain(*args): - self.add(item) - - def __repr__(self) -> str: - """Return the representation.""" - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, list(self)) - - def __eq__(self, other: Any) -> bool: - """Return the comparison.""" - if isinstance(other, OrderedSet): - return len(self) == len(other) and list(self) == list(other) - return set(self) == set(other) - - class Throttle: """A class for throttling the execution of tasks. diff --git a/tests/test_loader.py b/tests/test_loader.py index 6fecd5086b1..cceb9839d99 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1,63 +1,52 @@ """Test to verify that we can load components.""" -# pylint: disable=protected-access import asyncio -import unittest import pytest import homeassistant.loader as loader import homeassistant.components.http as http -from tests.common import ( - get_test_home_assistant, MockModule, async_mock_service) +from tests.common import MockModule, async_mock_service -class TestLoader(unittest.TestCase): - """Test the loader module.""" +def test_set_component(hass): + """Test if set_component works.""" + comp = object() + loader.set_component(hass, 'switch.test_set', comp) - # pylint: disable=invalid-name - def setUp(self): - """Set up tests.""" - self.hass = get_test_home_assistant() + assert loader.get_component(hass, 'switch.test_set') is comp - # pylint: disable=invalid-name - def tearDown(self): - """Stop everything that was started.""" - self.hass.stop() - def test_set_component(self): - """Test if set_component works.""" - comp = object() - loader.set_component(self.hass, 'switch.test_set', comp) +def test_get_component(hass): + """Test if get_component works.""" + assert http == loader.get_component(hass, 'http') - assert loader.get_component(self.hass, 'switch.test_set') is comp - def test_get_component(self): - """Test if get_component works.""" - assert http == loader.get_component(self.hass, 'http') +def test_component_dependencies(hass): + """Test if we can get the proper load order of components.""" + loader.set_component(hass, 'mod1', MockModule('mod1')) + loader.set_component(hass, 'mod2', MockModule('mod2', ['mod1'])) + loader.set_component(hass, 'mod3', MockModule('mod3', ['mod2'])) - def test_load_order_component(self): - """Test if we can get the proper load order of components.""" - loader.set_component(self.hass, 'mod1', MockModule('mod1')) - loader.set_component(self.hass, 'mod2', MockModule('mod2', ['mod1'])) - loader.set_component(self.hass, 'mod3', MockModule('mod3', ['mod2'])) + assert {'mod1', 'mod2', 'mod3'} == \ + loader.component_dependencies(hass, 'mod3') - assert ['mod1', 'mod2', 'mod3'] == \ - loader.load_order_component(self.hass, 'mod3') + # Create circular dependency + loader.set_component(hass, 'mod1', MockModule('mod1', ['mod3'])) - # Create circular dependency - loader.set_component(self.hass, 'mod1', MockModule('mod1', ['mod3'])) + with pytest.raises(loader.CircularDependency): + print(loader.component_dependencies(hass, 'mod3')) - assert [] == loader.load_order_component(self.hass, 'mod3') + # Depend on non-existing component + loader.set_component(hass, 'mod1', + MockModule('mod1', ['nonexisting'])) - # Depend on non-existing component - loader.set_component(self.hass, 'mod1', - MockModule('mod1', ['nonexisting'])) + with pytest.raises(loader.ComponentNotFound): + print(loader.component_dependencies(hass, 'mod1')) - assert [] == loader.load_order_component(self.hass, 'mod1') - - # Try to get load order for non-existing component - assert [] == loader.load_order_component(self.hass, 'mod1') + # Try to get dependencies for non-existing component + with pytest.raises(loader.ComponentNotFound): + print(loader.component_dependencies(hass, 'nonexisting')) def test_component_loader(hass): diff --git a/tests/util/test_init.py b/tests/util/test_init.py index 98fe8774b96..af957582ec0 100644 --- a/tests/util/test_init.py +++ b/tests/util/test_init.py @@ -105,67 +105,6 @@ class TestUtil(unittest.TestCase): with pytest.raises(TypeError): TestEnum.FIRST >= 1 - def test_ordered_set(self): - """Test ordering of set.""" - set1 = util.OrderedSet([1, 2, 3, 4]) - set2 = util.OrderedSet([3, 4, 5]) - - assert 4 == len(set1) - assert 3 == len(set2) - - assert 1 in set1 - assert 2 in set1 - assert 3 in set1 - assert 4 in set1 - assert 5 not in set1 - - assert 1 not in set2 - assert 2 not in set2 - assert 3 in set2 - assert 4 in set2 - assert 5 in set2 - - set1.add(5) - assert 5 in set1 - - set1.discard(5) - assert 5 not in set1 - - # Try again while key is not in - set1.discard(5) - assert 5 not in set1 - - assert [1, 2, 3, 4] == list(set1) - assert [4, 3, 2, 1] == list(reversed(set1)) - - assert 1 == set1.pop(False) - assert [2, 3, 4] == list(set1) - - assert 4 == set1.pop() - assert [2, 3] == list(set1) - - assert 'OrderedSet()' == str(util.OrderedSet()) - assert 'OrderedSet([2, 3])' == str(set1) - - assert set1 == util.OrderedSet([2, 3]) - assert set1 != util.OrderedSet([3, 2]) - assert set1 == set([2, 3]) - assert set1 == {3, 2} - assert set1 == [2, 3] - assert set1 == [3, 2] - assert set1 != {2} - - set3 = util.OrderedSet(set1) - set3.update(set2) - - assert [3, 4, 5, 2] == set3 - assert [3, 4, 5, 2] == set1 | set2 - assert [3] == set1 & set2 - assert [2] == set1 - set2 - - set1.update([1, 2], [5, 6]) - assert [2, 3, 1, 5, 6] == set1 - def test_throttle(self): """Test the add cooldown decorator.""" calls1 = [] From 542f024356fc1570ba03289fc6b1bd5a5beb8a83 Mon Sep 17 00:00:00 2001 From: Markus Ressel Date: Thu, 7 Feb 2019 23:21:41 +0100 Subject: [PATCH 096/242] XS1 component (#19115) * added xs1 main component added implementations for switch, sensor, climate and binary_sensor * updated code fixed styling added comments removed binary_sensor (wasn't working) * ran "gen_requirements_all.py" script * fixed linting issues * added config options for port and ssl small fixes * use already defined config constants instead of defining new ones * avoid passing in hass to the entity * use async keyword and proper asyncio calls limit updates with a global lock to prevent overwhelming the gateway with concurrent requests change info logger calls to debug * update dependency * removed unneeded constant * fix lint issues * updated requirements * removed unused imports * fixed some flake8 errors * fixed some flake8 errors * changed imports to absolute paths * fixed some lint errors * fixed some lint errors * fix update of attached sensor * reordered imports added config defaults check if platform is available changed docstring * lint fix * review fixes * isort * import fix * review fix * review fix * review fix * removed unused imports * lint fix * lint fix * climate fix exclude sensors that will be used in climate component from default sensor category * .coveragerc fix * lint fix * moved platform to it's own package --- .coveragerc | 1 + homeassistant/components/xs1/__init__.py | 119 +++++++++++++++++++++++ homeassistant/components/xs1/climate.py | 109 +++++++++++++++++++++ homeassistant/components/xs1/sensor.py | 57 +++++++++++ homeassistant/components/xs1/switch.py | 52 ++++++++++ requirements_all.txt | 3 + 6 files changed, 341 insertions(+) create mode 100644 homeassistant/components/xs1/__init__.py create mode 100644 homeassistant/components/xs1/climate.py create mode 100644 homeassistant/components/xs1/sensor.py create mode 100644 homeassistant/components/xs1/switch.py diff --git a/.coveragerc b/.coveragerc index 557567e7aaf..e0f797c4d04 100644 --- a/.coveragerc +++ b/.coveragerc @@ -655,6 +655,7 @@ omit = homeassistant/components/wirelesstag/* homeassistant/components/xiaomi_aqara/* homeassistant/components/xiaomi_miio/* + homeassistant/components/xs1/* homeassistant/components/zabbix/* homeassistant/components/zeroconf/* homeassistant/components/zha/__init__.py diff --git a/homeassistant/components/xs1/__init__.py b/homeassistant/components/xs1/__init__.py new file mode 100644 index 00000000000..14656737f5c --- /dev/null +++ b/homeassistant/components/xs1/__init__.py @@ -0,0 +1,119 @@ +""" +Support for the EZcontrol XS1 gateway. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/xs1/ +""" + +import asyncio +from functools import partial +import logging + +import voluptuous as vol + +from homeassistant.const import ( + CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_USERNAME) +from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity + +REQUIREMENTS = ['xs1-api-client==2.3.5'] + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = 'xs1' +ACTUATORS = 'actuators' +SENSORS = 'sensors' + +# define configuration parameters +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=80): cv.string, + vol.Optional(CONF_SSL, default=False): cv.boolean, + vol.Optional(CONF_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD): cv.string + }), +}, extra=vol.ALLOW_EXTRA) + +XS1_COMPONENTS = [ + 'switch', + 'sensor', + 'climate' +] + +# Lock used to limit the amount of concurrent update requests +# as the XS1 Gateway can only handle a very +# small amount of concurrent requests +UPDATE_LOCK = asyncio.Lock() + + +def _create_controller_api(host, port, ssl, user, password): + """Create an api instance to use for communication.""" + import xs1_api_client + + try: + return xs1_api_client.XS1( + host=host, + port=port, + ssl=ssl, + user=user, + password=password) + except ConnectionError as error: + _LOGGER.error("Failed to create XS1 api client " + "because of a connection error: %s", error) + return None + + +async def async_setup(hass, config): + """Set up XS1 Component.""" + _LOGGER.debug("Initializing XS1") + + host = config[DOMAIN][CONF_HOST] + port = config[DOMAIN][CONF_PORT] + ssl = config[DOMAIN][CONF_SSL] + user = config[DOMAIN].get(CONF_USERNAME) + password = config[DOMAIN].get(CONF_PASSWORD) + + # initialize XS1 API + xs1 = await hass.async_add_executor_job( + partial(_create_controller_api, + host, port, ssl, user, password)) + if xs1 is None: + return False + + _LOGGER.debug( + "Establishing connection to XS1 gateway and retrieving data...") + + hass.data[DOMAIN] = {} + + actuators = await hass.async_add_executor_job( + partial(xs1.get_all_actuators, enabled=True)) + sensors = await hass.async_add_executor_job( + partial(xs1.get_all_sensors, enabled=True)) + + hass.data[DOMAIN][ACTUATORS] = actuators + hass.data[DOMAIN][SENSORS] = sensors + + _LOGGER.debug("Loading components for XS1 platform...") + # load components for supported devices + for component in XS1_COMPONENTS: + hass.async_create_task( + discovery.async_load_platform( + hass, component, DOMAIN, {}, config)) + + return True + + +class XS1DeviceEntity(Entity): + """Representation of a base XS1 device.""" + + def __init__(self, device): + """Initialize the XS1 device.""" + self.device = device + + async def async_update(self): + """Retrieve latest device state.""" + async with UPDATE_LOCK: + await self.hass.async_add_executor_job( + partial(self.device.update)) diff --git a/homeassistant/components/xs1/climate.py b/homeassistant/components/xs1/climate.py new file mode 100644 index 00000000000..0417d3bcde0 --- /dev/null +++ b/homeassistant/components/xs1/climate.py @@ -0,0 +1,109 @@ +""" +Support for XS1 climate devices. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/climate.xs1/ +""" +from functools import partial +import logging + +from homeassistant.components.climate import ( + ATTR_TEMPERATURE, ClimateDevice, SUPPORT_TARGET_TEMPERATURE) +from homeassistant.components.xs1 import ( + ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity) + +DEPENDENCIES = ['xs1'] +_LOGGER = logging.getLogger(__name__) + +MIN_TEMP = 8 +MAX_TEMP = 25 + + +async def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): + """Set up the XS1 thermostat platform.""" + from xs1_api_client.api_constants import ActuatorType + + actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS] + sensors = hass.data[COMPONENT_DOMAIN][SENSORS] + + thermostat_entities = [] + for actuator in actuators: + if actuator.type() == ActuatorType.TEMPERATURE: + # Search for a matching sensor (by name) + actuator_name = actuator.name() + + matching_sensor = None + for sensor in sensors: + if actuator_name in sensor.name(): + matching_sensor = sensor + + break + + thermostat_entities.append( + XS1ThermostatEntity(actuator, matching_sensor)) + + async_add_entities(thermostat_entities) + + +class XS1ThermostatEntity(XS1DeviceEntity, ClimateDevice): + """Representation of a XS1 thermostat.""" + + def __init__(self, device, sensor): + """Initialize the actuator.""" + super().__init__(device) + self.sensor = sensor + + @property + def name(self): + """Return the name of the device if any.""" + return self.device.name() + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORT_TARGET_TEMPERATURE + + @property + def current_temperature(self): + """Return the current temperature.""" + if self.sensor is None: + return None + + return self.sensor.value() + + @property + def temperature_unit(self): + """Return the unit of measurement used by the platform.""" + return self.device.unit() + + @property + def target_temperature(self): + """Return the current target temperature.""" + return self.device.new_value() + + @property + def min_temp(self): + """Return the minimum temperature.""" + return MIN_TEMP + + @property + def max_temp(self): + """Return the maximum temperature.""" + return MAX_TEMP + + def set_temperature(self, **kwargs): + """Set new target temperature.""" + temp = kwargs.get(ATTR_TEMPERATURE) + + self.device.set_value(temp) + + if self.sensor is not None: + self.schedule_update_ha_state() + + async def async_update(self): + """Also update the sensor when available.""" + await super().async_update() + if self.sensor is not None: + await self.hass.async_add_executor_job( + partial(self.sensor.update)) diff --git a/homeassistant/components/xs1/sensor.py b/homeassistant/components/xs1/sensor.py new file mode 100644 index 00000000000..b4d9bfe5ff9 --- /dev/null +++ b/homeassistant/components/xs1/sensor.py @@ -0,0 +1,57 @@ +""" +Support for XS1 sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.xs1/ +""" + +import logging + +from homeassistant.components.xs1 import ( + ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity) +from homeassistant.helpers.entity import Entity + +DEPENDENCIES = ['xs1'] +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): + """Set up the XS1 sensor platform.""" + from xs1_api_client.api_constants import ActuatorType + + sensors = hass.data[COMPONENT_DOMAIN][SENSORS] + actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS] + + sensor_entities = [] + for sensor in sensors: + belongs_to_climate_actuator = False + for actuator in actuators: + if actuator.type() == ActuatorType.TEMPERATURE and \ + actuator.name() in sensor.name(): + belongs_to_climate_actuator = True + break + + if not belongs_to_climate_actuator: + sensor_entities.append(XS1Sensor(sensor)) + + async_add_entities(sensor_entities) + + +class XS1Sensor(XS1DeviceEntity, Entity): + """Representation of a Sensor.""" + + @property + def name(self): + """Return the name of the sensor.""" + return self.device.name() + + @property + def state(self): + """Return the state of the sensor.""" + return self.device.value() + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self.device.unit() diff --git a/homeassistant/components/xs1/switch.py b/homeassistant/components/xs1/switch.py new file mode 100644 index 00000000000..e6855865845 --- /dev/null +++ b/homeassistant/components/xs1/switch.py @@ -0,0 +1,52 @@ +""" +Support for XS1 switches. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.xs1/ +""" +import logging + +from homeassistant.components.xs1 import ( + ACTUATORS, DOMAIN as COMPONENT_DOMAIN, XS1DeviceEntity) +from homeassistant.helpers.entity import ToggleEntity + +DEPENDENCIES = ['xs1'] +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): + """Set up the XS1 switch platform.""" + from xs1_api_client.api_constants import ActuatorType + + actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS] + + switch_entities = [] + for actuator in actuators: + if (actuator.type() == ActuatorType.SWITCH) or \ + (actuator.type() == ActuatorType.DIMMER): + switch_entities.append(XS1SwitchEntity(actuator)) + + async_add_entities(switch_entities) + + +class XS1SwitchEntity(XS1DeviceEntity, ToggleEntity): + """Representation of a XS1 switch actuator.""" + + @property + def name(self): + """Return the name of the device if any.""" + return self.device.name() + + @property + def is_on(self): + """Return true if switch is on.""" + return self.device.value() == 100 + + def turn_on(self, **kwargs): + """Turn the device on.""" + self.device.turn_on() + + def turn_off(self, **kwargs): + """Turn the device off.""" + self.device.turn_off() diff --git a/requirements_all.txt b/requirements_all.txt index 7f618e0920a..452275bfcdc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1754,6 +1754,9 @@ xknx==0.9.3 # homeassistant.components.sensor.zestimate xmltodict==0.11.0 +# homeassistant.components.xs1 +xs1-api-client==2.3.5 + # homeassistant.components.sensor.yweather # homeassistant.components.weather.yweather yahooweather==0.10 From 32f2221b22785212ca0157dcbcc7afa7a248e65f Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Thu, 7 Feb 2019 18:09:47 -0500 Subject: [PATCH 097/242] Fix zha light bugs (#20825) --- homeassistant/components/zha/core/listeners.py | 2 ++ homeassistant/components/zha/light.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zha/core/listeners.py b/homeassistant/components/zha/core/listeners.py index 035c752c42b..3605f4a9885 100644 --- a/homeassistant/components/zha/core/listeners.py +++ b/homeassistant/components/zha/core/listeners.py @@ -69,6 +69,8 @@ def decorate_command(listener, command): "{}: {}".format("with args", args), "{}: {}".format("with kwargs", kwds), "{}: {}".format("and result", result)) + if isinstance(result, bool): + return result return result[1] is Status.SUCCESS except DeliveryError: _LOGGER.debug("%s: command failed: %s", listener.unique_id, diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index 1d1b4c5f921..09f1812cd76 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -113,7 +113,7 @@ class Light(ZhaEntity, light.Light): """Set the brightness of this light between 0..255.""" value = max(0, min(255, value)) self._brightness = value - self.async_set_state(value) + self.async_schedule_update_ha_state() @property def hs_color(self): From 5f76628665e9c122d59fed8763d77e09d2e12b3d Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 8 Feb 2019 00:25:30 +0100 Subject: [PATCH 098/242] Add MVP person component (#20290) * Add person component * Required first name. * Optional last name and user id. * Optionally track device trackers. Last device tracker state change will set state. * Set device tracker state entity_id as source attribute. * Set coordinates of device tracker state as state attributes. * Restore state. * Parse restored state too * Clean up * Add missing property decorator * Validate source entities as device trackers * Only use name instead of first and last name * Add user_id validation * Add unique_id * Remove not needed properties * Uniform docstrings * Fail component setup if no valid entities * Add tests * Add id and use that for unique_id * Clean up --- homeassistant/components/person/__init__.py | 145 +++++++++++++++ tests/components/person/__init__.py | 1 + tests/components/person/test_init.py | 186 ++++++++++++++++++++ 3 files changed, 332 insertions(+) create mode 100644 homeassistant/components/person/__init__.py create mode 100644 tests/components/person/__init__.py create mode 100644 tests/components/person/test_init.py diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py new file mode 100644 index 00000000000..2e8b10c457d --- /dev/null +++ b/homeassistant/components/person/__init__.py @@ -0,0 +1,145 @@ +""" +Support for tracking people. + +For more details about this component, please refer to the documentation. +https://home-assistant.io/components/person/ +""" +import logging + +import voluptuous as vol + +from homeassistant.components.device_tracker import ( + DOMAIN as DEVICE_TRACKER_DOMAIN) +from homeassistant.const import ( + ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_ID, CONF_NAME) +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.event import async_track_state_change +from homeassistant.helpers.restore_state import RestoreEntity + +_LOGGER = logging.getLogger(__name__) +ATTR_SOURCE = 'source' +ATTR_USER_ID = 'user_id' +CONF_DEVICE_TRACKERS = 'device_trackers' +CONF_USER_ID = 'user_id' +DOMAIN = 'person' + +PERSON_SCHEMA = vol.Schema({ + vol.Required(CONF_ID): cv.string, + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_USER_ID): cv.string, + vol.Optional(CONF_DEVICE_TRACKERS, default=[]): vol.All( + cv.ensure_list, cv.entities_domain(DEVICE_TRACKER_DOMAIN)), +}) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.All(cv.ensure_list, [PERSON_SCHEMA]) +}, extra=vol.ALLOW_EXTRA) + + +async def async_setup(hass, config): + """Set up the person component.""" + component = EntityComponent(_LOGGER, DOMAIN, hass) + conf = config[DOMAIN] + entities = [] + for person_conf in conf: + user_id = person_conf.get(CONF_USER_ID) + if (user_id is not None + and await hass.auth.async_get_user(user_id) is None): + _LOGGER.error( + "Invalid user_id detected for person %s", + person_conf[CONF_NAME]) + continue + entities.append(Person(person_conf, user_id)) + + if not entities: + _LOGGER.error("No persons could be set up") + return False + + await component.async_add_entities(entities) + + return True + + +class Person(RestoreEntity): + """Represent a tracked person.""" + + def __init__(self, config, user_id): + """Set up person.""" + self._id = config[CONF_ID] + self._latitude = None + self._longitude = None + self._name = config[CONF_NAME] + self._source = None + self._state = None + self._trackers = config.get(CONF_DEVICE_TRACKERS) + self._user_id = user_id + + @property + def name(self): + """Return the name of the entity.""" + return self._name + + @property + def should_poll(self): + """Return True if entity has to be polled for state. + + False if entity pushes its state to HA. + """ + return False + + @property + def state(self): + """Return the state of the person.""" + return self._state + + @property + def state_attributes(self): + """Return the state attributes of the person.""" + data = {} + data[ATTR_ID] = self._id + if self._latitude is not None: + data[ATTR_LATITUDE] = round(self._latitude, 5) + if self._longitude is not None: + data[ATTR_LONGITUDE] = round(self._longitude, 5) + if self._source is not None: + data[ATTR_SOURCE] = self._source + if self._user_id is not None: + data[ATTR_USER_ID] = self._user_id + return data + + @property + def unique_id(self): + """Return a unique ID for the person.""" + return self._id + + async def async_added_to_hass(self): + """Register device trackers.""" + await super().async_added_to_hass() + state = await self.async_get_last_state() + if state: + self._parse_source_state(state) + + if not self._trackers: + return + + @callback + def async_handle_tracker_update(entity, old_state, new_state): + """Handle the device tracker state changes.""" + self._parse_source_state(new_state) + self.async_schedule_update_ha_state() + + _LOGGER.debug( + "Subscribe to device trackers for %s", self.entity_id) + + for tracker in self._trackers: + async_track_state_change( + self.hass, tracker, async_handle_tracker_update) + + def _parse_source_state(self, state): + """Parse source state and set person attributes.""" + self._state = state.state + self._source = state.entity_id + self._latitude = state.attributes.get(ATTR_LATITUDE) + self._longitude = state.attributes.get(ATTR_LONGITUDE) diff --git a/tests/components/person/__init__.py b/tests/components/person/__init__.py new file mode 100644 index 00000000000..217189a78a9 --- /dev/null +++ b/tests/components/person/__init__.py @@ -0,0 +1 @@ +"""The tests for the person component.""" diff --git a/tests/components/person/test_init.py b/tests/components/person/test_init.py new file mode 100644 index 00000000000..4b10846ee3c --- /dev/null +++ b/tests/components/person/test_init.py @@ -0,0 +1,186 @@ +"""The tests for the person component.""" +from homeassistant.components.person import ATTR_SOURCE, ATTR_USER_ID, DOMAIN +from homeassistant.const import ( + ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, STATE_UNKNOWN) +from homeassistant.core import CoreState, State +from homeassistant.setup import async_setup_component + +from tests.common import mock_component, mock_restore_cache + +DEVICE_TRACKER = 'device_tracker.test_tracker' +DEVICE_TRACKER_2 = 'device_tracker.test_tracker_2' + + +async def test_minimal_setup(hass): + """Test minimal config with only name.""" + config = {DOMAIN: {'id': '1234', 'name': 'test person'}} + assert await async_setup_component(hass, DOMAIN, config) + + state = hass.states.get('person.test_person') + assert state.state == STATE_UNKNOWN + assert state.attributes.get(ATTR_LATITUDE) is None + assert state.attributes.get(ATTR_LONGITUDE) is None + assert state.attributes.get(ATTR_SOURCE) is None + assert state.attributes.get(ATTR_USER_ID) is None + + +async def test_setup_no_id(hass): + """Test config with no id.""" + config = {DOMAIN: {'name': 'test user'}} + assert not await async_setup_component(hass, DOMAIN, config) + + +async def test_setup_no_name(hass): + """Test config with no name.""" + config = {DOMAIN: {'id': '1234'}} + assert not await async_setup_component(hass, DOMAIN, config) + + +async def test_setup_user_id(hass, hass_owner_user): + """Test config with user id.""" + user_id = hass_owner_user.id + config = { + DOMAIN: {'id': '1234', 'name': 'test person', 'user_id': user_id}} + assert await async_setup_component(hass, DOMAIN, config) + + state = hass.states.get('person.test_person') + assert state.state == STATE_UNKNOWN + assert state.attributes.get(ATTR_ID) == '1234' + assert state.attributes.get(ATTR_LATITUDE) is None + assert state.attributes.get(ATTR_LONGITUDE) is None + assert state.attributes.get(ATTR_SOURCE) is None + assert state.attributes.get(ATTR_USER_ID) == user_id + + +async def test_setup_invalid_user_id(hass): + """Test config with invalid user id.""" + config = { + DOMAIN: { + 'id': '1234', 'name': 'test bad user', 'user_id': 'bad_user_id'}} + assert not await async_setup_component(hass, DOMAIN, config) + + +async def test_valid_invalid_user_ids(hass, hass_owner_user): + """Test a person with valid user id and a person with invalid user id .""" + user_id = hass_owner_user.id + config = {DOMAIN: [ + {'id': '1234', 'name': 'test valid user', 'user_id': user_id}, + {'id': '5678', 'name': 'test bad user', 'user_id': 'bad_user_id'}]} + assert await async_setup_component(hass, DOMAIN, config) + + state = hass.states.get('person.test_valid_user') + assert state.state == STATE_UNKNOWN + assert state.attributes.get(ATTR_ID) == '1234' + assert state.attributes.get(ATTR_LATITUDE) is None + assert state.attributes.get(ATTR_LONGITUDE) is None + assert state.attributes.get(ATTR_SOURCE) is None + assert state.attributes.get(ATTR_USER_ID) == user_id + state = hass.states.get('person.test_bad_user') + assert state is None + + +async def test_setup_tracker(hass, hass_owner_user): + """Test set up person with one device tracker.""" + user_id = hass_owner_user.id + config = {DOMAIN: { + 'id': '1234', 'name': 'tracked person', 'user_id': user_id, + 'device_trackers': DEVICE_TRACKER}} + assert await async_setup_component(hass, DOMAIN, config) + + state = hass.states.get('person.tracked_person') + assert state.state == STATE_UNKNOWN + assert state.attributes.get(ATTR_ID) == '1234' + assert state.attributes.get(ATTR_LATITUDE) is None + assert state.attributes.get(ATTR_LONGITUDE) is None + assert state.attributes.get(ATTR_SOURCE) is None + assert state.attributes.get(ATTR_USER_ID) == user_id + + hass.states.async_set(DEVICE_TRACKER, 'home') + await hass.async_block_till_done() + + state = hass.states.get('person.tracked_person') + assert state.state == 'home' + assert state.attributes.get(ATTR_ID) == '1234' + assert state.attributes.get(ATTR_LATITUDE) is None + assert state.attributes.get(ATTR_LONGITUDE) is None + assert state.attributes.get(ATTR_SOURCE) == DEVICE_TRACKER + assert state.attributes.get(ATTR_USER_ID) == user_id + + hass.states.async_set( + DEVICE_TRACKER, 'not_home', + {ATTR_LATITUDE: 10.123456, ATTR_LONGITUDE: 11.123456}) + await hass.async_block_till_done() + + state = hass.states.get('person.tracked_person') + assert state.state == 'not_home' + assert state.attributes.get(ATTR_ID) == '1234' + assert state.attributes.get(ATTR_LATITUDE) == 10.12346 + assert state.attributes.get(ATTR_LONGITUDE) == 11.12346 + assert state.attributes.get(ATTR_SOURCE) == DEVICE_TRACKER + assert state.attributes.get(ATTR_USER_ID) == user_id + + +async def test_setup_two_trackers(hass, hass_owner_user): + """Test set up person with two device trackers.""" + user_id = hass_owner_user.id + config = {DOMAIN: { + 'id': '1234', 'name': 'tracked person', 'user_id': user_id, + 'device_trackers': [DEVICE_TRACKER, DEVICE_TRACKER_2]}} + assert await async_setup_component(hass, DOMAIN, config) + + state = hass.states.get('person.tracked_person') + assert state.state == STATE_UNKNOWN + assert state.attributes.get(ATTR_ID) == '1234' + assert state.attributes.get(ATTR_LATITUDE) is None + assert state.attributes.get(ATTR_LONGITUDE) is None + assert state.attributes.get(ATTR_SOURCE) is None + assert state.attributes.get(ATTR_USER_ID) == user_id + + hass.states.async_set(DEVICE_TRACKER, 'home') + await hass.async_block_till_done() + + state = hass.states.get('person.tracked_person') + assert state.state == 'home' + assert state.attributes.get(ATTR_ID) == '1234' + assert state.attributes.get(ATTR_LATITUDE) is None + assert state.attributes.get(ATTR_LONGITUDE) is None + assert state.attributes.get(ATTR_SOURCE) == DEVICE_TRACKER + assert state.attributes.get(ATTR_USER_ID) == user_id + + hass.states.async_set( + DEVICE_TRACKER_2, 'not_home', + {ATTR_LATITUDE: 12.123456, ATTR_LONGITUDE: 13.123456}) + await hass.async_block_till_done() + + state = hass.states.get('person.tracked_person') + assert state.state == 'not_home' + assert state.attributes.get(ATTR_ID) == '1234' + assert state.attributes.get(ATTR_LATITUDE) == 12.12346 + assert state.attributes.get(ATTR_LONGITUDE) == 13.12346 + assert state.attributes.get(ATTR_SOURCE) == DEVICE_TRACKER_2 + assert state.attributes.get(ATTR_USER_ID) == user_id + + +async def test_restore_home_state(hass, hass_owner_user): + """Test that the state is restored for a person on startup.""" + user_id = hass_owner_user.id + attrs = { + ATTR_ID: '1234', ATTR_LATITUDE: 10.12346, ATTR_LONGITUDE: 11.12346, + ATTR_SOURCE: DEVICE_TRACKER, ATTR_USER_ID: user_id} + state = State('person.tracked_person', 'home', attrs) + mock_restore_cache(hass, (state, )) + hass.state = CoreState.starting + mock_component(hass, 'recorder') + config = {DOMAIN: { + 'id': '1234', 'name': 'tracked person', 'user_id': user_id, + 'device_trackers': DEVICE_TRACKER}} + assert await async_setup_component(hass, DOMAIN, config) + + state = hass.states.get('person.tracked_person') + assert state.state == 'home' + assert state.attributes.get(ATTR_ID) == '1234' + assert state.attributes.get(ATTR_LATITUDE) == 10.12346 + assert state.attributes.get(ATTR_LONGITUDE) == 11.12346 + # When restoring state the entity_id of the person will be used as source. + assert state.attributes.get(ATTR_SOURCE) == 'person.tracked_person' + assert state.attributes.get(ATTR_USER_ID) == user_id From 49bab574b9981b381c8165486fcfac2188f8234f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 7 Feb 2019 17:27:31 -0800 Subject: [PATCH 099/242] Clean up Z-Wave pt2 (#20842) --- tests/components/zwave/test_binary_sensor.py | 21 +++-- tests/components/zwave/test_climate.py | 16 ++-- tests/components/zwave/test_cover.py | 46 +++++------ tests/components/zwave/test_fan.py | 12 +-- tests/components/zwave/test_light.py | 71 +++++++++-------- tests/components/zwave/test_lock.py | 81 ++++++++++---------- tests/components/zwave/test_sensor.py | 27 ++++--- tests/components/zwave/test_switch.py | 12 +-- 8 files changed, 141 insertions(+), 145 deletions(-) diff --git a/tests/components/zwave/test_binary_sensor.py b/tests/components/zwave/test_binary_sensor.py index 786afb1b9ce..ee68971bc3e 100644 --- a/tests/components/zwave/test_binary_sensor.py +++ b/tests/components/zwave/test_binary_sensor.py @@ -3,8 +3,7 @@ import datetime from unittest.mock import patch -from homeassistant.components.zwave import const -import homeassistant.components.zwave.binary_sensor as zwave +from homeassistant.components.zwave import const, binary_sensor from tests.mock.zwave import ( MockNode, MockValue, MockEntityValues, value_changed) @@ -16,7 +15,7 @@ def test_get_device_detects_none(mock_openzwave): value = MockValue(data=False, node=node) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = binary_sensor.get_device(node=node, values=values, node_config={}) assert device is None @@ -27,8 +26,8 @@ def test_get_device_detects_trigger_sensor(mock_openzwave): value = MockValue(data=False, node=node) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) - assert isinstance(device, zwave.ZWaveTriggerSensor) + device = binary_sensor.get_device(node=node, values=values, node_config={}) + assert isinstance(device, binary_sensor.ZWaveTriggerSensor) assert device.device_class == "motion" @@ -39,8 +38,8 @@ def test_get_device_detects_workaround_sensor(mock_openzwave): command_class=const.COMMAND_CLASS_SENSOR_ALARM) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) - assert isinstance(device, zwave.ZWaveBinarySensor) + device = binary_sensor.get_device(node=node, values=values, node_config={}) + assert isinstance(device, binary_sensor.ZWaveBinarySensor) def test_get_device_detects_sensor(mock_openzwave): @@ -50,8 +49,8 @@ def test_get_device_detects_sensor(mock_openzwave): command_class=const.COMMAND_CLASS_SENSOR_BINARY) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) - assert isinstance(device, zwave.ZWaveBinarySensor) + device = binary_sensor.get_device(node=node, values=values, node_config={}) + assert isinstance(device, binary_sensor.ZWaveBinarySensor) def test_binary_sensor_value_changed(mock_openzwave): @@ -60,7 +59,7 @@ def test_binary_sensor_value_changed(mock_openzwave): value = MockValue(data=False, node=node, command_class=const.COMMAND_CLASS_SENSOR_BINARY) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = binary_sensor.get_device(node=node, values=values, node_config={}) assert not device.is_on @@ -77,7 +76,7 @@ async def test_trigger_sensor_value_changed(hass, mock_openzwave): value = MockValue(data=False, node=node) value_off_delay = MockValue(data=15, node=node) values = MockEntityValues(primary=value, off_delay=value_off_delay) - device = zwave.get_device(node=node, values=values, node_config={}) + device = binary_sensor.get_device(node=node, values=values, node_config={}) assert not device.is_on diff --git a/tests/components/zwave/test_climate.py b/tests/components/zwave/test_climate.py index e8be6d7382b..9a9ed41381f 100644 --- a/tests/components/zwave/test_climate.py +++ b/tests/components/zwave/test_climate.py @@ -2,7 +2,7 @@ import pytest from homeassistant.components.climate import STATE_COOL, STATE_HEAT -import homeassistant.components.zwave.climate as zwave +from homeassistant.components.zwave import climate from homeassistant.const import ( STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE) @@ -22,7 +22,7 @@ def device(hass, mock_openzwave): operating_state=MockValue(data=6, node=node), fan_state=MockValue(data=7, node=node), ) - device = zwave.get_device(hass, node=node, values=values, node_config={}) + device = climate.get_device(hass, node=node, values=values, node_config={}) yield device @@ -42,7 +42,7 @@ def device_zxt_120(hass, mock_openzwave): zxt_120_swing_mode=MockValue( data='test3', data_items=[6, 7, 8], node=node), ) - device = zwave.get_device(hass, node=node, values=values, node_config={}) + device = climate.get_device(hass, node=node, values=values, node_config={}) yield device @@ -60,7 +60,7 @@ def device_mapping(hass, mock_openzwave): operating_state=MockValue(data=6, node=node), fan_state=MockValue(data=7, node=node), ) - device = zwave.get_device(hass, node=node, values=values, node_config={}) + device = climate.get_device(hass, node=node, values=values, node_config={}) yield device @@ -196,15 +196,15 @@ def test_fan_mode_value_changed(device): def test_operating_state_value_changed(device): """Test values changed for climate device.""" - assert device.device_state_attributes[zwave.ATTR_OPERATING_STATE] == 6 + assert device.device_state_attributes[climate.ATTR_OPERATING_STATE] == 6 device.values.operating_state.data = 8 value_changed(device.values.operating_state) - assert device.device_state_attributes[zwave.ATTR_OPERATING_STATE] == 8 + assert device.device_state_attributes[climate.ATTR_OPERATING_STATE] == 8 def test_fan_state_value_changed(device): """Test values changed for climate device.""" - assert device.device_state_attributes[zwave.ATTR_FAN_STATE] == 7 + assert device.device_state_attributes[climate.ATTR_FAN_STATE] == 7 device.values.fan_state.data = 9 value_changed(device.values.fan_state) - assert device.device_state_attributes[zwave.ATTR_FAN_STATE] == 9 + assert device.device_state_attributes[climate.ATTR_FAN_STATE] == 9 diff --git a/tests/components/zwave/test_cover.py b/tests/components/zwave/test_cover.py index cf3bcdf993b..ce34111c612 100644 --- a/tests/components/zwave/test_cover.py +++ b/tests/components/zwave/test_cover.py @@ -2,8 +2,8 @@ from unittest.mock import MagicMock from homeassistant.components.cover import SUPPORT_OPEN, SUPPORT_CLOSE -import homeassistant.components.zwave.cover as zwave -from homeassistant.components.zwave import const +from homeassistant.components.zwave import ( + const, cover, CONF_INVERT_OPENCLOSE_BUTTONS) from tests.mock.zwave import ( MockNode, MockValue, MockEntityValues, value_changed) @@ -15,22 +15,22 @@ def test_get_device_detects_none(hass, mock_openzwave): value = MockValue(data=0, node=node) values = MockEntityValues(primary=value, node=node) - device = zwave.get_device(hass=hass, node=node, values=values, + device = cover.get_device(hass=hass, node=node, values=values, node_config={}) assert device is None def test_get_device_detects_rollershutter(hass, mock_openzwave): """Test device returns rollershutter.""" - hass.data[zwave.zwave.DATA_NETWORK] = MagicMock() + hass.data[const.DATA_NETWORK] = MagicMock() node = MockNode() value = MockValue(data=0, node=node, command_class=const.COMMAND_CLASS_SWITCH_MULTILEVEL) values = MockEntityValues(primary=value, open=None, close=None, node=node) - device = zwave.get_device(hass=hass, node=node, values=values, + device = cover.get_device(hass=hass, node=node, values=values, node_config={}) - assert isinstance(device, zwave.ZwaveRollershutter) + assert isinstance(device, cover.ZwaveRollershutter) def test_get_device_detects_garagedoor_switch(hass, mock_openzwave): @@ -40,9 +40,9 @@ def test_get_device_detects_garagedoor_switch(hass, mock_openzwave): command_class=const.COMMAND_CLASS_SWITCH_BINARY) values = MockEntityValues(primary=value, node=node) - device = zwave.get_device(hass=hass, node=node, values=values, + device = cover.get_device(hass=hass, node=node, values=values, node_config={}) - assert isinstance(device, zwave.ZwaveGarageDoorSwitch) + assert isinstance(device, cover.ZwaveGarageDoorSwitch) assert device.device_class == "garage" assert device.supported_features == SUPPORT_OPEN | SUPPORT_CLOSE @@ -54,21 +54,21 @@ def test_get_device_detects_garagedoor_barrier(hass, mock_openzwave): command_class=const.COMMAND_CLASS_BARRIER_OPERATOR) values = MockEntityValues(primary=value, node=node) - device = zwave.get_device(hass=hass, node=node, values=values, + device = cover.get_device(hass=hass, node=node, values=values, node_config={}) - assert isinstance(device, zwave.ZwaveGarageDoorBarrier) + assert isinstance(device, cover.ZwaveGarageDoorBarrier) assert device.device_class == "garage" assert device.supported_features == SUPPORT_OPEN | SUPPORT_CLOSE def test_roller_no_position_workaround(hass, mock_openzwave): """Test position changed.""" - hass.data[zwave.zwave.DATA_NETWORK] = MagicMock() + hass.data[const.DATA_NETWORK] = MagicMock() node = MockNode(manufacturer_id='0047', product_type='5a52') value = MockValue(data=45, node=node, command_class=const.COMMAND_CLASS_SWITCH_MULTILEVEL) values = MockEntityValues(primary=value, open=None, close=None, node=node) - device = zwave.get_device(hass=hass, node=node, values=values, + device = cover.get_device(hass=hass, node=node, values=values, node_config={}) assert device.current_cover_position is None @@ -76,12 +76,12 @@ def test_roller_no_position_workaround(hass, mock_openzwave): def test_roller_value_changed(hass, mock_openzwave): """Test position changed.""" - hass.data[zwave.zwave.DATA_NETWORK] = MagicMock() + hass.data[const.DATA_NETWORK] = MagicMock() node = MockNode() value = MockValue(data=None, node=node, command_class=const.COMMAND_CLASS_SWITCH_MULTILEVEL) values = MockEntityValues(primary=value, open=None, close=None, node=node) - device = zwave.get_device(hass=hass, node=node, values=values, + device = cover.get_device(hass=hass, node=node, values=values, node_config={}) assert device.current_cover_position is None @@ -108,7 +108,7 @@ def test_roller_value_changed(hass, mock_openzwave): def test_roller_commands(hass, mock_openzwave): """Test position changed.""" - mock_network = hass.data[zwave.zwave.DATA_NETWORK] = MagicMock() + mock_network = hass.data[const.DATA_NETWORK] = MagicMock() node = MockNode() value = MockValue(data=50, node=node, command_class=const.COMMAND_CLASS_SWITCH_MULTILEVEL) @@ -116,7 +116,7 @@ def test_roller_commands(hass, mock_openzwave): close_value = MockValue(data=False, node=node) values = MockEntityValues(primary=value, open=open_value, close=close_value, node=node) - device = zwave.get_device(hass=hass, node=node, values=values, + device = cover.get_device(hass=hass, node=node, values=values, node_config={}) device.set_cover_position(position=25) @@ -143,7 +143,7 @@ def test_roller_commands(hass, mock_openzwave): def test_roller_reverse_open_close(hass, mock_openzwave): """Test position changed.""" - mock_network = hass.data[zwave.zwave.DATA_NETWORK] = MagicMock() + mock_network = hass.data[const.DATA_NETWORK] = MagicMock() node = MockNode() value = MockValue(data=50, node=node, command_class=const.COMMAND_CLASS_SWITCH_MULTILEVEL) @@ -151,11 +151,11 @@ def test_roller_reverse_open_close(hass, mock_openzwave): close_value = MockValue(data=False, node=node) values = MockEntityValues(primary=value, open=open_value, close=close_value, node=node) - device = zwave.get_device( + device = cover.get_device( hass=hass, node=node, values=values, - node_config={zwave.zwave.CONF_INVERT_OPENCLOSE_BUTTONS: True}) + node_config={CONF_INVERT_OPENCLOSE_BUTTONS: True}) device.open_cover() assert mock_network.manager.pressButton.called @@ -179,7 +179,7 @@ def test_switch_garage_value_changed(hass, mock_openzwave): value = MockValue(data=False, node=node, command_class=const.COMMAND_CLASS_SWITCH_BINARY) values = MockEntityValues(primary=value, node=node) - device = zwave.get_device(hass=hass, node=node, values=values, + device = cover.get_device(hass=hass, node=node, values=values, node_config={}) assert device.is_closed @@ -195,7 +195,7 @@ def test_switch_garage_commands(hass, mock_openzwave): value = MockValue(data=False, node=node, command_class=const.COMMAND_CLASS_SWITCH_BINARY) values = MockEntityValues(primary=value, node=node) - device = zwave.get_device(hass=hass, node=node, values=values, + device = cover.get_device(hass=hass, node=node, values=values, node_config={}) assert value.data is False @@ -211,7 +211,7 @@ def test_barrier_garage_value_changed(hass, mock_openzwave): value = MockValue(data="Closed", node=node, command_class=const.COMMAND_CLASS_BARRIER_OPERATOR) values = MockEntityValues(primary=value, node=node) - device = zwave.get_device(hass=hass, node=node, values=values, + device = cover.get_device(hass=hass, node=node, values=values, node_config={}) assert device.is_closed @@ -243,7 +243,7 @@ def test_barrier_garage_commands(hass, mock_openzwave): value = MockValue(data="Closed", node=node, command_class=const.COMMAND_CLASS_BARRIER_OPERATOR) values = MockEntityValues(primary=value, node=node) - device = zwave.get_device(hass=hass, node=node, values=values, + device = cover.get_device(hass=hass, node=node, values=values, node_config={}) assert value.data == "Closed" diff --git a/tests/components/zwave/test_fan.py b/tests/components/zwave/test_fan.py index af3d16f6288..57a60cfa303 100644 --- a/tests/components/zwave/test_fan.py +++ b/tests/components/zwave/test_fan.py @@ -1,5 +1,5 @@ """Test Z-Wave fans.""" -import homeassistant.components.zwave.fan as zwave +from homeassistant.components.zwave import fan from homeassistant.components.fan import ( SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, SUPPORT_SET_SPEED) @@ -13,8 +13,8 @@ def test_get_device_detects_fan(mock_openzwave): value = MockValue(data=0, node=node) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) - assert isinstance(device, zwave.ZwaveFan) + device = fan.get_device(node=node, values=values, node_config={}) + assert isinstance(device, fan.ZwaveFan) assert device.supported_features == SUPPORT_SET_SPEED assert device.speed_list == [ SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] @@ -25,7 +25,7 @@ def test_fan_turn_on(mock_openzwave): node = MockNode() value = MockValue(data=0, node=node) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = fan.get_device(node=node, values=values, node_config={}) device.turn_on() @@ -80,7 +80,7 @@ def test_fan_turn_off(mock_openzwave): node = MockNode() value = MockValue(data=46, node=node) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = fan.get_device(node=node, values=values, node_config={}) device.turn_off() @@ -95,7 +95,7 @@ def test_fan_value_changed(mock_openzwave): node = MockNode() value = MockValue(data=0, node=node) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = fan.get_device(node=node, values=values, node_config={}) assert not device.is_on diff --git a/tests/components/zwave/test_light.py b/tests/components/zwave/test_light.py index 5e85f28da39..61e960077c9 100644 --- a/tests/components/zwave/test_light.py +++ b/tests/components/zwave/test_light.py @@ -1,9 +1,8 @@ """Test Z-Wave lights.""" from unittest.mock import patch, MagicMock -import homeassistant.components.zwave -from homeassistant.components.zwave import const -import homeassistant.components.zwave.light as zwave +from homeassistant.components import zwave +from homeassistant.components.zwave import const, light from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_TRANSITION, SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION, SUPPORT_COLOR, ATTR_WHITE_VALUE, @@ -30,8 +29,8 @@ def test_get_device_detects_dimmer(mock_openzwave): value = MockValue(data=0, node=node) values = MockLightValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) - assert isinstance(device, zwave.ZwaveDimmer) + device = light.get_device(node=node, values=values, node_config={}) + assert isinstance(device, light.ZwaveDimmer) assert device.supported_features == SUPPORT_BRIGHTNESS @@ -41,8 +40,8 @@ def test_get_device_detects_colorlight(mock_openzwave): value = MockValue(data=0, node=node) values = MockLightValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) - assert isinstance(device, zwave.ZwaveColorLight) + device = light.get_device(node=node, values=values, node_config={}) + assert isinstance(device, light.ZwaveColorLight) assert device.supported_features == SUPPORT_BRIGHTNESS | SUPPORT_COLOR @@ -52,8 +51,8 @@ def test_get_device_detects_zw098(mock_openzwave): command_classes=[const.COMMAND_CLASS_SWITCH_COLOR]) value = MockValue(data=0, node=node) values = MockLightValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) - assert isinstance(device, zwave.ZwaveColorLight) + device = light.get_device(node=node, values=values, node_config={}) + assert isinstance(device, light.ZwaveColorLight) assert device.supported_features == ( SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_COLOR_TEMP) @@ -67,9 +66,9 @@ def test_get_device_detects_rgbw_light(mock_openzwave): values = MockLightValues( primary=value, color=color, color_channels=color_channels) - device = zwave.get_device(node=node, values=values, node_config={}) + device = light.get_device(node=node, values=values, node_config={}) device.value_added() - assert isinstance(device, zwave.ZwaveColorLight) + assert isinstance(device, light.ZwaveColorLight) assert device.supported_features == ( SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_WHITE_VALUE) @@ -79,7 +78,7 @@ def test_dimmer_turn_on(mock_openzwave): node = MockNode() value = MockValue(data=0, node=node) values = MockLightValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = light.get_device(node=node, values=values, node_config={}) device.turn_on() @@ -98,7 +97,7 @@ def test_dimmer_turn_on(mock_openzwave): assert value_id == value.value_id assert brightness == 46 # int(120 / 255 * 99) - with patch.object(zwave, '_LOGGER', MagicMock()) as mock_logger: + with patch.object(light, '_LOGGER', MagicMock()) as mock_logger: device.turn_on(**{ATTR_TRANSITION: 35}) assert mock_logger.debug.called assert node.set_dimmer.called @@ -111,7 +110,7 @@ def test_dimmer_min_brightness(mock_openzwave): node = MockNode() value = MockValue(data=0, node=node) values = MockLightValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = light.get_device(node=node, values=values, node_config={}) assert not device.is_on @@ -132,7 +131,7 @@ def test_dimmer_transitions(mock_openzwave): value = MockValue(data=0, node=node) duration = MockValue(data=0, node=node) values = MockLightValues(primary=value, dimming_duration=duration) - device = zwave.get_device(node=node, values=values, node_config={}) + device = light.get_device(node=node, values=values, node_config={}) assert device.supported_features == SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION # Test turn_on @@ -175,7 +174,7 @@ def test_dimmer_turn_off(mock_openzwave): node = MockNode() value = MockValue(data=46, node=node) values = MockLightValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = light.get_device(node=node, values=values, node_config={}) device.turn_off() @@ -190,7 +189,7 @@ def test_dimmer_value_changed(mock_openzwave): node = MockNode() value = MockValue(data=0, node=node) values = MockLightValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = light.get_device(node=node, values=values, node_config={}) assert not device.is_on @@ -206,14 +205,14 @@ def test_dimmer_refresh_value(mock_openzwave): node = MockNode() value = MockValue(data=0, node=node) values = MockLightValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={ - homeassistant.components.zwave.CONF_REFRESH_VALUE: True, - homeassistant.components.zwave.CONF_REFRESH_DELAY: 5, + device = light.get_device(node=node, values=values, node_config={ + zwave.CONF_REFRESH_VALUE: True, + zwave.CONF_REFRESH_DELAY: 5, }) assert not device.is_on - with patch.object(zwave, 'Timer', MagicMock()) as mock_timer: + with patch.object(light, 'Timer', MagicMock()) as mock_timer: value.data = 46 value_changed(value) @@ -225,7 +224,7 @@ def test_dimmer_refresh_value(mock_openzwave): assert mock_timer().start.called assert len(mock_timer().start.mock_calls) == 1 - with patch.object(zwave, 'Timer', MagicMock()) as mock_timer_2: + with patch.object(light, 'Timer', MagicMock()) as mock_timer_2: value_changed(value) assert not device.is_on assert mock_timer().cancel.called @@ -249,7 +248,7 @@ def test_set_hs_color(mock_openzwave): color_channels = MockValue(data=0x1c, node=node) values = MockLightValues(primary=value, color=color, color_channels=color_channels) - device = zwave.get_device(node=node, values=values, node_config={}) + device = light.get_device(node=node, values=values, node_config={}) assert color.data == '#0000000000' @@ -267,7 +266,7 @@ def test_set_white_value(mock_openzwave): color_channels = MockValue(data=0x1d, node=node) values = MockLightValues(primary=value, color=color, color_channels=color_channels) - device = zwave.get_device(node=node, values=values, node_config={}) + device = light.get_device(node=node, values=values, node_config={}) assert color.data == '#0000000000' @@ -290,7 +289,7 @@ def test_disable_white_if_set_color(mock_openzwave): color_channels = MockValue(data=0x1c, node=node) values = MockLightValues(primary=value, color=color, color_channels=color_channels) - device = zwave.get_device(node=node, values=values, node_config={}) + device = light.get_device(node=node, values=values, node_config={}) device._white = 234 assert color.data == '#0000000000' @@ -312,7 +311,7 @@ def test_zw098_set_color_temp(mock_openzwave): color_channels = MockValue(data=0x1f, node=node) values = MockLightValues(primary=value, color=color, color_channels=color_channels) - device = zwave.get_device(node=node, values=values, node_config={}) + device = light.get_device(node=node, values=values, node_config={}) assert color.data == '#0000000000' @@ -334,7 +333,7 @@ def test_rgb_not_supported(mock_openzwave): color_channels = MockValue(data=0x01, node=node) values = MockLightValues(primary=value, color=color, color_channels=color_channels) - device = zwave.get_device(node=node, values=values, node_config={}) + device = light.get_device(node=node, values=values, node_config={}) assert device.hs_color is None @@ -344,7 +343,7 @@ def test_no_color_value(mock_openzwave): node = MockNode(command_classes=[const.COMMAND_CLASS_SWITCH_COLOR]) value = MockValue(data=0, node=node) values = MockLightValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = light.get_device(node=node, values=values, node_config={}) assert device.hs_color is None @@ -355,7 +354,7 @@ def test_no_color_channels_value(mock_openzwave): value = MockValue(data=0, node=node) color = MockValue(data='#0000000000', node=node) values = MockLightValues(primary=value, color=color) - device = zwave.get_device(node=node, values=values, node_config={}) + device = light.get_device(node=node, values=values, node_config={}) assert device.hs_color is None @@ -369,7 +368,7 @@ def test_rgb_value_changed(mock_openzwave): color_channels = MockValue(data=0x1c, node=node) values = MockLightValues(primary=value, color=color, color_channels=color_channels) - device = zwave.get_device(node=node, values=values, node_config={}) + device = light.get_device(node=node, values=values, node_config={}) assert device.hs_color == (0, 0) @@ -388,7 +387,7 @@ def test_rgbww_value_changed(mock_openzwave): color_channels = MockValue(data=0x1d, node=node) values = MockLightValues(primary=value, color=color, color_channels=color_channels) - device = zwave.get_device(node=node, values=values, node_config={}) + device = light.get_device(node=node, values=values, node_config={}) assert device.hs_color == (0, 0) assert device.white_value == 0 @@ -409,7 +408,7 @@ def test_rgbcw_value_changed(mock_openzwave): color_channels = MockValue(data=0x1e, node=node) values = MockLightValues(primary=value, color=color, color_channels=color_channels) - device = zwave.get_device(node=node, values=values, node_config={}) + device = light.get_device(node=node, values=values, node_config={}) assert device.hs_color == (0, 0) assert device.white_value == 0 @@ -431,16 +430,16 @@ def test_ct_value_changed(mock_openzwave): color_channels = MockValue(data=0x1f, node=node) values = MockLightValues(primary=value, color=color, color_channels=color_channels) - device = zwave.get_device(node=node, values=values, node_config={}) + device = light.get_device(node=node, values=values, node_config={}) - assert device.color_temp == zwave.TEMP_MID_HASS + assert device.color_temp == light.TEMP_MID_HASS color.data = '#000000ff00' value_changed(color) - assert device.color_temp == zwave.TEMP_WARM_HASS + assert device.color_temp == light.TEMP_WARM_HASS color.data = '#00000000ff' value_changed(color) - assert device.color_temp == zwave.TEMP_COLD_HASS + assert device.color_temp == light.TEMP_COLD_HASS diff --git a/tests/components/zwave/test_lock.py b/tests/components/zwave/test_lock.py index 98734db8d7c..2c49c79f4a8 100644 --- a/tests/components/zwave/test_lock.py +++ b/tests/components/zwave/test_lock.py @@ -2,8 +2,7 @@ from unittest.mock import patch, MagicMock from homeassistant import config_entries -import homeassistant.components.zwave.lock as zwave -from homeassistant.components.zwave import const +from homeassistant.components.zwave import const, lock from tests.mock.zwave import ( MockNode, MockValue, MockEntityValues, value_changed) @@ -19,8 +18,8 @@ def test_get_device_detects_lock(mock_openzwave): alarm_level=None, ) - device = zwave.get_device(node=node, values=values, node_config={}) - assert isinstance(device, zwave.ZwaveLock) + device = lock.get_device(node=node, values=values, node_config={}) + assert isinstance(device, lock.ZwaveLock) def test_lock_turn_on_and_off(mock_openzwave): @@ -32,7 +31,7 @@ def test_lock_turn_on_and_off(mock_openzwave): alarm_type=None, alarm_level=None, ) - device = zwave.get_device(node=node, values=values, node_config={}) + device = lock.get_device(node=node, values=values, node_config={}) assert not values.primary.data @@ -52,7 +51,7 @@ def test_lock_value_changed(mock_openzwave): alarm_type=None, alarm_level=None, ) - device = zwave.get_device(node=node, values=values, node_config={}) + device = lock.get_device(node=node, values=values, node_config={}) assert not device.is_locked @@ -71,7 +70,7 @@ def test_lock_state_workaround(mock_openzwave): alarm_type=None, alarm_level=None, ) - device = zwave.get_device(node=node, values=values) + device = lock.get_device(node=node, values=values) assert device.is_locked values.access_control.data = 2 value_changed(values.access_control) @@ -89,15 +88,15 @@ def test_track_message_workaround(mock_openzwave): alarm_level=None, ) - # Here we simulate an RF lock. The first zwave.get_device will call + # Here we simulate an RF lock. The first lock.get_device will call # update properties, simulating the first DoorLock report. We then trigger # a change, simulating the openzwave automatic refreshing behavior (which # is enabled for at least the lock that needs this workaround) node.stats['lastReceivedMessage'][5] = const.COMMAND_CLASS_DOOR_LOCK - device = zwave.get_device(node=node, values=values) + device = lock.get_device(node=node, values=values) value_changed(values.primary) assert device.is_locked - assert device.device_state_attributes[zwave.ATTR_NOTIFICATION] == 'RF Lock' + assert device.device_state_attributes[lock.ATTR_NOTIFICATION] == 'RF Lock' # Simulate a keypad unlock. We trigger a value_changed() which simulates # the Alarm notification received from the lock. Then, we trigger @@ -111,7 +110,7 @@ def test_track_message_workaround(mock_openzwave): values.primary.data = False value_changed(values.primary) assert not device.is_locked - assert device.device_state_attributes[zwave.ATTR_LOCK_STATUS] == \ + assert device.device_state_attributes[lock.ATTR_LOCK_STATUS] == \ 'Unlocked with Keypad by user 3' # Again, simulate an RF lock. @@ -119,7 +118,7 @@ def test_track_message_workaround(mock_openzwave): node.stats['lastReceivedMessage'][5] = const.COMMAND_CLASS_DOOR_LOCK value_changed(values.primary) assert device.is_locked - assert device.device_state_attributes[zwave.ATTR_NOTIFICATION] == 'RF Lock' + assert device.device_state_attributes[lock.ATTR_NOTIFICATION] == 'RF Lock' def test_v2btze_value_changed(mock_openzwave): @@ -132,7 +131,7 @@ def test_v2btze_value_changed(mock_openzwave): alarm_type=None, alarm_level=None, ) - device = zwave.get_device(node=node, values=values, node_config={}) + device = lock.get_device(node=node, values=values, node_config={}) assert device._v2btze assert not device.is_locked @@ -152,7 +151,7 @@ def test_alarm_type_workaround(mock_openzwave): alarm_type=MockValue(data=16, node=node), alarm_level=None, ) - device = zwave.get_device(node=node, values=values) + device = lock.get_device(node=node, values=values) assert not device.is_locked values.alarm_type.data = 18 @@ -193,9 +192,9 @@ def test_lock_access_control(mock_openzwave): alarm_type=None, alarm_level=None, ) - device = zwave.get_device(node=node, values=values, node_config={}) + device = lock.get_device(node=node, values=values, node_config={}) - assert device.device_state_attributes[zwave.ATTR_NOTIFICATION] == \ + assert device.device_state_attributes[lock.ATTR_NOTIFICATION] == \ 'Lock Jammed' @@ -208,28 +207,28 @@ def test_lock_alarm_type(mock_openzwave): alarm_type=MockValue(data=None, node=node), alarm_level=None, ) - device = zwave.get_device(node=node, values=values, node_config={}) + device = lock.get_device(node=node, values=values, node_config={}) - assert zwave.ATTR_LOCK_STATUS not in device.device_state_attributes + assert lock.ATTR_LOCK_STATUS not in device.device_state_attributes values.alarm_type.data = 21 value_changed(values.alarm_type) - assert device.device_state_attributes[zwave.ATTR_LOCK_STATUS] == \ + assert device.device_state_attributes[lock.ATTR_LOCK_STATUS] == \ 'Manually Locked None' values.alarm_type.data = 18 value_changed(values.alarm_type) - assert device.device_state_attributes[zwave.ATTR_LOCK_STATUS] == \ + assert device.device_state_attributes[lock.ATTR_LOCK_STATUS] == \ 'Locked with Keypad by user None' values.alarm_type.data = 161 value_changed(values.alarm_type) - assert device.device_state_attributes[zwave.ATTR_LOCK_STATUS] == \ + assert device.device_state_attributes[lock.ATTR_LOCK_STATUS] == \ 'Tamper Alarm: None' values.alarm_type.data = 9 value_changed(values.alarm_type) - assert device.device_state_attributes[zwave.ATTR_LOCK_STATUS] == \ + assert device.device_state_attributes[lock.ATTR_LOCK_STATUS] == \ 'Deadbolt Jammed' @@ -242,29 +241,29 @@ def test_lock_alarm_level(mock_openzwave): alarm_type=MockValue(data=None, node=node), alarm_level=MockValue(data=None, node=node), ) - device = zwave.get_device(node=node, values=values, node_config={}) + device = lock.get_device(node=node, values=values, node_config={}) - assert zwave.ATTR_LOCK_STATUS not in device.device_state_attributes + assert lock.ATTR_LOCK_STATUS not in device.device_state_attributes values.alarm_type.data = 21 values.alarm_level.data = 1 value_changed(values.alarm_type) value_changed(values.alarm_level) - assert device.device_state_attributes[zwave.ATTR_LOCK_STATUS] == \ + assert device.device_state_attributes[lock.ATTR_LOCK_STATUS] == \ 'Manually Locked by Key Cylinder or Inside thumb turn' values.alarm_type.data = 18 values.alarm_level.data = 'alice' value_changed(values.alarm_type) value_changed(values.alarm_level) - assert device.device_state_attributes[zwave.ATTR_LOCK_STATUS] == \ + assert device.device_state_attributes[lock.ATTR_LOCK_STATUS] == \ 'Locked with Keypad by user alice' values.alarm_type.data = 161 values.alarm_level.data = 1 value_changed(values.alarm_type) value_changed(values.alarm_level) - assert device.device_state_attributes[zwave.ATTR_LOCK_STATUS] == \ + assert device.device_state_attributes[lock.ATTR_LOCK_STATUS] == \ 'Tamper Alarm: Too many keypresses' @@ -282,7 +281,7 @@ async def setup_ozw(hass, mock_openzwave): async def test_lock_set_usercode_service(hass, mock_openzwave): """Test the zwave lock set_usercode service.""" - mock_network = hass.data[zwave.zwave.DATA_NETWORK] = MagicMock() + mock_network = hass.data[const.DATA_NETWORK] = MagicMock() node = MockNode(node_id=12) value0 = MockValue(data=' ', node=node, index=0) @@ -301,10 +300,10 @@ async def test_lock_set_usercode_service(hass, mock_openzwave): await hass.async_block_till_done() await hass.services.async_call( - zwave.DOMAIN, zwave.SERVICE_SET_USERCODE, { + lock.DOMAIN, lock.SERVICE_SET_USERCODE, { const.ATTR_NODE_ID: node.node_id, - zwave.ATTR_USERCODE: '1234', - zwave.ATTR_CODE_SLOT: 1, + lock.ATTR_USERCODE: '1234', + lock.ATTR_CODE_SLOT: 1, }) await hass.async_block_till_done() @@ -314,10 +313,10 @@ async def test_lock_set_usercode_service(hass, mock_openzwave): node.node_id: node } await hass.services.async_call( - zwave.DOMAIN, zwave.SERVICE_SET_USERCODE, { + lock.DOMAIN, lock.SERVICE_SET_USERCODE, { const.ATTR_NODE_ID: node.node_id, - zwave.ATTR_USERCODE: '123', - zwave.ATTR_CODE_SLOT: 1, + lock.ATTR_USERCODE: '123', + lock.ATTR_CODE_SLOT: 1, }) await hass.async_block_till_done() @@ -326,7 +325,7 @@ async def test_lock_set_usercode_service(hass, mock_openzwave): async def test_lock_get_usercode_service(hass, mock_openzwave): """Test the zwave lock get_usercode service.""" - mock_network = hass.data[zwave.zwave.DATA_NETWORK] = MagicMock() + mock_network = hass.data[const.DATA_NETWORK] = MagicMock() node = MockNode(node_id=12) value0 = MockValue(data=None, node=node, index=0) value1 = MockValue(data='1234', node=node, index=1) @@ -339,12 +338,12 @@ async def test_lock_get_usercode_service(hass, mock_openzwave): await setup_ozw(hass, mock_openzwave) await hass.async_block_till_done() - with patch.object(zwave, '_LOGGER') as mock_logger: + with patch.object(lock, '_LOGGER') as mock_logger: mock_network.nodes = {node.node_id: node} await hass.services.async_call( - zwave.DOMAIN, zwave.SERVICE_GET_USERCODE, { + lock.DOMAIN, lock.SERVICE_GET_USERCODE, { const.ATTR_NODE_ID: node.node_id, - zwave.ATTR_CODE_SLOT: 1, + lock.ATTR_CODE_SLOT: 1, }) await hass.async_block_till_done() # This service only seems to write to the log @@ -355,7 +354,7 @@ async def test_lock_get_usercode_service(hass, mock_openzwave): async def test_lock_clear_usercode_service(hass, mock_openzwave): """Test the zwave lock clear_usercode service.""" - mock_network = hass.data[zwave.zwave.DATA_NETWORK] = MagicMock() + mock_network = hass.data[const.DATA_NETWORK] = MagicMock() node = MockNode(node_id=12) value0 = MockValue(data=None, node=node, index=0) value1 = MockValue(data='123', node=node, index=1) @@ -373,9 +372,9 @@ async def test_lock_clear_usercode_service(hass, mock_openzwave): await hass.async_block_till_done() await hass.services.async_call( - zwave.DOMAIN, zwave.SERVICE_CLEAR_USERCODE, { + lock.DOMAIN, lock.SERVICE_CLEAR_USERCODE, { const.ATTR_NODE_ID: node.node_id, - zwave.ATTR_CODE_SLOT: 1 + lock.ATTR_CODE_SLOT: 1 }) await hass.async_block_till_done() diff --git a/tests/components/zwave/test_sensor.py b/tests/components/zwave/test_sensor.py index cce6bf9deaa..73613424d84 100644 --- a/tests/components/zwave/test_sensor.py +++ b/tests/components/zwave/test_sensor.py @@ -1,6 +1,5 @@ """Test Z-Wave sensor.""" -import homeassistant.components.zwave.sensor as zwave -from homeassistant.components.zwave import const +from homeassistant.components.zwave import const, sensor import homeassistant.const from tests.mock.zwave import ( @@ -13,7 +12,7 @@ def test_get_device_detects_none(mock_openzwave): value = MockValue(data=0, node=node) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = sensor.get_device(node=node, values=values, node_config={}) assert device is None @@ -24,8 +23,8 @@ def test_get_device_detects_alarmsensor(mock_openzwave): value = MockValue(data=0, node=node) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) - assert isinstance(device, zwave.ZWaveAlarmSensor) + device = sensor.get_device(node=node, values=values, node_config={}) + assert isinstance(device, sensor.ZWaveAlarmSensor) def test_get_device_detects_multilevelsensor(mock_openzwave): @@ -35,8 +34,8 @@ def test_get_device_detects_multilevelsensor(mock_openzwave): value = MockValue(data=0, node=node) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) - assert isinstance(device, zwave.ZWaveMultilevelSensor) + device = sensor.get_device(node=node, values=values, node_config={}) + assert isinstance(device, sensor.ZWaveMultilevelSensor) assert device.force_update @@ -46,8 +45,8 @@ def test_get_device_detects_multilevel_meter(mock_openzwave): value = MockValue(data=0, node=node, type=const.TYPE_DECIMAL) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) - assert isinstance(device, zwave.ZWaveMultilevelSensor) + device = sensor.get_device(node=node, values=values, node_config={}) + assert isinstance(device, sensor.ZWaveMultilevelSensor) def test_multilevelsensor_value_changed_temp_fahrenheit(mock_openzwave): @@ -57,7 +56,7 @@ def test_multilevelsensor_value_changed_temp_fahrenheit(mock_openzwave): value = MockValue(data=190.95555, units='F', node=node) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = sensor.get_device(node=node, values=values, node_config={}) assert device.state == 191.0 assert device.unit_of_measurement == homeassistant.const.TEMP_FAHRENHEIT value.data = 197.95555 @@ -72,7 +71,7 @@ def test_multilevelsensor_value_changed_temp_celsius(mock_openzwave): value = MockValue(data=38.85555, units='C', node=node) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = sensor.get_device(node=node, values=values, node_config={}) assert device.state == 38.9 assert device.unit_of_measurement == homeassistant.const.TEMP_CELSIUS value.data = 37.95555 @@ -87,7 +86,7 @@ def test_multilevelsensor_value_changed_other_units(mock_openzwave): value = MockValue(data=190.95555, units='kWh', node=node) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = sensor.get_device(node=node, values=values, node_config={}) assert device.state == 190.96 assert device.unit_of_measurement == 'kWh' value.data = 197.95555 @@ -102,7 +101,7 @@ def test_multilevelsensor_value_changed_integer(mock_openzwave): value = MockValue(data=5, units='counts', node=node) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = sensor.get_device(node=node, values=values, node_config={}) assert device.state == 5 assert device.unit_of_measurement == 'counts' value.data = 6 @@ -117,7 +116,7 @@ def test_alarm_sensor_value_changed(mock_openzwave): value = MockValue(data=12.34, node=node, units='%') values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = sensor.get_device(node=node, values=values, node_config={}) assert device.state == 12.34 assert device.unit_of_measurement == '%' value.data = 45.67 diff --git a/tests/components/zwave/test_switch.py b/tests/components/zwave/test_switch.py index 943be9fc4ea..e68f765ae38 100644 --- a/tests/components/zwave/test_switch.py +++ b/tests/components/zwave/test_switch.py @@ -1,7 +1,7 @@ """Test Z-Wave switches.""" from unittest.mock import patch -import homeassistant.components.zwave.switch as zwave +from homeassistant.components.zwave import switch from tests.mock.zwave import ( MockNode, MockValue, MockEntityValues, value_changed) @@ -13,8 +13,8 @@ def test_get_device_detects_switch(mock_openzwave): value = MockValue(data=0, node=node) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) - assert isinstance(device, zwave.ZwaveSwitch) + device = switch.get_device(node=node, values=values, node_config={}) + assert isinstance(device, switch.ZwaveSwitch) def test_switch_turn_on_and_off(mock_openzwave): @@ -22,7 +22,7 @@ def test_switch_turn_on_and_off(mock_openzwave): node = MockNode() value = MockValue(data=0, node=node) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = switch.get_device(node=node, values=values, node_config={}) device.turn_on() @@ -45,7 +45,7 @@ def test_switch_value_changed(mock_openzwave): node = MockNode() value = MockValue(data=False, node=node) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = switch.get_device(node=node, values=values, node_config={}) assert not device.is_on @@ -63,7 +63,7 @@ def test_switch_refresh_on_update(mock_counter, mock_openzwave): product_id='0005') value = MockValue(data=False, node=node, instance=1) values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, values=values, node_config={}) + device = switch.get_device(node=node, values=values, node_config={}) assert not device.is_on From 222c4ea6f31edd63950487e5cfb0e9809df85270 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 7 Feb 2019 20:12:58 -0700 Subject: [PATCH 100/242] Added Ambient PWS to device registry (#20841) --- homeassistant/components/ambient_station/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index c5ddd2734cb..ff9538738ee 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -254,6 +254,17 @@ class AmbientWeatherEntity(Entity): self._state = None self._station_name = station_name + @property + def device_info(self): + """Return device registry information for this entity.""" + return { + 'identifiers': { + (DOMAIN, self._mac_address) + }, + 'name': self._station_name, + 'manufacturer': 'Ambient Weather', + } + @property def name(self): """Return the name of the sensor.""" From e59240fa00a305d4966072b84b41770340bfa000 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 7 Feb 2019 20:07:15 -0800 Subject: [PATCH 101/242] Add default_config component (#20799) * Add default config component * Add default_config to default config * Fix comments --- .../components/default_config/__init__.py | 23 ++++++++++ .../components/discovery/__init__.py | 14 +++--- homeassistant/components/script/__init__.py | 2 +- homeassistant/config.py | 44 ++----------------- homeassistant/setup.py | 2 +- tests/components/default_config/__init__.py | 1 + tests/components/default_config/test_init.py | 27 ++++++++++++ 7 files changed, 66 insertions(+), 47 deletions(-) create mode 100644 homeassistant/components/default_config/__init__.py create mode 100644 tests/components/default_config/__init__.py create mode 100644 tests/components/default_config/test_init.py diff --git a/homeassistant/components/default_config/__init__.py b/homeassistant/components/default_config/__init__.py new file mode 100644 index 00000000000..3a99757b54b --- /dev/null +++ b/homeassistant/components/default_config/__init__.py @@ -0,0 +1,23 @@ +"""Component providing default configuration for new users.""" + +DOMAIN = 'default_config' +DEPENDENCIES = ( + 'automation', + 'cloud', + 'config', + 'conversation', + 'discovery', + 'frontend', + 'history', + 'logbook', + 'map', + 'script', + 'sun', + 'system_health', + 'updater', +) + + +async def async_setup(hass, config): + """Initialize default configuration.""" + return True diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py index d8198ba3033..87b89ddb44c 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -105,7 +105,7 @@ CONF_IGNORE = 'ignore' CONF_ENABLE = 'enable' CONFIG_SCHEMA = vol.Schema({ - vol.Required(DOMAIN): vol.Schema({ + vol.Optional(DOMAIN): vol.Schema({ vol.Optional(CONF_IGNORE, default=[]): vol.All(cv.ensure_list, [ vol.In(list(CONFIG_ENTRY_HANDLERS) + list(SERVICE_HANDLERS))]), @@ -126,11 +126,15 @@ async def async_setup(hass, config): # Disable zeroconf logging, it spams logging.getLogger('zeroconf').setLevel(logging.CRITICAL) - # Platforms ignore by config - ignored_platforms = config[DOMAIN][CONF_IGNORE] + if DOMAIN in config: + # Platforms ignore by config + ignored_platforms = config[DOMAIN][CONF_IGNORE] - # Optional platforms enabled by config - enabled_platforms = config[DOMAIN][CONF_ENABLE] + # Optional platforms enabled by config + enabled_platforms = config[DOMAIN][CONF_ENABLE] + else: + ignored_platforms = [] + enabled_platforms = [] async def new_service_found(service, info): """Handle a new service if one is found.""" diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index 15df6907468..e337a2ec251 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -124,7 +124,7 @@ async def _async_process_config(hass, config, component): scripts = [] - for object_id, cfg in config[DOMAIN].items(): + for object_id, cfg in config.get(DOMAIN, {}).items(): alias = cfg.get(CONF_ALIAS, object_id) script = ScriptEntity(hass, object_id, alias, cfg[CONF_SEQUENCE]) scripts.append(script) diff --git a/homeassistant/config.py b/homeassistant/config.py index 5dbf226ca25..3310cd3e160 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -65,49 +65,16 @@ DEFAULT_CORE_CONFIG = ( (CONF_CUSTOMIZE, '!include customize.yaml', None, 'Customization file'), ) # type: Tuple[Tuple[str, Any, Any, Optional[str]], ...] DEFAULT_CONFIG = """ -# Show links to resources in log and frontend +# Configure a default setup of Home Assistant (frontend, api, etc) +default_config: + +# Show the introduction message on startup. introduction: -# Enables the frontend -frontend: - -# Enables configuration UI -config: - # Uncomment this if you are using SSL/TLS, running in Docker container, etc. # http: # base_url: example.duckdns.org:8123 -# Checks for available updates -# Note: This component will send some information about your system to -# the developers to assist with development of Home Assistant. -# For more information, please see: -# https://home-assistant.io/blog/2016/10/25/explaining-the-updater/ -updater: - # Optional, allows Home Assistant developers to focus on popular components. - # include_used_components: true - -# Discover some devices automatically -discovery: - -# Allows you to issue voice commands from the frontend in enabled browsers -conversation: - -# Enables support for tracking state changes over time -history: - -# View all events in a logbook -logbook: - -# Enables a map showing the location of tracked devices -map: - -# Track the sun -sun: - -# Allow diagnosing system problems -system_health: - # Sensors sensor: # Weather prediction @@ -117,9 +84,6 @@ sensor: tts: - platform: google -# Cloud -cloud: - group: !include groups.yaml automation: !include automations.yaml script: !include scripts.yaml diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 33c5d5311b1..29c8e22d45d 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -63,7 +63,7 @@ async def _async_process_dependencies( blacklisted = [dep for dep in dependencies if dep in loader.DEPENDENCY_BLACKLIST] - if blacklisted: + if blacklisted and name != 'default_config': _LOGGER.error("Unable to set up dependencies of %s: " "found blacklisted dependencies: %s", name, ', '.join(blacklisted)) diff --git a/tests/components/default_config/__init__.py b/tests/components/default_config/__init__.py new file mode 100644 index 00000000000..7ee4658fed5 --- /dev/null +++ b/tests/components/default_config/__init__.py @@ -0,0 +1 @@ +"""Tests for the default config component.""" diff --git a/tests/components/default_config/test_init.py b/tests/components/default_config/test_init.py new file mode 100644 index 00000000000..94adf53cb2d --- /dev/null +++ b/tests/components/default_config/test_init.py @@ -0,0 +1,27 @@ +"""Test the default_config init.""" +from unittest.mock import patch + +from homeassistant.setup import async_setup_component + +import pytest + +from tests.common import MockDependency + + +@pytest.fixture(autouse=True) +def netdisco_mock(): + """Mock netdisco.""" + with MockDependency('netdisco', 'discovery'): + yield + + +@pytest.fixture(autouse=True) +def recorder_url_mock(): + """Mock recorder url.""" + with patch('homeassistant.components.recorder.DEFAULT_URL', 'sqlite://'): + yield + + +async def test_setup(hass): + """Test setup.""" + assert await async_setup_component(hass, 'default_config', {}) From c7df4cf092876ec50e8e50a984ac25ab27452002 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 7 Feb 2019 21:39:30 -0700 Subject: [PATCH 102/242] Make monitored_conditions more specific in Ambient PWS (#20803) * Make monitored_conditions more specific in Ambient PWS * Revert messing around with storing monitored_conditions elsewhere * Come on, Aaron --- .../components/ambient_station/__init__.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index ff9538738ee..4aa19dbc69e 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -96,12 +96,9 @@ SENSOR_TYPES = { CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ - vol.Required(CONF_APP_KEY): - cv.string, - vol.Required(CONF_API_KEY): - cv.string, - vol.Optional( - CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): + vol.Required(CONF_APP_KEY): cv.string, + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_MONITORED_CONDITIONS): vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), }) }, extra=vol.ALLOW_EXTRA) @@ -140,8 +137,7 @@ async def async_setup_entry(hass, config_entry): Client( config_entry.data[CONF_API_KEY], config_entry.data[CONF_APP_KEY], session), - config_entry.data.get( - CONF_MONITORED_CONDITIONS, list(SENSOR_TYPES))) + config_entry.data.get(CONF_MONITORED_CONDITIONS, [])) hass.loop.create_task(ambient.ws_connect()) hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = ambient except WebsocketConnectionError as err: @@ -206,6 +202,15 @@ class AmbientStation: _LOGGER.debug('New station subscription: %s', data) + # If the user hasn't specified monitored conditions, use only + # those that their station supports (and which are defined + # here): + if not self.monitored_conditions: + self.monitored_conditions = [ + k for k in station['lastData'].keys() + if k in SENSOR_TYPES + ] + self.stations[station['macAddress']] = { ATTR_LAST_DATA: station['lastData'], ATTR_LOCATION: station['info']['location'], From 706810bbce620d19286b047e458cb952ba715259 Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Thu, 7 Feb 2019 22:51:17 -0600 Subject: [PATCH 103/242] Add SmartThings Sensor platform (#20848) * Add Sensor platform and update pysmartthings 0.6.0 * Add tests for Sensor platform * Redesigned capability subscription process * Removed redundant Entity inheritance * Updated per review feedback. --- .../components/smartthings/__init__.py | 2 +- homeassistant/components/smartthings/const.py | 18 +- .../components/smartthings/sensor.py | 218 ++++++++++++++++++ .../components/smartthings/smartapp.py | 83 ++++--- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/smartthings/conftest.py | 20 +- .../smartthings/test_binary_sensor.py | 14 +- tests/components/smartthings/test_init.py | 2 +- tests/components/smartthings/test_sensor.py | 97 ++++++++ tests/components/smartthings/test_smartapp.py | 64 ++++- 11 files changed, 457 insertions(+), 65 deletions(-) create mode 100644 homeassistant/components/smartthings/sensor.py create mode 100644 tests/components/smartthings/test_sensor.py diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index bdbbfcf2590..b7b5436da3e 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -23,7 +23,7 @@ from .const import ( from .smartapp import ( setup_smartapp, setup_smartapp_endpoint, validate_installed_app) -REQUIREMENTS = ['pysmartapp==0.3.0', 'pysmartthings==0.5.0'] +REQUIREMENTS = ['pysmartapp==0.3.0', 'pysmartthings==0.6.0'] DEPENDENCIES = ['webhook'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/smartthings/const.py b/homeassistant/components/smartthings/const.py index 3d0e5cb95f8..9391c871b25 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -22,25 +22,9 @@ SUPPORTED_PLATFORMS = [ 'binary_sensor', 'fan', 'light', + 'sensor', 'switch' ] -SUPPORTED_CAPABILITIES = [ - 'accelerationSensor', - 'button', - 'colorControl', - 'colorTemperature', - 'contactSensor', - 'fanSpeed', - 'filterStatus', - 'motionSensor', - 'presenceSensor', - 'soundSensor', - 'switch', - 'switchLevel', - 'tamperAlert', - 'valve', - 'waterSensor' -] VAL_UID = "^(?:([0-9a-fA-F]{32})|([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]" \ "{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}))$" VAL_UID_MATCHER = re.compile(VAL_UID) diff --git a/homeassistant/components/smartthings/sensor.py b/homeassistant/components/smartthings/sensor.py new file mode 100644 index 00000000000..5539703e77e --- /dev/null +++ b/homeassistant/components/smartthings/sensor.py @@ -0,0 +1,218 @@ +""" +Support for sensors through the SmartThings cloud API. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/smartthings.sensor/ +""" +from collections import namedtuple + +from homeassistant.const import ( + DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP, MASS_KILOGRAMS, + TEMP_CELSIUS, TEMP_FAHRENHEIT) + +from . import SmartThingsEntity +from .const import DATA_BROKERS, DOMAIN + +DEPENDENCIES = ['smartthings'] + +Map = namedtuple("map", "attribute name default_unit device_class") + +CAPABILITY_TO_SENSORS = { + 'activityLightingMode': [ + Map('lightingMode', "Activity Lighting Mode", None, None)], + 'airConditionerMode': [ + Map('airConditionerMode', "Air Conditioner Mode", None, None)], + 'airQualitySensor': [ + Map('airQuality', "Air Quality", 'CAQI', None)], + 'alarm': [ + Map('alarm', "Alarm", None, None)], + 'audioVolume': [ + Map('volume', "Volume", "%", None)], + 'battery': [ + Map('battery', "Battery", "%", DEVICE_CLASS_BATTERY)], + 'bodyMassIndexMeasurement': [ + Map('bmiMeasurement', "Body Mass Index", "kg/m^2", None)], + 'bodyWeightMeasurement': [ + Map('bodyWeightMeasurement', "Body Weight", MASS_KILOGRAMS, None)], + 'carbonDioxideMeasurement': [ + Map('carbonDioxide', "Carbon Dioxide Measurement", "ppm", None)], + 'carbonMonoxideDetector': [ + Map('carbonMonoxide', "Carbon Monoxide Detector", None, None)], + 'carbonMonoxideMeasurement': [ + Map('carbonMonoxideLevel', "Carbon Monoxide Measurement", "ppm", + None)], + 'dishwasherOperatingState': [ + Map('machineState', "Dishwasher Machine State", None, None), + Map('dishwasherJobState', "Dishwasher Job State", None, None), + Map('completionTime', "Dishwasher Completion Time", None, + DEVICE_CLASS_TIMESTAMP)], + 'doorControl': [ + Map('door', "Door", None, None)], + 'dryerMode': [ + Map('dryerMode', "Dryer Mode", None, None)], + 'dryerOperatingState': [ + Map('machineState', "Dryer Machine State", None, None), + Map('dryerJobState', "Dryer Job State", None, None), + Map('completionTime', "Dryer Completion Time", None, + DEVICE_CLASS_TIMESTAMP)], + 'dustSensor': [ + Map('fineDustLevel', "Fine Dust Level", None, None), + Map('dustLevel', "Dust Level", None, None)], + 'energyMeter': [ + Map('energy', "Energy Meter", 'kWh', None)], + 'equivalentCarbonDioxideMeasurement': [ + Map('equivalentCarbonDioxideMeasurement', + 'Equivalent Carbon Dioxide Measurement', 'ppm', None)], + 'formaldehydeMeasurement': [ + Map('formaldehydeLevel', 'Formaldehyde Measurement', 'ppm', None)], + 'garageDoorControl': [ + Map('door', 'Garage Door', None, None)], + 'illuminanceMeasurement': [ + Map('illuminance', "Illuminance", 'lux', DEVICE_CLASS_ILLUMINANCE)], + 'infraredLevel': [ + Map('infraredLevel', "Infrared Level", '%', None)], + 'lock': [ + Map('lock', "Lock", None, None)], + 'mediaInputSource': [ + Map('inputSource', "Media Input Source", None, None)], + 'mediaPlaybackRepeat': [ + Map('playbackRepeatMode', "Media Playback Repeat", None, None)], + 'mediaPlaybackShuffle': [ + Map('playbackShuffle', "Media Playback Shuffle", None, None)], + 'mediaPlayback': [ + Map('playbackStatus', "Media Playback Status", None, None)], + 'odorSensor': [ + Map('odorLevel', "Odor Sensor", None, None)], + 'ovenMode': [ + Map('ovenMode', "Oven Mode", None, None)], + 'ovenOperatingState': [ + Map('machineState', "Oven Machine State", None, None), + Map('ovenJobState', "Oven Job State", None, None), + Map('completionTime', "Oven Completion Time", None, None)], + 'ovenSetpoint': [ + Map('ovenSetpoint', "Oven Set Point", None, None)], + 'powerMeter': [ + Map('power', "Power Meter", 'W', None)], + 'powerSource': [ + Map('powerSource', "Power Source", None, None)], + 'refrigerationSetpoint': [ + Map('refrigerationSetpoint', "Refrigeration Setpoint", TEMP_CELSIUS, + DEVICE_CLASS_TEMPERATURE)], + 'relativeHumidityMeasurement': [ + Map('humidity', "Relative Humidity Measurement", '%', + DEVICE_CLASS_HUMIDITY)], + 'robotCleanerCleaningMode': [ + Map('robotCleanerCleaningMode', "Robot Cleaner Cleaning Mode", + None, None)], + 'robotCleanerMovement': [ + Map('robotCleanerMovement', "Robot Cleaner Movement", None, None)], + 'robotCleanerTurboMode': [ + Map('robotCleanerTurboMode', "Robot Cleaner Turbo Mode", None, None)], + 'signalStrength': [ + Map('lqi', "LQI Signal Strength", None, None), + Map('rssi', "RSSI Signal Strength", None, None)], + 'smokeDetector': [ + Map('smoke', "Smoke Detector", None, None)], + 'temperatureMeasurement': [ + Map('temperature', "Temperature Measurement", TEMP_CELSIUS, + DEVICE_CLASS_TEMPERATURE)], + 'thermostatCoolingSetpoint': [ + Map('coolingSetpoint', "Thermostat Cooling Setpoint", TEMP_CELSIUS, + DEVICE_CLASS_TEMPERATURE)], + 'thermostatFanMode': [ + Map('thermostatFanMode', "Thermostat Fan Mode", None, None)], + 'thermostatHeatingSetpoint': [ + Map('heatingSetpoint', "Thermostat Heating Setpoint", TEMP_CELSIUS, + DEVICE_CLASS_TEMPERATURE)], + 'thermostatMode': [ + Map('thermostatMode', "Thermostat Mode", None, None)], + 'thermostatOperatingState': [ + Map('thermostatOperatingState', "Thermostat Operating State", + None, None)], + 'thermostatSetpoint': [ + Map('thermostatSetpoint', "Thermostat Setpoint", TEMP_CELSIUS, + DEVICE_CLASS_TEMPERATURE)], + 'tvChannel': [ + Map('tvChannel', "Tv Channel", None, None)], + 'tvocMeasurement': [ + Map('tvocLevel', "Tvoc Measurement", 'ppm', None)], + 'ultravioletIndex': [ + Map('ultravioletIndex', "Ultraviolet Index", None, None)], + 'voltageMeasurement': [ + Map('voltage', "Voltage Measurement", 'V', None)], + 'washerMode': [ + Map('washerMode', "Washer Mode", None, None)], + 'washerOperatingState': [ + Map('machineState', "Washer Machine State", None, None), + Map('washerJobState', "Washer Job State", None, None), + Map('completionTime', "Washer Completion Time", None, + DEVICE_CLASS_TIMESTAMP)], + 'windowShade': [ + Map('windowShade', 'Window Shade', None, None)] +} + +UNITS = { + 'C': TEMP_CELSIUS, + 'F': TEMP_FAHRENHEIT +} + + +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): + """Platform uses config entry setup.""" + pass + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Add binary sensors for a config entry.""" + broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] + sensors = [] + for device in broker.devices.values(): + for capability, maps in CAPABILITY_TO_SENSORS.items(): + if capability in device.capabilities: + sensors.extend([ + SmartThingsSensor( + device, m.attribute, m.name, m.default_unit, + m.device_class) + for m in maps]) + async_add_entities(sensors) + + +class SmartThingsSensor(SmartThingsEntity): + """Define a SmartThings Binary Sensor.""" + + def __init__(self, device, attribute: str, name: str, + default_unit: str, device_class: str): + """Init the class.""" + super().__init__(device) + self._attribute = attribute + self._name = name + self._device_class = device_class + self._default_unit = default_unit + + @property + def name(self) -> str: + """Return the name of the binary sensor.""" + return '{} {}'.format(self._device.label, self._name) + + @property + def unique_id(self) -> str: + """Return a unique ID.""" + return '{}.{}'.format(self._device.device_id, self._attribute) + + @property + def state(self): + """Return the state of the sensor.""" + return self._device.status.attributes[self._attribute].value + + @property + def device_class(self): + """Return the device class of the sensor.""" + return self._device_class + + @property + def unit_of_measurement(self): + """Return the unit this state is expressed in.""" + unit = self._device.status.attributes[self._attribute].unit + return UNITS.get(unit, unit) if unit else self._default_unit diff --git a/homeassistant/components/smartthings/smartapp.py b/homeassistant/components/smartthings/smartapp.py index 9d9dacf8460..89043d4f76c 100644 --- a/homeassistant/components/smartthings/smartapp.py +++ b/homeassistant/components/smartthings/smartapp.py @@ -22,8 +22,7 @@ from homeassistant.helpers.typing import HomeAssistantType from .const import ( APP_NAME_PREFIX, APP_OAUTH_SCOPES, CONF_APP_ID, CONF_INSTALLED_APP_ID, CONF_INSTANCE_ID, CONF_LOCATION_ID, DATA_BROKERS, DATA_MANAGER, DOMAIN, - SETTINGS_INSTANCE_ID, SIGNAL_SMARTAPP_PREFIX, STORAGE_KEY, STORAGE_VERSION, - SUPPORTED_CAPABILITIES) + SETTINGS_INSTANCE_ID, SIGNAL_SMARTAPP_PREFIX, STORAGE_KEY, STORAGE_VERSION) _LOGGER = logging.getLogger(__name__) @@ -176,6 +175,7 @@ async def setup_smartapp_endpoint(hass: HomeAssistantType): webhook.async_generate_path(config[CONF_WEBHOOK_ID]), dispatcher=dispatcher) manager.connect_install(functools.partial(smartapp_install, hass)) + manager.connect_update(functools.partial(smartapp_update, hass)) manager.connect_uninstall(functools.partial(smartapp_uninstall, hass)) webhook.async_register(hass, DOMAIN, 'SmartApp', @@ -189,6 +189,45 @@ async def setup_smartapp_endpoint(hass: HomeAssistantType): } +async def smartapp_sync_subscriptions( + hass: HomeAssistantType, auth_token: str, location_id: str, + installed_app_id: str, *, skip_delete=False): + """Synchronize subscriptions of an installed up.""" + from pysmartthings import ( + CAPABILITIES, SmartThings, SourceType, Subscription) + + api = SmartThings(async_get_clientsession(hass), auth_token) + devices = await api.devices(location_ids=[location_id]) + + # Build set of capabilities and prune unsupported ones + capabilities = set() + for device in devices: + capabilities.update(device.capabilities) + capabilities.intersection_update(CAPABILITIES) + + # Remove all (except for installs) + if not skip_delete: + await api.delete_subscriptions(installed_app_id) + + # Create for each capability + async def create_subscription(target): + sub = Subscription() + sub.installed_app_id = installed_app_id + sub.location_id = location_id + sub.source_type = SourceType.CAPABILITY + sub.capability = target + try: + await api.create_subscription(sub) + _LOGGER.debug("Created subscription for '%s' under app '%s'", + target, installed_app_id) + except Exception: # pylint:disable=broad-except + _LOGGER.exception("Failed to create subscription for '%s' under " + "app '%s'", target, installed_app_id) + + tasks = [create_subscription(c) for c in capabilities] + await asyncio.gather(*tasks) + + async def smartapp_install(hass: HomeAssistantType, req, resp, app): """ Handle when a SmartApp is installed by the user into a location. @@ -199,30 +238,9 @@ async def smartapp_install(hass: HomeAssistantType, req, resp, app): representing the installation if this is not the first installation under the account. """ - from pysmartthings import SmartThings, Subscription, SourceType - - # This access token is a temporary 'SmartApp token' that expires in 5 min - # and is used to create subscriptions only. - api = SmartThings(async_get_clientsession(hass), req.auth_token) - - async def create_subscription(target): - sub = Subscription() - sub.installed_app_id = req.installed_app_id - sub.location_id = req.location_id - sub.source_type = SourceType.CAPABILITY - sub.capability = target - try: - await api.create_subscription(sub) - _LOGGER.debug("Created subscription for '%s' under app '%s'", - target, req.installed_app_id) - except Exception: # pylint:disable=broad-except - _LOGGER.exception("Failed to create subscription for '%s' under " - "app '%s'", target, req.installed_app_id) - - tasks = [create_subscription(c) for c in SUPPORTED_CAPABILITIES] - await asyncio.gather(*tasks) - _LOGGER.debug("SmartApp '%s' under parent app '%s' was installed", - req.installed_app_id, app.app_id) + await smartapp_sync_subscriptions( + hass, req.auth_token, req.location_id, req.installed_app_id, + skip_delete=True) # The permanent access token is copied from another config flow with the # same parent app_id. If one is not found, that means the user is within @@ -244,6 +262,19 @@ async def smartapp_install(hass: HomeAssistantType, req, resp, app): }) +async def smartapp_update(hass: HomeAssistantType, req, resp, app): + """ + Handle when a SmartApp is updated (reconfigured) by the user. + + Synchronize subscriptions to ensure we're up-to-date. + """ + await smartapp_sync_subscriptions( + hass, req.auth_token, req.location_id, req.installed_app_id) + + _LOGGER.debug("SmartApp '%s' under parent app '%s' was updated", + req.installed_app_id, app.app_id) + + async def smartapp_uninstall(hass: HomeAssistantType, req, resp, app): """ Handle when a SmartApp is removed from a location by the user. diff --git a/requirements_all.txt b/requirements_all.txt index 452275bfcdc..6328e7f7135 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1231,7 +1231,7 @@ pysma==0.3.1 pysmartapp==0.3.0 # homeassistant.components.smartthings -pysmartthings==0.5.0 +pysmartthings==0.6.0 # homeassistant.components.device_tracker.snmp # homeassistant.components.sensor.snmp diff --git a/requirements_test_all.txt b/requirements_test_all.txt index dd455ef88fd..df750d69972 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -217,7 +217,7 @@ pyqwikswitch==0.8 pysmartapp==0.3.0 # homeassistant.components.smartthings -pysmartthings==0.5.0 +pysmartthings==0.6.0 # homeassistant.components.sonos pysonos==0.0.6 diff --git a/tests/components/smartthings/conftest.py b/tests/components/smartthings/conftest.py index 7358e05f346..c1a1769f04c 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -10,9 +10,10 @@ from pysmartthings.api import Api import pytest from homeassistant.components import webhook +from homeassistant.components.smartthings import DeviceBroker from homeassistant.components.smartthings.const import ( APP_NAME_PREFIX, CONF_APP_ID, CONF_INSTALLED_APP_ID, CONF_INSTANCE_ID, - CONF_LOCATION_ID, DOMAIN, SETTINGS_INSTANCE_ID, STORAGE_KEY, + CONF_LOCATION_ID, DATA_BROKERS, DOMAIN, SETTINGS_INSTANCE_ID, STORAGE_KEY, STORAGE_VERSION) from homeassistant.config_entries import ( CONN_CLASS_CLOUD_PUSH, SOURCE_USER, ConfigEntry) @@ -22,6 +23,23 @@ from homeassistant.setup import async_setup_component from tests.common import mock_coro +async def setup_platform(hass, platform: str, *devices): + """Set up the SmartThings platform and prerequisites.""" + hass.config.components.add(DOMAIN) + broker = DeviceBroker(hass, devices, '') + config_entry = ConfigEntry("1", DOMAIN, "Test", {}, + SOURCE_USER, CONN_CLASS_CLOUD_PUSH) + hass.data[DOMAIN] = { + DATA_BROKERS: { + config_entry.entry_id: broker + } + } + await hass.config_entries.async_forward_entry_setup( + config_entry, platform) + await hass.async_block_till_done() + return config_entry + + @pytest.fixture(autouse=True) async def setup_component(hass, config_file, hass_storage): """Load the SmartThing component.""" diff --git a/tests/components/smartthings/test_binary_sensor.py b/tests/components/smartthings/test_binary_sensor.py index 92d891c06d6..4b47537fa19 100644 --- a/tests/components/smartthings/test_binary_sensor.py +++ b/tests/components/smartthings/test_binary_sensor.py @@ -4,12 +4,12 @@ Test for the SmartThings binary_sensor platform. The only mocking required is of the underlying SmartThings API object so real HTTP calls are not initiated during testing. """ -from pysmartthings import Attribute, Capability +from pysmartthings import ATTRIBUTES, CAPABILITIES, Attribute, Capability from homeassistant.components.binary_sensor import DEVICE_CLASSES from homeassistant.components.smartthings import DeviceBroker, binary_sensor from homeassistant.components.smartthings.const import ( - DATA_BROKERS, DOMAIN, SIGNAL_SMARTTHINGS_UPDATE, SUPPORTED_CAPABILITIES) + DATA_BROKERS, DOMAIN, SIGNAL_SMARTTHINGS_UPDATE) from homeassistant.config_entries import ( CONN_CLASS_CLOUD_PUSH, SOURCE_USER, ConfigEntry) from homeassistant.const import ATTR_FRIENDLY_NAME @@ -35,14 +35,16 @@ async def _setup_platform(hass, *devices): async def test_mapping_integrity(): """Test ensures the map dicts have proper integrity.""" - # Ensure every CAPABILITY_TO_ATTRIB key is in SUPPORTED_CAPABILITIES + # Ensure every CAPABILITY_TO_ATTRIB key is in CAPABILITIES # Ensure every CAPABILITY_TO_ATTRIB value is in ATTRIB_TO_CLASS keys for capability, attrib in binary_sensor.CAPABILITY_TO_ATTRIB.items(): - assert capability in SUPPORTED_CAPABILITIES, capability + assert capability in CAPABILITIES, capability + assert attrib in ATTRIBUTES, attrib assert attrib in binary_sensor.ATTRIB_TO_CLASS.keys(), attrib # Ensure every ATTRIB_TO_CLASS value is in DEVICE_CLASSES - for device_class in binary_sensor.ATTRIB_TO_CLASS.values(): - assert device_class in DEVICE_CLASSES + for attrib, device_class in binary_sensor.ATTRIB_TO_CLASS.items(): + assert attrib in ATTRIBUTES, attrib + assert device_class in DEVICE_CLASSES, device_class async def test_async_setup_platform(): diff --git a/tests/components/smartthings/test_init.py b/tests/components/smartthings/test_init.py index 4aef42c1b6f..014cfe7da98 100644 --- a/tests/components/smartthings/test_init.py +++ b/tests/components/smartthings/test_init.py @@ -162,7 +162,7 @@ async def test_event_handler_dispatches_updated_devices( assert called for device in devices: - assert device.status.attributes['Updated'] == 'Value' + assert device.status.values['Updated'] == 'Value' async def test_event_handler_ignores_other_installed_app( diff --git a/tests/components/smartthings/test_sensor.py b/tests/components/smartthings/test_sensor.py new file mode 100644 index 00000000000..773f157dd87 --- /dev/null +++ b/tests/components/smartthings/test_sensor.py @@ -0,0 +1,97 @@ +""" +Test for the SmartThings sensors platform. + +The only mocking required is of the underlying SmartThings API object so +real HTTP calls are not initiated during testing. +""" +from pysmartthings import ATTRIBUTES, CAPABILITIES, Attribute, Capability + +from homeassistant.components.sensor import ( + DEVICE_CLASSES, DOMAIN as SENSOR_DOMAIN) +from homeassistant.components.smartthings import sensor +from homeassistant.components.smartthings.const import ( + DOMAIN, SIGNAL_SMARTTHINGS_UPDATE) +from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT +from homeassistant.helpers.dispatcher import async_dispatcher_send + +from .conftest import setup_platform + + +async def test_mapping_integrity(): + """Test ensures the map dicts have proper integrity.""" + for capability, maps in sensor.CAPABILITY_TO_SENSORS.items(): + assert capability in CAPABILITIES, capability + for sensor_map in maps: + assert sensor_map.attribute in ATTRIBUTES, sensor_map.attribute + if sensor_map.device_class: + assert sensor_map.device_class in DEVICE_CLASSES, \ + sensor_map.device_class + + +async def test_async_setup_platform(): + """Test setup platform does nothing (it uses config entries).""" + await sensor.async_setup_platform(None, None, None) + + +async def test_entity_state(hass, device_factory): + """Tests the state attributes properly match the light types.""" + device = device_factory('Sensor 1', [Capability.battery], + {Attribute.battery: 100}) + await setup_platform(hass, SENSOR_DOMAIN, device) + state = hass.states.get('sensor.sensor_1_battery') + assert state.state == '100' + assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == '%' + assert state.attributes[ATTR_FRIENDLY_NAME] ==\ + device.label + " Battery" + + +async def test_entity_and_device_attributes(hass, device_factory): + """Test the attributes of the entity are correct.""" + # Arrange + device = device_factory('Sensor 1', [Capability.battery], + {Attribute.battery: 100}) + entity_registry = await hass.helpers.entity_registry.async_get_registry() + device_registry = await hass.helpers.device_registry.async_get_registry() + # Act + await setup_platform(hass, SENSOR_DOMAIN, device) + # Assert + entry = entity_registry.async_get('sensor.sensor_1_battery') + assert entry + assert entry.unique_id == device.device_id + '.' + Attribute.battery + entry = device_registry.async_get_device( + {(DOMAIN, device.device_id)}, []) + assert entry + assert entry.name == device.label + assert entry.model == device.device_type_name + assert entry.manufacturer == 'Unavailable' + + +async def test_update_from_signal(hass, device_factory): + """Test the binary_sensor updates when receiving a signal.""" + # Arrange + device = device_factory('Sensor 1', [Capability.battery], + {Attribute.battery: 100}) + await setup_platform(hass, SENSOR_DOMAIN, device) + device.status.apply_attribute_update( + 'main', Capability.battery, Attribute.battery, 75) + # Act + async_dispatcher_send(hass, SIGNAL_SMARTTHINGS_UPDATE, + [device.device_id]) + # Assert + await hass.async_block_till_done() + state = hass.states.get('sensor.sensor_1_battery') + assert state is not None + assert state.state == '75' + + +async def test_unload_config_entry(hass, device_factory): + """Test the binary_sensor is removed when the config entry is unloaded.""" + # Arrange + device = device_factory('Sensor 1', [Capability.battery], + {Attribute.battery: 100}) + config_entry = await setup_platform(hass, SENSOR_DOMAIN, device) + # Act + await hass.config_entries.async_forward_entry_unload( + config_entry, 'sensor') + # Assert + assert not hass.states.get('sensor.sensor_1_battery') diff --git a/tests/components/smartthings/test_smartapp.py b/tests/components/smartthings/test_smartapp.py index 0f517222c4a..162a8f9a4e5 100644 --- a/tests/components/smartthings/test_smartapp.py +++ b/tests/components/smartthings/test_smartapp.py @@ -2,11 +2,10 @@ from unittest.mock import Mock, patch from uuid import uuid4 -from pysmartthings import AppEntity +from pysmartthings import AppEntity, Capability from homeassistant.components.smartthings import smartapp -from homeassistant.components.smartthings.const import ( - DATA_MANAGER, DOMAIN, SUPPORTED_CAPABILITIES) +from homeassistant.components.smartthings.const import DATA_MANAGER, DOMAIN from tests.common import mock_coro @@ -36,8 +35,10 @@ async def test_update_app_updated_needed(hass, app): assert mock_app.classifications == app.classifications -async def test_smartapp_install_abort_if_no_other(hass, smartthings_mock): +async def test_smartapp_install_abort_if_no_other( + hass, smartthings_mock, device_factory): """Test aborts if no other app was configured already.""" + # Arrange api = smartthings_mock.return_value api.create_subscription.return_value = mock_coro() app = Mock() @@ -46,17 +47,23 @@ async def test_smartapp_install_abort_if_no_other(hass, smartthings_mock): request.installed_app_id = uuid4() request.auth_token = uuid4() request.location_id = uuid4() - + devices = [ + device_factory('', [Capability.battery, 'ping']), + device_factory('', [Capability.switch, Capability.switch_level]), + device_factory('', [Capability.switch]) + ] + api.devices = Mock() + api.devices.return_value = mock_coro(return_value=devices) + # Act await smartapp.smartapp_install(hass, request, None, app) - + # Assert entries = hass.config_entries.async_entries('smartthings') assert not entries - assert api.create_subscription.call_count == \ - len(SUPPORTED_CAPABILITIES) + assert api.create_subscription.call_count == 3 async def test_smartapp_install_creates_flow( - hass, smartthings_mock, config_entry, location): + hass, smartthings_mock, config_entry, location, device_factory): """Test installation creates flow.""" # Arrange setattr(hass.config_entries, '_entries', [config_entry]) @@ -68,14 +75,20 @@ async def test_smartapp_install_creates_flow( request.installed_app_id = str(uuid4()) request.auth_token = str(uuid4()) request.location_id = location.location_id + devices = [ + device_factory('', [Capability.battery, 'ping']), + device_factory('', [Capability.switch, Capability.switch_level]), + device_factory('', [Capability.switch]) + ] + api.devices = Mock() + api.devices.return_value = mock_coro(return_value=devices) # Act await smartapp.smartapp_install(hass, request, None, app) # Assert await hass.async_block_till_done() entries = hass.config_entries.async_entries('smartthings') assert len(entries) == 2 - assert api.create_subscription.call_count == \ - len(SUPPORTED_CAPABILITIES) + assert api.create_subscription.call_count == 3 assert entries[1].data['app_id'] == app.app_id assert entries[1].data['installed_app_id'] == request.installed_app_id assert entries[1].data['location_id'] == request.location_id @@ -84,6 +97,35 @@ async def test_smartapp_install_creates_flow( assert entries[1].title == location.name +async def test_smartapp_update_syncs_subs( + hass, smartthings_mock, config_entry, location, device_factory): + """Test update synchronizes subscriptions.""" + # Arrange + setattr(hass.config_entries, '_entries', [config_entry]) + app = Mock() + app.app_id = config_entry.data['app_id'] + api = smartthings_mock.return_value + api.delete_subscriptions = Mock() + api.delete_subscriptions.return_value = mock_coro() + api.create_subscription.return_value = mock_coro() + request = Mock() + request.installed_app_id = str(uuid4()) + request.auth_token = str(uuid4()) + request.location_id = location.location_id + devices = [ + device_factory('', [Capability.battery, 'ping']), + device_factory('', [Capability.switch, Capability.switch_level]), + device_factory('', [Capability.switch]) + ] + api.devices = Mock() + api.devices.return_value = mock_coro(return_value=devices) + # Act + await smartapp.smartapp_update(hass, request, None, app) + # Assert + assert api.create_subscription.call_count == 3 + assert api.delete_subscriptions.call_count == 1 + + async def test_smartapp_uninstall(hass, config_entry): """Test the config entry is unloaded when the app is uninstalled.""" setattr(hass.config_entries, '_entries', [config_entry]) From 1e95719436393597ca01cdfe630cd9d474d5bcf7 Mon Sep 17 00:00:00 2001 From: Marvin Wichmann Date: Fri, 8 Feb 2019 08:28:52 +0100 Subject: [PATCH 104/242] Support knx tunable white and color temperature lights (#19699) * KNX: Bumped version to 0.9.4 and added support for tunable white and color temperature for lights. * Updated to the latest changes * return None when ct value is unknown - return None instead of default value when ct is unknown - remove DEFAULT_COLOR_TEMPERATURE * use Kelvin as base for relative color temperature use Kelvin as base for relative color temperature instead of Mireds * moved fallback value tests for clarity * Update request from oliverblaha Co-Authored-By: marvin-w * Address suggested changes * Update homeassistant/components/knx/light.py Co-Authored-By: marvin-w * Update homeassistant/components/knx/light.py Co-Authored-By: marvin-w --- homeassistant/components/knx/__init__.py | 2 +- homeassistant/components/knx/light.py | 166 ++++++++++++++++++++--- requirements_all.txt | 2 +- 3 files changed, 146 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 366ec4405cd..fae18bf8b77 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -17,7 +17,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.script import Script -REQUIREMENTS = ['xknx==0.9.3'] +REQUIREMENTS = ['xknx==0.9.4'] DOMAIN = "knx" DATA_KNX = "data_knx" diff --git a/homeassistant/components/knx/light.py b/homeassistant/components/knx/light.py index a1423cc6682..bb92f1d0ce0 100644 --- a/homeassistant/components/knx/light.py +++ b/homeassistant/components/knx/light.py @@ -4,28 +4,50 @@ Support for KNX/IP lights. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.knx/ """ +from enum import Enum import voluptuous as vol -from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_HS_COLOR, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, Light) + ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, PLATFORM_SCHEMA, + SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, + Light) from homeassistant.const import CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv import homeassistant.util.color as color_util +from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX + + CONF_ADDRESS = 'address' CONF_STATE_ADDRESS = 'state_address' CONF_BRIGHTNESS_ADDRESS = 'brightness_address' CONF_BRIGHTNESS_STATE_ADDRESS = 'brightness_state_address' CONF_COLOR_ADDRESS = 'color_address' CONF_COLOR_STATE_ADDRESS = 'color_state_address' +CONF_COLOR_TEMP_ADDRESS = 'color_temperature_address' +CONF_COLOR_TEMP_STATE_ADDRESS = 'color_temperature_state_address' +CONF_COLOR_TEMP_MODE = 'color_temperature_mode' +CONF_MIN_KELVIN = 'min_kelvin' +CONF_MAX_KELVIN = 'max_kelvin' DEFAULT_NAME = 'KNX Light' +DEFAULT_COLOR = [255, 255, 255] +DEFAULT_BRIGHTNESS = 255 +DEFAULT_COLOR_TEMP_MODE = 'absolute' +DEFAULT_MIN_KELVIN = 2700 # 370 mireds +DEFAULT_MAX_KELVIN = 6000 # 166 mireds DEPENDENCIES = ['knx'] + +class ColorTempModes(Enum): + """Color temperature modes for config validation.""" + + absolute = "DPT-7.600" + relative = "DPT-5.001" + + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_ADDRESS): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, @@ -34,6 +56,14 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): cv.string, vol.Optional(CONF_COLOR_ADDRESS): cv.string, vol.Optional(CONF_COLOR_STATE_ADDRESS): cv.string, + vol.Optional(CONF_COLOR_TEMP_ADDRESS): cv.string, + vol.Optional(CONF_COLOR_TEMP_STATE_ADDRESS): cv.string, + vol.Optional(CONF_COLOR_TEMP_MODE, default=DEFAULT_COLOR_TEMP_MODE): + cv.enum(ColorTempModes), + vol.Optional(CONF_MIN_KELVIN, default=DEFAULT_MIN_KELVIN): + vol.All(vol.Coerce(int), vol.Range(min=1)), + vol.Optional(CONF_MAX_KELVIN, default=DEFAULT_MAX_KELVIN): + vol.All(vol.Coerce(int), vol.Range(min=1)), }) @@ -60,16 +90,36 @@ def async_add_entities_discovery(hass, discovery_info, async_add_entities): def async_add_entities_config(hass, config, async_add_entities): """Set up light for KNX platform configured within platform.""" import xknx + + group_address_tunable_white = None + group_address_tunable_white_state = None + group_address_color_temp = None + group_address_color_temp_state = None + if config[CONF_COLOR_TEMP_MODE] == ColorTempModes.absolute: + group_address_color_temp = config.get(CONF_COLOR_TEMP_ADDRESS) + group_address_color_temp_state = \ + config.get(CONF_COLOR_TEMP_STATE_ADDRESS) + elif config[CONF_COLOR_TEMP_MODE] == ColorTempModes.relative: + group_address_tunable_white = config.get(CONF_COLOR_TEMP_ADDRESS) + group_address_tunable_white_state = \ + config.get(CONF_COLOR_TEMP_STATE_ADDRESS) + light = xknx.devices.Light( hass.data[DATA_KNX].xknx, - name=config.get(CONF_NAME), - group_address_switch=config.get(CONF_ADDRESS), + name=config[CONF_NAME], + group_address_switch=config[CONF_ADDRESS], group_address_switch_state=config.get(CONF_STATE_ADDRESS), group_address_brightness=config.get(CONF_BRIGHTNESS_ADDRESS), group_address_brightness_state=config.get( CONF_BRIGHTNESS_STATE_ADDRESS), group_address_color=config.get(CONF_COLOR_ADDRESS), - group_address_color_state=config.get(CONF_COLOR_STATE_ADDRESS)) + group_address_color_state=config.get(CONF_COLOR_STATE_ADDRESS), + group_address_tunable_white=group_address_tunable_white, + group_address_tunable_white_state=group_address_tunable_white_state, + group_address_color_temperature=group_address_color_temp, + group_address_color_temperature_state=group_address_color_temp_state, + min_kelvin=config[CONF_MIN_KELVIN], + max_kelvin=config[CONF_MAX_KELVIN]) hass.data[DATA_KNX].xknx.devices.add(light) async_add_entities([KNXLight(light)]) @@ -81,6 +131,13 @@ class KNXLight(Light): """Initialize of KNX light.""" self.device = device + self._min_kelvin = device.min_kelvin + self._max_kelvin = device.max_kelvin + self._min_mireds = \ + color_util.color_temperature_kelvin_to_mired(self._max_kelvin) + self._max_mireds = \ + color_util.color_temperature_kelvin_to_mired(self._min_kelvin) + @callback def async_register_callbacks(self): """Register callbacks to update hass after device was changed.""" @@ -111,26 +168,51 @@ class KNXLight(Light): @property def brightness(self): """Return the brightness of this light between 0..255.""" - return self.device.current_brightness \ - if self.device.supports_brightness else \ - None + if self.device.supports_color: + if self.device.current_color is None: + return None + return max(self.device.current_color) + if self.device.supports_brightness: + return self.device.current_brightness + return None @property def hs_color(self): """Return the HS color value.""" if self.device.supports_color: - return color_util.color_RGB_to_hs(*self.device.current_color) + rgb = self.device.current_color + if rgb is None: + return None + return color_util.color_RGB_to_hs(*rgb) return None @property def color_temp(self): - """Return the CT color temperature.""" + """Return the color temperature in mireds.""" + if self.device.supports_color_temperature: + kelvin = self.device.current_color_temperature + if kelvin is not None: + return color_util.color_temperature_kelvin_to_mired(kelvin) + if self.device.supports_tunable_white: + relative_ct = self.device.current_tunable_white + if relative_ct is not None: + # as KNX devices typically use Kelvin we use it as base for + # calculating ct from percent + return color_util.color_temperature_kelvin_to_mired( + self._min_kelvin + ( + (relative_ct / 255) * + (self._max_kelvin - self._min_kelvin))) return None @property - def white_value(self): - """Return the white value of this light between 0..255.""" - return None + def min_mireds(self): + """Return the coldest color temp this light supports in mireds.""" + return self._min_mireds + + @property + def max_mireds(self): + """Return the warmest color temp this light supports in mireds.""" + return self._max_mireds @property def effect_list(self): @@ -154,19 +236,59 @@ class KNXLight(Light): if self.device.supports_brightness: flags |= SUPPORT_BRIGHTNESS if self.device.supports_color: - flags |= SUPPORT_COLOR + flags |= SUPPORT_COLOR | SUPPORT_BRIGHTNESS + if self.device.supports_color_temperature or \ + self.device.supports_tunable_white: + flags |= SUPPORT_COLOR_TEMP return flags async def async_turn_on(self, **kwargs): """Turn the light on.""" - if ATTR_BRIGHTNESS in kwargs: - if self.device.supports_brightness: - await self.device.set_brightness(int(kwargs[ATTR_BRIGHTNESS])) - elif ATTR_HS_COLOR in kwargs: - if self.device.supports_color: - await self.device.set_color(color_util.color_hs_to_RGB( - *kwargs[ATTR_HS_COLOR])) + brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness) + hs_color = kwargs.get(ATTR_HS_COLOR, self.hs_color) + mireds = kwargs.get(ATTR_COLOR_TEMP, self.color_temp) + + update_brightness = ATTR_BRIGHTNESS in kwargs + update_color = ATTR_HS_COLOR in kwargs + update_color_temp = ATTR_COLOR_TEMP in kwargs + + # always only go one path for turning on (avoid conflicting changes + # and weird effects) + if self.device.supports_brightness and \ + (update_brightness and not update_color): + # if we don't need to update the color, try updating brightness + # directly if supported; don't do it if color also has to be + # changed, as RGB color implicitly sets the brightness as well + await self.device.set_brightness(brightness) + elif self.device.supports_color and \ + (update_brightness or update_color): + # change RGB color (includes brightness) + # if brightness or hs_color was not yet set use the default value + # to calculate RGB from as a fallback + if brightness is None: + brightness = DEFAULT_BRIGHTNESS + if hs_color is None: + hs_color = DEFAULT_COLOR + await self.device.set_color( + color_util.color_hsv_to_RGB(*hs_color, brightness * 100 / 255)) + elif self.device.supports_color_temperature and \ + update_color_temp: + # change color temperature without ON telegram + kelvin = int(color_util.color_temperature_mired_to_kelvin(mireds)) + if kelvin > self._max_kelvin: + kelvin = self._max_kelvin + elif kelvin < self._min_kelvin: + kelvin = self._min_kelvin + await self.device.set_color_temperature(kelvin) + elif self.device.supports_tunable_white and \ + update_color_temp: + # calculate relative_ct from Kelvin to fit typical KNX devices + kelvin = int(color_util.color_temperature_mired_to_kelvin(mireds)) + relative_ct = int(255 * (kelvin - self._min_kelvin) / + (self._max_kelvin - self._min_kelvin)) + await self.device.set_tunable_white(relative_ct) else: + # no color/brightness change requested, so just turn it on await self.device.set_on() async def async_turn_off(self, **kwargs): diff --git a/requirements_all.txt b/requirements_all.txt index 6328e7f7135..7033b48b414 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1745,7 +1745,7 @@ xbee-helper==0.0.7 xboxapi==0.1.1 # homeassistant.components.knx -xknx==0.9.3 +xknx==0.9.4 # homeassistant.components.media_player.bluesound # homeassistant.components.sensor.startca From 55d1d3d8ae2813ae3f0e5cc320e2f7831812d25b Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Fri, 8 Feb 2019 09:55:58 +0000 Subject: [PATCH 105/242] Move weather.ipma into a component (#20706) * initial version * works * lint * move * hound * fix formatting * update * add extra features * houmd * docstring * fix tests * lint * update requirements_all.txt * new tests * lint * update CODEOWNERS * MockDependency pyipma * hound * bump pyipma version * add config_flow tests * lint * improve test coverage * fix test * address comments by @MartinHjelmare * remove device_info * hound * stale comment * Add deprecation warning * address comments * lint --- CODEOWNERS | 3 + .../components/ipma/.translations/en.json | 18 +++ homeassistant/components/ipma/__init__.py | 31 +++++ homeassistant/components/ipma/config_flow.py | 53 ++++++++ homeassistant/components/ipma/const.py | 14 +++ homeassistant/components/ipma/strings.json | 19 +++ .../{weather/ipma.py => ipma/weather.py} | 48 ++++++- homeassistant/config_entries.py | 1 + requirements_all.txt | 4 +- tests/components/ipma/__init__.py | 1 + tests/components/ipma/test_config_flow.py | 118 ++++++++++++++++++ tests/components/ipma/test_weather.py | 103 +++++++++++++++ tests/components/weather/test_ipma.py | 85 ------------- 13 files changed, 408 insertions(+), 90 deletions(-) create mode 100644 homeassistant/components/ipma/.translations/en.json create mode 100644 homeassistant/components/ipma/__init__.py create mode 100644 homeassistant/components/ipma/config_flow.py create mode 100644 homeassistant/components/ipma/const.py create mode 100644 homeassistant/components/ipma/strings.json rename homeassistant/components/{weather/ipma.py => ipma/weather.py} (82%) create mode 100644 tests/components/ipma/__init__.py create mode 100644 tests/components/ipma/test_config_flow.py create mode 100644 tests/components/ipma/test_weather.py delete mode 100644 tests/components/weather/test_ipma.py diff --git a/CODEOWNERS b/CODEOWNERS index b9f5cec0d64..a10be84472a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -198,6 +198,9 @@ homeassistant/components/homekit/* @cdce8p homeassistant/components/huawei_lte/* @scop homeassistant/components/*/huawei_lte.py @scop +# I +homeassistant/components/ipma/* @dgomes + # K homeassistant/components/knx/* @Julius2342 homeassistant/components/*/knx.py @Julius2342 diff --git a/homeassistant/components/ipma/.translations/en.json b/homeassistant/components/ipma/.translations/en.json new file mode 100644 index 00000000000..d1386757305 --- /dev/null +++ b/homeassistant/components/ipma/.translations/en.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "name_exists": "Name already exists" + }, + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Name" + }, + "title": "Location" + } + }, + "title": "Portuguese weather service (IPMA)" + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/__init__.py b/homeassistant/components/ipma/__init__.py new file mode 100644 index 00000000000..87f62371b55 --- /dev/null +++ b/homeassistant/components/ipma/__init__.py @@ -0,0 +1,31 @@ +""" +Component for the Portuguese weather service - IPMA. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/ipma/ +""" +from homeassistant.core import Config, HomeAssistant +from .config_flow import IpmaFlowHandler # noqa +from .const import DOMAIN # noqa + +DEFAULT_NAME = 'ipma' + + +async def async_setup(hass: HomeAssistant, config: Config) -> bool: + """Set up configured IPMA.""" + # No support for component configuration + return True + + +async def async_setup_entry(hass, config_entry): + """Set up IPMA station as config entry.""" + hass.async_create_task(hass.config_entries.async_forward_entry_setup( + config_entry, 'weather')) + return True + + +async def async_unload_entry(hass, config_entry): + """Unload a config entry.""" + await hass.config_entries.async_forward_entry_unload( + config_entry, 'weather') + return True diff --git a/homeassistant/components/ipma/config_flow.py b/homeassistant/components/ipma/config_flow.py new file mode 100644 index 00000000000..bb42a00742e --- /dev/null +++ b/homeassistant/components/ipma/config_flow.py @@ -0,0 +1,53 @@ +"""Config flow to configure IPMA component.""" +import voluptuous as vol + +from homeassistant import config_entries, data_entry_flow +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +import homeassistant.helpers.config_validation as cv + +from .const import DOMAIN, HOME_LOCATION_NAME + + +@config_entries.HANDLERS.register(DOMAIN) +class IpmaFlowHandler(data_entry_flow.FlowHandler): + """Config flow for IPMA component.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + def __init__(self): + """Init IpmaFlowHandler.""" + self._errors = {} + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + self._errors = {} + + if user_input is not None: + if user_input[CONF_NAME] not in\ + self.hass.config_entries.async_entries(DOMAIN): + return self.async_create_entry( + title=user_input[CONF_NAME], + data=user_input, + ) + + self._errors[CONF_NAME] = 'name_exists' + + # default location is set hass configuration + return await self._show_config_form( + name=HOME_LOCATION_NAME, + latitude=self.hass.config.latitude, + longitude=self.hass.config.longitude) + + async def _show_config_form(self, name=None, latitude=None, + longitude=None): + """Show the configuration form to edit location data.""" + return self.async_show_form( + step_id='user', + data_schema=vol.Schema({ + vol.Required(CONF_NAME, default=name): str, + vol.Required(CONF_LATITUDE, default=latitude): cv.latitude, + vol.Required(CONF_LONGITUDE, default=longitude): cv.longitude + }), + errors=self._errors, + ) diff --git a/homeassistant/components/ipma/const.py b/homeassistant/components/ipma/const.py new file mode 100644 index 00000000000..dbb19294541 --- /dev/null +++ b/homeassistant/components/ipma/const.py @@ -0,0 +1,14 @@ +"""Constants in ipma component.""" +import logging + +from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN + +DOMAIN = 'ipma' + +HOME_LOCATION_NAME = 'Home' + +ENTITY_ID_SENSOR_FORMAT = WEATHER_DOMAIN + ".ipma_{}" +ENTITY_ID_SENSOR_FORMAT_HOME = ENTITY_ID_SENSOR_FORMAT.format( + HOME_LOCATION_NAME) + +_LOGGER = logging.getLogger('homeassistant.components.ipma') diff --git a/homeassistant/components/ipma/strings.json b/homeassistant/components/ipma/strings.json new file mode 100644 index 00000000000..f22d1b62fe4 --- /dev/null +++ b/homeassistant/components/ipma/strings.json @@ -0,0 +1,19 @@ +{ + "config": { + "title": "Portuguese weather service (IPMA)", + "step": { + "user": { + "title": "Location", + "description": "Instituto Português do Mar e Atmosfera", + "data": { + "name": "Name", + "latitude": "Latitude", + "longitude": "Longitude" + } + } + }, + "error": { + "name_exists": "Name already exists" + } + } +} diff --git a/homeassistant/components/weather/ipma.py b/homeassistant/components/ipma/weather.py similarity index 82% rename from homeassistant/components/weather/ipma.py rename to homeassistant/components/ipma/weather.py index fda0fef4f25..ec9b6fec2e8 100644 --- a/homeassistant/components/weather/ipma.py +++ b/homeassistant/components/ipma/weather.py @@ -20,7 +20,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers import config_validation as cv from homeassistant.util import Throttle -REQUIREMENTS = ['pyipma==1.1.6'] +REQUIREMENTS = ['pyipma==1.2.1'] _LOGGER = logging.getLogger(__name__) @@ -56,7 +56,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the ipma platform.""" + """Set up the ipma platform. + + Deprecated. + """ + _LOGGER.warning('Loading IPMA via platform config is deprecated') + latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) @@ -64,6 +69,23 @@ async def async_setup_platform(hass, config, async_add_entities, _LOGGER.error("Latitude or longitude not set in Home Assistant config") return + station = await async_get_station(hass, latitude, longitude) + + async_add_entities([IPMAWeather(station, config)], True) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Add a weather entity from a config_entry.""" + latitude = config_entry.data[CONF_LATITUDE] + longitude = config_entry.data[CONF_LONGITUDE] + + station = await async_get_station(hass, latitude, longitude) + + async_add_entities([IPMAWeather(station, config_entry.data)], True) + + +async def async_get_station(hass, latitude, longitude): + """Retrieve weather station, station name to be used as the entity name.""" from pyipma import Station websession = async_get_clientsession(hass) @@ -74,7 +96,7 @@ async def async_setup_platform(hass, config, async_add_entities, _LOGGER.debug("Initializing for coordinates %s, %s -> station %s", latitude, longitude, station.local) - async_add_entities([IPMAWeather(station, config)], True) + return station class IPMAWeather(WeatherEntity): @@ -103,6 +125,11 @@ class IPMAWeather(WeatherEntity): self._forecast = await self._station.forecast() self._description = self._forecast[0].description + @property + def unique_id(self) -> str: + """Return a unique id.""" + return '{}, {}'.format(self._station.latitude, self._station.longitude) + @property def attribution(self): """Return the attribution.""" @@ -125,26 +152,41 @@ class IPMAWeather(WeatherEntity): @property def temperature(self): """Return the current temperature.""" + if not self._condition: + return None + return self._condition.temperature @property def pressure(self): """Return the current pressure.""" + if not self._condition: + return None + return self._condition.pressure @property def humidity(self): """Return the name of the sensor.""" + if not self._condition: + return None + return self._condition.humidity @property def wind_speed(self): """Return the current windspeed.""" + if not self._condition: + return None + return self._condition.windspeed @property def wind_bearing(self): """Return the current wind bearing (degrees).""" + if not self._condition: + return None + return self._condition.winddirection @property diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index c72f0f22827..7c6da2644f6 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -149,6 +149,7 @@ FLOWS = [ 'hue', 'ifttt', 'ios', + 'ipma', 'lifx', 'locative', 'luftdaten', diff --git a/requirements_all.txt b/requirements_all.txt index 7033b48b414..8b2a0a7cc6b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1063,8 +1063,8 @@ pyialarm==0.3 # homeassistant.components.device_tracker.icloud pyicloud==0.9.1 -# homeassistant.components.weather.ipma -pyipma==1.1.6 +# homeassistant.components.ipma.weather +pyipma==1.2.1 # homeassistant.components.sensor.irish_rail_transport pyirishrail==0.0.2 diff --git a/tests/components/ipma/__init__.py b/tests/components/ipma/__init__.py new file mode 100644 index 00000000000..35099c405bb --- /dev/null +++ b/tests/components/ipma/__init__.py @@ -0,0 +1 @@ +"""Tests for the IPMA component.""" diff --git a/tests/components/ipma/test_config_flow.py b/tests/components/ipma/test_config_flow.py new file mode 100644 index 00000000000..4a72318128e --- /dev/null +++ b/tests/components/ipma/test_config_flow.py @@ -0,0 +1,118 @@ +"""Tests for IPMA config flow.""" +from unittest.mock import Mock, patch + +from tests.common import mock_coro + +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE +from homeassistant.components.ipma import config_flow + + +async def test_show_config_form(): + """Test show configuration form.""" + hass = Mock() + flow = config_flow.IpmaFlowHandler() + flow.hass = hass + + result = await flow._show_config_form() + + assert result['type'] == 'form' + assert result['step_id'] == 'user' + + +async def test_show_config_form_default_values(): + """Test show configuration form.""" + hass = Mock() + flow = config_flow.IpmaFlowHandler() + flow.hass = hass + + result = await flow._show_config_form( + name="test", latitude='0', longitude='0') + + assert result['type'] == 'form' + assert result['step_id'] == 'user' + + +async def test_flow_with_home_location(hass): + """Test config flow . + + Tests the flow when a default location is configured + then it should return a form with default values + """ + flow = config_flow.IpmaFlowHandler() + flow.hass = hass + + hass.config.location_name = 'Home' + hass.config.latitude = 1 + hass.config.longitude = 1 + + result = await flow.async_step_user() + assert result['type'] == 'form' + assert result['step_id'] == 'user' + + +async def test_flow_show_form(): + """Test show form scenarios first time. + + Test when the form should show when no configurations exists + """ + hass = Mock() + flow = config_flow.IpmaFlowHandler() + flow.hass = hass + + with \ + patch.object(flow, '_show_config_form', + return_value=mock_coro()) as config_form: + await flow.async_step_user() + assert len(config_form.mock_calls) == 1 + + +async def test_flow_entry_created_from_user_input(): + """Test that create data from user input. + + Test when the form should show when no configurations exists + """ + hass = Mock() + flow = config_flow.IpmaFlowHandler() + flow.hass = hass + + test_data = {'name': 'home', CONF_LONGITUDE: '0', CONF_LATITUDE: '0'} + + # Test that entry created when user_input name not exists + with \ + patch.object(flow, '_show_config_form', + return_value=mock_coro()) as config_form,\ + patch.object(flow.hass.config_entries, 'async_entries', + return_value=mock_coro()) as config_entries: + + result = await flow.async_step_user(user_input=test_data) + + assert result['type'] == 'create_entry' + assert result['data'] == test_data + assert len(config_entries.mock_calls) == 1 + assert not config_form.mock_calls + + +async def test_flow_entry_config_entry_already_exists(): + """Test that create data from user input and config_entry already exists. + + Test when the form should show when user puts existing name + in the config gui. Then the form should show with error + """ + hass = Mock() + flow = config_flow.IpmaFlowHandler() + flow.hass = hass + + test_data = {'name': 'home', CONF_LONGITUDE: '0', CONF_LATITUDE: '0'} + + # Test that entry created when user_input name not exists + with \ + patch.object(flow, '_show_config_form', + return_value=mock_coro()) as config_form,\ + patch.object(flow.hass.config_entries, 'async_entries', + return_value={'home': test_data}) as config_entries: + + await flow.async_step_user(user_input=test_data) + + assert len(config_form.mock_calls) == 1 + assert len(config_entries.mock_calls) == 1 + assert len(flow._errors) == 1 diff --git a/tests/components/ipma/test_weather.py b/tests/components/ipma/test_weather.py new file mode 100644 index 00000000000..c141fbae7f1 --- /dev/null +++ b/tests/components/ipma/test_weather.py @@ -0,0 +1,103 @@ +"""The tests for the IPMA weather component.""" +from unittest.mock import patch +from collections import namedtuple + +from homeassistant.components import weather +from homeassistant.components.weather import ( + ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_PRESSURE, ATTR_WEATHER_TEMPERATURE, + ATTR_WEATHER_WIND_BEARING, ATTR_WEATHER_WIND_SPEED, + DOMAIN as WEATHER_DOMAIN) + +from tests.common import MockConfigEntry, mock_coro +from homeassistant.setup import async_setup_component + +TEST_CONFIG = { + "name": "HomeTown", + "latitude": "40.00", + "longitude": "-8.00", +} + + +class MockStation(): + """Mock Station from pyipma.""" + + async def observation(self): + """Mock Observation.""" + Observation = namedtuple('Observation', ['temperature', 'humidity', + 'windspeed', 'winddirection', + 'precipitation', 'pressure', + 'description']) + + return Observation(18, 71.0, 3.94, 'NW', 0, 1000.0, '---') + + async def forecast(self): + """Mock Forecast.""" + Forecast = namedtuple('Forecast', ['precipitaProb', 'tMin', 'tMax', + 'predWindDir', 'idWeatherType', + 'classWindSpeed', 'longitude', + 'forecastDate', 'classPrecInt', + 'latitude', 'description']) + + return [Forecast(73.0, 13.7, 18.7, 'NW', 6, 2, -8.64, + '2018-05-31', 2, 40.61, + 'Aguaceiros, com vento Moderado de Noroeste')] + + @property + def local(self): + """Mock location.""" + return "HomeTown" + + @property + def latitude(self): + """Mock latitude.""" + return 0 + + @property + def longitude(self): + """Mock longitude.""" + return 0 + + +async def test_setup_configuration(hass): + """Test for successfully setting up the IPMA platform.""" + with patch('homeassistant.components.ipma.weather.async_get_station', + return_value=mock_coro(MockStation())): + assert await async_setup_component(hass, weather.DOMAIN, { + 'weather': { + 'name': 'HomeTown', + 'platform': 'ipma', + } + }) + await hass.async_block_till_done() + + state = hass.states.get('weather.hometown') + assert state.state == 'rainy' + + data = state.attributes + assert data.get(ATTR_WEATHER_TEMPERATURE) == 18.0 + assert data.get(ATTR_WEATHER_HUMIDITY) == 71 + assert data.get(ATTR_WEATHER_PRESSURE) == 1000.0 + assert data.get(ATTR_WEATHER_WIND_SPEED) == 3.94 + assert data.get(ATTR_WEATHER_WIND_BEARING) == 'NW' + assert state.attributes.get('friendly_name') == 'HomeTown' + + +async def test_setup_config_flow(hass): + """Test for successfully setting up the IPMA platform.""" + with patch('homeassistant.components.ipma.weather.async_get_station', + return_value=mock_coro(MockStation())): + entry = MockConfigEntry(domain='ipma', data=TEST_CONFIG) + await hass.config_entries.async_forward_entry_setup( + entry, WEATHER_DOMAIN) + await hass.async_block_till_done() + + state = hass.states.get('weather.hometown') + assert state.state == 'rainy' + + data = state.attributes + assert data.get(ATTR_WEATHER_TEMPERATURE) == 18.0 + assert data.get(ATTR_WEATHER_HUMIDITY) == 71 + assert data.get(ATTR_WEATHER_PRESSURE) == 1000.0 + assert data.get(ATTR_WEATHER_WIND_SPEED) == 3.94 + assert data.get(ATTR_WEATHER_WIND_BEARING) == 'NW' + assert state.attributes.get('friendly_name') == 'HomeTown' diff --git a/tests/components/weather/test_ipma.py b/tests/components/weather/test_ipma.py deleted file mode 100644 index c7c89ecdbdb..00000000000 --- a/tests/components/weather/test_ipma.py +++ /dev/null @@ -1,85 +0,0 @@ -"""The tests for the IPMA weather component.""" -import unittest -from unittest.mock import patch -from collections import namedtuple - -from homeassistant.components import weather -from homeassistant.components.weather import ( - ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_PRESSURE, ATTR_WEATHER_TEMPERATURE, - ATTR_WEATHER_WIND_BEARING, ATTR_WEATHER_WIND_SPEED) -from homeassistant.util.unit_system import METRIC_SYSTEM -from homeassistant.setup import setup_component - -from tests.common import get_test_home_assistant, MockDependency - - -class MockStation(): - """Mock Station from pyipma.""" - - @classmethod - async def get(cls, websession, lat, lon): - """Mock Factory.""" - return MockStation() - - async def observation(self): - """Mock Observation.""" - Observation = namedtuple('Observation', ['temperature', 'humidity', - 'windspeed', 'winddirection', - 'precipitation', 'pressure', - 'description']) - - return Observation(18, 71.0, 3.94, 'NW', 0, 1000.0, '---') - - async def forecast(self): - """Mock Forecast.""" - Forecast = namedtuple('Forecast', ['precipitaProb', 'tMin', 'tMax', - 'predWindDir', 'idWeatherType', - 'classWindSpeed', 'longitude', - 'forecastDate', 'classPrecInt', - 'latitude', 'description']) - - return [Forecast(73.0, 13.7, 18.7, 'NW', 6, 2, -8.64, - '2018-05-31', 2, 40.61, - 'Aguaceiros, com vento Moderado de Noroeste')] - - @property - def local(self): - """Mock location.""" - return "HomeTown" - - -class TestIPMA(unittest.TestCase): - """Test the IPMA weather component.""" - - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.hass.config.units = METRIC_SYSTEM - self.lat = self.hass.config.latitude = 40.00 - self.lon = self.hass.config.longitude = -8.00 - - def tearDown(self): - """Stop down everything that was started.""" - self.hass.stop() - - @MockDependency("pyipma") - @patch("pyipma.Station", new=MockStation) - def test_setup(self, mock_pyipma): - """Test for successfully setting up the IPMA platform.""" - assert setup_component(self.hass, weather.DOMAIN, { - 'weather': { - 'name': 'HomeTown', - 'platform': 'ipma', - } - }) - - state = self.hass.states.get('weather.hometown') - assert state.state == 'rainy' - - data = state.attributes - assert data.get(ATTR_WEATHER_TEMPERATURE) == 18.0 - assert data.get(ATTR_WEATHER_HUMIDITY) == 71 - assert data.get(ATTR_WEATHER_PRESSURE) == 1000.0 - assert data.get(ATTR_WEATHER_WIND_SPEED) == 3.94 - assert data.get(ATTR_WEATHER_WIND_BEARING) == 'NW' - assert state.attributes.get('friendly_name') == 'HomeTown' From ee3631e93e409d81b3fecc5a51431fe99e39d203 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Fri, 8 Feb 2019 10:00:51 +0000 Subject: [PATCH 106/242] Fix homekit_controller non-standard hk characteristics (#20824) --- homeassistant/components/homekit_controller/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index 72b7a502aa2..77d0825ef0b 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -224,7 +224,12 @@ class HomeKitEntity(Entity): if service['iid'] != self._iid: continue for char in service['characteristics']: - uuid = CharacteristicsTypes.get_uuid(char['type']) + try: + uuid = CharacteristicsTypes.get_uuid(char['type']) + except KeyError: + # If a KeyError is raised its a non-standard + # characteristic. We must ignore it in this case. + continue if uuid not in characteristic_types: continue self._setup_characteristic(char) From d5fad335992c185fe668574574240f109cd5bc69 Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Fri, 8 Feb 2019 02:14:50 -0800 Subject: [PATCH 107/242] Add better handling of deprecated configs (#20565) * Add better handling of deprecated configs * Embed the call to has_at_most_one_key in deprecated * Add tests for checking the deprecated logs * Add thoroughly documented tests * Always check has_at_most_one_key * Fix typing * Move logging helpers to homea new logging helper * Lint * Rename to KeywordMessage instead of BraceMessage * Remove unneeded KeywordStyleAdapter * Lint * Use dict directly rather than dict.keys() when creating set * Patch the version in unit tests, update logging and use parse_version * Re-add KeywordStyleAdapter and fix tests * Lint * Lint --- homeassistant/components/freedns/__init__.py | 32 +- homeassistant/helpers/config_validation.py | 111 ++++++- homeassistant/helpers/logging.py | 49 +++ tests/components/freedns/test_init.py | 2 +- tests/helpers/test_config_validation.py | 333 ++++++++++++++++++- 5 files changed, 487 insertions(+), 40 deletions(-) create mode 100644 homeassistant/helpers/logging.py diff --git a/homeassistant/components/freedns/__init__.py b/homeassistant/components/freedns/__init__.py index ec38bb59cc7..7da51cd42e4 100644 --- a/homeassistant/components/freedns/__init__.py +++ b/homeassistant/components/freedns/__init__.py @@ -13,7 +13,7 @@ import async_timeout import voluptuous as vol from homeassistant.const import (CONF_URL, CONF_ACCESS_TOKEN, - CONF_UPDATE_INTERVAL) + CONF_UPDATE_INTERVAL, CONF_SCAN_INTERVAL) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -26,21 +26,31 @@ TIMEOUT = 10 UPDATE_URL = 'https://freedns.afraid.org/dynamic/update.php' CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Exclusive(CONF_URL, DOMAIN): cv.string, - vol.Exclusive(CONF_ACCESS_TOKEN, DOMAIN): cv.string, - vol.Optional(CONF_UPDATE_INTERVAL, default=DEFAULT_INTERVAL): vol.All( - cv.time_period, cv.positive_timedelta), - - }) + DOMAIN: vol.All( + vol.Schema({ + vol.Exclusive(CONF_URL, DOMAIN): cv.string, + vol.Exclusive(CONF_ACCESS_TOKEN, DOMAIN): cv.string, + vol.Optional(CONF_UPDATE_INTERVAL): + vol.All(cv.time_period, cv.positive_timedelta), + vol.Optional(CONF_SCAN_INTERVAL): + vol.All(cv.time_period, cv.positive_timedelta), + }), + cv.deprecated( + CONF_UPDATE_INTERVAL, + replacement_key=CONF_SCAN_INTERVAL, + invalidation_version='1.0.0', + default=DEFAULT_INTERVAL + ) + ) }, extra=vol.ALLOW_EXTRA) async def async_setup(hass, config): """Initialize the FreeDNS component.""" - url = config[DOMAIN].get(CONF_URL) - auth_token = config[DOMAIN].get(CONF_ACCESS_TOKEN) - update_interval = config[DOMAIN].get(CONF_UPDATE_INTERVAL) + conf = config[DOMAIN] + url = conf.get(CONF_URL) + auth_token = conf.get(CONF_ACCESS_TOKEN) + update_interval = conf[CONF_SCAN_INTERVAL] session = hass.helpers.aiohttp_client.async_get_clientsession() diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 3a4b9ced0ab..3b01a01fc96 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1,27 +1,29 @@ """Helpers for config validation using voluptuous.""" -from datetime import (timedelta, datetime as datetime_sys, - time as time_sys, date as date_sys) +import inspect +import logging import os import re -from urllib.parse import urlparse +from datetime import (timedelta, datetime as datetime_sys, + time as time_sys, date as date_sys) from socket import _GLOBAL_DEFAULT_TIMEOUT -import logging -import inspect -from typing import Any, Union, TypeVar, Callable, Sequence, Dict +from typing import Any, Union, TypeVar, Callable, Sequence, Dict, Optional +from urllib.parse import urlparse import voluptuous as vol +from pkg_resources import parse_version +import homeassistant.util.dt as dt_util from homeassistant.const import ( CONF_PLATFORM, CONF_SCAN_INTERVAL, TEMP_CELSIUS, TEMP_FAHRENHEIT, CONF_ALIAS, CONF_ENTITY_ID, CONF_VALUE_TEMPLATE, WEEKDAYS, CONF_CONDITION, CONF_BELOW, CONF_ABOVE, CONF_TIMEOUT, SUN_EVENT_SUNSET, SUN_EVENT_SUNRISE, CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC, - ENTITY_MATCH_ALL, CONF_ENTITY_NAMESPACE) + ENTITY_MATCH_ALL, CONF_ENTITY_NAMESPACE, __version__) from homeassistant.core import valid_entity_id, split_entity_id from homeassistant.exceptions import TemplateError -import homeassistant.util.dt as dt_util -from homeassistant.util import slugify as util_slugify from homeassistant.helpers import template as template_helper +from homeassistant.helpers.logging import KeywordStyleAdapter +from homeassistant.util import slugify as util_slugify # pylint: disable=invalid-name @@ -67,6 +69,22 @@ def has_at_least_one_key(*keys: str) -> Callable: return validate +def has_at_most_one_key(*keys: str) -> Callable: + """Validate that zero keys exist or one key exists.""" + def validate(obj: Dict) -> Dict: + """Test zero keys exist or one key exists in dict.""" + if not isinstance(obj, dict): + raise vol.Invalid('expected dictionary') + + if len(set(keys) & set(obj)) > 1: + raise vol.Invalid( + 'must contain at most one of {}.'.format(', '.join(keys)) + ) + return obj + + return validate + + def boolean(value: Any) -> bool: """Validate and coerce a boolean value.""" if isinstance(value, str): @@ -520,18 +538,79 @@ def ensure_list_csv(value: Any) -> Sequence: return ensure_list(value) -def deprecated(key): - """Log key as deprecated.""" +def deprecated(key: str, + replacement_key: Optional[str] = None, + invalidation_version: Optional[str] = None, + default: Optional[Any] = None): + """ + Log key as deprecated and provide a replacement (if exists). + + Expected behavior: + - Outputs the appropriate deprecation warning if key is detected + - Processes schema moving the value from key to replacement_key + - Processes schema changing nothing if only replacement_key provided + - No warning if only replacement_key provided + - No warning if neither key nor replacement_key are provided + - Adds replacement_key with default value in this case + - Once the invalidation_version is crossed, raises vol.Invalid if key + is detected + """ module_name = inspect.getmodule(inspect.stack()[1][0]).__name__ - def validator(config): + if replacement_key and invalidation_version: + warning = ("The '{key}' option (with value '{value}') is" + " deprecated, please replace it with '{replacement_key}'." + " This option will become invalid in version" + " {invalidation_version}") + elif replacement_key: + warning = ("The '{key}' option (with value '{value}') is" + " deprecated, please replace it with '{replacement_key}'") + elif invalidation_version: + warning = ("The '{key}' option (with value '{value}') is" + " deprecated, please remove it from your configuration." + " This option will become invalid in version" + " {invalidation_version}") + else: + warning = ("The '{key}' option (with value '{value}') is" + " deprecated, please remove it from your configuration") + + def check_for_invalid_version(value: Optional[Any]): + """Raise error if current version has reached invalidation.""" + if not invalidation_version: + return + + if parse_version(__version__) >= parse_version(invalidation_version): + raise vol.Invalid( + warning.format( + key=key, + value=value, + replacement_key=replacement_key, + invalidation_version=invalidation_version + ) + ) + + def validator(config: Dict): """Check if key is in config and log warning.""" if key in config: - logging.getLogger(module_name).warning( - "The '%s' option (with value '%s') is deprecated, please " - "remove it from your configuration.", key, config[key]) + value = config[key] + check_for_invalid_version(value) + KeywordStyleAdapter(logging.getLogger(module_name)).warning( + warning, + key=key, + value=value, + replacement_key=replacement_key, + invalidation_version=invalidation_version + ) + if replacement_key: + config.pop(key) + else: + value = default + if (replacement_key + and replacement_key not in config + and value is not None): + config[replacement_key] = value - return config + return has_at_most_one_key(key, replacement_key)(config) return validator diff --git a/homeassistant/helpers/logging.py b/homeassistant/helpers/logging.py new file mode 100644 index 00000000000..ea596eb3c15 --- /dev/null +++ b/homeassistant/helpers/logging.py @@ -0,0 +1,49 @@ +"""Helpers for logging allowing more advanced logging styles to be used.""" +import inspect +import logging + + +class KeywordMessage: + """ + Represents a logging message with keyword arguments. + + Adapted from: https://stackoverflow.com/a/24683360/2267718 + """ + + def __init__(self, fmt, args, kwargs): + """Initialize a new BraceMessage object.""" + self._fmt = fmt + self._args = args + self._kwargs = kwargs + + def __str__(self): + """Convert the object to a string for logging.""" + return str(self._fmt).format(*self._args, **self._kwargs) + + +class KeywordStyleAdapter(logging.LoggerAdapter): + """Represents an adapter wrapping the logger allowing KeywordMessages.""" + + def __init__(self, logger, extra=None): + """Initialize a new StyleAdapter for the provided logger.""" + super(KeywordStyleAdapter, self).__init__(logger, extra or {}) + + def log(self, level, msg, *args, **kwargs): + """Log the message provided at the appropriate level.""" + if self.isEnabledFor(level): + msg, log_kwargs = self.process(msg, kwargs) + self.logger._log( # pylint: disable=protected-access + level, KeywordMessage(msg, args, kwargs), (), **log_kwargs + ) + + def process(self, msg, kwargs): + """Process the keyward args in preparation for logging.""" + return ( + msg, + { + k: kwargs[k] + for k in inspect.getfullargspec( + self.logger._log # pylint: disable=protected-access + ).args[1:] if k in kwargs + } + ) diff --git a/tests/components/freedns/test_init.py b/tests/components/freedns/test_init.py index b8e38e9c3a8..784926912cd 100644 --- a/tests/components/freedns/test_init.py +++ b/tests/components/freedns/test_init.py @@ -40,7 +40,7 @@ def test_setup(hass, aioclient_mock): result = yield from async_setup_component(hass, freedns.DOMAIN, { freedns.DOMAIN: { 'access_token': ACCESS_TOKEN, - 'update_interval': UPDATE_INTERVAL, + 'scan_interval': UPDATE_INTERVAL, } }) assert result diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 119725b06dd..cefde564035 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -5,6 +5,7 @@ import os from socket import _GLOBAL_DEFAULT_TIMEOUT from unittest.mock import Mock, patch +import homeassistant import pytest import voluptuous as vol @@ -275,7 +276,6 @@ def test_time_period(): {}, {'wrong_key': -10} ) for value in options: - with pytest.raises(vol.MultipleInvalid): schema(value) @@ -489,26 +489,323 @@ def test_datetime(): schema('2016-11-23T18:59:08') -def test_deprecated(caplog): - """Test deprecation log.""" - schema = vol.Schema({ +@pytest.fixture +def schema(): + """Create a schema used for testing deprecation.""" + return vol.Schema({ 'venus': cv.boolean, - 'mars': cv.boolean + 'mars': cv.boolean, + 'jupiter': cv.boolean }) + + +@pytest.fixture +def version(monkeypatch): + """Patch the version used for testing to 0.5.0.""" + monkeypatch.setattr(homeassistant.const, '__version__', '0.5.0') + + +def test_deprecated_with_no_optionals(caplog, schema): + """ + Test deprecation behaves correctly when optional params are None. + + Expected behavior: + - Outputs the appropriate deprecation warning if key is detected + - Processes schema without changing any values + - No warning or difference in output if key is not provided + """ deprecated_schema = vol.All( cv.deprecated('mars'), schema ) - deprecated_schema({'venus': True}) - # pylint: disable=len-as-condition - assert len(caplog.records) == 0 - - deprecated_schema({'mars': True}) + test_data = {'mars': True} + output = deprecated_schema(test_data.copy()) assert len(caplog.records) == 1 assert caplog.records[0].name == __name__ assert ("The 'mars' option (with value 'True') is deprecated, " - "please remove it from your configuration.") in caplog.text + "please remove it from your configuration") in caplog.text + assert test_data == output + + caplog.clear() + assert len(caplog.records) == 0 + + test_data = {'venus': True} + output = deprecated_schema(test_data.copy()) + assert len(caplog.records) == 0 + assert test_data == output + + +def test_deprecated_with_replacement_key(caplog, schema): + """ + Test deprecation behaves correctly when only a replacement key is provided. + + Expected behavior: + - Outputs the appropriate deprecation warning if key is detected + - Processes schema moving the value from key to replacement_key + - Processes schema changing nothing if only replacement_key provided + - No warning if only replacement_key provided + - No warning or difference in output if neither key nor + replacement_key are provided + """ + deprecated_schema = vol.All( + cv.deprecated('mars', replacement_key='jupiter'), + schema + ) + + test_data = {'mars': True} + output = deprecated_schema(test_data.copy()) + assert len(caplog.records) == 1 + assert ("The 'mars' option (with value 'True') is deprecated, " + "please replace it with 'jupiter'") in caplog.text + assert {'jupiter': True} == output + + caplog.clear() + assert len(caplog.records) == 0 + + test_data = {'jupiter': True} + output = deprecated_schema(test_data.copy()) + assert len(caplog.records) == 0 + assert test_data == output + + test_data = {'venus': True} + output = deprecated_schema(test_data.copy()) + assert len(caplog.records) == 0 + assert test_data == output + + +def test_deprecated_with_invalidation_version(caplog, schema, version): + """ + Test deprecation behaves correctly with only an invalidation_version. + + Expected behavior: + - Outputs the appropriate deprecation warning if key is detected + - Processes schema without changing any values + - No warning or difference in output if key is not provided + - Once the invalidation_version is crossed, raises vol.Invalid if key + is detected + """ + deprecated_schema = vol.All( + cv.deprecated('mars', invalidation_version='1.0.0'), + schema + ) + + message = ("The 'mars' option (with value 'True') is deprecated, " + "please remove it from your configuration. " + "This option will become invalid in version 1.0.0") + + test_data = {'mars': True} + output = deprecated_schema(test_data.copy()) + assert len(caplog.records) == 1 + assert message in caplog.text + assert test_data == output + + caplog.clear() + assert len(caplog.records) == 0 + + test_data = {'venus': False} + output = deprecated_schema(test_data.copy()) + assert len(caplog.records) == 0 + assert test_data == output + + invalidated_schema = vol.All( + cv.deprecated('mars', invalidation_version='0.1.0'), + schema + ) + test_data = {'mars': True} + with pytest.raises(vol.MultipleInvalid) as exc_info: + invalidated_schema(test_data) + assert ("The 'mars' option (with value 'True') is deprecated, " + "please remove it from your configuration. This option will " + "become invalid in version 0.1.0") == str(exc_info.value) + + +def test_deprecated_with_replacement_key_and_invalidation_version( + caplog, schema, version +): + """ + Test deprecation behaves with a replacement key & invalidation_version. + + Expected behavior: + - Outputs the appropriate deprecation warning if key is detected + - Processes schema moving the value from key to replacement_key + - Processes schema changing nothing if only replacement_key provided + - No warning if only replacement_key provided + - No warning or difference in output if neither key nor + replacement_key are provided + - Once the invalidation_version is crossed, raises vol.Invalid if key + is detected + """ + deprecated_schema = vol.All( + cv.deprecated( + 'mars', replacement_key='jupiter', invalidation_version='1.0.0' + ), + schema + ) + + warning = ("The 'mars' option (with value 'True') is deprecated, " + "please replace it with 'jupiter'. This option will become " + "invalid in version 1.0.0") + + test_data = {'mars': True} + output = deprecated_schema(test_data.copy()) + assert len(caplog.records) == 1 + assert warning in caplog.text + assert {'jupiter': True} == output + + caplog.clear() + assert len(caplog.records) == 0 + + test_data = {'jupiter': True} + output = deprecated_schema(test_data.copy()) + assert len(caplog.records) == 0 + assert test_data == output + + test_data = {'venus': True} + output = deprecated_schema(test_data.copy()) + assert len(caplog.records) == 0 + assert test_data == output + + invalidated_schema = vol.All( + cv.deprecated( + 'mars', replacement_key='jupiter', invalidation_version='0.1.0' + ), + schema + ) + test_data = {'mars': True} + with pytest.raises(vol.MultipleInvalid) as exc_info: + invalidated_schema(test_data) + assert ("The 'mars' option (with value 'True') is deprecated, " + "please replace it with 'jupiter'. This option will become " + "invalid in version 0.1.0") == str(exc_info.value) + + +def test_deprecated_with_default(caplog, schema): + """ + Test deprecation behaves correctly with a default value. + + This is likely a scenario that would never occur. + + Expected behavior: + - Behaves identically as when the default value was not present + """ + deprecated_schema = vol.All( + cv.deprecated('mars', default=False), + schema + ) + + test_data = {'mars': True} + output = deprecated_schema(test_data.copy()) + assert len(caplog.records) == 1 + assert caplog.records[0].name == __name__ + assert ("The 'mars' option (with value 'True') is deprecated, " + "please remove it from your configuration") in caplog.text + assert test_data == output + + caplog.clear() + assert len(caplog.records) == 0 + + test_data = {'venus': True} + output = deprecated_schema(test_data.copy()) + assert len(caplog.records) == 0 + assert test_data == output + + +def test_deprecated_with_replacement_key_and_default(caplog, schema): + """ + Test deprecation behaves correctly when only a replacement key is provided. + + Expected behavior: + - Outputs the appropriate deprecation warning if key is detected + - Processes schema moving the value from key to replacement_key + - Processes schema changing nothing if only replacement_key provided + - No warning if only replacement_key provided + - No warning if neither key nor replacement_key are provided + - Adds replacement_key with default value in this case + """ + deprecated_schema = vol.All( + cv.deprecated('mars', replacement_key='jupiter', default=False), + schema + ) + + test_data = {'mars': True} + output = deprecated_schema(test_data.copy()) + assert len(caplog.records) == 1 + assert ("The 'mars' option (with value 'True') is deprecated, " + "please replace it with 'jupiter'") in caplog.text + assert {'jupiter': True} == output + + caplog.clear() + assert len(caplog.records) == 0 + + test_data = {'jupiter': True} + output = deprecated_schema(test_data.copy()) + assert len(caplog.records) == 0 + assert test_data == output + + test_data = {'venus': True} + output = deprecated_schema(test_data.copy()) + assert len(caplog.records) == 0 + assert {'venus': True, 'jupiter': False} == output + + +def test_deprecated_with_replacement_key_invalidation_version_default( + caplog, schema, version +): + """ + Test deprecation with a replacement key, invalidation_version & default. + + Expected behavior: + - Outputs the appropriate deprecation warning if key is detected + - Processes schema moving the value from key to replacement_key + - Processes schema changing nothing if only replacement_key provided + - No warning if only replacement_key provided + - No warning if neither key nor replacement_key are provided + - Adds replacement_key with default value in this case + - Once the invalidation_version is crossed, raises vol.Invalid if key + is detected + """ + deprecated_schema = vol.All( + cv.deprecated( + 'mars', replacement_key='jupiter', invalidation_version='1.0.0', + default=False + ), + schema + ) + + test_data = {'mars': True} + output = deprecated_schema(test_data.copy()) + assert len(caplog.records) == 1 + assert ("The 'mars' option (with value 'True') is deprecated, " + "please replace it with 'jupiter'. This option will become " + "invalid in version 1.0.0") in caplog.text + assert {'jupiter': True} == output + + caplog.clear() + assert len(caplog.records) == 0 + + test_data = {'jupiter': True} + output = deprecated_schema(test_data.copy()) + assert len(caplog.records) == 0 + assert test_data == output + + test_data = {'venus': True} + output = deprecated_schema(test_data.copy()) + assert len(caplog.records) == 0 + assert {'venus': True, 'jupiter': False} == output + + invalidated_schema = vol.All( + cv.deprecated( + 'mars', replacement_key='jupiter', invalidation_version='0.1.0' + ), + schema + ) + test_data = {'mars': True} + with pytest.raises(vol.MultipleInvalid) as exc_info: + invalidated_schema(test_data) + assert ("The 'mars' option (with value 'True') is deprecated, " + "please replace it with 'jupiter'. This option will become " + "invalid in version 0.1.0") == str(exc_info.value) def test_key_dependency(): @@ -530,6 +827,18 @@ def test_key_dependency(): schema(value) +def test_has_at_most_one_key(): + """Test has_at_most_one_key validator.""" + schema = vol.Schema(cv.has_at_most_one_key('beer', 'soda')) + + for value in (None, [], {'beer': None, 'soda': None}): + with pytest.raises(vol.MultipleInvalid): + schema(value) + + for value in ({}, {'beer': None}, {'soda': None}): + schema(value) + + def test_has_at_least_one_key(): """Test has_at_least_one_key validator.""" schema = vol.Schema(cv.has_at_least_one_key('beer', 'soda')) @@ -582,7 +891,7 @@ def test_matches_regex(): schema(" nrtd ") test_str = "This is a test including uiae." - assert(schema(test_str) == test_str) + assert (schema(test_str) == test_str) def test_is_regex(): From ca0e5a75ec7c3968cc1b8b79d3002181e5b77838 Mon Sep 17 00:00:00 2001 From: Markus Jankowski <5650106+SukramJ@users.noreply.github.com> Date: Fri, 8 Feb 2019 12:43:48 +0100 Subject: [PATCH 108/242] Add additional devices and features to Homematic IP (#20747) * Homematic IP: updated dependency homematicip to 0.10.5 * Homematic IP: Added LightSensor * Homematic IP: Added power measure for XXXSwitchMeasuring * reverted unnessessary change * reverted unnessessary change * removed device_class from core * Removed optional property device_class * Added description for property * Changed comment to fix travis build * Changed comment to fix travis build --- .../components/homematicip_cloud/__init__.py | 2 +- .../components/homematicip_cloud/sensor.py | 44 ++++++++++++++++--- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/homematicip_cloud/__init__.py b/homeassistant/components/homematicip_cloud/__init__.py index b0ea1a3b348..f048a50d1d0 100644 --- a/homeassistant/components/homematicip_cloud/__init__.py +++ b/homeassistant/components/homematicip_cloud/__init__.py @@ -19,7 +19,7 @@ from .const import ( from .device import HomematicipGenericDevice # noqa: F401 from .hap import HomematicipAuth, HomematicipHAP # noqa: F401 -REQUIREMENTS = ['homematicip==0.10.4'] +REQUIREMENTS = ['homematicip==0.10.5'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/homematicip_cloud/sensor.py b/homeassistant/components/homematicip_cloud/sensor.py index 77feda20be1..911c00e45bc 100644 --- a/homeassistant/components/homematicip_cloud/sensor.py +++ b/homeassistant/components/homematicip_cloud/sensor.py @@ -7,11 +7,10 @@ https://home-assistant.io/components/sensor.homematicip_cloud/ import logging from homeassistant.components.homematicip_cloud import ( - HMIPC_HAPID, HomematicipGenericDevice) -from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN + DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice) from homeassistant.const import ( - DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, - TEMP_CELSIUS) + DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS) _LOGGER = logging.getLogger(__name__) @@ -36,7 +35,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): AsyncHeatingThermostat, AsyncTemperatureHumiditySensorWithoutDisplay, AsyncTemperatureHumiditySensorDisplay, AsyncMotionDetectorIndoor, AsyncTemperatureHumiditySensorOutdoor, - AsyncMotionDetectorPushButton) + AsyncMotionDetectorPushButton, AsyncLightSensor, + AsyncPlugableSwitchMeasuring, AsyncBrandSwitchMeasuring, + AsyncFullFlushSwitchMeasuring) home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home devices = [HomematicipAccesspointStatus(home)] @@ -51,6 +52,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if isinstance(device, (AsyncMotionDetectorIndoor, AsyncMotionDetectorPushButton)): devices.append(HomematicipIlluminanceSensor(home, device)) + if isinstance(device, AsyncLightSensor): + devices.append(HomematicipLightSensor(home, device)) + if isinstance(device, (AsyncPlugableSwitchMeasuring, + AsyncBrandSwitchMeasuring, + AsyncFullFlushSwitchMeasuring)): + devices.append(HomematicipPowerSensor(home, device)) if devices: async_add_entities(devices) @@ -184,3 +191,30 @@ class HomematicipIlluminanceSensor(HomematicipGenericDevice): def unit_of_measurement(self): """Return the unit this state is expressed in.""" return 'lx' + + +class HomematicipLightSensor(HomematicipIlluminanceSensor): + """Represenation of a HomematicIP Illuminance device.""" + + @property + def state(self): + """Return the state.""" + return self._device.averageIllumination + + +class HomematicipPowerSensor(HomematicipGenericDevice): + """Represenation of a HomematicIP power measuring device.""" + + def __init__(self, home, device): + """Initialize the device.""" + super().__init__(home, device, 'Power') + + @property + def state(self): + """Represenation of the HomematicIP power comsumption value.""" + return self._device.currentPowerConsumption + + @property + def unit_of_measurement(self): + """Return the unit this state is expressed in.""" + return 'W' diff --git a/requirements_all.txt b/requirements_all.txt index 8b2a0a7cc6b..9f43fc82a5d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -538,7 +538,7 @@ homeassistant-pyozw==0.1.2 homekit==0.12.2 # homeassistant.components.homematicip_cloud -homematicip==0.10.4 +homematicip==0.10.5 # homeassistant.components.google # homeassistant.components.remember_the_milk diff --git a/requirements_test_all.txt b/requirements_test_all.txt index df750d69972..9d768779ccc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -122,7 +122,7 @@ home-assistant-frontend==20190203.0 homekit==0.12.2 # homeassistant.components.homematicip_cloud -homematicip==0.10.4 +homematicip==0.10.5 # homeassistant.components.influxdb # homeassistant.components.sensor.influxdb From c99d1406518f2b3fbb7dd2f7f13d9feb62deb5c8 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Fri, 8 Feb 2019 13:44:08 +0100 Subject: [PATCH 109/242] Upgrade youtube_dl to 2019.02.08 (#20859) --- homeassistant/components/media_extractor/__init__.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_extractor/__init__.py b/homeassistant/components/media_extractor/__init__.py index 333f62a9aa7..482f961765f 100644 --- a/homeassistant/components/media_extractor/__init__.py +++ b/homeassistant/components/media_extractor/__init__.py @@ -14,7 +14,7 @@ from homeassistant.components.media_player import ( SERVICE_PLAY_MEDIA) from homeassistant.helpers import config_validation as cv -REQUIREMENTS = ['youtube_dl==2019.01.24'] +REQUIREMENTS = ['youtube_dl==2019.02.08'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 9f43fc82a5d..62efa76cac2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1771,7 +1771,7 @@ yeelight==0.4.3 yeelightsunflower==0.0.10 # homeassistant.components.media_extractor -youtube_dl==2019.01.24 +youtube_dl==2019.02.08 # homeassistant.components.light.zengge zengge==0.2 From 6a78ad8ab642a3553ec0574106284c3d28865c50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Fri, 8 Feb 2019 14:35:38 +0100 Subject: [PATCH 110/242] Fix STATE_UNLOCKED for verisure (#20858) --- homeassistant/components/verisure/lock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index cf7d58b17a8..be9a0a24fee 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -80,7 +80,7 @@ class VerisureDoorlock(LockDevice): "$.doorLockStatusList[?(@.deviceLabel=='%s')].lockedState", self._device_label) if status == 'UNLOCKED': - self._state = None + self._state = STATE_UNLOCKED elif status == 'LOCKED': self._state = STATE_LOCKED elif status != 'PENDING': From faf7ae29b1e8be5fd4a7831ddacea7c55ca6b1f9 Mon Sep 17 00:00:00 2001 From: MatteGary Date: Fri, 8 Feb 2019 18:15:14 +0100 Subject: [PATCH 111/242] Fix init of TransmissionData (#20817) * Fix init of TransmissionData Fix in order to avoid null object on first update of Turtle Mode Switch * Using async functionality * Various fix * HoundBot fix * Removed some async calls * Fix compilation Error * Fix * PEP fix --- .../components/transmission/__init__.py | 11 ++++++ .../components/transmission/sensor.py | 30 ++++++++++++---- .../components/transmission/switch.py | 34 +++++++++++++------ 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index b14881fccca..dd10c4ecfdf 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -19,6 +19,7 @@ from homeassistant.const import ( CONF_SCAN_INTERVAL ) from homeassistant.helpers import discovery, config_validation as cv +from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.event import track_time_interval @@ -26,6 +27,7 @@ REQUIREMENTS = ['transmissionrpc==0.11'] _LOGGER = logging.getLogger(__name__) DOMAIN = 'transmission' +DATA_UPDATED = 'transmission_data_updated' DATA_TRANSMISSION = 'data_transmission' DEFAULT_NAME = 'Transmission' @@ -83,6 +85,8 @@ def setup(hass, config): tm_data = hass.data[DATA_TRANSMISSION] = TransmissionData( hass, config, api) + + tm_data.update() tm_data.init_torrent_list() def refresh(event_time): @@ -94,10 +98,12 @@ def setup(hass, config): sensorconfig = { 'sensors': config[DOMAIN][CONF_MONITORED_CONDITIONS], 'client_name': config[DOMAIN][CONF_NAME]} + discovery.load_platform(hass, 'sensor', DOMAIN, sensorconfig, config) if config[DOMAIN][TURTLE_MODE]: discovery.load_platform(hass, 'switch', DOMAIN, sensorconfig, config) + return True @@ -127,6 +133,8 @@ class TransmissionData: self.check_completed_torrent() self.check_started_torrent() + dispatcher_send(self.hass, DATA_UPDATED) + _LOGGER.debug("Torrent Data updated") self.available = True except TransmissionError: @@ -189,4 +197,7 @@ class TransmissionData: def get_alt_speed_enabled(self): """Get the alternative speed flag.""" + if self.session is None: + return None + return self.session.alt_speed_enabled diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index 84c7d54306e..cb592a74758 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -9,10 +9,11 @@ from datetime import timedelta import logging from homeassistant.components.transmission import ( - DATA_TRANSMISSION, SENSOR_TYPES) + DATA_TRANSMISSION, SENSOR_TYPES, DATA_UPDATED) from homeassistant.const import STATE_IDLE +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle DEPENDENCIES = ['transmission'] @@ -23,7 +24,11 @@ DEFAULT_NAME = 'Transmission' SCAN_INTERVAL = timedelta(seconds=120) -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform( + hass, + config, + async_add_entities, + discovery_info=None): """Set up the Transmission sensors.""" if discovery_info is None: return @@ -41,7 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): SENSOR_TYPES[sensor_type][0], SENSOR_TYPES[sensor_type][1])) - add_entities(dev, True) + async_add_entities(dev, True) class TransmissionSensor(Entity): @@ -73,6 +78,11 @@ class TransmissionSensor(Entity): """Return the state of the sensor.""" return self._state + @property + def should_poll(self): + """Return the polling requirement for this sensor.""" + return False + @property def unit_of_measurement(self): """Return the unit of measurement of this entity, if any.""" @@ -83,10 +93,18 @@ class TransmissionSensor(Entity): """Could the device be accessed during the last update call.""" return self._transmission_api.available - @Throttle(SCAN_INTERVAL) + async def async_added_to_hass(self): + """Handle entity which will be added.""" + async_dispatcher_connect( + self.hass, DATA_UPDATED, self._schedule_immediate_update + ) + + @callback + def _schedule_immediate_update(self): + self.async_schedule_update_ha_state(True) + def update(self): """Get the latest data from Transmission and updates the state.""" - self._transmission_api.update() self._data = self._transmission_api.data if self.type == 'completed_torrents': diff --git a/homeassistant/components/transmission/switch.py b/homeassistant/components/transmission/switch.py index 8e6c0a8cb44..aac946dee8b 100644 --- a/homeassistant/components/transmission/switch.py +++ b/homeassistant/components/transmission/switch.py @@ -4,16 +4,15 @@ Support for setting the Transmission BitTorrent client Turtle Mode. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.transmission/ """ -from datetime import timedelta - import logging from homeassistant.components.transmission import ( - DATA_TRANSMISSION) + DATA_TRANSMISSION, DATA_UPDATED) from homeassistant.const import ( STATE_OFF, STATE_ON) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import ToggleEntity -from homeassistant.util import Throttle DEPENDENCIES = ['transmission'] @@ -21,10 +20,12 @@ _LOGGING = logging.getLogger(__name__) DEFAULT_NAME = 'Transmission Turtle Mode' -SCAN_INTERVAL = timedelta(seconds=120) - -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform( + hass, + config, + async_add_entities, + discovery_info=None): """Set up the Transmission switch.""" if discovery_info is None: return @@ -33,7 +34,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): transmission_api = hass.data[component_name] name = discovery_info['client_name'] - add_entities([TransmissionSwitch(transmission_api, name)], True) + async_add_entities([TransmissionSwitch(transmission_api, name)], True) class TransmissionSwitch(ToggleEntity): @@ -58,7 +59,7 @@ class TransmissionSwitch(ToggleEntity): @property def should_poll(self): """Poll for status regularly.""" - return True + return False @property def is_on(self): @@ -75,8 +76,21 @@ class TransmissionSwitch(ToggleEntity): _LOGGING.debug("Turning Turtle Mode of Transmission off") self.transmission_client.set_alt_speed_enabled(False) - @Throttle(SCAN_INTERVAL) + async def async_added_to_hass(self): + """Handle entity which will be added.""" + async_dispatcher_connect( + self.hass, DATA_UPDATED, self._schedule_immediate_update + ) + + @callback + def _schedule_immediate_update(self): + self.async_schedule_update_ha_state(True) + def update(self): """Get the latest data from Transmission and updates the state.""" active = self.transmission_client.get_alt_speed_enabled() + + if active is None: + return + self._state = STATE_ON if active else STATE_OFF From d16d14b648b00ffe97c10180472fd004a131c99c Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Fri, 8 Feb 2019 23:18:18 +0100 Subject: [PATCH 112/242] Media player const.py move (#20822) * Move more constants to const.py * Import constants directly from const * ATTR_ENTITY_ID is not defined in media_player * MEDIA_PLAYER_PLAY_MEDIA_SCHEMA is still in __init__.py * Correct imports in tts * PLATFORM_SCHEMA, SCHEMA is still defined in __init__.py * Pandora imports several services * Some additional fixes for move of const in media_player * Fix hound lengths --- homeassistant/components/alexa/smart_home.py | 46 +++++++------- .../components/apple_tv/media_player.py | 6 +- homeassistant/components/cast/media_player.py | 6 +- .../components/emulated_hue/hue_api.py | 2 +- .../components/hdmi_cec/media_player.py | 5 +- homeassistant/components/homekit/util.py | 14 +++-- .../components/media_extractor/__init__.py | 9 ++- .../components/media_player/__init__.py | 40 ++++--------- .../components/media_player/anthemav.py | 6 +- .../components/media_player/aquostv.py | 6 +- .../components/media_player/blackbird.py | 6 +- .../components/media_player/bluesound.py | 7 ++- .../components/media_player/braviatv.py | 6 +- .../components/media_player/channels.py | 7 ++- .../components/media_player/clementine.py | 6 +- homeassistant/components/media_player/cmus.py | 7 ++- .../components/media_player/const.py | 27 +++++++++ homeassistant/components/media_player/demo.py | 5 +- .../components/media_player/denon.py | 7 ++- .../components/media_player/denonavr.py | 6 +- .../components/media_player/directv.py | 7 ++- .../components/media_player/dlna_dmr.py | 6 +- .../components/media_player/dunehd.py | 6 +- homeassistant/components/media_player/emby.py | 6 +- .../components/media_player/epson.py | 7 ++- .../components/media_player/firetv.py | 4 +- .../media_player/frontier_silicon.py | 7 ++- .../components/media_player/gpmdp.py | 7 ++- .../components/media_player/gstreamer.py | 6 +- .../media_player/harman_kardon_avr.py | 5 +- .../components/media_player/horizon.py | 6 +- .../components/media_player/itunes.py | 6 +- homeassistant/components/media_player/kodi.py | 9 +-- .../components/media_player/lg_netcast.py | 7 ++- .../components/media_player/lg_soundbar.py | 4 +- .../components/media_player/liveboxplaytv.py | 6 +- .../components/media_player/mediaroom.py | 6 +- .../components/media_player/monoprice.py | 6 +- .../components/media_player/mpchc.py | 6 +- homeassistant/components/media_player/mpd.py | 6 +- homeassistant/components/media_player/nad.py | 7 ++- .../components/media_player/onkyo.py | 6 +- .../components/media_player/openhome.py | 5 +- .../media_player/panasonic_bluray.py | 6 +- .../media_player/panasonic_viera.py | 6 +- .../components/media_player/pandora.py | 14 ++--- .../components/media_player/philips_js.py | 7 ++- .../components/media_player/pioneer.py | 7 ++- .../components/media_player/pjlink.py | 6 +- homeassistant/components/media_player/plex.py | 7 ++- .../components/media_player/russound_rio.py | 7 ++- .../components/media_player/russound_rnet.py | 6 +- .../components/media_player/samsungtv.py | 7 ++- .../components/media_player/snapcast.py | 6 +- .../components/media_player/songpal.py | 6 +- .../components/media_player/soundtouch.py | 7 ++- .../components/media_player/spotify.py | 7 ++- .../components/media_player/squeezebox.py | 8 ++- .../components/media_player/ue_smart_radio.py | 7 ++- .../components/media_player/universal.py | 6 +- .../components/media_player/vizio.py | 7 ++- homeassistant/components/media_player/vlc.py | 7 ++- .../components/media_player/volumio.py | 7 ++- .../components/media_player/xiaomi_tv.py | 5 +- .../components/media_player/yamaha.py | 6 +- .../media_player/yamaha_musiccast.py | 7 ++- .../media_player/ziggo_mediabox_xl.py | 6 +- homeassistant/components/roku/media_player.py | 5 +- .../components/sisyphus/media_player.py | 5 +- .../components/sonos/media_player.py | 6 +- homeassistant/components/tts/__init__.py | 4 +- .../components/webostv/media_player.py | 6 +- tests/components/google/test_tts.py | 2 +- .../homekit/test_get_accessories.py | 8 +-- .../homekit/test_type_media_players.py | 2 +- tests/components/media_player/common.py | 2 +- .../components/media_player/test_blackbird.py | 2 +- tests/components/media_player/test_directv.py | 60 ++++++++++--------- .../components/media_player/test_monoprice.py | 2 +- .../components/media_player/test_samsungtv.py | 2 +- tests/components/sonos/test_media_player.py | 2 +- tests/components/tts/test_init.py | 2 +- tests/components/tts/test_marytts.py | 2 +- tests/components/tts/test_voicerss.py | 2 +- tests/components/tts/test_yandextts.py | 2 +- 85 files changed, 383 insertions(+), 263 deletions(-) diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index 7240912883a..156dfa84a8a 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -27,9 +27,9 @@ from homeassistant.const import ( CONF_NAME, SERVICE_LOCK, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP, SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON, - SERVICE_UNLOCK, SERVICE_VOLUME_SET, STATE_LOCKED, STATE_ON, - STATE_UNAVAILABLE, STATE_UNLOCKED, TEMP_CELSIUS, TEMP_FAHRENHEIT, - MATCH_ALL) + SERVICE_UNLOCK, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_UP, SERVICE_VOLUME_SET, + SERVICE_VOLUME_MUTE, STATE_LOCKED, STATE_ON, STATE_UNAVAILABLE, + STATE_UNLOCKED, TEMP_CELSIUS, TEMP_FAHRENHEIT, MATCH_ALL) import homeassistant.core as ha import homeassistant.util.color as color_util from homeassistant.util.decorator import Registry @@ -883,7 +883,7 @@ class _LockCapabilities(_AlexaEntity): _AlexaEndpointHealth(self.hass, self.entity)] -@ENTITY_ADAPTERS.register(media_player.DOMAIN) +@ENTITY_ADAPTERS.register(media_player.const.DOMAIN) class _MediaPlayerCapabilities(_AlexaEntity): def default_display_categories(self): return [_DisplayCategory.TV] @@ -893,19 +893,19 @@ class _MediaPlayerCapabilities(_AlexaEntity): yield _AlexaEndpointHealth(self.hass, self.entity) supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if supported & media_player.SUPPORT_VOLUME_SET: + if supported & media_player.const.SUPPORT_VOLUME_SET: yield _AlexaSpeaker(self.entity) - step_volume_features = (media_player.SUPPORT_VOLUME_MUTE | - media_player.SUPPORT_VOLUME_STEP) + step_volume_features = (media_player.const.SUPPORT_VOLUME_MUTE | + media_player.const.SUPPORT_VOLUME_STEP) if supported & step_volume_features: yield _AlexaStepSpeaker(self.entity) - playback_features = (media_player.SUPPORT_PLAY | - media_player.SUPPORT_PAUSE | - media_player.SUPPORT_STOP | - media_player.SUPPORT_NEXT_TRACK | - media_player.SUPPORT_PREVIOUS_TRACK) + playback_features = (media_player.const.SUPPORT_PLAY | + media_player.const.SUPPORT_PAUSE | + media_player.const.SUPPORT_STOP | + media_player.const.SUPPORT_NEXT_TRACK | + media_player.const.SUPPORT_PREVIOUS_TRACK) if supported & playback_features: yield _AlexaPlaybackController(self.entity) @@ -1792,7 +1792,7 @@ async def async_api_set_volume(hass, config, directive, context): data = { ATTR_ENTITY_ID: entity.entity_id, - media_player.ATTR_MEDIA_VOLUME_LEVEL: volume, + media_player.const.ATTR_MEDIA_VOLUME_LEVEL: volume, } await hass.services.async_call( @@ -1809,7 +1809,8 @@ async def async_api_select_input(hass, config, directive, context): entity = directive.entity # attempt to map the ALL UPPERCASE payload name to a source - source_list = entity.attributes[media_player.ATTR_INPUT_SOURCE_LIST] or [] + source_list = entity.attributes[ + media_player.const.ATTR_INPUT_SOURCE_LIST] or [] for source in source_list: # response will always be space separated, so format the source in the # most likely way to find a match @@ -1824,7 +1825,7 @@ async def async_api_select_input(hass, config, directive, context): data = { ATTR_ENTITY_ID: entity.entity_id, - media_player.ATTR_INPUT_SOURCE: media_input, + media_player.const.ATTR_INPUT_SOURCE: media_input, } await hass.services.async_call( @@ -1840,7 +1841,8 @@ async def async_api_adjust_volume(hass, config, directive, context): volume_delta = int(directive.payload['volume']) entity = directive.entity - current_level = entity.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL) + current_level = entity.attributes.get( + media_player.const.ATTR_MEDIA_VOLUME_LEVEL) # read current state try: @@ -1852,11 +1854,11 @@ async def async_api_adjust_volume(hass, config, directive, context): data = { ATTR_ENTITY_ID: entity.entity_id, - media_player.ATTR_MEDIA_VOLUME_LEVEL: volume, + media_player.const.ATTR_MEDIA_VOLUME_LEVEL: volume, } await hass.services.async_call( - entity.domain, media_player.SERVICE_VOLUME_SET, + entity.domain, SERVICE_VOLUME_SET, data, blocking=False, context=context) return directive.response() @@ -1878,11 +1880,11 @@ async def async_api_adjust_volume_step(hass, config, directive, context): if volume_step > 0: await hass.services.async_call( - entity.domain, media_player.SERVICE_VOLUME_UP, + entity.domain, SERVICE_VOLUME_UP, data, blocking=False, context=context) elif volume_step < 0: await hass.services.async_call( - entity.domain, media_player.SERVICE_VOLUME_DOWN, + entity.domain, SERVICE_VOLUME_DOWN, data, blocking=False, context=context) return directive.response() @@ -1897,11 +1899,11 @@ async def async_api_set_mute(hass, config, directive, context): data = { ATTR_ENTITY_ID: entity.entity_id, - media_player.ATTR_MEDIA_VOLUME_MUTED: mute, + media_player.const.ATTR_MEDIA_VOLUME_MUTED: mute, } await hass.services.async_call( - entity.domain, media_player.SERVICE_VOLUME_MUTE, + entity.domain, SERVICE_VOLUME_MUTE, data, blocking=False, context=context) return directive.response() diff --git a/homeassistant/components/apple_tv/media_player.py b/homeassistant/components/apple_tv/media_player.py index bff8834639d..0b38a256e40 100644 --- a/homeassistant/components/apple_tv/media_player.py +++ b/homeassistant/components/apple_tv/media_player.py @@ -8,11 +8,11 @@ import logging from homeassistant.components.apple_tv import ( ATTR_ATV, ATTR_POWER, DATA_APPLE_TV, DATA_ENTITIES) -from homeassistant.components.media_player import ( +from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - MediaPlayerDevice) + SUPPORT_SEEK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON) from homeassistant.const import ( CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, STATE_STANDBY) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index c66f74f74a9..b80a8ce5e0f 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -14,10 +14,12 @@ import voluptuous as vol from homeassistant.components.cast import DOMAIN as CAST_DOMAIN from homeassistant.components.media_player import ( - MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, PLATFORM_SCHEMA, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import ( CONF_HOST, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING) diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 815e28b4fa4..174ee38715a 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -12,7 +12,7 @@ from homeassistant.const import ( from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS ) -from homeassistant.components.media_player import ( +from homeassistant.components.media_player.const import ( ATTR_MEDIA_VOLUME_LEVEL, SUPPORT_VOLUME_SET, ) from homeassistant.components.fan import ( diff --git a/homeassistant/components/hdmi_cec/media_player.py b/homeassistant/components/hdmi_cec/media_player.py index d69d8a74ce6..6e691cad94f 100644 --- a/homeassistant/components/hdmi_cec/media_player.py +++ b/homeassistant/components/hdmi_cec/media_player.py @@ -7,10 +7,11 @@ https://home-assistant.io/components/hdmi_cec/ import logging from homeassistant.components.hdmi_cec import ATTR_NEW, CecDevice -from homeassistant.components.media_player import ( +from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player.const import ( DOMAIN, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, MediaPlayerDevice) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP) from homeassistant.const import ( STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING) diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 7ad0cea48e7..f1327f8b527 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -63,7 +63,7 @@ def validate_entity_config(values): if domain in ('alarm_control_panel', 'lock'): config = CODE_SCHEMA(config) - elif domain == media_player.DOMAIN: + elif domain == media_player.const.DOMAIN: config = FEATURE_SCHEMA(config) feature_list = {} for feature in config[CONF_FEATURE_LIST]: @@ -90,14 +90,16 @@ def validate_media_player_features(state, feature_list): features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported_modes = [] - if features & (media_player.SUPPORT_TURN_ON | - media_player.SUPPORT_TURN_OFF): + if features & (media_player.const.SUPPORT_TURN_ON | + media_player.const.SUPPORT_TURN_OFF): supported_modes.append(FEATURE_ON_OFF) - if features & (media_player.SUPPORT_PLAY | media_player.SUPPORT_PAUSE): + if features & (media_player.const.SUPPORT_PLAY | + media_player.const.SUPPORT_PAUSE): supported_modes.append(FEATURE_PLAY_PAUSE) - if features & (media_player.SUPPORT_PLAY | media_player.SUPPORT_STOP): + if features & (media_player.const.SUPPORT_PLAY | + media_player.const.SUPPORT_STOP): supported_modes.append(FEATURE_PLAY_STOP) - if features & media_player.SUPPORT_VOLUME_MUTE: + if features & media_player.const.SUPPORT_VOLUME_MUTE: supported_modes.append(FEATURE_TOGGLE_MUTE) error_list = [] diff --git a/homeassistant/components/media_extractor/__init__.py b/homeassistant/components/media_extractor/__init__.py index 482f961765f..8e00949da07 100644 --- a/homeassistant/components/media_extractor/__init__.py +++ b/homeassistant/components/media_extractor/__init__.py @@ -9,9 +9,12 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - ATTR_ENTITY_ID, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, - DOMAIN as MEDIA_PLAYER_DOMAIN, MEDIA_PLAYER_PLAY_MEDIA_SCHEMA, - SERVICE_PLAY_MEDIA) + MEDIA_PLAYER_PLAY_MEDIA_SCHEMA) +from homeassistant.components.media_player.const import ( + ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, + DOMAIN as MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA) +from homeassistant.const import ( + ATTR_ENTITY_ID) from homeassistant.helpers import config_validation as cv REQUIREMENTS = ['youtube_dl==2019.02.08'] diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 840b745eebd..ad29a645765 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -68,6 +68,19 @@ from .const import ( SERVICE_PLAY_MEDIA, SERVICE_SELECT_SOUND_MODE, SERVICE_SELECT_SOURCE, + SUPPORT_PAUSE, + SUPPORT_SEEK, + SUPPORT_VOLUME_SET, + SUPPORT_VOLUME_MUTE, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_NEXT_TRACK, + SUPPORT_PLAY_MEDIA, + SUPPORT_SELECT_SOURCE, + SUPPORT_STOP, + SUPPORT_CLEAR_PLAYLIST, + SUPPORT_PLAY, + SUPPORT_SHUFFLE_SET, + SUPPORT_SELECT_SOUND_MODE, ) from .reproduce_state import async_reproduce_states # noqa @@ -89,35 +102,8 @@ ENTITY_IMAGE_CACHE = { CACHE_MAXSIZE: 16 } -MEDIA_TYPE_MUSIC = 'music' -MEDIA_TYPE_TVSHOW = 'tvshow' -MEDIA_TYPE_MOVIE = 'movie' -MEDIA_TYPE_VIDEO = 'video' -MEDIA_TYPE_EPISODE = 'episode' -MEDIA_TYPE_CHANNEL = 'channel' -MEDIA_TYPE_PLAYLIST = 'playlist' -MEDIA_TYPE_URL = 'url' - SCAN_INTERVAL = timedelta(seconds=10) -SUPPORT_PAUSE = 1 -SUPPORT_SEEK = 2 -SUPPORT_VOLUME_SET = 4 -SUPPORT_VOLUME_MUTE = 8 -SUPPORT_PREVIOUS_TRACK = 16 -SUPPORT_NEXT_TRACK = 32 - -SUPPORT_TURN_ON = 128 -SUPPORT_TURN_OFF = 256 -SUPPORT_PLAY_MEDIA = 512 -SUPPORT_VOLUME_STEP = 1024 -SUPPORT_SELECT_SOURCE = 2048 -SUPPORT_STOP = 4096 -SUPPORT_CLEAR_PLAYLIST = 8192 -SUPPORT_PLAY = 16384 -SUPPORT_SHUFFLE_SET = 32768 -SUPPORT_SELECT_SOUND_MODE = 65536 - # Service call validation schemas MEDIA_PLAYER_SCHEMA = vol.Schema({ ATTR_ENTITY_ID: cv.comp_entity_ids, diff --git a/homeassistant/components/media_player/anthemav.py b/homeassistant/components/media_player/anthemav.py index a0bc3d05dcb..d48f90d2bd7 100644 --- a/homeassistant/components/media_player/anthemav.py +++ b/homeassistant/components/media_player/anthemav.py @@ -9,8 +9,10 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice) + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON) diff --git a/homeassistant/components/media_player/aquostv.py b/homeassistant/components/media_player/aquostv.py index 5c1994e65fc..59723b47522 100644 --- a/homeassistant/components/media_player/aquostv.py +++ b/homeassistant/components/media_player/aquostv.py @@ -9,10 +9,12 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, MediaPlayerDevice) + SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_TIMEOUT, CONF_USERNAME, STATE_OFF, STATE_ON) diff --git a/homeassistant/components/media_player/blackbird.py b/homeassistant/components/media_player/blackbird.py index 2c78bb24bbd..2daa2656e83 100644 --- a/homeassistant/components/media_player/blackbird.py +++ b/homeassistant/components/media_player/blackbird.py @@ -10,8 +10,10 @@ import socket import voluptuous as vol from homeassistant.components.media_player import ( - DOMAIN, MEDIA_PLAYER_SCHEMA, PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, SUPPORT_TURN_ON, MediaPlayerDevice) + MediaPlayerDevice, MEDIA_PLAYER_SCHEMA, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + DOMAIN, SUPPORT_SELECT_SOURCE, + SUPPORT_TURN_OFF, SUPPORT_TURN_ON) from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PORT, CONF_TYPE, STATE_OFF, STATE_ON) diff --git a/homeassistant/components/media_player/bluesound.py b/homeassistant/components/media_player/bluesound.py index 998f559bc8a..c6a8c51ca58 100644 --- a/homeassistant/components/media_player/bluesound.py +++ b/homeassistant/components/media_player/bluesound.py @@ -16,12 +16,13 @@ import async_timeout import voluptuous as vol from homeassistant.components.media_player import ( - ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_STOP, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, - MediaPlayerDevice) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, CONF_HOSTS, CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, diff --git a/homeassistant/components/media_player/braviatv.py b/homeassistant/components/media_player/braviatv.py index 04dc013108f..7efb7abd569 100644 --- a/homeassistant/components/media_player/braviatv.py +++ b/homeassistant/components/media_player/braviatv.py @@ -10,10 +10,12 @@ import re import voluptuous as vol from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, MediaPlayerDevice) + SUPPORT_VOLUME_STEP) from homeassistant.const import CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON import homeassistant.helpers.config_validation as cv from homeassistant.util.json import load_json, save_json diff --git a/homeassistant/components/media_player/channels.py b/homeassistant/components/media_player/channels.py index 43259c40f65..2f7b169601c 100644 --- a/homeassistant/components/media_player/channels.py +++ b/homeassistant/components/media_player/channels.py @@ -9,11 +9,12 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( DOMAIN, MEDIA_TYPE_CHANNEL, MEDIA_TYPE_EPISODE, MEDIA_TYPE_MOVIE, - MEDIA_TYPE_TVSHOW, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + MEDIA_TYPE_TVSHOW, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_VOLUME_MUTE, - MediaPlayerDevice) + SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_VOLUME_MUTE) from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PORT, STATE_IDLE, STATE_PAUSED, STATE_PLAYING) diff --git a/homeassistant/components/media_player/clementine.py b/homeassistant/components/media_player/clementine.py index 2add2bd682a..24df7c24611 100644 --- a/homeassistant/components/media_player/clementine.py +++ b/homeassistant/components/media_player/clementine.py @@ -11,9 +11,11 @@ import time import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, - SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, MediaPlayerDevice) + SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_PAUSED, STATE_PLAYING) diff --git a/homeassistant/components/media_player/cmus.py b/homeassistant/components/media_player/cmus.py index 2711ac1ff11..20b292749b4 100644 --- a/homeassistant/components/media_player/cmus.py +++ b/homeassistant/components/media_player/cmus.py @@ -9,10 +9,11 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_SET, - MediaPlayerDevice) + SUPPORT_SEEK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_SET) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, STATE_OFF, STATE_PAUSED, STATE_PLAYING) diff --git a/homeassistant/components/media_player/const.py b/homeassistant/components/media_player/const.py index b926d893414..bf7e6b4e0ce 100644 --- a/homeassistant/components/media_player/const.py +++ b/homeassistant/components/media_player/const.py @@ -29,7 +29,34 @@ ATTR_SOUND_MODE_LIST = 'sound_mode_list' DOMAIN = 'media_player' +MEDIA_TYPE_MUSIC = 'music' +MEDIA_TYPE_TVSHOW = 'tvshow' +MEDIA_TYPE_MOVIE = 'movie' +MEDIA_TYPE_VIDEO = 'video' +MEDIA_TYPE_EPISODE = 'episode' +MEDIA_TYPE_CHANNEL = 'channel' +MEDIA_TYPE_PLAYLIST = 'playlist' +MEDIA_TYPE_URL = 'url' + SERVICE_CLEAR_PLAYLIST = 'clear_playlist' SERVICE_PLAY_MEDIA = 'play_media' SERVICE_SELECT_SOUND_MODE = 'select_sound_mode' SERVICE_SELECT_SOURCE = 'select_source' + +SUPPORT_PAUSE = 1 +SUPPORT_SEEK = 2 +SUPPORT_VOLUME_SET = 4 +SUPPORT_VOLUME_MUTE = 8 +SUPPORT_PREVIOUS_TRACK = 16 +SUPPORT_NEXT_TRACK = 32 + +SUPPORT_TURN_ON = 128 +SUPPORT_TURN_OFF = 256 +SUPPORT_PLAY_MEDIA = 512 +SUPPORT_VOLUME_STEP = 1024 +SUPPORT_SELECT_SOURCE = 2048 +SUPPORT_STOP = 4096 +SUPPORT_CLEAR_PLAYLIST = 8192 +SUPPORT_PLAY = 16384 +SUPPORT_SHUFFLE_SET = 32768 +SUPPORT_SELECT_SOUND_MODE = 65536 diff --git a/homeassistant/components/media_player/demo.py b/homeassistant/components/media_player/demo.py index 8a88e3bd74e..de455879d3d 100644 --- a/homeassistant/components/media_player/demo.py +++ b/homeassistant/components/media_player/demo.py @@ -6,12 +6,13 @@ https://home-assistant.io/components/demo/ """ import homeassistant.util.dt as dt_util from homeassistant.components.media_player import ( + MediaPlayerDevice) +from homeassistant.components.media_player.const import ( MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOUND_MODE, SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - MediaPlayerDevice) + SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING diff --git a/homeassistant/components/media_player/denon.py b/homeassistant/components/media_player/denon.py index 79b69b551ce..3dc4e550d9b 100644 --- a/homeassistant/components/media_player/denon.py +++ b/homeassistant/components/media_player/denon.py @@ -10,10 +10,11 @@ import telnetlib import voluptuous as vol from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_STOP, - SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - MediaPlayerDevice) + SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/media_player/denonavr.py b/homeassistant/components/media_player/denonavr.py index c565a161b10..d6caf361961 100644 --- a/homeassistant/components/media_player/denonavr.py +++ b/homeassistant/components/media_player/denonavr.py @@ -11,11 +11,13 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOUND_MODE, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, MediaPlayerDevice) + SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_TIMEOUT, CONF_ZONE, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING) diff --git a/homeassistant/components/media_player/directv.py b/homeassistant/components/media_player/directv.py index 707014328c6..9c5a3bf07b8 100644 --- a/homeassistant/components/media_player/directv.py +++ b/homeassistant/components/media_player/directv.py @@ -9,10 +9,11 @@ import requests import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MOVIE, MEDIA_TYPE_TVSHOW, PLATFORM_SCHEMA, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MOVIE, MEDIA_TYPE_TVSHOW, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - MediaPlayerDevice) + SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON) from homeassistant.const import ( CONF_DEVICE, CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_PAUSED, STATE_PLAYING) diff --git a/homeassistant/components/media_player/dlna_dmr.py b/homeassistant/components/media_player/dlna_dmr.py index 802b2b597fc..03015cd5c01 100644 --- a/homeassistant/components/media_player/dlna_dmr.py +++ b/homeassistant/components/media_player/dlna_dmr.py @@ -14,9 +14,11 @@ import aiohttp import voluptuous as vol from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_STOP, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import ( CONF_NAME, CONF_URL, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING) diff --git a/homeassistant/components/media_player/dunehd.py b/homeassistant/components/media_player/dunehd.py index 00c8ff3f4df..796aea86414 100644 --- a/homeassistant/components/media_player/dunehd.py +++ b/homeassistant/components/media_player/dunehd.py @@ -7,9 +7,11 @@ https://home-assistant.io/components/media_player.dunehd/ import voluptuous as vol from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, MediaPlayerDevice) + SUPPORT_TURN_ON) from homeassistant.const import ( CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/media_player/emby.py b/homeassistant/components/media_player/emby.py index dd43d48ee6a..b1259db913d 100644 --- a/homeassistant/components/media_player/emby.py +++ b/homeassistant/components/media_player/emby.py @@ -9,9 +9,11 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, - PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_STOP, MediaPlayerDevice) + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, + SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_STOP) from homeassistant.const import ( CONF_API_KEY, CONF_HOST, CONF_PORT, CONF_SSL, DEVICE_DEFAULT_NAME, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, diff --git a/homeassistant/components/media_player/epson.py b/homeassistant/components/media_player/epson.py index bb1618f2351..38c0ffacc32 100644 --- a/homeassistant/components/media_player/epson.py +++ b/homeassistant/components/media_player/epson.py @@ -9,10 +9,11 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - DOMAIN, MEDIA_PLAYER_SCHEMA, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, + MediaPlayerDevice, MEDIA_PLAYER_SCHEMA, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + DOMAIN, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, - MediaPlayerDevice) + SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP) from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PORT, CONF_SSL, STATE_OFF, STATE_ON) diff --git a/homeassistant/components/media_player/firetv.py b/homeassistant/components/media_player/firetv.py index 6e7d431c9ae..58f1913b9f9 100644 --- a/homeassistant/components/media_player/firetv.py +++ b/homeassistant/components/media_player/firetv.py @@ -10,7 +10,9 @@ import threading import voluptuous as vol from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON) from homeassistant.const import ( diff --git a/homeassistant/components/media_player/frontier_silicon.py b/homeassistant/components/media_player/frontier_silicon.py index 67c84bd7b1b..ed7041a3b82 100644 --- a/homeassistant/components/media_player/frontier_silicon.py +++ b/homeassistant/components/media_player/frontier_silicon.py @@ -9,11 +9,12 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, - MediaPlayerDevice) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_PORT, STATE_OFF, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN) diff --git a/homeassistant/components/media_player/gpmdp.py b/homeassistant/components/media_player/gpmdp.py index b4ede671d52..c72d14ebb8a 100644 --- a/homeassistant/components/media_player/gpmdp.py +++ b/homeassistant/components/media_player/gpmdp.py @@ -12,9 +12,10 @@ import time import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, - SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_VOLUME_SET, - MediaPlayerDevice) + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_VOLUME_SET) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_PAUSED, STATE_PLAYING) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/media_player/gstreamer.py b/homeassistant/components/media_player/gstreamer.py index fa8545bae03..c6571894472 100644 --- a/homeassistant/components/media_player/gstreamer.py +++ b/homeassistant/components/media_player/gstreamer.py @@ -9,8 +9,10 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, - SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_VOLUME_SET, MediaPlayerDevice) + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_VOLUME_SET) from homeassistant.const import CONF_NAME, EVENT_HOMEASSISTANT_STOP, STATE_IDLE import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/media_player/harman_kardon_avr.py b/homeassistant/components/media_player/harman_kardon_avr.py index 46d1dd4d698..334757c086d 100644 --- a/homeassistant/components/media_player/harman_kardon_avr.py +++ b/homeassistant/components/media_player/harman_kardon_avr.py @@ -10,9 +10,10 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.media_player import ( + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, - PLATFORM_SCHEMA, SUPPORT_TURN_ON, SUPPORT_SELECT_SOURCE, - MediaPlayerDevice) + SUPPORT_TURN_ON, SUPPORT_SELECT_SOURCE) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON) diff --git a/homeassistant/components/media_player/horizon.py b/homeassistant/components/media_player/horizon.py index 058796ea46d..e7cfcfe62b1 100644 --- a/homeassistant/components/media_player/horizon.py +++ b/homeassistant/components/media_player/horizon.py @@ -11,9 +11,11 @@ import voluptuous as vol from homeassistant import util from homeassistant.components.media_player import ( - MEDIA_TYPE_CHANNEL, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, MediaPlayerDevice) + SUPPORT_TURN_ON) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_PAUSED, STATE_PLAYING) from homeassistant.exceptions import PlatformNotReady diff --git a/homeassistant/components/media_player/itunes.py b/homeassistant/components/media_player/itunes.py index e2ae179676b..f8380032aea 100644 --- a/homeassistant/components/media_player/itunes.py +++ b/homeassistant/components/media_player/itunes.py @@ -10,10 +10,12 @@ import requests import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, MediaPlayerDevice) + SUPPORT_VOLUME_SET) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PORT, CONF_SSL, STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING) diff --git a/homeassistant/components/media_player/kodi.py b/homeassistant/components/media_player/kodi.py index a83287eb617..f8d0cdc5a12 100644 --- a/homeassistant/components/media_player/kodi.py +++ b/homeassistant/components/media_player/kodi.py @@ -16,13 +16,14 @@ import aiohttp import voluptuous as vol from homeassistant.components.media_player import ( - DOMAIN, MEDIA_PLAYER_SCHEMA, MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MOVIE, + MediaPlayerDevice, MEDIA_PLAYER_SCHEMA, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + DOMAIN, MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, - PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SHUFFLE_SET, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, - MediaPlayerDevice) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_PROXY_SSL, CONF_TIMEOUT, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, diff --git a/homeassistant/components/media_player/lg_netcast.py b/homeassistant/components/media_player/lg_netcast.py index c2f63c71f89..7c5d9789372 100644 --- a/homeassistant/components/media_player/lg_netcast.py +++ b/homeassistant/components/media_player/lg_netcast.py @@ -12,10 +12,11 @@ import voluptuous as vol from homeassistant import util from homeassistant.components.media_player import ( - MEDIA_TYPE_CHANNEL, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, - MediaPlayerDevice) + SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME, STATE_OFF, STATE_PAUSED, STATE_PLAYING) diff --git a/homeassistant/components/media_player/lg_soundbar.py b/homeassistant/components/media_player/lg_soundbar.py index 38b27bd074a..b45baf88bca 100644 --- a/homeassistant/components/media_player/lg_soundbar.py +++ b/homeassistant/components/media_player/lg_soundbar.py @@ -7,8 +7,10 @@ https://home-assistant.io/components/media_player.lg_soundbar/ import logging from homeassistant.components.media_player import ( + MediaPlayerDevice) +from homeassistant.components.media_player.const import ( SUPPORT_SELECT_SOURCE, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_SELECT_SOUND_MODE, MediaPlayerDevice) + SUPPORT_SELECT_SOUND_MODE) from homeassistant.const import STATE_ON diff --git a/homeassistant/components/media_player/liveboxplaytv.py b/homeassistant/components/media_player/liveboxplaytv.py index 3f8ea2cfd48..f69c3c67aec 100644 --- a/homeassistant/components/media_player/liveboxplaytv.py +++ b/homeassistant/components/media_player/liveboxplaytv.py @@ -11,10 +11,12 @@ import requests import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_CHANNEL, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_STEP, MediaPlayerDevice) + SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING) diff --git a/homeassistant/components/media_player/mediaroom.py b/homeassistant/components/media_player/mediaroom.py index 345b58cbbe4..29cc7332936 100644 --- a/homeassistant/components/media_player/mediaroom.py +++ b/homeassistant/components/media_player/mediaroom.py @@ -9,10 +9,12 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_CHANNEL, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_STEP, MediaPlayerDevice) + SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_OPTIMISTIC, CONF_TIMEOUT, EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_PAUSED, STATE_PLAYING, diff --git a/homeassistant/components/media_player/monoprice.py b/homeassistant/components/media_player/monoprice.py index 96b8dcbcf26..e98ad47a6e7 100644 --- a/homeassistant/components/media_player/monoprice.py +++ b/homeassistant/components/media_player/monoprice.py @@ -9,9 +9,11 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - DOMAIN, MEDIA_PLAYER_SCHEMA, PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, + MediaPlayerDevice, MEDIA_PLAYER_SCHEMA, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + DOMAIN, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, MediaPlayerDevice) + SUPPORT_VOLUME_STEP) from homeassistant.const import ( ATTR_ENTITY_ID, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/media_player/mpchc.py b/homeassistant/components/media_player/mpchc.py index e6bc1f2699d..61d89c6d0b1 100644 --- a/homeassistant/components/media_player/mpchc.py +++ b/homeassistant/components/media_player/mpchc.py @@ -11,9 +11,11 @@ import requests import voluptuous as vol from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_STEP, MediaPlayerDevice) + SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PORT, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING) diff --git a/homeassistant/components/media_player/mpd.py b/homeassistant/components/media_player/mpd.py index 09d0a976b82..9d8015109b2 100644 --- a/homeassistant/components/media_player/mpd.py +++ b/homeassistant/components/media_player/mpd.py @@ -11,12 +11,14 @@ import os import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, PLATFORM_SCHEMA, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, MediaPlayerDevice) + SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, STATE_OFF, STATE_PAUSED, STATE_PLAYING) diff --git a/homeassistant/components/media_player/nad.py b/homeassistant/components/media_player/nad.py index 57dca116f6b..127be02dac4 100644 --- a/homeassistant/components/media_player/nad.py +++ b/homeassistant/components/media_player/nad.py @@ -10,9 +10,10 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, - MediaPlayerDevice) + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) from homeassistant.const import CONF_NAME, STATE_OFF, STATE_ON REQUIREMENTS = ['nad_receiver==0.0.11'] diff --git a/homeassistant/components/media_player/onkyo.py b/homeassistant/components/media_player/onkyo.py index 5ff54201b3c..df30c7e0782 100644 --- a/homeassistant/components/media_player/onkyo.py +++ b/homeassistant/components/media_player/onkyo.py @@ -12,9 +12,11 @@ from typing import List # noqa: F401 import voluptuous as vol from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_SELECT_SOURCE, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, MediaPlayerDevice, DOMAIN) + SUPPORT_VOLUME_STEP, DOMAIN) from homeassistant.const import ( CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, ATTR_ENTITY_ID) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/media_player/openhome.py b/homeassistant/components/media_player/openhome.py index ab23f8a7f9a..d828284a563 100644 --- a/homeassistant/components/media_player/openhome.py +++ b/homeassistant/components/media_player/openhome.py @@ -7,10 +7,11 @@ https://home-assistant.io/components/media_player.openhome/ import logging from homeassistant.components.media_player import ( + MediaPlayerDevice) +from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, - MediaPlayerDevice) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) from homeassistant.const import ( STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING) diff --git a/homeassistant/components/media_player/panasonic_bluray.py b/homeassistant/components/media_player/panasonic_bluray.py index 041efed74bf..36a3160d3b5 100644 --- a/homeassistant/components/media_player/panasonic_bluray.py +++ b/homeassistant/components/media_player/panasonic_bluray.py @@ -10,8 +10,10 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_STOP, - SUPPORT_TURN_OFF, SUPPORT_TURN_ON, MediaPlayerDevice) + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_STOP, + SUPPORT_TURN_OFF, SUPPORT_TURN_ON) from homeassistant.const import ( CONF_HOST, CONF_NAME, STATE_IDLE, STATE_OFF, STATE_PLAYING) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/media_player/panasonic_viera.py b/homeassistant/components/media_player/panasonic_viera.py index bff108d70d7..e5ce22e9524 100644 --- a/homeassistant/components/media_player/panasonic_viera.py +++ b/homeassistant/components/media_player/panasonic_viera.py @@ -9,10 +9,12 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_URL, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_URL, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, MediaPlayerDevice) + SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/media_player/pandora.py b/homeassistant/components/media_player/pandora.py index 231ea5302ae..ca78f7a4318 100644 --- a/homeassistant/components/media_player/pandora.py +++ b/homeassistant/components/media_player/pandora.py @@ -12,14 +12,14 @@ import shutil import signal from homeassistant import util -from homeassistant.components.media_player import ( - MEDIA_TYPE_MUSIC, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PLAY, - SERVICE_MEDIA_PLAY_PAUSE, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_UP, - SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, SUPPORT_TURN_ON, MediaPlayerDevice) +from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, + SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON) from homeassistant.const import ( - EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, STATE_PAUSED, - STATE_PLAYING) + EVENT_HOMEASSISTANT_STOP, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PLAY, + SERVICE_MEDIA_PLAY_PAUSE, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_UP, + STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING) REQUIREMENTS = ['pexpect==4.6.0'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/media_player/philips_js.py b/homeassistant/components/media_player/philips_js.py index 506e5a9e479..4f8a1339781 100644 --- a/homeassistant/components/media_player/philips_js.py +++ b/homeassistant/components/media_player/philips_js.py @@ -10,10 +10,11 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_NEXT_TRACK, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, - MediaPlayerDevice) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_API_VERSION, CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/media_player/pioneer.py b/homeassistant/components/media_player/pioneer.py index 171343d4adb..00fa453100a 100644 --- a/homeassistant/components/media_player/pioneer.py +++ b/homeassistant/components/media_player/pioneer.py @@ -10,9 +10,10 @@ import telnetlib import voluptuous as vol from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - MediaPlayerDevice) + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_SELECT_SOURCE, + SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PORT, CONF_TIMEOUT, STATE_OFF, STATE_ON) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/media_player/pjlink.py b/homeassistant/components/media_player/pjlink.py index 0609b75f98d..c1b883a0295 100644 --- a/homeassistant/components/media_player/pjlink.py +++ b/homeassistant/components/media_player/pjlink.py @@ -9,8 +9,10 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, MediaPlayerDevice) + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, STATE_OFF, STATE_ON) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index 2110c42d371..c67849edee9 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -13,10 +13,11 @@ import voluptuous as vol from homeassistant import util from homeassistant.components.media_player import ( - MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, PLATFORM_SCHEMA, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, - SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - MediaPlayerDevice) + SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import ( DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING) from homeassistant.helpers import config_validation as cv diff --git a/homeassistant/components/media_player/russound_rio.py b/homeassistant/components/media_player/russound_rio.py index 19cc2228d32..972594e07e6 100644 --- a/homeassistant/components/media_player/russound_rio.py +++ b/homeassistant/components/media_player/russound_rio.py @@ -9,9 +9,10 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - MediaPlayerDevice) + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_MUSIC, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON) diff --git a/homeassistant/components/media_player/russound_rnet.py b/homeassistant/components/media_player/russound_rnet.py index 7f4d04eb634..6d919cdf7a8 100644 --- a/homeassistant/components/media_player/russound_rnet.py +++ b/homeassistant/components/media_player/russound_rnet.py @@ -9,8 +9,10 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice) + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/media_player/samsungtv.py b/homeassistant/components/media_player/samsungtv.py index 9def9875ab4..db6bd317c40 100644 --- a/homeassistant/components/media_player/samsungtv.py +++ b/homeassistant/components/media_player/samsungtv.py @@ -12,10 +12,11 @@ import socket import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_CHANNEL, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, - MediaPlayerDevice) + SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT, CONF_TIMEOUT, STATE_OFF, STATE_ON) diff --git a/homeassistant/components/media_player/snapcast.py b/homeassistant/components/media_player/snapcast.py index cfe2f997295..74b17ae5ff1 100644 --- a/homeassistant/components/media_player/snapcast.py +++ b/homeassistant/components/media_player/snapcast.py @@ -10,8 +10,10 @@ import socket import voluptuous as vol from homeassistant.components.media_player import ( - DOMAIN, PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, MediaPlayerDevice) + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + DOMAIN, SUPPORT_SELECT_SOURCE, SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET) from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, CONF_PORT, STATE_IDLE, STATE_OFF, STATE_ON, STATE_PLAYING, STATE_UNKNOWN) diff --git a/homeassistant/components/media_player/songpal.py b/homeassistant/components/media_player/songpal.py index e67578539ad..7665b409d1d 100644 --- a/homeassistant/components/media_player/songpal.py +++ b/homeassistant/components/media_player/songpal.py @@ -11,9 +11,11 @@ from collections import OrderedDict import voluptuous as vol from homeassistant.components.media_player import ( - DOMAIN, PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + DOMAIN, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, MediaPlayerDevice) + SUPPORT_VOLUME_STEP) from homeassistant.const import ( ATTR_ENTITY_ID, CONF_NAME, STATE_OFF, STATE_ON, EVENT_HOMEASSISTANT_STOP) from homeassistant.exceptions import PlatformNotReady diff --git a/homeassistant/components/media_player/soundtouch.py b/homeassistant/components/media_player/soundtouch.py index 037a9b88fc6..b2045b9b65e 100644 --- a/homeassistant/components/media_player/soundtouch.py +++ b/homeassistant/components/media_player/soundtouch.py @@ -10,10 +10,11 @@ import re import voluptuous as vol from homeassistant.components.media_player import ( - DOMAIN, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + DOMAIN, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, - MediaPlayerDevice) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_PAUSED, STATE_PLAYING, STATE_UNAVAILABLE) diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py index 4fbd43f3f16..9965487ded9 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -11,10 +11,11 @@ import voluptuous as vol from homeassistant.components.http import HomeAssistantView from homeassistant.components.media_player import ( - MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_VOLUME_SET, - MediaPlayerDevice) + SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_VOLUME_SET) from homeassistant.const import ( CONF_NAME, STATE_IDLE, STATE_PAUSED, STATE_PLAYING) from homeassistant.core import callback diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py index 73b6a070419..5f6fd525a11 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -14,11 +14,13 @@ import async_timeout import voluptuous as vol from homeassistant.components.media_player import ( - ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_PLAYER_SCHEMA, MEDIA_TYPE_MUSIC, - PLATFORM_SCHEMA, SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + MediaPlayerDevice, MEDIA_PLAYER_SCHEMA, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, + SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SHUFFLE_SET, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import ( ATTR_COMMAND, CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING) diff --git a/homeassistant/components/media_player/ue_smart_radio.py b/homeassistant/components/media_player/ue_smart_radio.py index 75f6d92a98c..2261aadc2f6 100644 --- a/homeassistant/components/media_player/ue_smart_radio.py +++ b/homeassistant/components/media_player/ue_smart_radio.py @@ -11,10 +11,11 @@ import requests import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - MediaPlayerDevice) + SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import ( CONF_PASSWORD, CONF_USERNAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING) diff --git a/homeassistant/components/media_player/universal.py b/homeassistant/components/media_player/universal.py index a0c99dbec45..5730a086731 100644 --- a/homeassistant/components/media_player/universal.py +++ b/homeassistant/components/media_player/universal.py @@ -10,6 +10,8 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( ATTR_APP_ID, ATTR_APP_NAME, ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE_LIST, ATTR_MEDIA_ALBUM_ARTIST, ATTR_MEDIA_ALBUM_NAME, ATTR_MEDIA_ARTIST, ATTR_MEDIA_CHANNEL, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, @@ -17,11 +19,11 @@ from homeassistant.components.media_player import ( ATTR_MEDIA_POSITION, ATTR_MEDIA_POSITION_UPDATED_AT, ATTR_MEDIA_SEASON, ATTR_MEDIA_SEEK_POSITION, ATTR_MEDIA_SERIES_TITLE, ATTR_MEDIA_SHUFFLE, ATTR_MEDIA_TITLE, ATTR_MEDIA_TRACK, ATTR_MEDIA_VOLUME_LEVEL, - ATTR_MEDIA_VOLUME_MUTED, DOMAIN, PLATFORM_SCHEMA, SERVICE_CLEAR_PLAYLIST, + ATTR_MEDIA_VOLUME_MUTED, DOMAIN, SERVICE_CLEAR_PLAYLIST, SERVICE_PLAY_MEDIA, SERVICE_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST, SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, MediaPlayerDevice) + SUPPORT_VOLUME_STEP) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, ATTR_SUPPORTED_FEATURES, CONF_NAME, CONF_STATE, CONF_STATE_TEMPLATE, SERVICE_MEDIA_NEXT_TRACK, diff --git a/homeassistant/components/media_player/vizio.py b/homeassistant/components/media_player/vizio.py index 5aae8661bd8..395f5bb369e 100644 --- a/homeassistant/components/media_player/vizio.py +++ b/homeassistant/components/media_player/vizio.py @@ -11,10 +11,11 @@ import voluptuous as vol from homeassistant import util from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, - MediaPlayerDevice) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON) from homeassistant.helpers import config_validation as cv diff --git a/homeassistant/components/media_player/vlc.py b/homeassistant/components/media_player/vlc.py index 5cc4196d4e1..592243938d7 100644 --- a/homeassistant/components/media_player/vlc.py +++ b/homeassistant/components/media_player/vlc.py @@ -9,9 +9,10 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_PAUSE, SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, SUPPORT_STOP, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - MediaPlayerDevice) + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_MUSIC, SUPPORT_PAUSE, SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, SUPPORT_STOP, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import ( CONF_NAME, STATE_IDLE, STATE_PAUSED, STATE_PLAYING) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/media_player/volumio.py b/homeassistant/components/media_player/volumio.py index bd43e6c3710..a72f34fac1d 100644 --- a/homeassistant/components/media_player/volumio.py +++ b/homeassistant/components/media_player/volumio.py @@ -15,11 +15,12 @@ import aiohttp import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_CLEAR_PLAYLIST, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_MUSIC, SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SELECT_SOURCE, SUPPORT_STOP, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, - MediaPlayerDevice) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PORT, STATE_IDLE, STATE_PAUSED, STATE_PLAYING) from homeassistant.helpers.aiohttp_client import async_get_clientsession diff --git a/homeassistant/components/media_player/xiaomi_tv.py b/homeassistant/components/media_player/xiaomi_tv.py index 09d36f82db0..e3b25c3c31f 100644 --- a/homeassistant/components/media_player/xiaomi_tv.py +++ b/homeassistant/components/media_player/xiaomi_tv.py @@ -9,8 +9,9 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_STEP, - MediaPlayerDevice) + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_STEP) from homeassistant.const import CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/media_player/yamaha.py b/homeassistant/components/media_player/yamaha.py index 67c55dad0d1..f652d95e713 100644 --- a/homeassistant/components/media_player/yamaha.py +++ b/homeassistant/components/media_player/yamaha.py @@ -10,11 +10,13 @@ import requests import voluptuous as vol from homeassistant.components.media_player import ( - DOMAIN, MEDIA_PLAYER_SCHEMA, MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, + MediaPlayerDevice, MEDIA_PLAYER_SCHEMA, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + DOMAIN, MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_SELECT_SOUND_MODE, MediaPlayerDevice) + SUPPORT_SELECT_SOUND_MODE) from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, STATE_IDLE, STATE_OFF, STATE_ON, diff --git a/homeassistant/components/media_player/yamaha_musiccast.py b/homeassistant/components/media_player/yamaha_musiccast.py index 535a2ad01ca..6aa06b604c5 100644 --- a/homeassistant/components/media_player/yamaha_musiccast.py +++ b/homeassistant/components/media_player/yamaha_musiccast.py @@ -9,10 +9,11 @@ import logging import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_STOP, - SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - MediaPlayerDevice) + SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import ( CONF_HOST, CONF_PORT, STATE_IDLE, STATE_ON, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN) diff --git a/homeassistant/components/media_player/ziggo_mediabox_xl.py b/homeassistant/components/media_player/ziggo_mediabox_xl.py index 57ef69c923e..abad22d89eb 100644 --- a/homeassistant/components/media_player/ziggo_mediabox_xl.py +++ b/homeassistant/components/media_player/ziggo_mediabox_xl.py @@ -10,9 +10,11 @@ import socket import voluptuous as vol from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, MediaPlayerDevice) + SUPPORT_TURN_ON) from homeassistant.const import ( CONF_HOST, CONF_NAME, STATE_OFF, STATE_PAUSED, STATE_PLAYING) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 9dc1151064d..7d1977aadc1 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -7,10 +7,11 @@ https://home-assistant.io/components/media_player.roku/ import logging import requests.exceptions -from homeassistant.components.media_player import ( +from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player.const import ( MEDIA_TYPE_MOVIE, SUPPORT_NEXT_TRACK, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import (CONF_HOST, STATE_HOME, STATE_IDLE, STATE_PLAYING) diff --git a/homeassistant/components/sisyphus/media_player.py b/homeassistant/components/sisyphus/media_player.py index ef6b02514f0..fd8c228d396 100644 --- a/homeassistant/components/sisyphus/media_player.py +++ b/homeassistant/components/sisyphus/media_player.py @@ -6,10 +6,11 @@ https://home-assistant.io/components/media_player.sisyphus/ """ import logging -from homeassistant.components.media_player import ( +from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SHUFFLE_SET, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.components.sisyphus import DATA_SISYPHUS from homeassistant.const import ( CONF_HOST, CONF_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING) diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index b34aabd4c51..2106e46cb5d 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -15,11 +15,13 @@ import requests import voluptuous as vol from homeassistant.components.media_player import ( - ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_STOP, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.components.sonos import DOMAIN as SONOS_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TIME, CONF_HOSTS, STATE_IDLE, STATE_OFF, STATE_PAUSED, diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index 3f8d1b6fdec..475ed2c6892 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -18,10 +18,10 @@ from aiohttp import web import voluptuous as vol from homeassistant.components.http import HomeAssistantView -from homeassistant.components.media_player import ( +from homeassistant.components.media_player.const import ( ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, MEDIA_TYPE_MUSIC, SERVICE_PLAY_MEDIA) -from homeassistant.components.media_player import DOMAIN as DOMAIN_MP +from homeassistant.components.media_player.const import DOMAIN as DOMAIN_MP from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index f80e29d35a0..d34dbe80778 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -14,10 +14,12 @@ import voluptuous as vol from homeassistant import util from homeassistant.components.media_player import ( - MEDIA_TYPE_CHANNEL, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, MediaPlayerDevice) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_CUSTOMIZE, CONF_FILENAME, CONF_HOST, CONF_NAME, CONF_TIMEOUT, STATE_OFF, STATE_PAUSED, STATE_PLAYING) diff --git a/tests/components/google/test_tts.py b/tests/components/google/test_tts.py index 2b5346bf639..78bdd50b6d7 100644 --- a/tests/components/google/test_tts.py +++ b/tests/components/google/test_tts.py @@ -5,7 +5,7 @@ import shutil from unittest.mock import patch import homeassistant.components.tts as tts -from homeassistant.components.media_player import ( +from homeassistant.components.media_player.const import ( SERVICE_PLAY_MEDIA, ATTR_MEDIA_CONTENT_ID, DOMAIN as DOMAIN_MP) from homeassistant.setup import setup_component diff --git a/tests/components/homekit/test_get_accessories.py b/tests/components/homekit/test_get_accessories.py index d39609b079a..e395402b958 100644 --- a/tests/components/homekit/test_get_accessories.py +++ b/tests/components/homekit/test_get_accessories.py @@ -6,7 +6,7 @@ import pytest from homeassistant.core import State import homeassistant.components.cover as cover import homeassistant.components.climate as climate -import homeassistant.components.media_player as media_player +import homeassistant.components.media_player.const as media_player_c from homeassistant.components.homekit import get_accessory, TYPES from homeassistant.components.homekit.const import ( CONF_FEATURE_LIST, FEATURE_ON_OFF, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, @@ -60,9 +60,9 @@ def test_customize_options(config, name): ('Light', 'light.test', 'on', {}, {}), ('Lock', 'lock.test', 'locked', {}, {ATTR_CODE: '1234'}), ('MediaPlayer', 'media_player.test', 'on', - {ATTR_SUPPORTED_FEATURES: media_player.SUPPORT_TURN_ON | - media_player.SUPPORT_TURN_OFF}, {CONF_FEATURE_LIST: - {FEATURE_ON_OFF: None}}), + {ATTR_SUPPORTED_FEATURES: media_player_c.SUPPORT_TURN_ON | + media_player_c.SUPPORT_TURN_OFF}, {CONF_FEATURE_LIST: + {FEATURE_ON_OFF: None}}), ('SecuritySystem', 'alarm_control_panel.test', 'armed_away', {}, {ATTR_CODE: '1234'}), ('Thermostat', 'climate.test', 'auto', {}, {}), diff --git a/tests/components/homekit/test_type_media_players.py b/tests/components/homekit/test_type_media_players.py index 6b23b3cc58e..065d1845fdb 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -4,7 +4,7 @@ from homeassistant.components.homekit.const import ( ATTR_VALUE, CONF_FEATURE_LIST, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE) from homeassistant.components.homekit.type_media_players import MediaPlayer -from homeassistant.components.media_player import ( +from homeassistant.components.media_player.const import ( ATTR_MEDIA_VOLUME_MUTED, DOMAIN) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, STATE_IDLE, STATE_OFF, STATE_ON, diff --git a/tests/components/media_player/common.py b/tests/components/media_player/common.py index 2174967eae5..4a53920d758 100644 --- a/tests/components/media_player/common.py +++ b/tests/components/media_player/common.py @@ -3,7 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ -from homeassistant.components.media_player import ( +from homeassistant.components.media_player.const import ( ATTR_INPUT_SOURCE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_ENQUEUE, ATTR_MEDIA_SEEK_POSITION, ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, DOMAIN, SERVICE_CLEAR_PLAYLIST, diff --git a/tests/components/media_player/test_blackbird.py b/tests/components/media_player/test_blackbird.py index 38a63f294f9..6ab3b69b558 100644 --- a/tests/components/media_player/test_blackbird.py +++ b/tests/components/media_player/test_blackbird.py @@ -4,7 +4,7 @@ from unittest import mock import voluptuous as vol from collections import defaultdict -from homeassistant.components.media_player import ( +from homeassistant.components.media_player.const import ( DOMAIN, SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_SELECT_SOURCE) from homeassistant.const import STATE_ON, STATE_OFF diff --git a/tests/components/media_player/test_directv.py b/tests/components/media_player/test_directv.py index d8e561d8d2a..5918946fe6c 100644 --- a/tests/components/media_player/test_directv.py +++ b/tests/components/media_player/test_directv.py @@ -5,10 +5,14 @@ from datetime import datetime, timedelta import requests import pytest -import homeassistant.components.media_player as mp -from homeassistant.components.media_player import ( - ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_ENQUEUE, DOMAIN, - SERVICE_PLAY_MEDIA) +from homeassistant.components.media_player.const import ( + ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, MEDIA_TYPE_TVSHOW, + ATTR_MEDIA_ENQUEUE, ATTR_MEDIA_DURATION, ATTR_MEDIA_TITLE, + ATTR_MEDIA_POSITION, ATTR_MEDIA_SERIES_TITLE, ATTR_MEDIA_CHANNEL, + ATTR_INPUT_SOURCE, ATTR_MEDIA_POSITION_UPDATED_AT, DOMAIN, + SERVICE_PLAY_MEDIA, SUPPORT_PAUSE, SUPPORT_TURN_ON, SUPPORT_TURN_OFF, + SUPPORT_PLAY_MEDIA, SUPPORT_STOP, SUPPORT_NEXT_TRACK, + SUPPORT_PREVIOUS_TRACK, SUPPORT_PLAY) from homeassistant.components.media_player.directv import ( ATTR_MEDIA_CURRENTLY_RECORDING, ATTR_MEDIA_RATING, ATTR_MEDIA_RECORDED, ATTR_MEDIA_START_TIME, DEFAULT_DEVICE, DEFAULT_PORT) @@ -149,7 +153,7 @@ def platforms(hass, dtv_side_effect, mock_now): patch('DirectPy.DIRECTV', side_effect=dtv_side_effect), \ patch('homeassistant.util.dt.utcnow', return_value=mock_now): hass.loop.run_until_complete(async_setup_component( - hass, mp.DOMAIN, config)) + hass, DOMAIN, config)) hass.loop.run_until_complete(hass.async_block_till_done()) yield @@ -281,7 +285,7 @@ async def test_setup_platform_config(hass): with MockDependency('DirectPy'), \ patch('DirectPy.DIRECTV', new=MockDirectvClass): - await async_setup_component(hass, mp.DOMAIN, WORKING_CONFIG) + await async_setup_component(hass, DOMAIN, WORKING_CONFIG) await hass.async_block_till_done() state = hass.states.get(MAIN_ENTITY_ID) @@ -295,7 +299,7 @@ async def test_setup_platform_discover(hass): patch('DirectPy.DIRECTV', new=MockDirectvClass): hass.async_create_task( - async_load_platform(hass, mp.DOMAIN, 'directv', DISCOVERY_INFO, + async_load_platform(hass, DOMAIN, 'directv', DISCOVERY_INFO, {'media_player': {}}) ) await hass.async_block_till_done() @@ -310,10 +314,10 @@ async def test_setup_platform_discover_duplicate(hass): with MockDependency('DirectPy'), \ patch('DirectPy.DIRECTV', new=MockDirectvClass): - await async_setup_component(hass, mp.DOMAIN, WORKING_CONFIG) + await async_setup_component(hass, DOMAIN, WORKING_CONFIG) await hass.async_block_till_done() hass.async_create_task( - async_load_platform(hass, mp.DOMAIN, 'directv', DISCOVERY_INFO, + async_load_platform(hass, DOMAIN, 'directv', DISCOVERY_INFO, {'media_player': {}}) ) await hass.async_block_till_done() @@ -337,11 +341,11 @@ async def test_setup_platform_discover_client(hass): with MockDependency('DirectPy'), \ patch('DirectPy.DIRECTV', new=MockDirectvClass): - await async_setup_component(hass, mp.DOMAIN, WORKING_CONFIG) + await async_setup_component(hass, DOMAIN, WORKING_CONFIG) await hass.async_block_till_done() hass.async_create_task( - async_load_platform(hass, mp.DOMAIN, 'directv', DISCOVERY_INFO, + async_load_platform(hass, DOMAIN, 'directv', DISCOVERY_INFO, {'media_player': {}}) ) await hass.async_block_till_done() @@ -362,16 +366,16 @@ async def test_supported_features(hass, platforms): """Test supported features.""" # Features supported for main DVR state = hass.states.get(MAIN_ENTITY_ID) - assert mp.SUPPORT_PAUSE | mp.SUPPORT_TURN_ON | mp.SUPPORT_TURN_OFF |\ - mp.SUPPORT_PLAY_MEDIA | mp.SUPPORT_STOP | mp.SUPPORT_NEXT_TRACK |\ - mp.SUPPORT_PREVIOUS_TRACK | mp.SUPPORT_PLAY ==\ + assert SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF |\ + SUPPORT_PLAY_MEDIA | SUPPORT_STOP | SUPPORT_NEXT_TRACK |\ + SUPPORT_PREVIOUS_TRACK | SUPPORT_PLAY ==\ state.attributes.get('supported_features') # Feature supported for clients. state = hass.states.get(CLIENT_ENTITY_ID) - assert mp.SUPPORT_PAUSE |\ - mp.SUPPORT_PLAY_MEDIA | mp.SUPPORT_STOP | mp.SUPPORT_NEXT_TRACK |\ - mp.SUPPORT_PREVIOUS_TRACK | mp.SUPPORT_PLAY ==\ + assert SUPPORT_PAUSE |\ + SUPPORT_PLAY_MEDIA | SUPPORT_STOP | SUPPORT_NEXT_TRACK |\ + SUPPORT_PREVIOUS_TRACK | SUPPORT_PLAY ==\ state.attributes.get('supported_features') @@ -391,21 +395,21 @@ async def test_check_attributes(hass, platforms, mock_now): state = hass.states.get(CLIENT_ENTITY_ID) assert state.state == STATE_PLAYING - assert state.attributes.get(mp.ATTR_MEDIA_CONTENT_ID) == \ + assert state.attributes.get(ATTR_MEDIA_CONTENT_ID) == \ RECORDING['programId'] - assert state.attributes.get(mp.ATTR_MEDIA_CONTENT_TYPE) == \ - mp.MEDIA_TYPE_TVSHOW - assert state.attributes.get(mp.ATTR_MEDIA_DURATION) == \ + assert state.attributes.get(ATTR_MEDIA_CONTENT_TYPE) == \ + MEDIA_TYPE_TVSHOW + assert state.attributes.get(ATTR_MEDIA_DURATION) == \ RECORDING['duration'] - assert state.attributes.get(mp.ATTR_MEDIA_POSITION) == 2 + assert state.attributes.get(ATTR_MEDIA_POSITION) == 2 assert state.attributes.get( - mp.ATTR_MEDIA_POSITION_UPDATED_AT) == next_update - assert state.attributes.get(mp.ATTR_MEDIA_TITLE) == RECORDING['title'] - assert state.attributes.get(mp.ATTR_MEDIA_SERIES_TITLE) == \ + ATTR_MEDIA_POSITION_UPDATED_AT) == next_update + assert state.attributes.get(ATTR_MEDIA_TITLE) == RECORDING['title'] + assert state.attributes.get(ATTR_MEDIA_SERIES_TITLE) == \ RECORDING['episodeTitle'] - assert state.attributes.get(mp.ATTR_MEDIA_CHANNEL) == \ + assert state.attributes.get(ATTR_MEDIA_CHANNEL) == \ "{} ({})".format(RECORDING['callsign'], RECORDING['major']) - assert state.attributes.get(mp.ATTR_INPUT_SOURCE) == RECORDING['major'] + assert state.attributes.get(ATTR_INPUT_SOURCE) == RECORDING['major'] assert state.attributes.get(ATTR_MEDIA_CURRENTLY_RECORDING) == \ RECORDING['isRecording'] assert state.attributes.get(ATTR_MEDIA_RATING) == RECORDING['rating'] @@ -423,7 +427,7 @@ async def test_check_attributes(hass, platforms, mock_now): state = hass.states.get(CLIENT_ENTITY_ID) assert state.state == STATE_PAUSED assert state.attributes.get( - mp.ATTR_MEDIA_POSITION_UPDATED_AT) == next_update + ATTR_MEDIA_POSITION_UPDATED_AT) == next_update async def test_main_services(hass, platforms, main_dtv, mock_now): diff --git a/tests/components/media_player/test_monoprice.py b/tests/components/media_player/test_monoprice.py index c6a6b3036d9..d6374cf9dd7 100644 --- a/tests/components/media_player/test_monoprice.py +++ b/tests/components/media_player/test_monoprice.py @@ -4,7 +4,7 @@ from unittest import mock import voluptuous as vol from collections import defaultdict -from homeassistant.components.media_player import ( +from homeassistant.components.media_player.const import ( DOMAIN, SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE) from homeassistant.const import STATE_ON, STATE_OFF diff --git a/tests/components/media_player/test_samsungtv.py b/tests/components/media_player/test_samsungtv.py index c2f5d28fd5d..a74f476981a 100644 --- a/tests/components/media_player/test_samsungtv.py +++ b/tests/components/media_player/test_samsungtv.py @@ -8,7 +8,7 @@ from asynctest import mock import pytest import tests.common -from homeassistant.components.media_player import SUPPORT_TURN_ON, \ +from homeassistant.components.media_player.const import SUPPORT_TURN_ON, \ MEDIA_TYPE_CHANNEL, MEDIA_TYPE_URL from homeassistant.components.media_player.samsungtv import setup_platform, \ CONF_TIMEOUT, SamsungTVDevice, SUPPORT_SAMSUNGTV diff --git a/tests/components/sonos/test_media_player.py b/tests/components/sonos/test_media_player.py index 79eab1c1699..55ff96f202a 100644 --- a/tests/components/sonos/test_media_player.py +++ b/tests/components/sonos/test_media_player.py @@ -9,7 +9,7 @@ from pysonos import alarms from homeassistant.setup import setup_component from homeassistant.components.sonos import media_player as sonos -from homeassistant.components.media_player import DOMAIN +from homeassistant.components.media_player.const import DOMAIN from homeassistant.components.sonos.media_player import CONF_INTERFACE_ADDR from homeassistant.const import CONF_HOSTS, CONF_PLATFORM diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index 977b0669880..4786370f24f 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -10,7 +10,7 @@ import requests import homeassistant.components.http as http import homeassistant.components.tts as tts from homeassistant.components.tts.demo import DemoProvider -from homeassistant.components.media_player import ( +from homeassistant.components.media_player.const import ( SERVICE_PLAY_MEDIA, MEDIA_TYPE_MUSIC, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, DOMAIN as DOMAIN_MP) from homeassistant.setup import setup_component, async_setup_component diff --git a/tests/components/tts/test_marytts.py b/tests/components/tts/test_marytts.py index 110473d75a8..7520ba2fbaa 100644 --- a/tests/components/tts/test_marytts.py +++ b/tests/components/tts/test_marytts.py @@ -5,7 +5,7 @@ import shutil import homeassistant.components.tts as tts from homeassistant.setup import setup_component -from homeassistant.components.media_player import ( +from homeassistant.components.media_player.const import ( SERVICE_PLAY_MEDIA, DOMAIN as DOMAIN_MP) from tests.common import ( diff --git a/tests/components/tts/test_voicerss.py b/tests/components/tts/test_voicerss.py index 334e35a9386..af4bdf3976c 100644 --- a/tests/components/tts/test_voicerss.py +++ b/tests/components/tts/test_voicerss.py @@ -4,7 +4,7 @@ import os import shutil import homeassistant.components.tts as tts -from homeassistant.components.media_player import ( +from homeassistant.components.media_player.const import ( SERVICE_PLAY_MEDIA, ATTR_MEDIA_CONTENT_ID, DOMAIN as DOMAIN_MP) from homeassistant.setup import setup_component diff --git a/tests/components/tts/test_yandextts.py b/tests/components/tts/test_yandextts.py index 2675e322507..70c75b1f2ed 100644 --- a/tests/components/tts/test_yandextts.py +++ b/tests/components/tts/test_yandextts.py @@ -5,7 +5,7 @@ import shutil import homeassistant.components.tts as tts from homeassistant.setup import setup_component -from homeassistant.components.media_player import ( +from homeassistant.components.media_player.const import ( SERVICE_PLAY_MEDIA, DOMAIN as DOMAIN_MP) from tests.common import ( get_test_home_assistant, assert_setup_component, mock_service) From 9db9a817935ffe5b5bd2abd8d4d78d89a66aede2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9-Marc=20Simard?= Date: Fri, 8 Feb 2019 22:38:39 -0500 Subject: [PATCH 113/242] Set GTFS icon by route type (#20876) --- homeassistant/components/sensor/gtfs.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 3ccc60457b6..cc1137e23fd 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -29,6 +29,16 @@ DEFAULT_NAME = 'GTFS Sensor' DEFAULT_PATH = 'gtfs' ICON = 'mdi:train' +ICONS = { + 0: 'mdi:tram', + 1: 'mdi:subway', + 2: 'mdi:train', + 3: 'mdi:bus', + 4: 'mdi:ferry', + 5: 'mdi:train-variant', + 6: 'mdi:gondola', + 7: 'mdi:stairs', +} TIME_FORMAT = '%Y-%m-%d %H:%M:%S' @@ -193,6 +203,7 @@ class GTFSDepartureSensor(Entity): self.destination = destination self._offset = offset self._custom_name = name + self._icon = ICON self._name = '' self._unit_of_measurement = 'min' self._state = 0 @@ -223,7 +234,7 @@ class GTFSDepartureSensor(Entity): @property def icon(self): """Icon to use in the frontend, if any.""" - return ICON + return self._icon def update(self): """Get the latest data from GTFS and update the states.""" @@ -257,6 +268,8 @@ class GTFSDepartureSensor(Entity): self._attributes = {} self._attributes['offset'] = self._offset.seconds / 60 + self._icon = ICONS.get(route.route_type, ICON) + def dict_for_table(resource): """Return a dict for the SQLAlchemy resource given.""" return dict((col, getattr(resource, col)) From a014c2be59f33d4981cad520f1bf3152973175a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9-Marc=20Simard?= Date: Fri, 8 Feb 2019 22:47:02 -0500 Subject: [PATCH 114/242] Cleanup GTFS query (#20874) --- homeassistant/components/sensor/gtfs.py | 89 ++++++++++++------------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index cc1137e23fd..081361aa32e 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -65,22 +65,20 @@ def get_next_departure(sched, start_station_id, end_station_id, offset): sql_query = text(""" SELECT trip.trip_id, trip.route_id, - time(origin_stop_time.departure_time), - time(destination_stop_time.arrival_time), - time(origin_stop_time.arrival_time), - time(origin_stop_time.departure_time), - origin_stop_time.drop_off_type, - origin_stop_time.pickup_type, - origin_stop_time.shape_dist_traveled, - origin_stop_time.stop_headsign, - origin_stop_time.stop_sequence, - time(destination_stop_time.arrival_time), - time(destination_stop_time.departure_time), - destination_stop_time.drop_off_type, - destination_stop_time.pickup_type, - destination_stop_time.shape_dist_traveled, - destination_stop_time.stop_headsign, - destination_stop_time.stop_sequence + time(origin_stop_time.arrival_time) AS origin_arrival_time, + time(origin_stop_time.departure_time) AS origin_depart_time, + origin_stop_time.drop_off_type AS origin_drop_off_type, + origin_stop_time.pickup_type AS origin_pickup_type, + origin_stop_time.shape_dist_traveled AS origin_shape_dist_traveled, + origin_stop_time.stop_headsign AS origin_stop_headsign, + origin_stop_time.stop_sequence AS origin_stop_sequence, + time(destination_stop_time.arrival_time) AS dest_arrival_time, + time(destination_stop_time.departure_time) AS dest_depart_time, + destination_stop_time.drop_off_type AS dest_drop_off_type, + destination_stop_time.pickup_type AS dest_pickup_type, + destination_stop_time.shape_dist_traveled AS dest_dist_traveled, + destination_stop_time.stop_headsign AS dest_stop_headsign, + destination_stop_time.stop_sequence AS dest_stop_sequence FROM trips trip INNER JOIN calendar calendar ON trip.service_id = calendar.service_id @@ -93,13 +91,14 @@ def get_next_departure(sched, start_station_id, end_station_id, offset): INNER JOIN stops end_station ON destination_stop_time.stop_id = end_station.stop_id WHERE calendar.{day_name} = 1 - AND time(origin_stop_time.departure_time) > time(:now_str) + AND origin_depart_time > time(:now_str) AND start_station.stop_id = :origin_station_id AND end_station.stop_id = :end_station_id - AND origin_stop_time.stop_sequence < destination_stop_time.stop_sequence + AND origin_stop_sequence < dest_stop_sequence AND calendar.start_date <= :today AND calendar.end_date >= :today - ORDER BY origin_stop_time.departure_time LIMIT 1; + ORDER BY origin_stop_time.departure_time + LIMIT 1 """.format(day_name=day_name)) result = sched.engine.execute(sql_query, now_str=now_str, origin_station_id=origin_station.id, @@ -112,46 +111,46 @@ def get_next_departure(sched, start_station_id, end_station_id, offset): if item == {}: return None - departure_time_string = '{} {}'.format(today, item[2]) - arrival_time_string = '{} {}'.format(today, item[3]) - departure_time = datetime.datetime.strptime(departure_time_string, - TIME_FORMAT) - arrival_time = datetime.datetime.strptime(arrival_time_string, - TIME_FORMAT) + origin_arrival_time = '{} {}'.format(today, item['origin_arrival_time']) + origin_depart_time = '{} {}'.format(today, item['origin_depart_time']) + dest_arrival_time = '{} {}'.format(today, item['dest_arrival_time']) + dest_depart_time = '{} {}'.format(today, item['dest_depart_time']) - seconds_until = (departure_time-datetime.datetime.now()).total_seconds() + depart_time = datetime.datetime.strptime(origin_depart_time, TIME_FORMAT) + arrival_time = datetime.datetime.strptime(dest_arrival_time, TIME_FORMAT) + + seconds_until = (depart_time - datetime.datetime.now()).total_seconds() minutes_until = int(seconds_until / 60) - route = sched.routes_by_id(item[1])[0] - - origin_stoptime_arrival_time = '{} {}'.format(today, item[4]) - origin_stoptime_departure_time = '{} {}'.format(today, item[5]) - dest_stoptime_arrival_time = '{} {}'.format(today, item[11]) - dest_stoptime_depart_time = '{} {}'.format(today, item[12]) + route = sched.routes_by_id(item['route_id'])[0] origin_stop_time_dict = { - 'Arrival Time': origin_stoptime_arrival_time, - 'Departure Time': origin_stoptime_departure_time, - 'Drop Off Type': item[6], 'Pickup Type': item[7], - 'Shape Dist Traveled': item[8], 'Headsign': item[9], - 'Sequence': item[10] + 'Arrival Time': origin_arrival_time, + 'Departure Time': origin_depart_time, + 'Drop Off Type': item['origin_drop_off_type'], + 'Pickup Type': item['origin_pickup_type'], + 'Shape Dist Traveled': item['origin_shape_dist_traveled'], + 'Headsign': item['origin_stop_headsign'], + 'Sequence': item['origin_stop_sequence'] } destination_stop_time_dict = { - 'Arrival Time': dest_stoptime_arrival_time, - 'Departure Time': dest_stoptime_depart_time, - 'Drop Off Type': item[13], 'Pickup Type': item[14], - 'Shape Dist Traveled': item[15], 'Headsign': item[16], - 'Sequence': item[17] + 'Arrival Time': dest_arrival_time, + 'Departure Time': dest_depart_time, + 'Drop Off Type': item['dest_drop_off_type'], + 'Pickup Type': item['dest_pickup_type'], + 'Shape Dist Traveled': item['dest_dist_traveled'], + 'Headsign': item['dest_stop_headsign'], + 'Sequence': item['dest_stop_sequence'] } return { - 'trip_id': item[0], - 'trip': sched.trips_by_id(item[0])[0], + 'trip_id': item['trip_id'], + 'trip': sched.trips_by_id(item['trip_id'])[0], 'route': route, 'agency': sched.agencies_by_id(route.agency_id)[0], 'origin_station': origin_station, - 'departure_time': departure_time, + 'departure_time': depart_time, 'destination_station': destination_station, 'arrival_time': arrival_time, 'seconds_until_departure': seconds_until, From 33d607bb2290864e3b4fad078afbacb9c72fd41f Mon Sep 17 00:00:00 2001 From: Matt N Date: Fri, 8 Feb 2019 21:44:33 -0800 Subject: [PATCH 115/242] Upgrade zm-py to 0.3.3 (#20886) Fixes #20833 --- homeassistant/components/zoneminder/__init__.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zoneminder/__init__.py b/homeassistant/components/zoneminder/__init__.py index 4591e14a006..2cefa2e1049 100644 --- a/homeassistant/components/zoneminder/__init__.py +++ b/homeassistant/components/zoneminder/__init__.py @@ -16,7 +16,7 @@ from homeassistant.helpers.discovery import async_load_platform _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['zm-py==0.3.1'] +REQUIREMENTS = ['zm-py==0.3.3'] CONF_PATH_ZMS = 'path_zms' diff --git a/requirements_all.txt b/requirements_all.txt index 62efa76cac2..c67bebac1a9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1798,4 +1798,4 @@ zigpy-xbee==0.1.1 zigpy==0.2.0 # homeassistant.components.zoneminder -zm-py==0.3.1 +zm-py==0.3.3 From 876e2a0a111f9f8b755f8e04f14f72ad20eb4fea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 9 Feb 2019 08:32:14 +0200 Subject: [PATCH 116/242] Upgrade mypy to 0.660 (#20873) --- requirements_test.txt | 2 +- requirements_test_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_test.txt b/requirements_test.txt index d99d878ef63..b9da9890c61 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -6,7 +6,7 @@ coveralls==1.2.0 flake8-docstrings==1.3.0 flake8==3.7.5 mock-open==1.3.1 -mypy==0.650 +mypy==0.660 pydocstyle==3.0.0 pylint==2.2.2 pytest-aiohttp==0.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9d768779ccc..ff63edada63 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ coveralls==1.2.0 flake8-docstrings==1.3.0 flake8==3.7.5 mock-open==1.3.1 -mypy==0.650 +mypy==0.660 pydocstyle==3.0.0 pylint==2.2.2 pytest-aiohttp==0.3.0 From 33dcb071dab25dd93ce7bcfae70315ace5af4702 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 8 Feb 2019 23:10:04 -0800 Subject: [PATCH 117/242] Use text= instead of body= for creating web responses (#20879) --- homeassistant/components/geofency/__init__.py | 2 +- homeassistant/components/gpslogger/__init__.py | 4 ++-- homeassistant/components/locative/__init__.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/geofency/__init__.py b/homeassistant/components/geofency/__init__.py index f58580b83c7..f265bd3492a 100644 --- a/homeassistant/components/geofency/__init__.py +++ b/homeassistant/components/geofency/__init__.py @@ -123,7 +123,7 @@ def _set_location(hass, data, location_name): ) return web.Response( - body="Setting location for {}".format(device), + text="Setting location for {}".format(device), status=HTTP_OK ) diff --git a/homeassistant/components/gpslogger/__init__.py b/homeassistant/components/gpslogger/__init__.py index d4150900223..39d795dcd25 100644 --- a/homeassistant/components/gpslogger/__init__.py +++ b/homeassistant/components/gpslogger/__init__.py @@ -66,7 +66,7 @@ async def handle_webhook(hass, webhook_id, request): data = WEBHOOK_SCHEMA(dict(await request.post())) except vol.MultipleInvalid as error: return web.Response( - body=error.error_message, + text=error.error_message, status=HTTP_UNPROCESSABLE_ENTITY ) @@ -91,7 +91,7 @@ async def handle_webhook(hass, webhook_id, request): ) return web.Response( - body='Setting location for {}'.format(device), + text='Setting location for {}'.format(device), status=HTTP_OK ) diff --git a/homeassistant/components/locative/__init__.py b/homeassistant/components/locative/__init__.py index 1f7f9c3a686..5a27fbaec63 100644 --- a/homeassistant/components/locative/__init__.py +++ b/homeassistant/components/locative/__init__.py @@ -101,7 +101,7 @@ async def handle_webhook(hass, webhook_id, request): location_name ) return web.Response( - body='Setting location to not home', + text='Setting location to not home', status=HTTP_OK ) @@ -110,7 +110,7 @@ async def handle_webhook(hass, webhook_id, request): # before the previous zone was exited. The enter message will # be sent first, then the exit message will be sent second. return web.Response( - body='Ignoring exit from {} (already in {})'.format( + text='Ignoring exit from {} (already in {})'.format( location_name, current_state ), status=HTTP_OK @@ -120,14 +120,14 @@ async def handle_webhook(hass, webhook_id, request): # In the app, a test message can be sent. Just return something to # the user to let them know that it works. return web.Response( - body='Received test message.', + text='Received test message.', status=HTTP_OK ) _LOGGER.error('Received unidentified message from Locative: %s', direction) return web.Response( - body='Received unidentified message: {}'.format(direction), + text='Received unidentified message: {}'.format(direction), status=HTTP_UNPROCESSABLE_ENTITY ) From 24914aade50f7f4f813380b58292d5e91dc47f41 Mon Sep 17 00:00:00 2001 From: Brian Towles Date: Sat, 9 Feb 2019 09:01:02 -0600 Subject: [PATCH 118/242] Set August doorbell availability state from online state (#20883) The available state for the August Doorbell is currently set based on the state of the binary ding sensor. This means that if there is no door bell rings in a while on startup the doorbell is shown as Unavailable (#20421) . This ties the availability of the doorbell to the actual online state thats in the device information retrieved from august so that even if there has not been a doorbell ring activity on a while the device shows as online when it is. --- homeassistant/components/august/binary_sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/august/binary_sensor.py b/homeassistant/components/august/binary_sensor.py index 4116a791b01..581b7a3e0df 100644 --- a/homeassistant/components/august/binary_sensor.py +++ b/homeassistant/components/august/binary_sensor.py @@ -181,4 +181,4 @@ class AugustDoorbellBinarySensor(BinarySensorDevice): """Get the latest state of the sensor.""" state_provider = SENSOR_TYPES_DOORBELL[self._sensor_type][2] self._state = state_provider(self._data, self._doorbell) - self._available = self._state is not None + self._available = self._doorbell.is_online From cfd1563bc8f1335338925641169bc998556e6519 Mon Sep 17 00:00:00 2001 From: Eliran Turgeman Date: Sat, 9 Feb 2019 17:47:35 +0200 Subject: [PATCH 119/242] Added more language options (#20890) This is a Small PR, Just Added 3 more language : * he: Hebrew * ko: Korean * lv: Latvian --- homeassistant/components/sensor/darksky.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/darksky.py b/homeassistant/components/sensor/darksky.py index 28a51bd8ef2..6e2ca2dc6c5 100644 --- a/homeassistant/components/sensor/darksky.py +++ b/homeassistant/components/sensor/darksky.py @@ -162,9 +162,9 @@ CONDITION_PICTURES = { # Language Supported Codes LANGUAGE_CODES = [ 'ar', 'az', 'be', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'el', 'en', 'es', - 'et', 'fi', 'fr', 'hr', 'hu', 'id', 'is', 'it', 'ja', 'ka', 'kw', 'nb', - 'nl', 'pl', 'pt', 'ro', 'ru', 'sk', 'sl', 'sr', 'sv', 'tet', 'tr', 'uk', - 'x-pig-latin', 'zh', 'zh-tw', + 'et', 'fi', 'fr', 'he', 'hr', 'hu', 'id', 'is', 'it', 'ja', 'ka', 'ko', + 'kw', 'lv', 'nb', 'nl', 'pl', 'pt', 'ro', 'ru', 'sk', 'sl', 'sr', 'sv', + 'tet', 'tr', 'uk', 'x-pig-latin', 'zh', 'zh-tw', ] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ From 987b5cd905b550004a5a6997f6a01cef596ed650 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 9 Feb 2019 10:41:40 -0800 Subject: [PATCH 120/242] Person component: add storage and WS commands (#20852) * Forbid duplicate IDs * Allow loading persons from storage * Convert to PersonManager * Add storage support and WS commands to Person component * Convert list command to differentiate types * Allow loading person component without defining persons * Fix cleanups after update/delete * Address comments * Start tracking when HA started --- homeassistant/components/person/__init__.py | 342 +++++++++++++++--- .../components/websocket_api/connection.py | 10 + homeassistant/helpers/entity.py | 2 +- tests/components/person/test_init.py | 263 +++++++++++++- 4 files changed, 554 insertions(+), 63 deletions(-) diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 2e8b10c457d..8ad03e3f0ff 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -4,26 +4,37 @@ Support for tracking people. For more details about this component, please refer to the documentation. https://home-assistant.io/components/person/ """ +from collections import OrderedDict import logging +import uuid import voluptuous as vol from homeassistant.components.device_tracker import ( DOMAIN as DEVICE_TRACKER_DOMAIN) from homeassistant.const import ( - ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_ID, CONF_NAME) + ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_ID, CONF_NAME, + EVENT_HOMEASSISTANT_START) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.storage import Store from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.components import websocket_api +from homeassistant.helpers.typing import HomeAssistantType, ConfigType +from homeassistant.util import dt as dt_util _LOGGER = logging.getLogger(__name__) +ATTR_EDITABLE = 'editable' ATTR_SOURCE = 'source' ATTR_USER_ID = 'user_id' CONF_DEVICE_TRACKERS = 'device_trackers' CONF_USER_ID = 'user_id' DOMAIN = 'person' +STORAGE_KEY = DOMAIN +STORAGE_VERSION = 1 +SAVE_DELAY = 10 PERSON_SCHEMA = vol.Schema({ vol.Required(CONF_ID): cv.string, @@ -34,30 +45,161 @@ PERSON_SCHEMA = vol.Schema({ }) CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.All(cv.ensure_list, [PERSON_SCHEMA]) + vol.Optional(DOMAIN): vol.Any(vol.All(cv.ensure_list, [PERSON_SCHEMA]), {}) }, extra=vol.ALLOW_EXTRA) +_UNDEF = object() -async def async_setup(hass, config): + +class PersonManager: + """Manage person data.""" + + def __init__(self, hass: HomeAssistantType, component: EntityComponent, + config_persons): + """Initialize person storage.""" + self.hass = hass + self.component = component + self.store = Store(hass, STORAGE_VERSION, STORAGE_KEY) + self.storage_data = None + + config_data = self.config_data = OrderedDict() + for conf in config_persons: + person_id = conf[CONF_ID] + + if person_id in config_data: + _LOGGER.error("Found config user with duplicate ID: %s", + person_id) + continue + + config_data[person_id] = conf + + @property + def storage_persons(self): + """Iterate over persons stored in storage.""" + return list(self.storage_data.values()) + + @property + def config_persons(self): + """Iterate over persons stored in config.""" + return list(self.config_data.values()) + + async def async_initialize(self): + """Get the person data.""" + raw_storage = await self.store.async_load() + + if raw_storage is None: + raw_storage = { + 'persons': [] + } + + storage_data = self.storage_data = OrderedDict() + + for person in raw_storage['persons']: + storage_data[person[CONF_ID]] = person + + entities = [] + + for person_conf in self.config_data.values(): + person_id = person_conf[CONF_ID] + user_id = person_conf.get(CONF_USER_ID) + + if (user_id is not None + and await self.hass.auth.async_get_user(user_id) is None): + _LOGGER.error( + "Invalid user_id detected for person %s", person_id) + continue + + entities.append(Person(person_conf, False)) + + for person_conf in storage_data.values(): + if person_conf[CONF_ID] in self.config_data: + _LOGGER.error( + "Skipping adding person from storage with same ID as" + " configuration.yaml entry: %s", person_id) + continue + + entities.append(Person(person_conf, True)) + + if entities: + await self.component.async_add_entities(entities) + + async def async_create_person(self, *, name, device_trackers=None, + user_id=None): + """Create a new person.""" + person = { + CONF_ID: uuid.uuid4().hex, + CONF_NAME: name, + CONF_USER_ID: user_id, + CONF_DEVICE_TRACKERS: device_trackers, + } + self.storage_data[person[CONF_ID]] = person + self._async_schedule_save() + await self.component.async_add_entities([Person(person, True)]) + return person + + async def async_update_person(self, person_id, *, name=_UNDEF, + device_trackers=_UNDEF, user_id=_UNDEF): + """Update person.""" + if person_id not in self.storage_data: + raise ValueError("Invalid person specified.") + + changes = { + key: value for key, value in ( + ('name', name), + ('device_trackers', device_trackers), + ('user_id', user_id) + ) if value is not _UNDEF + } + + self.storage_data[person_id].update(changes) + self._async_schedule_save() + + for entity in self.component.entities: + if entity.unique_id == person_id: + entity.person_updated() + break + + return self.storage_data[person_id] + + async def async_delete_person(self, person_id): + """Delete person.""" + if person_id not in self.storage_data: + raise ValueError("Invalid person specified.") + + self.storage_data.pop(person_id) + self._async_schedule_save() + ent_reg = await self.hass.helpers.entity_registry.async_get_registry() + + for entity in self.component.entities: + if entity.unique_id == person_id: + await entity.async_remove() + ent_reg.async_remove(entity.entity_id) + break + + @callback + def _async_schedule_save(self) -> None: + """Schedule saving the area registry.""" + self.store.async_delay_save(self._data_to_save, SAVE_DELAY) + + @callback + def _data_to_save(self) -> dict: + """Return data of area registry to store in a file.""" + return { + 'persons': list(self.storage_data.values()) + } + + +async def async_setup(hass: HomeAssistantType, config: ConfigType): """Set up the person component.""" component = EntityComponent(_LOGGER, DOMAIN, hass) - conf = config[DOMAIN] - entities = [] - for person_conf in conf: - user_id = person_conf.get(CONF_USER_ID) - if (user_id is not None - and await hass.auth.async_get_user(user_id) is None): - _LOGGER.error( - "Invalid user_id detected for person %s", - person_conf[CONF_NAME]) - continue - entities.append(Person(person_conf, user_id)) + conf_persons = config.get(DOMAIN, []) + manager = hass.data[DOMAIN] = PersonManager(hass, component, conf_persons) + await manager.async_initialize() - if not entities: - _LOGGER.error("No persons could be set up") - return False - - await component.async_add_entities(entities) + websocket_api.async_register_command(hass, ws_list_person) + websocket_api.async_register_command(hass, ws_create_person) + websocket_api.async_register_command(hass, ws_update_person) + websocket_api.async_register_command(hass, ws_delete_person) return True @@ -65,21 +207,20 @@ async def async_setup(hass, config): class Person(RestoreEntity): """Represent a tracked person.""" - def __init__(self, config, user_id): + def __init__(self, config, editable): """Set up person.""" - self._id = config[CONF_ID] + self._config = config + self._editable = editable self._latitude = None self._longitude = None - self._name = config[CONF_NAME] self._source = None self._state = None - self._trackers = config.get(CONF_DEVICE_TRACKERS) - self._user_id = user_id + self._unsub_track_device = None @property def name(self): """Return the name of the entity.""" - return self._name + return self._config[CONF_NAME] @property def should_poll(self): @@ -97,22 +238,25 @@ class Person(RestoreEntity): @property def state_attributes(self): """Return the state attributes of the person.""" - data = {} - data[ATTR_ID] = self._id + data = { + ATTR_EDITABLE: self._editable, + ATTR_ID: self.unique_id, + } if self._latitude is not None: data[ATTR_LATITUDE] = round(self._latitude, 5) if self._longitude is not None: data[ATTR_LONGITUDE] = round(self._longitude, 5) if self._source is not None: data[ATTR_SOURCE] = self._source - if self._user_id is not None: - data[ATTR_USER_ID] = self._user_id + user_id = self._config.get(CONF_USER_ID) + if user_id is not None: + data[ATTR_USER_ID] = user_id return data @property def unique_id(self): """Return a unique ID for the person.""" - return self._id + return self._config[CONF_ID] async def async_added_to_hass(self): """Register device trackers.""" @@ -121,25 +265,137 @@ class Person(RestoreEntity): if state: self._parse_source_state(state) - if not self._trackers: - return - @callback - def async_handle_tracker_update(entity, old_state, new_state): - """Handle the device tracker state changes.""" - self._parse_source_state(new_state) - self.async_schedule_update_ha_state() + def person_start_hass(now): + self.person_updated() - _LOGGER.debug( - "Subscribe to device trackers for %s", self.entity_id) + self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, + person_start_hass) - for tracker in self._trackers: - async_track_state_change( - self.hass, tracker, async_handle_tracker_update) + @callback + def person_updated(self): + """Handle when the config is updated.""" + if self._unsub_track_device is not None: + self._unsub_track_device() + self._unsub_track_device = None + trackers = self._config.get(CONF_DEVICE_TRACKERS) + + if trackers: + def sort_key(state): + if state: + return state.last_updated + return dt_util.utc_from_timestamp(0) + + latest = max( + [self.hass.states.get(entity_id) for entity_id in trackers], + key=sort_key + ) + + @callback + def async_handle_tracker_update(entity, old_state, new_state): + """Handle the device tracker state changes.""" + self._parse_source_state(new_state) + self.async_schedule_update_ha_state() + + _LOGGER.debug( + "Subscribe to device trackers for %s", self.entity_id) + + self._unsub_track_device = async_track_state_change( + self.hass, trackers, async_handle_tracker_update) + + else: + latest = None + + if latest: + self._parse_source_state(latest) + else: + self._state = None + self._source = None + self._latitude = None + self._longitude = None + + self.async_schedule_update_ha_state() + + @callback def _parse_source_state(self, state): - """Parse source state and set person attributes.""" + """Parse source state and set person attributes. + + This is a device tracker state or the restored person state. + """ self._state = state.state self._source = state.entity_id self._latitude = state.attributes.get(ATTR_LATITUDE) self._longitude = state.attributes.get(ATTR_LONGITUDE) + + +@websocket_api.websocket_command({ + vol.Required('type'): 'person/list', +}) +def ws_list_person(hass: HomeAssistantType, + connection: websocket_api.ActiveConnection, msg): + """List persons.""" + manager = hass.data[DOMAIN] # type: PersonManager + connection.send_result(msg['id'], { + 'storage': manager.storage_persons, + 'config': manager.config_persons, + }) + + +@websocket_api.websocket_command({ + vol.Required('type'): 'person/create', + vol.Required('name'): str, + vol.Optional('user_id'): vol.Any(str, None), + vol.Optional('device_trackers', default=[]): vol.All( + cv.ensure_list, cv.entities_domain(DEVICE_TRACKER_DOMAIN)), +}) +@websocket_api.require_admin +@websocket_api.async_response +async def ws_create_person(hass: HomeAssistantType, + connection: websocket_api.ActiveConnection, msg): + """Create a person.""" + manager = hass.data[DOMAIN] # type: PersonManager + person = await manager.async_create_person( + name=msg['name'], + user_id=msg.get('user_id'), + device_trackers=msg['device_trackers'] + ) + connection.send_result(msg['id'], person) + + +@websocket_api.websocket_command({ + vol.Required('type'): 'person/update', + vol.Required('person_id'): str, + vol.Optional('name'): str, + vol.Optional('user_id'): vol.Any(str, None), + vol.Optional(CONF_DEVICE_TRACKERS, default=[]): vol.All( + cv.ensure_list, cv.entities_domain(DEVICE_TRACKER_DOMAIN)), +}) +@websocket_api.require_admin +@websocket_api.async_response +async def ws_update_person(hass: HomeAssistantType, + connection: websocket_api.ActiveConnection, msg): + """Update a person.""" + manager = hass.data[DOMAIN] # type: PersonManager + changes = {} + for key in ('name', 'user_id', 'device_trackers'): + if key in msg: + changes[key] = msg[key] + + person = await manager.async_update_person(msg['person_id'], **changes) + connection.send_result(msg['id'], person) + + +@websocket_api.websocket_command({ + vol.Required('type'): 'person/delete', + vol.Required('person_id'): str, +}) +@websocket_api.require_admin +@websocket_api.async_response +async def ws_delete_person(hass: HomeAssistantType, + connection: websocket_api.ActiveConnection, + msg): + """Delete a person.""" + manager = hass.data[DOMAIN] # type: PersonManager + await manager.async_delete_person(msg['person_id']) + connection.send_result(msg['id']) diff --git a/homeassistant/components/websocket_api/connection.py b/homeassistant/components/websocket_api/connection.py index 60e2caa54ac..041aad3969e 100644 --- a/homeassistant/components/websocket_api/connection.py +++ b/homeassistant/components/websocket_api/connection.py @@ -31,6 +31,16 @@ class ActiveConnection: return Context() return Context(user_id=user.id) + @callback + def send_result(self, msg_id, result=None): + """Send a result message.""" + self.send_message(messages.result_message(msg_id, result)) + + @callback + def send_error(self, msg_id, code, message): + """Send a error message.""" + self.send_message(messages.error_message(msg_id, code, message)) + @callback def async_handle(self, msg): """Handle a single incoming message.""" diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 2d4ad68dbbe..c13ebe7cfab 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -319,7 +319,7 @@ class Entity: @callback def async_schedule_update_ha_state(self, force_refresh=False): """Schedule an update ha state change task.""" - self.hass.async_add_job(self.async_update_ha_state(force_refresh)) + self.hass.async_create_task(self.async_update_ha_state(force_refresh)) async def async_device_update(self, warning=True): """Process 'update' or 'async_update' from entity. diff --git a/tests/components/person/test_init.py b/tests/components/person/test_init.py index 4b10846ee3c..9b76135f743 100644 --- a/tests/components/person/test_init.py +++ b/tests/components/person/test_init.py @@ -1,16 +1,41 @@ """The tests for the person component.""" from homeassistant.components.person import ATTR_SOURCE, ATTR_USER_ID, DOMAIN from homeassistant.const import ( - ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, STATE_UNKNOWN) + ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, STATE_UNKNOWN, + EVENT_HOMEASSISTANT_START) from homeassistant.core import CoreState, State from homeassistant.setup import async_setup_component +import pytest + from tests.common import mock_component, mock_restore_cache DEVICE_TRACKER = 'device_tracker.test_tracker' DEVICE_TRACKER_2 = 'device_tracker.test_tracker_2' +@pytest.fixture +def storage_setup(hass, hass_storage, hass_admin_user): + """Storage setup.""" + hass_storage[DOMAIN] = { + 'key': DOMAIN, + 'version': 1, + 'data': { + 'persons': [ + { + 'id': '1234', + 'name': 'tracked person', + 'user_id': hass_admin_user.id, + 'device_trackers': DEVICE_TRACKER + } + ] + } + } + assert hass.loop.run_until_complete( + async_setup_component(hass, DOMAIN, {}) + ) + + async def test_minimal_setup(hass): """Test minimal config with only name.""" config = {DOMAIN: {'id': '1234', 'name': 'test person'}} @@ -36,9 +61,9 @@ async def test_setup_no_name(hass): assert not await async_setup_component(hass, DOMAIN, config) -async def test_setup_user_id(hass, hass_owner_user): +async def test_setup_user_id(hass, hass_admin_user): """Test config with user id.""" - user_id = hass_owner_user.id + user_id = hass_admin_user.id config = { DOMAIN: {'id': '1234', 'name': 'test person', 'user_id': user_id}} assert await async_setup_component(hass, DOMAIN, config) @@ -52,17 +77,9 @@ async def test_setup_user_id(hass, hass_owner_user): assert state.attributes.get(ATTR_USER_ID) == user_id -async def test_setup_invalid_user_id(hass): - """Test config with invalid user id.""" - config = { - DOMAIN: { - 'id': '1234', 'name': 'test bad user', 'user_id': 'bad_user_id'}} - assert not await async_setup_component(hass, DOMAIN, config) - - -async def test_valid_invalid_user_ids(hass, hass_owner_user): +async def test_valid_invalid_user_ids(hass, hass_admin_user): """Test a person with valid user id and a person with invalid user id .""" - user_id = hass_owner_user.id + user_id = hass_admin_user.id config = {DOMAIN: [ {'id': '1234', 'name': 'test valid user', 'user_id': user_id}, {'id': '5678', 'name': 'test bad user', 'user_id': 'bad_user_id'}]} @@ -79,9 +96,9 @@ async def test_valid_invalid_user_ids(hass, hass_owner_user): assert state is None -async def test_setup_tracker(hass, hass_owner_user): +async def test_setup_tracker(hass, hass_admin_user): """Test set up person with one device tracker.""" - user_id = hass_owner_user.id + user_id = hass_admin_user.id config = {DOMAIN: { 'id': '1234', 'name': 'tracked person', 'user_id': user_id, 'device_trackers': DEVICE_TRACKER}} @@ -98,6 +115,12 @@ async def test_setup_tracker(hass, hass_owner_user): hass.states.async_set(DEVICE_TRACKER, 'home') await hass.async_block_till_done() + state = hass.states.get('person.tracked_person') + assert state.state == STATE_UNKNOWN + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + state = hass.states.get('person.tracked_person') assert state.state == 'home' assert state.attributes.get(ATTR_ID) == '1234' @@ -120,9 +143,9 @@ async def test_setup_tracker(hass, hass_owner_user): assert state.attributes.get(ATTR_USER_ID) == user_id -async def test_setup_two_trackers(hass, hass_owner_user): +async def test_setup_two_trackers(hass, hass_admin_user): """Test set up person with two device trackers.""" - user_id = hass_owner_user.id + user_id = hass_admin_user.id config = {DOMAIN: { 'id': '1234', 'name': 'tracked person', 'user_id': user_id, 'device_trackers': [DEVICE_TRACKER, DEVICE_TRACKER_2]}} @@ -136,6 +159,8 @@ async def test_setup_two_trackers(hass, hass_owner_user): assert state.attributes.get(ATTR_SOURCE) is None assert state.attributes.get(ATTR_USER_ID) == user_id + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() hass.states.async_set(DEVICE_TRACKER, 'home') await hass.async_block_till_done() @@ -161,9 +186,9 @@ async def test_setup_two_trackers(hass, hass_owner_user): assert state.attributes.get(ATTR_USER_ID) == user_id -async def test_restore_home_state(hass, hass_owner_user): +async def test_restore_home_state(hass, hass_admin_user): """Test that the state is restored for a person on startup.""" - user_id = hass_owner_user.id + user_id = hass_admin_user.id attrs = { ATTR_ID: '1234', ATTR_LATITUDE: 10.12346, ATTR_LONGITUDE: 11.12346, ATTR_SOURCE: DEVICE_TRACKER, ATTR_USER_ID: user_id} @@ -184,3 +209,203 @@ async def test_restore_home_state(hass, hass_owner_user): # When restoring state the entity_id of the person will be used as source. assert state.attributes.get(ATTR_SOURCE) == 'person.tracked_person' assert state.attributes.get(ATTR_USER_ID) == user_id + + +async def test_duplicate_ids(hass, hass_admin_user): + """Test we don't allow duplicate IDs.""" + config = {DOMAIN: [ + {'id': '1234', 'name': 'test user 1'}, + {'id': '1234', 'name': 'test user 2'}]} + assert await async_setup_component(hass, DOMAIN, config) + + assert len(hass.states.async_entity_ids('person')) == 1 + assert hass.states.get('person.test_user_1') is not None + assert hass.states.get('person.test_user_2') is None + + +async def test_load_person_storage(hass, hass_admin_user, storage_setup): + """Test set up person from storage.""" + state = hass.states.get('person.tracked_person') + assert state.state == STATE_UNKNOWN + assert state.attributes.get(ATTR_ID) == '1234' + assert state.attributes.get(ATTR_LATITUDE) is None + assert state.attributes.get(ATTR_LONGITUDE) is None + assert state.attributes.get(ATTR_SOURCE) is None + assert state.attributes.get(ATTR_USER_ID) == hass_admin_user.id + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + hass.states.async_set(DEVICE_TRACKER, 'home') + await hass.async_block_till_done() + + state = hass.states.get('person.tracked_person') + assert state.state == 'home' + assert state.attributes.get(ATTR_ID) == '1234' + assert state.attributes.get(ATTR_LATITUDE) is None + assert state.attributes.get(ATTR_LONGITUDE) is None + assert state.attributes.get(ATTR_SOURCE) == DEVICE_TRACKER + assert state.attributes.get(ATTR_USER_ID) == hass_admin_user.id + + +async def test_ws_list(hass, hass_ws_client, storage_setup): + """Test listing via WS.""" + manager = hass.data[DOMAIN] + + client = await hass_ws_client(hass) + + resp = await client.send_json({ + 'id': 6, + 'type': 'person/list', + }) + resp = await client.receive_json() + assert resp['success'] + assert resp['result']['storage'] == manager.storage_persons + assert len(resp['result']['storage']) == 1 + assert len(resp['result']['config']) == 0 + + +async def test_ws_create(hass, hass_ws_client, storage_setup, + hass_read_only_user): + """Test creating via WS.""" + manager = hass.data[DOMAIN] + + client = await hass_ws_client(hass) + + resp = await client.send_json({ + 'id': 6, + 'type': 'person/create', + 'name': 'Hello', + 'device_trackers': [DEVICE_TRACKER], + 'user_id': hass_read_only_user.id, + }) + resp = await client.receive_json() + + persons = manager.storage_persons + assert len(persons) == 2 + + assert resp['success'] + assert resp['result'] == persons[1] + + +async def test_ws_create_requires_admin(hass, hass_ws_client, storage_setup, + hass_admin_user, hass_read_only_user): + """Test creating via WS requires admin.""" + hass_admin_user.groups = [] + manager = hass.data[DOMAIN] + + client = await hass_ws_client(hass) + + resp = await client.send_json({ + 'id': 6, + 'type': 'person/create', + 'name': 'Hello', + 'device_trackers': [DEVICE_TRACKER], + 'user_id': hass_read_only_user.id, + }) + resp = await client.receive_json() + + persons = manager.storage_persons + assert len(persons) == 1 + + assert not resp['success'] + + +async def test_ws_update(hass, hass_ws_client, storage_setup): + """Test updating via WS.""" + manager = hass.data[DOMAIN] + + client = await hass_ws_client(hass) + persons = manager.storage_persons + + resp = await client.send_json({ + 'id': 6, + 'type': 'person/update', + 'person_id': persons[0]['id'], + 'name': 'Updated Name', + 'device_trackers': [DEVICE_TRACKER_2], + 'user_id': None, + }) + resp = await client.receive_json() + + persons = manager.storage_persons + assert len(persons) == 1 + + assert resp['success'] + assert resp['result'] == persons[0] + assert persons[0]['name'] == 'Updated Name' + assert persons[0]['name'] == 'Updated Name' + assert persons[0]['device_trackers'] == [DEVICE_TRACKER_2] + assert persons[0]['user_id'] is None + + state = hass.states.get('person.tracked_person') + assert state.name == 'Updated Name' + + +async def test_ws_update_require_admin(hass, hass_ws_client, storage_setup, + hass_admin_user): + """Test updating via WS requires admin.""" + hass_admin_user.groups = [] + manager = hass.data[DOMAIN] + + client = await hass_ws_client(hass) + original = dict(manager.storage_persons[0]) + + resp = await client.send_json({ + 'id': 6, + 'type': 'person/update', + 'person_id': original['id'], + 'name': 'Updated Name', + 'device_trackers': [DEVICE_TRACKER_2], + 'user_id': None, + }) + resp = await client.receive_json() + assert not resp['success'] + + not_updated = dict(manager.storage_persons[0]) + assert original == not_updated + + +async def test_ws_delete(hass, hass_ws_client, storage_setup): + """Test deleting via WS.""" + manager = hass.data[DOMAIN] + + client = await hass_ws_client(hass) + persons = manager.storage_persons + + resp = await client.send_json({ + 'id': 6, + 'type': 'person/delete', + 'person_id': persons[0]['id'], + }) + resp = await client.receive_json() + + persons = manager.storage_persons + assert len(persons) == 0 + + assert resp['success'] + assert len(hass.states.async_entity_ids('person')) == 0 + ent_reg = await hass.helpers.entity_registry.async_get_registry() + assert not ent_reg.async_is_registered('person.tracked_person') + + +async def test_ws_delete_require_admin(hass, hass_ws_client, storage_setup, + hass_admin_user): + """Test deleting via WS requires admin.""" + hass_admin_user.groups = [] + manager = hass.data[DOMAIN] + + client = await hass_ws_client(hass) + + resp = await client.send_json({ + 'id': 6, + 'type': 'person/delete', + 'person_id': manager.storage_persons[0]['id'], + 'name': 'Updated Name', + 'device_trackers': [DEVICE_TRACKER_2], + 'user_id': None, + }) + resp = await client.receive_json() + assert not resp['success'] + + persons = manager.storage_persons + assert len(persons) == 1 From 8137b0bb9e337e9735c810b3c0f31d3e1ce9ae03 Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Sat, 9 Feb 2019 13:13:12 -0800 Subject: [PATCH 121/242] Fix coroutine never awaited warning in test (#20892) --- tests/test_core.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index f1900979bec..3cb5b87b4bb 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -58,9 +58,9 @@ def test_async_add_job_schedule_partial_callback(): assert len(hass.add_job.mock_calls) == 0 -def test_async_add_job_schedule_coroutinefunction(): +def test_async_add_job_schedule_coroutinefunction(loop): """Test that we schedule coroutines and add jobs to the job pool.""" - hass = MagicMock() + hass = MagicMock(loop=MagicMock(wraps=loop)) async def job(): pass @@ -71,9 +71,9 @@ def test_async_add_job_schedule_coroutinefunction(): assert len(hass.add_job.mock_calls) == 0 -def test_async_add_job_schedule_partial_coroutinefunction(): +def test_async_add_job_schedule_partial_coroutinefunction(loop): """Test that we schedule partial coros and add jobs to the job pool.""" - hass = MagicMock() + hass = MagicMock(loop=MagicMock(wraps=loop)) async def job(): pass @@ -98,9 +98,9 @@ def test_async_add_job_add_threaded_job_to_pool(): assert len(hass.loop.run_in_executor.mock_calls) == 1 -def test_async_create_task_schedule_coroutine(): +def test_async_create_task_schedule_coroutine(loop): """Test that we schedule coroutines and add jobs to the job pool.""" - hass = MagicMock() + hass = MagicMock(loop=MagicMock(wraps=loop)) async def job(): pass From 326010629c6d5bb1274d1db1231f5b84c394b4e4 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Sun, 10 Feb 2019 06:29:36 -0500 Subject: [PATCH 122/242] Add some api tests for ZHA (#20909) * start API tests * blank lines --- tests/components/zha/test_api.py | 111 +++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 tests/components/zha/test_api.py diff --git a/tests/components/zha/test_api.py b/tests/components/zha/test_api.py new file mode 100644 index 00000000000..4a3201d7768 --- /dev/null +++ b/tests/components/zha/test_api.py @@ -0,0 +1,111 @@ +"""Test ZHA API.""" +from unittest.mock import Mock +import pytest +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.components.switch import DOMAIN +from homeassistant.components.zha.api import ( + async_load_api, WS_ENTITIES_BY_IEEE, WS_ENTITY_CLUSTERS, ATTR_IEEE, TYPE, + ID, NAME, WS_ENTITY_CLUSTER_ATTRIBUTES, WS_ENTITY_CLUSTER_COMMANDS +) +from homeassistant.components.zha.core.const import ( + ATTR_CLUSTER_ID, ATTR_CLUSTER_TYPE, IN +) +from .common import async_init_zigpy_device + + +@pytest.fixture +async def zha_client(hass, config_entry, zha_gateway, hass_ws_client): + """Test zha switch platform.""" + from zigpy.zcl.clusters.general import OnOff + + # load the ZHA API + async_load_api(hass, Mock(), zha_gateway) + + # create zigpy device + await async_init_zigpy_device( + hass, [OnOff.cluster_id], [], None, zha_gateway) + + # load up switch domain + await hass.config_entries.async_forward_entry_setup( + config_entry, DOMAIN) + await hass.async_block_till_done() + + return await hass_ws_client(hass) + + +async def test_entities_by_ieee(hass, config_entry, zha_gateway, zha_client): + """Test getting entity refs by ieee address.""" + await zha_client.send_json({ + ID: 5, + TYPE: WS_ENTITIES_BY_IEEE, + }) + + msg = await zha_client.receive_json() + + assert '00:0d:6f:00:0a:90:69:e7' in msg['result'] + assert len(msg['result']['00:0d:6f:00:0a:90:69:e7']) == 2 + + +async def test_entity_clusters(hass, config_entry, zha_gateway, zha_client): + """Test getting entity cluster info.""" + await zha_client.send_json({ + ID: 5, + TYPE: WS_ENTITY_CLUSTERS, + ATTR_ENTITY_ID: 'switch.fakemanufacturer_fakemodel_0a9069e7_1_6', + ATTR_IEEE: '00:0d:6f:00:0a:90:69:e7' + }) + + msg = await zha_client.receive_json() + + assert len(msg['result']) == 1 + + cluster_info = msg['result'][0] + + assert cluster_info[TYPE] == IN + assert cluster_info[ID] == 6 + assert cluster_info[NAME] == 'OnOff' + + +async def test_entity_cluster_attributes( + hass, config_entry, zha_gateway, zha_client): + """Test getting entity cluster attributes.""" + await zha_client.send_json({ + ID: 5, + TYPE: WS_ENTITY_CLUSTER_ATTRIBUTES, + ATTR_ENTITY_ID: 'switch.fakemanufacturer_fakemodel_0a9069e7_1_6', + ATTR_IEEE: '00:0d:6f:00:0a:90:69:e7', + ATTR_CLUSTER_ID: 6, + ATTR_CLUSTER_TYPE: IN + }) + + msg = await zha_client.receive_json() + + attributes = msg['result'] + assert len(attributes) == 4 + + for attribute in attributes: + assert attribute[ID] is not None + assert attribute[NAME] is not None + + +async def test_entity_cluster_commands( + hass, config_entry, zha_gateway, zha_client): + """Test getting entity cluster commands.""" + await zha_client.send_json({ + ID: 5, + TYPE: WS_ENTITY_CLUSTER_COMMANDS, + ATTR_ENTITY_ID: 'switch.fakemanufacturer_fakemodel_0a9069e7_1_6', + ATTR_IEEE: '00:0d:6f:00:0a:90:69:e7', + ATTR_CLUSTER_ID: 6, + ATTR_CLUSTER_TYPE: IN + }) + + msg = await zha_client.receive_json() + + commands = msg['result'] + assert len(commands) == 6 + + for command in commands: + assert command[ID] is not None + assert command[NAME] is not None + assert command[TYPE] is not None From 5def64156fc8a1c748ac656c72e28419eb82ca0a Mon Sep 17 00:00:00 2001 From: CV Date: Sun, 10 Feb 2019 12:34:54 +0100 Subject: [PATCH 123/242] Missing Binary Sensor (#20921) TiltIP Binary Sensor is missing in the discover list. --- homeassistant/components/homematic/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/homematic/__init__.py b/homeassistant/components/homematic/__init__.py index 9a496d914fc..3439a23adb3 100644 --- a/homeassistant/components/homematic/__init__.py +++ b/homeassistant/components/homematic/__init__.py @@ -90,7 +90,7 @@ HM_DEVICE_TYPES = { 'IPShutterContact', 'HMWIOSwitch', 'MaxShutterContact', 'Rain', 'WiredSensor', 'PresenceIP', 'IPWeatherSensor', 'IPPassageSensor', 'SmartwareMotion', 'IPWeatherSensorPlus', 'MotionIPV2', 'WaterIP', - 'IPMultiIO'], + 'IPMultiIO', 'TiltIP'], DISCOVER_COVER: ['Blind', 'KeyBlind', 'IPKeyBlind', 'IPKeyBlindTilt'], DISCOVER_LOCKS: ['KeyMatic'] } From 5f7f7777a032714a453138e9d4a2a124b24499c4 Mon Sep 17 00:00:00 2001 From: emontnemery Date: Sun, 10 Feb 2019 12:35:54 +0100 Subject: [PATCH 124/242] Fix encoding for MQTT camera (#20932) --- homeassistant/components/mqtt/camera.py | 3 ++- tests/components/mqtt/test_camera.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index e5fb8fc66b0..569d69a9ad8 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -110,7 +110,8 @@ class MqttCamera(MqttDiscoveryUpdate, Camera): self.hass, self._sub_state, {'state_topic': {'topic': self._config.get(CONF_TOPIC), 'msg_callback': message_received, - 'qos': self._qos}}) + 'qos': self._qos, + 'encoding': None}}) async def async_will_remove_from_hass(self): """Unsubscribe when removed.""" diff --git a/tests/components/mqtt/test_camera.py b/tests/components/mqtt/test_camera.py index 15b4ed22378..5726a64ba11 100644 --- a/tests/components/mqtt/test_camera.py +++ b/tests/components/mqtt/test_camera.py @@ -168,7 +168,7 @@ async def test_entity_id_update(hass, mqtt_mock): state = hass.states.get('camera.beer') assert state is not None assert mock_mqtt.async_subscribe.call_count == 1 - mock_mqtt.async_subscribe.assert_any_call('test-topic', ANY, 0, 'utf-8') + mock_mqtt.async_subscribe.assert_any_call('test-topic', ANY, 0, None) mock_mqtt.async_subscribe.reset_mock() registry.async_update_entity('camera.beer', new_entity_id='camera.milk') @@ -181,4 +181,4 @@ async def test_entity_id_update(hass, mqtt_mock): state = hass.states.get('camera.milk') assert state is not None assert mock_mqtt.async_subscribe.call_count == 1 - mock_mqtt.async_subscribe.assert_any_call('test-topic', ANY, 0, 'utf-8') + mock_mqtt.async_subscribe.assert_any_call('test-topic', ANY, 0, None) From 13421b326bcd25def93cd6c6dcf12a8d22203e6e Mon Sep 17 00:00:00 2001 From: javicalle <31999997+javicalle@users.noreply.github.com> Date: Sun, 10 Feb 2019 12:50:40 +0100 Subject: [PATCH 125/242] Fix RFLink restore state (#20588) * some minor tests refactor * unused import * async/await refactor * Correct tests failures * Restore state bug added call to super().async_added_to_hass() * Show brightness attribute if device supports it * Fix light/test_rflink Dimmable devices defaults to 255 brightness * delete super().device_state_attributes call --- homeassistant/components/light/rflink.py | 16 ++++++++++++++++ homeassistant/components/rflink/__init__.py | 1 + tests/components/light/test_rflink.py | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/light/rflink.py b/homeassistant/components/light/rflink.py index ef389bb84f9..726433b4f70 100644 --- a/homeassistant/components/light/rflink.py +++ b/homeassistant/components/light/rflink.py @@ -185,6 +185,14 @@ class DimmableRflinkLight(SwitchableRflinkDevice, Light): """Return the brightness of this light between 0..255.""" return self._brightness + @property + def device_state_attributes(self): + """Return the device state attributes.""" + attr = {} + if self._brightness is not None: + attr[ATTR_BRIGHTNESS] = self._brightness + return attr + @property def supported_features(self): """Flag supported features.""" @@ -239,6 +247,14 @@ class HybridRflinkLight(SwitchableRflinkDevice, Light): """Return the brightness of this light between 0..255.""" return self._brightness + @property + def device_state_attributes(self): + """Return the device state attributes.""" + attr = {} + if self._brightness is not None: + attr[ATTR_BRIGHTNESS] = self._brightness + return attr + @property def supported_features(self): """Flag supported features.""" diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py index b91ff251bd1..ce9777151cf 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -345,6 +345,7 @@ class RflinkDevice(Entity): async def async_added_to_hass(self): """Register update callback.""" + await super().async_added_to_hass() # Remove temporary bogus entity_id if added tmp_entity = TMP_ENTITY.format(self._device_id) if tmp_entity in self.hass.data[DATA_ENTITY_LOOKUP][ diff --git a/tests/components/light/test_rflink.py b/tests/components/light/test_rflink.py index e2b80d12ce0..901a2d3a286 100644 --- a/tests/components/light/test_rflink.py +++ b/tests/components/light/test_rflink.py @@ -632,7 +632,7 @@ async def test_restore_state(hass, monkeypatch): state = hass.states.get(DOMAIN + '.l4') assert state assert state.state == STATE_OFF - assert not state.attributes.get(ATTR_BRIGHTNESS) + assert state.attributes[ATTR_BRIGHTNESS] == 255 assert state.attributes['assumed_state'] # test coverage for dimmable light From ace1ae85ddcf83a6fa56326d85e0190a6bca0b5d Mon Sep 17 00:00:00 2001 From: Peter Nijssen Date: Sun, 10 Feb 2019 14:54:30 +0100 Subject: [PATCH 126/242] add fan support for spider thermostats (#20897) * add fan support for spider thermostats * resolved feedback on pull request --- homeassistant/components/spider/__init__.py | 2 +- homeassistant/components/spider/climate.py | 32 +++++++++++++++++++-- requirements_all.txt | 2 +- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/spider/__init__.py b/homeassistant/components/spider/__init__.py index a10fa129fe5..5b991e0d3e2 100644 --- a/homeassistant/components/spider/__init__.py +++ b/homeassistant/components/spider/__init__.py @@ -14,7 +14,7 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform -REQUIREMENTS = ['spiderpy==1.2.0'] +REQUIREMENTS = ['spiderpy==1.3.1'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/spider/climate.py b/homeassistant/components/spider/climate.py index a9d966bd499..cfef50e8255 100644 --- a/homeassistant/components/spider/climate.py +++ b/homeassistant/components/spider/climate.py @@ -9,12 +9,23 @@ import logging from homeassistant.components.climate import ( ATTR_TEMPERATURE, STATE_COOL, STATE_HEAT, STATE_IDLE, - SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice) + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, + SUPPORT_FAN_MODE, ClimateDevice) from homeassistant.components.spider import DOMAIN as SPIDER_DOMAIN from homeassistant.const import TEMP_CELSIUS DEPENDENCIES = ['spider'] +FAN_LIST = [ + 'Auto', + 'Low', + 'Medium', + 'High', + 'Boost 10', + 'Boost 20', + 'Boost 30' +] + OPERATION_LIST = [ STATE_HEAT, STATE_COOL, @@ -55,7 +66,10 @@ class SpiderThermostat(ClimateDevice): supports = SUPPORT_TARGET_TEMPERATURE if self.thermostat.has_operation_mode: - supports = supports | SUPPORT_OPERATION_MODE + supports |= SUPPORT_OPERATION_MODE + + if self.thermostat.has_fan_mode: + supports |= SUPPORT_FAN_MODE return supports @@ -122,6 +136,20 @@ class SpiderThermostat(ClimateDevice): self.thermostat.set_operation_mode( HA_STATE_TO_SPIDER.get(operation_mode)) + @property + def current_fan_mode(self): + """Return the fan setting.""" + return self.thermostat.current_fan_speed + + def set_fan_mode(self, fan_mode): + """Set fan mode.""" + self.thermostat.set_fan_speed(fan_mode) + + @property + def fan_list(self): + """List of available fan modes.""" + return FAN_LIST + def update(self): """Get the latest data.""" self.thermostat = self.api.get_thermostat(self.unique_id) diff --git a/requirements_all.txt b/requirements_all.txt index c67bebac1a9..9f2f8ec6366 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1573,7 +1573,7 @@ somecomfort==0.5.2 speedtest-cli==2.0.2 # homeassistant.components.spider -spiderpy==1.2.0 +spiderpy==1.3.1 # homeassistant.components.sensor.spotcrime spotcrime==1.0.3 From 898b69931131cd844732f3ab4edf56402c4b92a8 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Sun, 10 Feb 2019 09:56:27 -0500 Subject: [PATCH 127/242] Add quirks info to ZHA device (#20923) * add quirks info to zha device * move import * remove device entity part --- homeassistant/components/zha/core/const.py | 3 +++ homeassistant/components/zha/core/device.py | 13 +++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index cb3a311c985..5edcadc7fce 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -88,6 +88,9 @@ SIGNAL_STATE_ATTR = "update_state_attribute" SIGNAL_AVAILABLE = 'available' SIGNAL_REMOVE = 'remove' +QUIRK_APPLIED = 'quirk_applied' +QUIRK_CLASS = 'quirk_class' + class RadioType(enum.Enum): """Possible options for radio type.""" diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 2322df5452c..6bbfcd43a94 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -14,7 +14,8 @@ from .const import ( ATTR_MANUFACTURER, LISTENER_BATTERY, SIGNAL_AVAILABLE, IN, OUT, ATTR_CLUSTER_ID, ATTR_ATTRIBUTE, ATTR_VALUE, ATTR_COMMAND, SERVER, ATTR_COMMAND_TYPE, ATTR_ARGS, CLIENT_COMMANDS, SERVER_COMMANDS, - ATTR_ENDPOINT_ID, IEEE, MODEL, NAME, UNKNOWN + ATTR_ENDPOINT_ID, IEEE, MODEL, NAME, UNKNOWN, QUIRK_APPLIED, + QUIRK_CLASS ) from .listeners import EventRelayListener @@ -52,6 +53,12 @@ class ZHADevice: self._available_signal, self.async_initialize ) + from zigpy.quirks import CustomDevice + self.quirk_applied = isinstance(self._zigpy_device, CustomDevice) + self.quirk_class = "{}.{}".format( + self._zigpy_device.__class__.__module__, + self._zigpy_device.__class__.__name__ + ) @property def name(self): @@ -143,7 +150,9 @@ class ZHADevice: IEEE: ieee, ATTR_MANUFACTURER: self.manufacturer, MODEL: self.model, - NAME: self.name or ieee + NAME: self.name or ieee, + QUIRK_APPLIED: self.quirk_applied, + QUIRK_CLASS: self.quirk_class } def add_cluster_listener(self, cluster_listener): From 44d7c3584db1cc5ff6d7b68ff574eafd0424f4dd Mon Sep 17 00:00:00 2001 From: Matt White Date: Sun, 10 Feb 2019 09:00:03 -0600 Subject: [PATCH 128/242] Added IDs and enabled workarounds for Yale YRD220, YRL220, YRD120 (#20929) --- homeassistant/components/zwave/lock.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave/lock.py b/homeassistant/components/zwave/lock.py index c907d5101a9..b19b06761c4 100644 --- a/homeassistant/components/zwave/lock.py +++ b/homeassistant/components/zwave/lock.py @@ -42,7 +42,10 @@ DEVICE_MAPPINGS = { # Yale YRD210, Yale YRD240 (0x0129, 0x0209): WORKAROUND_DEVICE_STATE | WORKAROUND_ALARM_TYPE, (0x0129, 0xAA00): WORKAROUND_DEVICE_STATE, - (0x0129, 0x0000): WORKAROUND_DEVICE_STATE, + # Yale YRL220/YRD220 + (0x0129, 0x0000): WORKAROUND_DEVICE_STATE | WORKAROUND_ALARM_TYPE, + # Yale YRD120 + (0x0129, 0x0800): WORKAROUND_DEVICE_STATE | WORKAROUND_ALARM_TYPE, # Yale YRD220 (as reported by adrum in PR #17386) (0x0109, 0x0000): WORKAROUND_DEVICE_STATE | WORKAROUND_ALARM_TYPE, # Schlage BE469 From 852d67b95c9d25d1035b59bc4ce10983b56d7899 Mon Sep 17 00:00:00 2001 From: Martin Gross Date: Sun, 10 Feb 2019 16:02:53 +0100 Subject: [PATCH 129/242] Fix #19990: Alexa-support for climate in manual-mode (#20910) --- homeassistant/components/alexa/smart_home.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index 156dfa84a8a..acb23fe4278 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -63,6 +63,7 @@ API_THERMOSTAT_MODES = OrderedDict([ (climate.STATE_COOL, 'COOL'), (climate.STATE_AUTO, 'AUTO'), (climate.STATE_ECO, 'ECO'), + (climate.STATE_MANUAL, 'AUTO'), (climate.STATE_OFF, 'OFF'), (climate.STATE_IDLE, 'OFF'), (climate.STATE_FAN_ONLY, 'OFF'), From 9f7443ba97f8c6deb3d32e6a4d4117bc30fcfada Mon Sep 17 00:00:00 2001 From: Aaron Godfrey Date: Sun, 10 Feb 2019 07:41:29 -0800 Subject: [PATCH 130/242] Reverts 2105724. (#20915) This change broke functionality for existing users using hdmi grabbers. --- homeassistant/components/light/hyperion.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/light/hyperion.py b/homeassistant/components/light/hyperion.py index ebe209c745e..16be7d45825 100644 --- a/homeassistant/components/light/hyperion.py +++ b/homeassistant/components/light/hyperion.py @@ -177,6 +177,11 @@ class Hyperion(Light): def turn_off(self, **kwargs): """Disconnect all remotes.""" self.json_request({'command': 'clearall'}) + self.json_request({ + 'command': 'color', + 'priority': self._priority, + 'color': [0, 0, 0] + }) def update(self): """Get the lights status.""" From 16154ab4452b3c6ee1ddd1d64e3f69c7d476775f Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Sun, 10 Feb 2019 13:01:07 -0500 Subject: [PATCH 131/242] Update ZHA helpers (#20898) * update helpers * review comments * remove ternary * use correct timeout --- homeassistant/components/zha/core/helpers.py | 13 +++++++++---- homeassistant/components/zha/core/listeners.py | 14 ++------------ 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/zha/core/helpers.py b/homeassistant/components/zha/core/helpers.py index 6957edc4f3f..643e44ada1b 100644 --- a/homeassistant/components/zha/core/helpers.py +++ b/homeassistant/components/zha/core/helpers.py @@ -6,7 +6,7 @@ https://home-assistant.io/components/zha/ """ import asyncio import logging - +from concurrent.futures import TimeoutError as Timeout from .const import ( DEFAULT_BAUDRATE, REPORT_CONFIG_MAX_INT, REPORT_CONFIG_MIN_INT, REPORT_CONFIG_RPT_CHANGE, RadioType) @@ -48,7 +48,7 @@ async def bind_cluster(entity_id, cluster): _LOGGER.debug( "%s: bound '%s' cluster: %s", entity_id, cluster_name, res[0] ) - except DeliveryError as ex: + except (DeliveryError, Timeout) as ex: _LOGGER.debug( "%s: Failed to bind '%s' cluster: %s", entity_id, cluster_name, str(ex) @@ -68,7 +68,12 @@ async def configure_reporting(entity_id, cluster, attr, from zigpy.exceptions import DeliveryError attr_name = cluster.attributes.get(attr, [attr])[0] - attr_id = get_attr_id_by_name(cluster, attr_name) + + if isinstance(attr, str): + attr_id = get_attr_id_by_name(cluster, attr_name) + else: + attr_id = attr + cluster_name = cluster.ep_attribute kwargs = {} if manufacturer: @@ -82,7 +87,7 @@ async def configure_reporting(entity_id, cluster, attr, entity_id, attr_name, cluster_name, min_report, max_report, reportable_change, res ) - except DeliveryError as ex: + except (DeliveryError, Timeout) as ex: _LOGGER.debug( "%s: failed to set reporting for '%s' attr on '%s' cluster: %s", entity_id, attr_name, cluster_name, str(ex) diff --git a/homeassistant/components/zha/core/listeners.py b/homeassistant/components/zha/core/listeners.py index 3605f4a9885..1b240d499b4 100644 --- a/homeassistant/components/zha/core/listeners.py +++ b/homeassistant/components/zha/core/listeners.py @@ -15,7 +15,7 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send from .helpers import ( bind_configure_reporting, construct_unique_id, - safe_read, get_attr_id_by_name) + safe_read, get_attr_id_by_name, bind_cluster) from .const import ( CLUSTER_REPORT_CONFIGS, REPORT_CONFIG_DEFAULT, SIGNAL_ATTR_UPDATED, SIGNAL_MOVE_LEVEL, SIGNAL_SET_LEVEL, SIGNAL_STATE_ATTR, ATTR_LEVEL @@ -373,18 +373,8 @@ class IASZoneListener(ClusterListener): from zigpy.exceptions import DeliveryError _LOGGER.debug("%s: started IASZoneListener configuration", self._unique_id) - try: - res = await self._cluster.bind() - _LOGGER.debug( - "%s: bound '%s' cluster: %s", - self.unique_id, self._cluster.ep_attribute, res[0] - ) - except DeliveryError as ex: - _LOGGER.debug( - "%s: Failed to bind '%s' cluster: %s", - self.unique_id, self._cluster.ep_attribute, str(ex) - ) + await bind_cluster(self.unique_id, self._cluster) ieee = self._cluster.endpoint.device.application.ieee try: From d8993af548878139e0c7e251812881a9df2ef36a Mon Sep 17 00:00:00 2001 From: On Freund Date: Sun, 10 Feb 2019 20:34:39 +0200 Subject: [PATCH 132/242] CoolMasterNet Climate platform (#20787) * CoolMasterNet Climate platform * Address Coolmaster PR comments * Fix docstrings on climate demo platform * Additional CoolMaster PR review fixes --- .coveragerc | 1 + CODEOWNERS | 1 + .../components/climate/coolmaster.py | 188 ++++++++++++++++++ homeassistant/components/climate/demo.py | 8 +- requirements_all.txt | 3 + 5 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/climate/coolmaster.py diff --git a/.coveragerc b/.coveragerc index e0f797c4d04..b615c4250f8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -67,6 +67,7 @@ omit = homeassistant/components/camera/xiaomi.py homeassistant/components/camera/yi.py homeassistant/components/cast/* + homeassistant/components/climate/coolmaster.py homeassistant/components/climate/ephember.py homeassistant/components/climate/eq3btsmart.py homeassistant/components/climate/flexit.py diff --git a/CODEOWNERS b/CODEOWNERS index a10be84472a..64263598121 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -53,6 +53,7 @@ homeassistant/components/binary_sensor/hikvision.py @mezz64 homeassistant/components/binary_sensor/threshold.py @fabaff homeassistant/components/binary_sensor/uptimerobot.py @ludeeus homeassistant/components/camera/yi.py @bachya +homeassistant/components/climate/coolmaster.py @OnFreund homeassistant/components/climate/ephember.py @ttroy50 homeassistant/components/climate/eq3btsmart.py @rytilahti homeassistant/components/climate/mill.py @danielhiversen diff --git a/homeassistant/components/climate/coolmaster.py b/homeassistant/components/climate/coolmaster.py new file mode 100644 index 00000000000..32c77b93eea --- /dev/null +++ b/homeassistant/components/climate/coolmaster.py @@ -0,0 +1,188 @@ +""" +CoolMasterNet platform that offers control of CoolMasteNet Climate Devices. + +For more details about this platform, please refer to the documentation +https://www.home-assistant.io/components/climate.coolmaster/ +""" + +import logging + +import voluptuous as vol + +from homeassistant.components.climate import ( + PLATFORM_SCHEMA, STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, + STATE_HEAT, SUPPORT_FAN_MODE, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, ClimateDevice) +from homeassistant.const import ( + ATTR_TEMPERATURE, CONF_HOST, CONF_PORT, TEMP_CELSIUS, TEMP_FAHRENHEIT) +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['pycoolmasternet==0.0.4'] + +SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE | + SUPPORT_OPERATION_MODE | SUPPORT_ON_OFF) + +DEFAULT_PORT = 10102 + +AVAILABLE_MODES = [STATE_HEAT, STATE_COOL, STATE_AUTO, STATE_DRY, + STATE_FAN_ONLY] + +CM_TO_HA_STATE = { + 'heat': STATE_HEAT, + 'cool': STATE_COOL, + 'auto': STATE_AUTO, + 'dry': STATE_DRY, + 'fan': STATE_FAN_ONLY, +} + +HA_STATE_TO_CM = {value: key for key, value in CM_TO_HA_STATE.items()} + +FAN_MODES = ['low', 'med', 'high', 'auto'] + +CONF_SUPPORTED_MODES = 'supported_modes' +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_SUPPORTED_MODES, default=AVAILABLE_MODES): + vol.All(cv.ensure_list, [vol.In(AVAILABLE_MODES)]), +}) + +_LOGGER = logging.getLogger(__name__) + + +def _build_entity(device, supported_modes): + _LOGGER.debug("Found device %s", device.uid) + return CoolmasterClimate(device, supported_modes) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the CoolMasterNet climate platform.""" + from pycoolmasternet import CoolMasterNet + + supported_modes = config.get(CONF_SUPPORTED_MODES) + host = config[CONF_HOST] + port = config[CONF_PORT] + cool = CoolMasterNet(host, port=port) + devices = cool.devices() + + all_devices = [_build_entity(device, supported_modes) + for device in devices] + + add_entities(all_devices, True) + + +class CoolmasterClimate(ClimateDevice): + """Representation of a coolmaster climate device.""" + + def __init__(self, device, supported_modes): + """Initialize the climate device.""" + self._device = device + self._uid = device.uid + self._operation_list = supported_modes + self._target_temperature = None + self._current_temperature = None + self._current_fan_mode = None + self._current_operation = None + self._on = None + self._unit = None + + def update(self): + """Pull state from CoolMasterNet.""" + status = self._device.status + self._target_temperature = status['thermostat'] + self._current_temperature = status['temperature'] + self._current_fan_mode = status['fan_speed'] + self._on = status['is_on'] + + device_mode = status['mode'] + self._current_operation = CM_TO_HA_STATE[device_mode] + + if status['unit'] == 'celsius': + self._unit = TEMP_CELSIUS + else: + self._unit = TEMP_FAHRENHEIT + + @property + def unique_id(self): + """Return unique ID for this device.""" + return self._uid + + @property + def supported_features(self): + """Return the list of supported features.""" + return SUPPORT_FLAGS + + @property + def name(self): + """Return the name of the climate device.""" + return self.unique_id + + @property + def temperature_unit(self): + """Return the unit of measurement.""" + return self._unit + + @property + def current_temperature(self): + """Return the current temperature.""" + return self._current_temperature + + @property + def target_temperature(self): + """Return the temperature we are trying to reach.""" + return self._target_temperature + + @property + def current_operation(self): + """Return current operation ie. heat, cool, idle.""" + return self._current_operation + + @property + def operation_list(self): + """Return the list of available operation modes.""" + return self._operation_list + + @property + def is_on(self): + """Return true if the device is on.""" + return self._on + + @property + def current_fan_mode(self): + """Return the fan setting.""" + return self._current_fan_mode + + @property + def fan_list(self): + """Return the list of available fan modes.""" + return FAN_MODES + + def set_temperature(self, **kwargs): + """Set new target temperatures.""" + temp = kwargs.get(ATTR_TEMPERATURE) + if temp is not None: + _LOGGER.debug("Setting temp of %s to %s", self.unique_id, + str(temp)) + self._device.set_thermostat(str(temp)) + + def set_fan_mode(self, fan_mode): + """Set new fan mode.""" + _LOGGER.debug("Setting fan mode of %s to %s", self.unique_id, + fan_mode) + self._device.set_fan_speed(fan_mode) + + def set_operation_mode(self, operation_mode): + """Set new operation mode.""" + _LOGGER.debug("Setting operation mode of %s to %s", self.unique_id, + operation_mode) + self._device.set_mode(HA_STATE_TO_CM[operation_mode]) + + def turn_on(self): + """Turn on.""" + _LOGGER.debug("Turning %s on", self.unique_id) + self._device.turn_on() + + def turn_off(self): + """Turn off.""" + _LOGGER.debug("Turning %s off", self.unique_id) + self._device.turn_off() diff --git a/homeassistant/components/climate/demo.py b/homeassistant/components/climate/demo.py index bc0b9bd52ee..14c22cefbe9 100644 --- a/homeassistant/components/climate/demo.py +++ b/homeassistant/components/climate/demo.py @@ -186,22 +186,22 @@ class DemoClimate(ClimateDevice): self.schedule_update_ha_state() def set_humidity(self, humidity): - """Set new target temperature.""" + """Set new humidity level.""" self._target_humidity = humidity self.schedule_update_ha_state() def set_swing_mode(self, swing_mode): - """Set new target temperature.""" + """Set new swing mode.""" self._current_swing_mode = swing_mode self.schedule_update_ha_state() def set_fan_mode(self, fan_mode): - """Set new target temperature.""" + """Set new fan mode.""" self._current_fan_mode = fan_mode self.schedule_update_ha_state() def set_operation_mode(self, operation_mode): - """Set new target temperature.""" + """Set new operation mode.""" self._current_operation = operation_mode self.schedule_update_ha_state() diff --git a/requirements_all.txt b/requirements_all.txt index 9f2f8ec6366..5911eb938b9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -957,6 +957,9 @@ pycmus==0.1.1 # homeassistant.components.comfoconnect pycomfoconnect==0.3 +# homeassistant.components.climate.coolmaster +pycoolmasternet==0.0.4 + # homeassistant.components.tts.microsoft pycsspeechtts==1.0.2 From 1ebdc2e2c2fae0e93b80fb0f1392b6635db27636 Mon Sep 17 00:00:00 2001 From: Markus Jankowski Date: Sun, 10 Feb 2019 19:49:16 +0100 Subject: [PATCH 133/242] Add device HmIP-BSL to Homematic IP (#20865) * Added support from HmIP-BSL * Fixed setup of initial on * Minor changes Removed Black from Dictionary added extra case to turn_on added comments * moved 3rd party libraries inside methods * Fixed comment * Removed code block to keep component behavior consisten to other dimmers Minimum brightness is 10, otherwise the led is not visible anymore * moved 3rd party libraries inside methods 2nd * corrected spelling and variable assignment * implemented feedback * removed own state implementation it is the same as in parent class * reduced device_state_attributes brightness is alread in parent class * On/Off is only determined by brightness now turn_off sets brightness to 0. turn_on now uses the previous used color an sets the brightness to 255 * Fixed string sorting of unique_id * improved usage of base class * Update code after review by MartinHjelmare * Fix for the hound --- .../components/homematicip_cloud/light.py | 146 +++++++++++++++++- 1 file changed, 142 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/homematicip_cloud/light.py b/homeassistant/components/homematicip_cloud/light.py index 2837edbd5b7..5d604d2c665 100644 --- a/homeassistant/components/homematicip_cloud/light.py +++ b/homeassistant/components/homematicip_cloud/light.py @@ -7,10 +7,10 @@ https://home-assistant.io/components/light.homematicip_cloud/ import logging from homeassistant.components.homematicip_cloud import ( - HMIPC_HAPID, HomematicipGenericDevice) -from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN + DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice) from homeassistant.components.light import ( - ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) + ATTR_BRIGHTNESS, ATTR_COLOR_NAME, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, Light) DEPENDENCIES = ['homematicip_cloud'] @@ -30,13 +30,20 @@ async def async_setup_platform( async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the HomematicIP Cloud lights from a config entry.""" from homematicip.aio.device import AsyncBrandSwitchMeasuring, AsyncDimmer,\ - AsyncPluggableDimmer, AsyncBrandDimmer, AsyncFullFlushDimmer + AsyncPluggableDimmer, AsyncBrandDimmer, AsyncFullFlushDimmer,\ + AsyncBrandSwitchNotificationLight home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home devices = [] for device in home.devices: if isinstance(device, AsyncBrandSwitchMeasuring): devices.append(HomematicipLightMeasuring(home, device)) + elif isinstance(device, AsyncBrandSwitchNotificationLight): + devices.append(HomematicipLight(home, device)) + devices.append(HomematicipNotificationLight( + home, device, device.topLightChannelIndex)) + devices.append(HomematicipNotificationLight( + home, device, device.bottomLightChannelIndex)) elif isinstance(device, (AsyncDimmer, AsyncPluggableDimmer, AsyncBrandDimmer, AsyncFullFlushDimmer)): @@ -117,3 +124,134 @@ class HomematicipDimmer(HomematicipGenericDevice, Light): async def async_turn_off(self, **kwargs): """Turn the light off.""" await self._device.set_dim_level(0) + + +class HomematicipNotificationLight(HomematicipGenericDevice, Light): + """Representation of HomematicIP Cloud dimmer light device.""" + + def __init__(self, home, device, channel_index): + """Initialize the dimmer light device.""" + self._channel_index = channel_index + if self._channel_index == 2: + super().__init__(home, device, 'Top') + else: + super().__init__(home, device, 'Bottom') + + from homematicip.base.enums import RGBColorState + self._color_switcher = { + RGBColorState.WHITE: [0.0, 0.0], + RGBColorState.RED: [0.0, 100.0], + RGBColorState.YELLOW: [60.0, 100.0], + RGBColorState.GREEN: [120.0, 100.0], + RGBColorState.TURQUOISE: [180.0, 100.0], + RGBColorState.BLUE: [240.0, 100.0], + RGBColorState.PURPLE: [300.0, 100.0] + } + + @property + def _channel(self): + return self._device.functionalChannels[self._channel_index] + + @property + def is_on(self): + """Return true if device is on.""" + return self._channel.dimLevel > 0.0 + + @property + def brightness(self): + """Return the brightness of this light between 0..255.""" + return int(self._channel.dimLevel * 255) + + @property + def hs_color(self): + """Return the hue and saturation color value [float, float].""" + simple_rgb_color = self._channel.simpleRGBColorState + return self._color_switcher.get(simple_rgb_color, [0.0, 0.0]) + + @property + def device_state_attributes(self): + """Return the state attributes of the generic device.""" + attr = super().device_state_attributes + if self.is_on: + attr.update({ + ATTR_COLOR_NAME: + self._channel.simpleRGBColorState + }) + return attr + + @property + def name(self): + """Return the name of the generic device.""" + return "{} {}".format(super().name, 'Notification') + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORT_BRIGHTNESS | SUPPORT_COLOR + + @property + def unique_id(self) -> str: + """Return a unique ID.""" + return "{}_{}_{}".format(self.__class__.__name__, + self.post, + self._device.id) + + async def async_turn_on(self, **kwargs): + """Turn the light on.""" + # Use hs_color from kwargs, + # if not applicable use current hs_color. + hs_color = kwargs.get(ATTR_HS_COLOR, self.hs_color) + simple_rgb_color = _convert_color(hs_color) + + # Use brightness from kwargs, + # if not applicable use current brightness. + brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness) + + # If no kwargs, use default value. + if not kwargs: + brightness = 255 + + # Minimum brightness is 10, otherwise the led is disabled + brightness = max(10, brightness) + dim_level = brightness / 255.0 + + await self._device.set_rgb_dim_level( + self._channel_index, + simple_rgb_color, + dim_level) + + async def async_turn_off(self, **kwargs): + """Turn the light off.""" + simple_rgb_color = self._channel.simpleRGBColorState + await self._device.set_rgb_dim_level( + self._channel_index, + simple_rgb_color, 0.0) + + +def _convert_color(color): + """ + Convert the given color to the reduced RGBColorState color. + + RGBColorStat contains only 8 colors including white and black, + so a conversion is required. + """ + from homematicip.base.enums import RGBColorState + + if color is None: + return RGBColorState.WHITE + + hue = int(color[0]) + saturation = int(color[1]) + if saturation < 5: + return RGBColorState.WHITE + if 30 < hue <= 90: + return RGBColorState.YELLOW + if 90 < hue <= 160: + return RGBColorState.GREEN + if 150 < hue <= 210: + return RGBColorState.TURQUOISE + if 210 < hue <= 270: + return RGBColorState.BLUE + if 270 < hue <= 330: + return RGBColorState.PURPLE + return RGBColorState.RED From d049b521b2e85131edfa77d16322c120c73a4e27 Mon Sep 17 00:00:00 2001 From: Tim van Cann Date: Sun, 10 Feb 2019 21:45:46 +0100 Subject: [PATCH 134/242] Add Google pubsub component (#20049) * Add google pubsub component * Add tests and requirements * Make python3.5 compatible * Fix linting * Fix pubsub test * Code review comments * Add missing docstrings * Update requirements_all * Code review comment - Remove pylint ignores - Don't modify global environment --- .../components/google_pubsub/__init__.py | 99 +++++++++++++++++++ requirements_all.txt | 3 + tests/components/google_pubsub/test_pubsub.py | 22 +++++ 3 files changed, 124 insertions(+) create mode 100644 homeassistant/components/google_pubsub/__init__.py create mode 100644 tests/components/google_pubsub/test_pubsub.py diff --git a/homeassistant/components/google_pubsub/__init__.py b/homeassistant/components/google_pubsub/__init__.py new file mode 100644 index 00000000000..af8bb60f8b1 --- /dev/null +++ b/homeassistant/components/google_pubsub/__init__.py @@ -0,0 +1,99 @@ +""" +Support for Google Cloud Pub/Sub. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/google_pubsub/ +""" +import datetime +import json +import logging +import os +from typing import Any, Dict + +import voluptuous as vol + +from homeassistant.const import ( + EVENT_STATE_CHANGED, STATE_UNAVAILABLE, STATE_UNKNOWN) +from homeassistant.core import Event, HomeAssistant +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entityfilter import FILTER_SCHEMA + +_LOGGER = logging.getLogger(__name__) + +REQUIREMENTS = ['google-cloud-pubsub==0.39.1'] + +DOMAIN = 'google_pubsub' + +CONF_PROJECT_ID = 'project_id' +CONF_TOPIC_NAME = 'topic_name' +CONF_SERVICE_PRINCIPAL = 'credentials_json' +CONF_FILTER = 'filter' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_PROJECT_ID): cv.string, + vol.Required(CONF_TOPIC_NAME): cv.string, + vol.Required(CONF_SERVICE_PRINCIPAL): cv.string, + vol.Required(CONF_FILTER): FILTER_SCHEMA + }), +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass: HomeAssistant, yaml_config: Dict[str, Any]): + """Activate Google Pub/Sub component.""" + from google.cloud import pubsub_v1 + + config = yaml_config[DOMAIN] + project_id = config[CONF_PROJECT_ID] + topic_name = config[CONF_TOPIC_NAME] + service_principal_path = os.path.join(hass.config.config_dir, + config[CONF_SERVICE_PRINCIPAL]) + + if not os.path.isfile(service_principal_path): + _LOGGER.error("Path to credentials file cannot be found") + return False + + entities_filter = config[CONF_FILTER] + + publisher = (pubsub_v1 + .PublisherClient + .from_service_account_json(service_principal_path) + ) + + topic_path = publisher.topic_path(project_id, # pylint: disable=E1101 + topic_name) + + encoder = DateTimeJSONEncoder() + + def send_to_pubsub(event: Event): + """Send states to Pub/Sub.""" + state = event.data.get('new_state') + if (state is None + or state.state in (STATE_UNKNOWN, '', STATE_UNAVAILABLE) + or not entities_filter(state.entity_id)): + return + + as_dict = state.as_dict() + data = json.dumps( + obj=as_dict, + default=encoder.encode + ).encode('utf-8') + + publisher.publish(topic_path, data=data) + + hass.bus.listen(EVENT_STATE_CHANGED, send_to_pubsub) + + return True + + +class DateTimeJSONEncoder(json.JSONEncoder): + """Encode python objects. + + Additionally add encoding for datetime objects as isoformat. + """ + + def default(self, o): # pylint: disable=E0202 + """Implement encoding logic.""" + if isinstance(o, datetime.datetime): + return o.isoformat() + return super().default(o) diff --git a/requirements_all.txt b/requirements_all.txt index 5911eb938b9..683f69ba29c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -471,6 +471,9 @@ gntp==1.0.3 # homeassistant.components.google google-api-python-client==1.6.4 +# homeassistant.components.google_pubsub +google-cloud-pubsub==0.39.1 + # homeassistant.components.googlehome googledevices==1.0.2 diff --git a/tests/components/google_pubsub/test_pubsub.py b/tests/components/google_pubsub/test_pubsub.py new file mode 100644 index 00000000000..b97dc33f8b1 --- /dev/null +++ b/tests/components/google_pubsub/test_pubsub.py @@ -0,0 +1,22 @@ +"""The tests for the Google Pub/Sub component.""" +from datetime import datetime + +from homeassistant.components.google_pubsub import ( + DateTimeJSONEncoder as victim) + + +class TestDateTimeJSONEncoder(object): + """Bundle for DateTimeJSONEncoder tests.""" + + def test_datetime(self): + """Test datetime encoding.""" + time = datetime(2019, 1, 13, 12, 30, 5) + assert victim().encode(time) == '"2019-01-13T12:30:05"' + + def test_no_datetime(self): + """Test integer encoding.""" + assert victim().encode(42) == '42' + + def test_nested(self): + """Test dictionary encoding.""" + assert victim().encode({'foo': 'bar'}) == '{"foo": "bar"}' From 5df02f3a7871724d6201181847e1723b236e05ae Mon Sep 17 00:00:00 2001 From: Fredrik Erlandsson Date: Sun, 10 Feb 2019 21:48:33 +0100 Subject: [PATCH 135/242] fix missing sensor values for Point (#20937) --- homeassistant/components/point/__init__.py | 2 +- homeassistant/components/point/sensor.py | 2 ++ requirements_all.txt | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/point/__init__.py b/homeassistant/components/point/__init__.py index 6616d6b24ec..66044678e28 100644 --- a/homeassistant/components/point/__init__.py +++ b/homeassistant/components/point/__init__.py @@ -26,7 +26,7 @@ from .const import ( CONF_WEBHOOK_URL, DOMAIN, EVENT_RECEIVED, POINT_DISCOVERY_NEW, SCAN_INTERVAL, SIGNAL_UPDATE_ENTITY, SIGNAL_WEBHOOK) -REQUIREMENTS = ['pypoint==1.0.6'] +REQUIREMENTS = ['pypoint==1.0.7'] DEPENDENCIES = ['webhook'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/point/sensor.py b/homeassistant/components/point/sensor.py index 9413cf163d9..eb320de0efd 100644 --- a/homeassistant/components/point/sensor.py +++ b/homeassistant/components/point/sensor.py @@ -67,6 +67,8 @@ class MinutPointSensor(MinutPointEntity): @property def state(self): """Return the state of the sensor.""" + if self.value is None: + return None return round(self.value, self._device_prop[1]) @property diff --git a/requirements_all.txt b/requirements_all.txt index 683f69ba29c..3d6a40a31b3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1192,7 +1192,7 @@ pypck==0.5.9 pypjlink2==1.2.0 # homeassistant.components.point -pypoint==1.0.6 +pypoint==1.0.7 # homeassistant.components.sensor.pollen pypollencom==2.2.2 From 203a6fd3490a9a1dffa75692454706a21e66b828 Mon Sep 17 00:00:00 2001 From: Rudolf Offereins Date: Sun, 10 Feb 2019 21:49:45 +0100 Subject: [PATCH 136/242] Fixed Thethingsnetwork sensor issue so that it takes the most recent (last) item from the TTN data storage query result instead of the first. (#20790) * Fixed Thethingsnetwork sensor issue so that it takes the most recent (last) item from the TTN data storage query result instead of the first. * Update sensor.py More pythonic way to get the last item of the sensor value list. --- homeassistant/components/thethingsnetwork/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/thethingsnetwork/sensor.py b/homeassistant/components/thethingsnetwork/sensor.py index 0f1220f9b07..13b51d505c3 100644 --- a/homeassistant/components/thethingsnetwork/sensor.py +++ b/homeassistant/components/thethingsnetwork/sensor.py @@ -153,7 +153,7 @@ class TtnDataStorage: return False data = await req.json() - self.data = data[0] + self.data = data[-1] for value in self._values.items(): if value[0] not in self.data.keys(): From 8f249f9149b103dbfa89609c2096f57501e977ec Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Sun, 10 Feb 2019 12:52:35 -0800 Subject: [PATCH 137/242] Use CONF_RECIPIENT for default recipient in config (#20925) * Use CONF_RECIPIENT for default recipient in config * Fix typo --- .../components/tplink_lte/__init__.py | 23 +++++++++++++++---- homeassistant/components/tplink_lte/notify.py | 3 ++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/tplink_lte/__init__.py b/homeassistant/components/tplink_lte/__init__.py index 17288a881aa..3f33cbe9fb3 100644 --- a/homeassistant/components/tplink_lte/__init__.py +++ b/homeassistant/components/tplink_lte/__init__.py @@ -13,7 +13,8 @@ import voluptuous as vol from homeassistant.components.notify import ATTR_TARGET from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP) + CONF_HOST, CONF_NAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP, + CONF_RECIPIENT) from homeassistant.core import callback from homeassistant.helpers import config_validation as cv, discovery from homeassistant.helpers.aiohttp_client import async_create_clientsession @@ -27,10 +28,22 @@ DATA_KEY = 'tplink_lte' CONF_NOTIFY = "notify" -_NOTIFY_SCHEMA = vol.All(vol.Schema({ - vol.Optional(CONF_NAME): cv.string, - vol.Required(ATTR_TARGET): vol.All(cv.ensure_list, [cv.string]), -})) +# Deprecated in 0.88.0, invalidated in 0.91.0, remove in 0.92.0 +ATTR_TARGET_INVALIDATION_VERSION = '0.91.0' + +_NOTIFY_SCHEMA = vol.All( + vol.Schema({ + vol.Optional(CONF_NAME): cv.string, + vol.Optional(ATTR_TARGET): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_RECIPIENT): vol.All(cv.ensure_list, [cv.string]) + }), + cv.deprecated( + ATTR_TARGET, + replacement_key=CONF_RECIPIENT, + invalidation_version=ATTR_TARGET_INVALIDATION_VERSION + ), + cv.has_at_least_one_key(CONF_RECIPIENT), +) CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.All(cv.ensure_list, [vol.Schema({ diff --git a/homeassistant/components/tplink_lte/notify.py b/homeassistant/components/tplink_lte/notify.py index 9bb80e2591c..f80345a4362 100644 --- a/homeassistant/components/tplink_lte/notify.py +++ b/homeassistant/components/tplink_lte/notify.py @@ -10,6 +10,7 @@ import attr from homeassistant.components.notify import ( ATTR_TARGET, BaseNotificationService) +from homeassistant.const import CONF_RECIPIENT from ..tplink_lte import DATA_KEY @@ -40,7 +41,7 @@ class TplinkNotifyService(BaseNotificationService): _LOGGER.error("No modem available") return - phone = self.config[ATTR_TARGET] + phone = self.config[CONF_RECIPIENT] targets = kwargs.get(ATTR_TARGET, phone) if targets and message: for target in targets: From fd991bd1a472e43f0d792079cf7417064b573b53 Mon Sep 17 00:00:00 2001 From: CrazYoshi Date: Sun, 10 Feb 2019 22:04:18 +0100 Subject: [PATCH 138/242] Ebusd integration (#19607) * ebusd component and sensor splitted ebusd component and sensor splitted and tested * houndci-bot fixes * pep8 validated * Update requirements_all.txt * travis fixes * Fix __init__.py for travis * translation updated * proposed changed * move logic from component to ebusdpy lib * hound fixes * Update requirements_all.txt * update pypi library to V0.0.11 * error management in command_result Avoid sensor status change in case an error in reading occurs * add opMode translations add opMode translations * send type to read ebusdpy API * timeframe as attribute for time schedule type sensors * hound fix * bugfix on library * ebusd sensor moved to ebusd component directory * update ebusdpy dependency * improvement proposed * travis fix * update error managing * insert log debug start setup * changes requested * exception tuple on init * cla-bot stucked pull * added bai circuit support * merged coveragerc from dev * configuration get change --- .coveragerc | 3 +- .../ebusd/.translations/ebusd.en.json | 7 ++ .../ebusd/.translations/ebusd.it.json | 7 ++ homeassistant/components/ebusd/__init__.py | 118 ++++++++++++++++++ homeassistant/components/ebusd/const.py | 100 +++++++++++++++ homeassistant/components/ebusd/sensor.py | 105 ++++++++++++++++ homeassistant/components/ebusd/services.yaml | 6 + homeassistant/components/ebusd/strings.json | 6 + requirements_all.txt | 3 + 9 files changed, 354 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/ebusd/.translations/ebusd.en.json create mode 100644 homeassistant/components/ebusd/.translations/ebusd.it.json create mode 100644 homeassistant/components/ebusd/__init__.py create mode 100644 homeassistant/components/ebusd/const.py create mode 100644 homeassistant/components/ebusd/sensor.py create mode 100644 homeassistant/components/ebusd/services.yaml create mode 100644 homeassistant/components/ebusd/strings.json diff --git a/.coveragerc b/.coveragerc index b615c4250f8..8ef830c8a8a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -142,6 +142,7 @@ omit = homeassistant/components/dovado/* homeassistant/components/downloader/* homeassistant/components/dweet/* + homeassistant/components/ebusd/* homeassistant/components/ecoal_boiler/* homeassistant/components/ecobee/* homeassistant/components/ecovacs/* @@ -686,4 +687,4 @@ exclude_lines = # Don't complain if tests don't hit defensive assertion code: raise AssertionError - raise NotImplementedError + raise NotImplementedError \ No newline at end of file diff --git a/homeassistant/components/ebusd/.translations/ebusd.en.json b/homeassistant/components/ebusd/.translations/ebusd.en.json new file mode 100644 index 00000000000..16ab79fc582 --- /dev/null +++ b/homeassistant/components/ebusd/.translations/ebusd.en.json @@ -0,0 +1,7 @@ +{ + "state": { + "day": "Day", + "night": "Night", + "auto": "Automatic" + } +} \ No newline at end of file diff --git a/homeassistant/components/ebusd/.translations/ebusd.it.json b/homeassistant/components/ebusd/.translations/ebusd.it.json new file mode 100644 index 00000000000..d0b95daaafa --- /dev/null +++ b/homeassistant/components/ebusd/.translations/ebusd.it.json @@ -0,0 +1,7 @@ +{ + "state": { + "day": "Giorno", + "night": "Notte", + "auto": "Automatico" + } +} \ No newline at end of file diff --git a/homeassistant/components/ebusd/__init__.py b/homeassistant/components/ebusd/__init__.py new file mode 100644 index 00000000000..951e6f13b91 --- /dev/null +++ b/homeassistant/components/ebusd/__init__.py @@ -0,0 +1,118 @@ +""" +Support for Ebusd daemon for communication with eBUS heating systems. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/ebus/ +""" + +from datetime import timedelta +import logging +import socket + +import voluptuous as vol + +from homeassistant.const import ( + CONF_NAME, CONF_HOST, CONF_PORT, CONF_MONITORED_CONDITIONS) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import load_platform +from homeassistant.util import Throttle + +from .const import (DOMAIN, SENSOR_TYPES) + +REQUIREMENTS = ['ebusdpy==0.0.16'] + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_NAME = 'ebusd' +DEFAULT_PORT = 8888 +CONF_CIRCUIT = 'circuit' +CACHE_TTL = 900 +SERVICE_EBUSD_WRITE = 'ebusd_write' + +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=15) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_CIRCUIT): cv.string, + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_MONITORED_CONDITIONS, default=[]): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES['700'])]) + }) +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, config): + """Set up the eBusd component.""" + conf = config[DOMAIN] + name = conf[CONF_NAME] + circuit = conf[CONF_CIRCUIT] + monitored_conditions = conf.get(CONF_MONITORED_CONDITIONS) + server_address = ( + conf.get(CONF_HOST), conf.get(CONF_PORT)) + + try: + _LOGGER.debug("Ebusd component setup started.") + import ebusdpy + ebusdpy.init(server_address) + hass.data[DOMAIN] = EbusdData(server_address, circuit) + + sensor_config = { + 'monitored_conditions': monitored_conditions, + 'client_name': name, + 'sensor_types': SENSOR_TYPES[circuit] + } + load_platform(hass, 'sensor', DOMAIN, sensor_config, config) + + hass.services.register( + DOMAIN, SERVICE_EBUSD_WRITE, hass.data[DOMAIN].write) + + _LOGGER.debug("Ebusd component setup completed.") + return True + except (socket.timeout, socket.error): + return False + + +class EbusdData: + """Get the latest data from Ebusd.""" + + def __init__(self, address, circuit): + """Initialize the data object.""" + self._circuit = circuit + self._address = address + self.value = {} + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self, name, stype): + """Call the Ebusd API to update the data.""" + import ebusdpy + + try: + _LOGGER.debug("Opening socket to ebusd %s", name) + command_result = ebusdpy.read( + self._address, self._circuit, name, stype, CACHE_TTL) + if command_result is not None: + if 'ERR:' in command_result: + _LOGGER.warning(command_result) + else: + self.value[name] = command_result + except RuntimeError as err: + _LOGGER.error(err) + raise RuntimeError(err) + + def write(self, call): + """Call write methon on ebusd.""" + import ebusdpy + name = call.data.get('name') + value = call.data.get('value') + + try: + _LOGGER.debug("Opening socket to ebusd %s", name) + command_result = ebusdpy.write( + self._address, self._circuit, name, value) + if command_result is not None: + if 'done' not in command_result: + _LOGGER.warning('Write command failed: %s', name) + except RuntimeError as err: + _LOGGER.error(err) diff --git a/homeassistant/components/ebusd/const.py b/homeassistant/components/ebusd/const.py new file mode 100644 index 00000000000..c36981c5278 --- /dev/null +++ b/homeassistant/components/ebusd/const.py @@ -0,0 +1,100 @@ +"""Constants for ebus component.""" +DOMAIN = 'ebusd' + +# SensorTypes: +# 0='decimal', 1='time-schedule', 2='switch', 3='string', 4='value;status' + +SENSOR_TYPES = { + '700': { + 'ActualFlowTemperatureDesired': + ['Hc1ActualFlowTempDesired', '°C', 'mdi:thermometer', 0], + 'MaxFlowTemperatureDesired': + ['Hc1MaxFlowTempDesired', '°C', 'mdi:thermometer', 0], + 'MinFlowTemperatureDesired': + ['Hc1MinFlowTempDesired', '°C', 'mdi:thermometer', 0], + 'PumpStatus': + ['Hc1PumpStatus', None, 'mdi:toggle-switch', 2], + 'HCSummerTemperatureLimit': + ['Hc1SummerTempLimit', '°C', 'mdi:weather-sunny', 0], + 'HolidayTemperature': + ['HolidayTemp', '°C', 'mdi:thermometer', 0], + 'HWTemperatureDesired': + ['HwcTempDesired', '°C', 'mdi:thermometer', 0], + 'HWTimerMonday': + ['hwcTimer.Monday', None, 'mdi:timer', 1], + 'HWTimerTuesday': + ['hwcTimer.Tuesday', None, 'mdi:timer', 1], + 'HWTimerWednesday': + ['hwcTimer.Wednesday', None, 'mdi:timer', 1], + 'HWTimerThursday': + ['hwcTimer.Thursday', None, 'mdi:timer', 1], + 'HWTimerFriday': + ['hwcTimer.Friday', None, 'mdi:timer', 1], + 'HWTimerSaturday': + ['hwcTimer.Saturday', None, 'mdi:timer', 1], + 'HWTimerSunday': + ['hwcTimer.Sunday', None, 'mdi:timer', 1], + 'WaterPressure': + ['WaterPressure', 'bar', 'mdi:water-pump', 0], + 'Zone1RoomZoneMapping': + ['z1RoomZoneMapping', None, 'mdi:label', 0], + 'Zone1NightTemperature': + ['z1NightTemp', '°C', 'mdi:weather-night', 0], + 'Zone1DayTemperature': + ['z1DayTemp', '°C', 'mdi:weather-sunny', 0], + 'Zone1HolidayTemperature': + ['z1HolidayTemp', '°C', 'mdi:thermometer', 0], + 'Zone1RoomTemperature': + ['z1RoomTemp', '°C', 'mdi:thermometer', 0], + 'Zone1ActualRoomTemperatureDesired': + ['z1ActualRoomTempDesired', '°C', 'mdi:thermometer', 0], + 'Zone1TimerMonday': + ['z1Timer.Monday', None, 'mdi:timer', 1], + 'Zone1TimerTuesday': + ['z1Timer.Tuesday', None, 'mdi:timer', 1], + 'Zone1TimerWednesday': + ['z1Timer.Wednesday', None, 'mdi:timer', 1], + 'Zone1TimerThursday': + ['z1Timer.Thursday', None, 'mdi:timer', 1], + 'Zone1TimerFriday': + ['z1Timer.Friday', None, 'mdi:timer', 1], + 'Zone1TimerSaturday': + ['z1Timer.Saturday', None, 'mdi:timer', 1], + 'Zone1TimerSunday': + ['z1Timer.Sunday', None, 'mdi:timer', 1], + 'Zone1OperativeMode': + ['z1OpMode', None, 'mdi:math-compass', 3], + 'ContinuosHeating': + ['ContinuosHeating', '°C', 'mdi:weather-snowy', 0], + 'PowerEnergyConsumptionLastMonth': + ['PrEnergySumHcLastMonth', 'kWh', 'mdi:flash', 0], + 'PowerEnergyConsumptionThisMonth': + ['PrEnergySumHcThisMonth', 'kWh', 'mdi:flash', 0] + }, + 'ehp': { + 'HWTemperature': + ['HwcTemp', '°C', 'mdi:thermometer', 4], + 'OutsideTemp': + ['OutsideTemp', '°C', 'mdi:thermometer', 4] + }, + 'bai': { + 'ReturnTemperature': + ['ReturnTemp', '°C', 'mdi:thermometer', 4], + 'CentralHeatingPump': + ['WP', None, 'mdi:toggle-switch', 2], + 'HeatingSwitch': + ['HeatingSwitch', None, 'mdi:toggle-switch', 2], + 'FlowTemperature': + ['FlowTemp', '°C', 'mdi:thermometer', 4], + 'Flame': + ['Flame', None, 'mdi:toggle-switch', 2], + 'PowerEnergyConsumptionHeatingCircuit': + ['PrEnergySumHc1', 'kWh', 'mdi:flash', 0], + 'PowerEnergyConsumptionHotWaterCircuit': + ['PrEnergySumHwc1', 'kWh', 'mdi:flash', 0], + 'RoomThermostat': + ['DCRoomthermostat', None, 'mdi:toggle-switch', 2], + 'HeatingPartLoad': + ['PartloadHcKW', 'kWh', 'mdi:flash', 0] + } +} diff --git a/homeassistant/components/ebusd/sensor.py b/homeassistant/components/ebusd/sensor.py new file mode 100644 index 00000000000..24ca263898c --- /dev/null +++ b/homeassistant/components/ebusd/sensor.py @@ -0,0 +1,105 @@ +""" +Support for Ebusd daemon for communication with eBUS heating systems. + +For more details about ebusd deamon, please refer to the documentation at +https://github.com/john30/ebusd +""" + +import logging +import datetime + +from homeassistant.helpers.entity import Entity + +from .const import DOMAIN + +DEPENDENCIES = ['ebusd'] + +TIME_FRAME1_BEGIN = 'time_frame1_begin' +TIME_FRAME1_END = 'time_frame1_end' +TIME_FRAME2_BEGIN = 'time_frame2_begin' +TIME_FRAME2_END = 'time_frame2_end' +TIME_FRAME3_BEGIN = 'time_frame3_begin' +TIME_FRAME3_END = 'time_frame3_end' + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the Ebus sensor.""" + ebusd_api = hass.data[DOMAIN] + monitored_conditions = discovery_info['monitored_conditions'] + name = discovery_info['client_name'] + + dev = [] + for condition in monitored_conditions: + dev.append(EbusdSensor( + ebusd_api, discovery_info['sensor_types'][condition], name)) + + add_entities(dev, True) + + +class EbusdSensor(Entity): + """Ebusd component sensor methods definition.""" + + def __init__(self, data, sensor, name): + """Initialize the sensor.""" + self._state = None + self._client_name = name + self._name, self._unit_of_measurement, self._icon, self._type = sensor + self.data = data + + @property + def name(self): + """Return the name of the sensor.""" + return '{} {}'.format(self._client_name, self._name) + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def device_state_attributes(self): + """Return the device state attributes.""" + if self._type == 1 and self._state is not None: + schedule = { + TIME_FRAME1_BEGIN: None, + TIME_FRAME1_END: None, + TIME_FRAME2_BEGIN: None, + TIME_FRAME2_END: None, + TIME_FRAME3_BEGIN: None, + TIME_FRAME3_END: None + } + time_frame = self._state.split(';') + for index, item in enumerate(sorted(schedule.items())): + if index < len(time_frame): + parsed = datetime.datetime.strptime( + time_frame[index], '%H:%M') + parsed = parsed.replace( + datetime.datetime.now().year, + datetime.datetime.now().month, + datetime.datetime.now().day) + schedule[item[0]] = parsed.isoformat() + return schedule + return None + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return self._icon + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self._unit_of_measurement + + def update(self): + """Fetch new state data for the sensor.""" + try: + self.data.update(self._name, self._type) + if self._name not in self.data.value: + return + + self._state = self.data.value[self._name] + except RuntimeError: + _LOGGER.debug("EbusdData.update exception") diff --git a/homeassistant/components/ebusd/services.yaml b/homeassistant/components/ebusd/services.yaml new file mode 100644 index 00000000000..0f64533f7f1 --- /dev/null +++ b/homeassistant/components/ebusd/services.yaml @@ -0,0 +1,6 @@ +write: + description: Call ebusd write command. + fields: + call: + description: Property name and value to set + example: '{"name": "Hc1MaxFlowTempDesired", "value": 21}' \ No newline at end of file diff --git a/homeassistant/components/ebusd/strings.json b/homeassistant/components/ebusd/strings.json new file mode 100644 index 00000000000..ee62df8ddad --- /dev/null +++ b/homeassistant/components/ebusd/strings.json @@ -0,0 +1,6 @@ +{ + "state": { + "day": "Day", + "night": "Night" + } +} \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index 3d6a40a31b3..a025998e877 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -348,6 +348,9 @@ dsmr_parser==0.12 # homeassistant.components.dweet.sensor dweepy==0.3.0 +# homeassistant.components.ebusd +ebusdpy==0.0.16 + # homeassistant.components.ecoal_boiler ecoaliface==0.4.0 From 88d0aa14eeb42e79ead101ed125fad945a535846 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 11 Feb 2019 02:13:03 +0100 Subject: [PATCH 139/242] Update denonavr to 0.7.8 (add various sound modes) (#20951) --- homeassistant/components/media_player/denonavr.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/denonavr.py b/homeassistant/components/media_player/denonavr.py index d6caf361961..380484add53 100644 --- a/homeassistant/components/media_player/denonavr.py +++ b/homeassistant/components/media_player/denonavr.py @@ -23,7 +23,7 @@ from homeassistant.const import ( STATE_PAUSED, STATE_PLAYING) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['denonavr==0.7.7'] +REQUIREMENTS = ['denonavr==0.7.8'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index a025998e877..7f21c604827 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -318,7 +318,7 @@ defusedxml==0.5.0 deluge-client==1.4.0 # homeassistant.components.media_player.denonavr -denonavr==0.7.7 +denonavr==0.7.8 # homeassistant.components.media_player.directv directpy==0.5 From a55c2514d1135944f77b044a00e7d7ada8ea7bca Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 11 Feb 2019 01:54:29 -0700 Subject: [PATCH 140/242] Add missing data fields to Ambient PWS (#20808) * Fix binary sensor in Ambient PWS * Add missing data points for Ambient PWS * Member comments * Binary sensor doesn't need state property --- .../components/ambient_station/__init__.py | 120 ++++++++++++++++++ .../ambient_station/binary_sensor.py | 9 +- 2 files changed, 127 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index 4aa19dbc69e..cf5625620a2 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -33,6 +33,16 @@ DEFAULT_SOCKET_MIN_RETRY = 15 TYPE_24HOURRAININ = '24hourrainin' TYPE_BAROMABSIN = 'baromabsin' TYPE_BAROMRELIN = 'baromrelin' +TYPE_BATT1 = 'batt1' +TYPE_BATT10 = 'batt10' +TYPE_BATT2 = 'batt2' +TYPE_BATT3 = 'batt3' +TYPE_BATT4 = 'batt4' +TYPE_BATT5 = 'batt5' +TYPE_BATT6 = 'batt6' +TYPE_BATT7 = 'batt7' +TYPE_BATT8 = 'batt8' +TYPE_BATT9 = 'batt9' TYPE_BATTOUT = 'battout' TYPE_CO2 = 'co2' TYPE_DAILYRAININ = 'dailyrainin' @@ -41,11 +51,61 @@ TYPE_EVENTRAININ = 'eventrainin' TYPE_FEELSLIKE = 'feelsLike' TYPE_HOURLYRAININ = 'hourlyrainin' TYPE_HUMIDITY = 'humidity' +TYPE_HUMIDITY1 = 'humidity1' +TYPE_HUMIDITY10 = 'humidity10' +TYPE_HUMIDITY2 = 'humidity2' +TYPE_HUMIDITY3 = 'humidity3' +TYPE_HUMIDITY4 = 'humidity4' +TYPE_HUMIDITY5 = 'humidity5' +TYPE_HUMIDITY6 = 'humidity6' +TYPE_HUMIDITY7 = 'humidity7' +TYPE_HUMIDITY8 = 'humidity8' +TYPE_HUMIDITY9 = 'humidity9' TYPE_HUMIDITYIN = 'humidityin' TYPE_LASTRAIN = 'lastRain' TYPE_MAXDAILYGUST = 'maxdailygust' TYPE_MONTHLYRAININ = 'monthlyrainin' +TYPE_RELAY1 = 'relay1' +TYPE_RELAY10 = 'relay10' +TYPE_RELAY2 = 'relay2' +TYPE_RELAY3 = 'relay3' +TYPE_RELAY4 = 'relay4' +TYPE_RELAY5 = 'relay5' +TYPE_RELAY6 = 'relay6' +TYPE_RELAY7 = 'relay7' +TYPE_RELAY8 = 'relay8' +TYPE_RELAY9 = 'relay9' +TYPE_SOILHUM1 = 'soilhum1' +TYPE_SOILHUM10 = 'soilhum10' +TYPE_SOILHUM2 = 'soilhum2' +TYPE_SOILHUM3 = 'soilhum3' +TYPE_SOILHUM4 = 'soilhum4' +TYPE_SOILHUM5 = 'soilhum5' +TYPE_SOILHUM6 = 'soilhum6' +TYPE_SOILHUM7 = 'soilhum7' +TYPE_SOILHUM8 = 'soilhum8' +TYPE_SOILHUM9 = 'soilhum9' +TYPE_SOILTEMP1F = 'soiltemp1f' +TYPE_SOILTEMP10F = 'soiltemp10f' +TYPE_SOILTEMP2F = 'soiltemp2f' +TYPE_SOILTEMP3F = 'soiltemp3f' +TYPE_SOILTEMP4F = 'soiltemp4f' +TYPE_SOILTEMP5F = 'soiltemp5f' +TYPE_SOILTEMP6F = 'soiltemp6f' +TYPE_SOILTEMP7F = 'soiltemp7f' +TYPE_SOILTEMP8F = 'soiltemp8f' +TYPE_SOILTEMP9F = 'soiltemp9f' TYPE_SOLARRADIATION = 'solarradiation' +TYPE_TEMP10F = 'temp10f' +TYPE_TEMP1F = 'temp1f' +TYPE_TEMP2F = 'temp2f' +TYPE_TEMP3F = 'temp3f' +TYPE_TEMP4F = 'temp4f' +TYPE_TEMP5F = 'temp5f' +TYPE_TEMP6F = 'temp6f' +TYPE_TEMP7F = 'temp7f' +TYPE_TEMP8F = 'temp8f' +TYPE_TEMP9F = 'temp9f' TYPE_TEMPF = 'tempf' TYPE_TEMPINF = 'tempinf' TYPE_TOTALRAININ = 'totalrainin' @@ -64,6 +124,16 @@ SENSOR_TYPES = { TYPE_24HOURRAININ: ('24 Hr Rain', 'in', TYPE_SENSOR, None), TYPE_BAROMABSIN: ('Abs Pressure', 'inHg', TYPE_SENSOR, None), TYPE_BAROMRELIN: ('Rel Pressure', 'inHg', TYPE_SENSOR, None), + TYPE_BATT10: ('Battery 10', None, TYPE_BINARY_SENSOR, 'battery'), + TYPE_BATT1: ('Battery 1', None, TYPE_BINARY_SENSOR, 'battery'), + TYPE_BATT2: ('Battery 2', None, TYPE_BINARY_SENSOR, 'battery'), + TYPE_BATT3: ('Battery 3', None, TYPE_BINARY_SENSOR, 'battery'), + TYPE_BATT4: ('Battery 4', None, TYPE_BINARY_SENSOR, 'battery'), + TYPE_BATT5: ('Battery 5', None, TYPE_BINARY_SENSOR, 'battery'), + TYPE_BATT6: ('Battery 6', None, TYPE_BINARY_SENSOR, 'battery'), + TYPE_BATT7: ('Battery 7', None, TYPE_BINARY_SENSOR, 'battery'), + TYPE_BATT8: ('Battery 8', None, TYPE_BINARY_SENSOR, 'battery'), + TYPE_BATT9: ('Battery 9', None, TYPE_BINARY_SENSOR, 'battery'), TYPE_BATTOUT: ('Battery', None, TYPE_BINARY_SENSOR, 'battery'), TYPE_CO2: ('co2', 'ppm', TYPE_SENSOR, None), TYPE_DAILYRAININ: ('Daily Rain', 'in', TYPE_SENSOR, None), @@ -71,12 +141,62 @@ SENSOR_TYPES = { TYPE_EVENTRAININ: ('Event Rain', 'in', TYPE_SENSOR, None), TYPE_FEELSLIKE: ('Feels Like', '°F', TYPE_SENSOR, None), TYPE_HOURLYRAININ: ('Hourly Rain Rate', 'in/hr', TYPE_SENSOR, None), + TYPE_HUMIDITY10: ('Humidity 10', '%', TYPE_SENSOR, None), + TYPE_HUMIDITY1: ('Humidity 1', '%', TYPE_SENSOR, None), + TYPE_HUMIDITY2: ('Humidity 2', '%', TYPE_SENSOR, None), + TYPE_HUMIDITY3: ('Humidity 3', '%', TYPE_SENSOR, None), + TYPE_HUMIDITY4: ('Humidity 4', '%', TYPE_SENSOR, None), + TYPE_HUMIDITY5: ('Humidity 5', '%', TYPE_SENSOR, None), + TYPE_HUMIDITY6: ('Humidity 6', '%', TYPE_SENSOR, None), + TYPE_HUMIDITY7: ('Humidity 7', '%', TYPE_SENSOR, None), + TYPE_HUMIDITY8: ('Humidity 8', '%', TYPE_SENSOR, None), + TYPE_HUMIDITY9: ('Humidity 9', '%', TYPE_SENSOR, None), TYPE_HUMIDITY: ('Humidity', '%', TYPE_SENSOR, None), TYPE_HUMIDITYIN: ('Humidity In', '%', TYPE_SENSOR, None), TYPE_LASTRAIN: ('Last Rain', None, TYPE_SENSOR, None), TYPE_MAXDAILYGUST: ('Max Gust', 'mph', TYPE_SENSOR, None), TYPE_MONTHLYRAININ: ('Monthly Rain', 'in', TYPE_SENSOR, None), + TYPE_RELAY10: ('Relay 10', None, TYPE_BINARY_SENSOR, 'connectivity'), + TYPE_RELAY1: ('Relay 1', None, TYPE_BINARY_SENSOR, 'connectivity'), + TYPE_RELAY2: ('Relay 2', None, TYPE_BINARY_SENSOR, 'connectivity'), + TYPE_RELAY3: ('Relay 3', None, TYPE_BINARY_SENSOR, 'connectivity'), + TYPE_RELAY4: ('Relay 4', None, TYPE_BINARY_SENSOR, 'connectivity'), + TYPE_RELAY5: ('Relay 5', None, TYPE_BINARY_SENSOR, 'connectivity'), + TYPE_RELAY6: ('Relay 6', None, TYPE_BINARY_SENSOR, 'connectivity'), + TYPE_RELAY7: ('Relay 7', None, TYPE_BINARY_SENSOR, 'connectivity'), + TYPE_RELAY8: ('Relay 8', None, TYPE_BINARY_SENSOR, 'connectivity'), + TYPE_RELAY9: ('Relay 9', None, TYPE_BINARY_SENSOR, 'connectivity'), + TYPE_SOILHUM10: ('Soil Humidity 10', '%', TYPE_SENSOR, None), + TYPE_SOILHUM1: ('Soil Humidity 1', '%', TYPE_SENSOR, None), + TYPE_SOILHUM2: ('Soil Humidity 2', '%', TYPE_SENSOR, None), + TYPE_SOILHUM3: ('Soil Humidity 3', '%', TYPE_SENSOR, None), + TYPE_SOILHUM4: ('Soil Humidity 4', '%', TYPE_SENSOR, None), + TYPE_SOILHUM5: ('Soil Humidity 5', '%', TYPE_SENSOR, None), + TYPE_SOILHUM6: ('Soil Humidity 6', '%', TYPE_SENSOR, None), + TYPE_SOILHUM7: ('Soil Humidity 7', '%', TYPE_SENSOR, None), + TYPE_SOILHUM8: ('Soil Humidity 8', '%', TYPE_SENSOR, None), + TYPE_SOILHUM9: ('Soil Humidity 9', '%', TYPE_SENSOR, None), + TYPE_SOILTEMP10F: ('Soil Temp 10', '°F', TYPE_SENSOR, None), + TYPE_SOILTEMP1F: ('Soil Temp 1', '°F', TYPE_SENSOR, None), + TYPE_SOILTEMP2F: ('Soil Temp 2', '°F', TYPE_SENSOR, None), + TYPE_SOILTEMP3F: ('Soil Temp 3', '°F', TYPE_SENSOR, None), + TYPE_SOILTEMP4F: ('Soil Temp 4', '°F', TYPE_SENSOR, None), + TYPE_SOILTEMP5F: ('Soil Temp 5', '°F', TYPE_SENSOR, None), + TYPE_SOILTEMP6F: ('Soil Temp 6', '°F', TYPE_SENSOR, None), + TYPE_SOILTEMP7F: ('Soil Temp 7', '°F', TYPE_SENSOR, None), + TYPE_SOILTEMP8F: ('Soil Temp 8', '°F', TYPE_SENSOR, None), + TYPE_SOILTEMP9F: ('Soil Temp 9', '°F', TYPE_SENSOR, None), TYPE_SOLARRADIATION: ('Solar Rad', 'W/m^2', TYPE_SENSOR, None), + TYPE_TEMP10F: ('Temp 10', '°F', TYPE_SENSOR, None), + TYPE_TEMP1F: ('Temp 1', '°F', TYPE_SENSOR, None), + TYPE_TEMP2F: ('Temp 2', '°F', TYPE_SENSOR, None), + TYPE_TEMP3F: ('Temp 3', '°F', TYPE_SENSOR, None), + TYPE_TEMP4F: ('Temp 4', '°F', TYPE_SENSOR, None), + TYPE_TEMP5F: ('Temp 5', '°F', TYPE_SENSOR, None), + TYPE_TEMP6F: ('Temp 6', '°F', TYPE_SENSOR, None), + TYPE_TEMP7F: ('Temp 7', '°F', TYPE_SENSOR, None), + TYPE_TEMP8F: ('Temp 8', '°F', TYPE_SENSOR, None), + TYPE_TEMP9F: ('Temp 9', '°F', TYPE_SENSOR, None), TYPE_TEMPF: ('Temp', '°F', TYPE_SENSOR, None), TYPE_TEMPINF: ('Inside Temp', '°F', TYPE_SENSOR, None), TYPE_TOTALRAININ: ('Lifetime Rain', 'in', TYPE_SENSOR, None), diff --git a/homeassistant/components/ambient_station/binary_sensor.py b/homeassistant/components/ambient_station/binary_sensor.py index c9c0160cf7c..9d3b90a08a1 100644 --- a/homeassistant/components/ambient_station/binary_sensor.py +++ b/homeassistant/components/ambient_station/binary_sensor.py @@ -7,7 +7,9 @@ https://home-assistant.io/components/binary_sensor.ambient_station/ import logging from homeassistant.components.ambient_station import ( - SENSOR_TYPES, TYPE_BATTOUT, AmbientWeatherEntity) + SENSOR_TYPES, TYPE_BATT1, TYPE_BATT10, TYPE_BATT2, TYPE_BATT3, TYPE_BATT4, + TYPE_BATT5, TYPE_BATT6, TYPE_BATT7, TYPE_BATT8, TYPE_BATT9, TYPE_BATTOUT, + AmbientWeatherEntity) from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.const import ATTR_NAME @@ -60,7 +62,10 @@ class AmbientWeatherBinarySensor(AmbientWeatherEntity, BinarySensorDevice): @property def is_on(self): """Return the status of the sensor.""" - if self._sensor_type == TYPE_BATTOUT: + if self._sensor_type in (TYPE_BATT1, TYPE_BATT10, TYPE_BATT2, + TYPE_BATT3, TYPE_BATT4, TYPE_BATT5, + TYPE_BATT6, TYPE_BATT7, TYPE_BATT8, + TYPE_BATT9, TYPE_BATTOUT): return self._state == 0 return self._state == 1 From e5383209013bef56d11574ac4b5c8b56c464f3f0 Mon Sep 17 00:00:00 2001 From: Mattias Welponer Date: Mon, 11 Feb 2019 10:20:00 +0100 Subject: [PATCH 141/242] HomematicIP fix cover direction (#20901) * Fix cover direction * Update for better readability * Fix lint --- homeassistant/components/homematicip_cloud/cover.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/homematicip_cloud/cover.py b/homeassistant/components/homematicip_cloud/cover.py index 27f26805e81..80fc8f7b430 100644 --- a/homeassistant/components/homematicip_cloud/cover.py +++ b/homeassistant/components/homematicip_cloud/cover.py @@ -15,6 +15,9 @@ DEPENDENCIES = ['homematicip_cloud'] _LOGGER = logging.getLogger(__name__) +HMIP_COVER_OPEN = 0 +HMIP_COVER_CLOSED = 1 + async def async_setup_platform( hass, config, async_add_entities, discovery_info=None): @@ -47,23 +50,24 @@ class HomematicipCoverShutter(HomematicipGenericDevice, CoverDevice): async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" position = kwargs[ATTR_POSITION] - level = position / 100.0 + # HmIP cover is closed:1 -> open:0 + level = 1 - position / 100.0 await self._device.set_shutter_level(level) @property def is_closed(self): """Return if the cover is closed.""" if self._device.shutterLevel is not None: - return self._device.shutterLevel == 0 + return self._device.shutterLevel == HMIP_COVER_CLOSED return None async def async_open_cover(self, **kwargs): """Open the cover.""" - await self._device.set_shutter_level(1) + await self._device.set_shutter_level(HMIP_COVER_OPEN) async def async_close_cover(self, **kwargs): """Close the cover.""" - await self._device.set_shutter_level(0) + await self._device.set_shutter_level(HMIP_COVER_CLOSED) async def async_stop_cover(self, **kwargs): """Stop the device if in motion.""" From de2892caa820be2646a2324c25efc08a1f8f1dea Mon Sep 17 00:00:00 2001 From: Stefan Burke <33483213+StefanBCN@users.noreply.github.com> Date: Mon, 11 Feb 2019 13:32:43 +0000 Subject: [PATCH 142/242] Update pyHS100 to 0.3.4 (#20979) --- homeassistant/components/light/tplink.py | 2 +- homeassistant/components/switch/tplink.py | 2 +- requirements_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/light/tplink.py b/homeassistant/components/light/tplink.py index b0f4c6d1a3c..bd1621a0b35 100644 --- a/homeassistant/components/light/tplink.py +++ b/homeassistant/components/light/tplink.py @@ -19,7 +19,7 @@ from homeassistant.util.color import \ from homeassistant.util.color import ( color_temperature_kelvin_to_mired as kelvin_to_mired) -REQUIREMENTS = ['pyHS100==0.3.3'] +REQUIREMENTS = ['pyHS100==0.3.4'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/switch/tplink.py b/homeassistant/components/switch/tplink.py index 01d5c2cc72c..67c8094a1f2 100644 --- a/homeassistant/components/switch/tplink.py +++ b/homeassistant/components/switch/tplink.py @@ -14,7 +14,7 @@ from homeassistant.components.switch import ( from homeassistant.const import (CONF_HOST, CONF_NAME, ATTR_VOLTAGE) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyHS100==0.3.3'] +REQUIREMENTS = ['pyHS100==0.3.4'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 7f21c604827..22952917384 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -891,7 +891,7 @@ pyCEC==0.4.13 # homeassistant.components.light.tplink # homeassistant.components.switch.tplink -pyHS100==0.3.3 +pyHS100==0.3.4 # homeassistant.components.weather.met pyMetno==0.3.0 From 0425c8195fc3861892af812c3b3fb5a8c7148813 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 11 Feb 2019 14:33:57 +0100 Subject: [PATCH 143/242] Upgrade slixmpp to 1.4.2 (#20971) --- homeassistant/components/notify/xmpp.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/notify/xmpp.py b/homeassistant/components/notify/xmpp.py index eac20c62797..462dd007d53 100644 --- a/homeassistant/components/notify/xmpp.py +++ b/homeassistant/components/notify/xmpp.py @@ -21,7 +21,7 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv import homeassistant.helpers.template as template_helper -REQUIREMENTS = ['slixmpp==1.4.1'] +REQUIREMENTS = ['slixmpp==1.4.2'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 22952917384..0ad580ddd5d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1550,7 +1550,7 @@ slacker==0.12.0 sleepyq==0.6 # homeassistant.components.notify.xmpp -slixmpp==1.4.1 +slixmpp==1.4.2 # homeassistant.components.smappee smappy==0.2.16 From 788f7988e7d83c1e8cedcfb932a69ee7eee6ba35 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 11 Feb 2019 17:18:25 +0100 Subject: [PATCH 144/242] Upgrade ruamel.yaml to 0.15.87 (#20955) --- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 1f0fb9d1a3b..b94e383ec9e 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ python-slugify==1.2.6 pytz>=2018.07 pyyaml>=3.13,<4 requests==2.21.0 -ruamel.yaml==0.15.85 +ruamel.yaml==0.15.87 voluptuous==0.11.5 voluptuous-serialize==2.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index 0ad580ddd5d..182268212be 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -13,7 +13,7 @@ python-slugify==1.2.6 pytz>=2018.07 pyyaml>=3.13,<4 requests==2.21.0 -ruamel.yaml==0.15.85 +ruamel.yaml==0.15.87 voluptuous==0.11.5 voluptuous-serialize==2.0.0 diff --git a/setup.py b/setup.py index ef4c010d42f..cdd377b7673 100755 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ REQUIRES = [ 'pytz>=2018.07', 'pyyaml>=3.13,<4', 'requests==2.21.0', - 'ruamel.yaml==0.15.85', + 'ruamel.yaml==0.15.87', 'voluptuous==0.11.5', 'voluptuous-serialize==2.0.0', ] From 49ecca9cb9b81dfa0a49bc9d2834c768e8669d98 Mon Sep 17 00:00:00 2001 From: "Patrick T.C" <124277+ptc@users.noreply.github.com> Date: Mon, 11 Feb 2019 19:59:34 +0100 Subject: [PATCH 145/242] Set cover level using emulated_hue (#19594) * set cover level using emulated_hue * changed mapping for service turn_on/off for cover. * removed whitespace for the sake of hound * using const for domains instead of hardcoded strings. * change length of lines for the sake of hound * fixed under-intended line * changed intent for the sake of hound --- .../components/emulated_hue/hue_api.py | 45 +++++-- tests/components/emulated_hue/test_hue_api.py | 120 +++++++++++++++++- 2 files changed, 151 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 174ee38715a..fdf1d35201e 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -19,6 +19,16 @@ from homeassistant.components.fan import ( ATTR_SPEED, SUPPORT_SET_SPEED, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH ) + +from homeassistant.components.cover import ( + ATTR_CURRENT_POSITION, ATTR_POSITION, SERVICE_SET_COVER_POSITION, + SUPPORT_SET_POSITION +) + +from homeassistant.components import ( + cover, fan, media_player, light, script, scene +) + from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.const import KEY_REAL_IP from homeassistant.util.network import is_local @@ -239,13 +249,13 @@ class HueOneLightChangeView(HomeAssistantView): # Make sure the entity actually supports brightness entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if entity.domain == "light": + if entity.domain == light.DOMAIN: if entity_features & SUPPORT_BRIGHTNESS: if brightness is not None: data[ATTR_BRIGHTNESS] = brightness # If the requested entity is a script add some variables - elif entity.domain == "script": + elif entity.domain == script.DOMAIN: data['variables'] = { 'requested_state': STATE_ON if result else STATE_OFF } @@ -254,7 +264,7 @@ class HueOneLightChangeView(HomeAssistantView): data['variables']['requested_level'] = brightness # If the requested entity is a media player, convert to volume - elif entity.domain == "media_player": + elif entity.domain == media_player.DOMAIN: if entity_features & SUPPORT_VOLUME_SET: if brightness is not None: turn_on_needed = True @@ -264,15 +274,21 @@ class HueOneLightChangeView(HomeAssistantView): data[ATTR_MEDIA_VOLUME_LEVEL] = brightness / 100.0 # If the requested entity is a cover, convert to open_cover/close_cover - elif entity.domain == "cover": + elif entity.domain == cover.DOMAIN: domain = entity.domain if service == SERVICE_TURN_ON: service = SERVICE_OPEN_COVER else: service = SERVICE_CLOSE_COVER + if entity_features & SUPPORT_SET_POSITION: + if brightness is not None: + domain = entity.domain + service = SERVICE_SET_COVER_POSITION + data[ATTR_POSITION] = brightness + # If the requested entity is a fan, convert to speed - elif entity.domain == "fan": + elif entity.domain == fan.DOMAIN: if entity_features & SUPPORT_SET_SPEED: if brightness is not None: domain = entity.domain @@ -344,19 +360,19 @@ def parse_hue_api_put_light_body(request_json, entity): # Make sure the entity actually supports brightness entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if entity.domain == "light": + if entity.domain == light.DOMAIN: if entity_features & SUPPORT_BRIGHTNESS: report_brightness = True result = (brightness > 0) - elif entity.domain == "scene": + elif entity.domain == scene.DOMAIN: brightness = None report_brightness = False result = True - elif (entity.domain == "script" or - entity.domain == "media_player" or - entity.domain == "fan"): + elif entity.domain in [ + script.DOMAIN, media_player.DOMAIN, + fan.DOMAIN, cover.DOMAIN]: # Convert 0-255 to 0-100 level = brightness / 255 * 100 brightness = round(level) @@ -378,16 +394,16 @@ def get_entity_state(config, entity): # Make sure the entity actually supports brightness entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if entity.domain == "light": + if entity.domain == light.DOMAIN: if entity_features & SUPPORT_BRIGHTNESS: pass - elif entity.domain == "media_player": + elif entity.domain == media_player.DOMAIN: level = entity.attributes.get( ATTR_MEDIA_VOLUME_LEVEL, 1.0 if final_state else 0.0) # Convert 0.0-1.0 to 0-255 final_brightness = round(min(1.0, level) * 255) - elif entity.domain == "fan": + elif entity.domain == fan.DOMAIN: speed = entity.attributes.get(ATTR_SPEED, 0) # Convert 0.0-1.0 to 0-255 final_brightness = 0 @@ -397,6 +413,9 @@ def get_entity_state(config, entity): final_brightness = 170 elif speed == SPEED_HIGH: final_brightness = 255 + elif entity.domain == cover.DOMAIN: + level = entity.attributes.get(ATTR_CURRENT_POSITION, 0) + final_brightness = round(level / 100 * 255) else: final_state, final_brightness = cached_state # Make sure brightness is valid diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 70fe894debf..5e3d6d1019c 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -11,13 +11,17 @@ from tests.common import get_test_instance_port from homeassistant import core, const, setup import homeassistant.components as core_components from homeassistant.components import ( - fan, http, light, script, emulated_hue, media_player) + fan, http, light, script, emulated_hue, media_player, cover) from homeassistant.components.emulated_hue import Config from homeassistant.components.emulated_hue.hue_api import ( HUE_API_STATE_ON, HUE_API_STATE_BRI, HueUsernameView, HueOneLightStateView, HueAllLightsStateView, HueOneLightChangeView, HueAllGroupsStateView) from homeassistant.const import STATE_ON, STATE_OFF +import homeassistant.util.dt as dt_util +from datetime import timedelta +from tests.common import async_fire_time_changed + HTTP_SERVER_PORT = get_test_instance_port() BRIDGE_SERVER_PORT = get_test_instance_port() @@ -91,6 +95,15 @@ def hass_hue(loop, hass): ] })) + loop.run_until_complete( + setup.async_setup_component(hass, cover.DOMAIN, { + 'cover': [ + { + 'platform': 'demo', + } + ] + })) + # Kitchen light is explicitly excluded from being exposed kitchen_light_entity = hass.states.get('light.kitchen_lights') attrs = dict(kitchen_light_entity.attributes) @@ -115,6 +128,14 @@ def hass_hue(loop, hass): script_entity.entity_id, script_entity.state, attributes=attrs ) + # Expose cover + cover_entity = hass.states.get('cover.living_room_window') + attrs = dict(cover_entity.attributes) + attrs[emulated_hue.ATTR_EMULATED_HUE_HIDDEN] = False + hass.states.async_set( + cover_entity.entity_id, cover_entity.state, attributes=attrs + ) + return hass @@ -127,7 +148,11 @@ def hue_client(loop, hass_hue, aiohttp_client): emulated_hue.CONF_ENTITIES: { 'light.bed_light': { emulated_hue.CONF_ENTITY_HIDDEN: True + }, + 'cover.living_room_window': { + emulated_hue.CONF_ENTITY_HIDDEN: False } + } }) @@ -163,6 +188,7 @@ def test_discover_lights(hue_client): assert 'media_player.lounge_room' in devices assert 'fan.living_room_fan' in devices assert 'fan.ceiling_fan' not in devices + assert 'cover.living_room_window' in devices @asyncio.coroutine @@ -317,6 +343,98 @@ def test_put_light_state_media_player(hass_hue, hue_client): assert walkman.attributes[media_player.ATTR_MEDIA_VOLUME_LEVEL] == level +async def test_close_cover(hass_hue, hue_client): + """Test opening cover .""" + COVER_ID = "cover.living_room_window" + # Turn the office light off first + await hass_hue.services.async_call( + cover.DOMAIN, const.SERVICE_CLOSE_COVER, + {const.ATTR_ENTITY_ID: COVER_ID}, + blocking=True) + + cover_test = hass_hue.states.get(COVER_ID) + assert cover_test.state == 'closing' + + for _ in range(7): + future = dt_util.utcnow() + timedelta(seconds=1) + async_fire_time_changed(hass_hue, future) + await hass_hue.async_block_till_done() + + cover_test = hass_hue.states.get(COVER_ID) + assert cover_test.state == 'closed' + + # Go through the API to turn it on + cover_result = await perform_put_light_state( + hass_hue, hue_client, + COVER_ID, True, 100) + + assert cover_result.status == 200 + assert 'application/json' in cover_result.headers['content-type'] + + for _ in range(7): + future = dt_util.utcnow() + timedelta(seconds=1) + async_fire_time_changed(hass_hue, future) + await hass_hue.async_block_till_done() + + cover_result_json = await cover_result.json() + + assert len(cover_result_json) == 2 + + # Check to make sure the state changed + cover_test_2 = hass_hue.states.get(COVER_ID) + assert cover_test_2.state == 'open' + + +async def test_set_position_cover(hass_hue, hue_client): + """Test setting postion cover .""" + COVER_ID = "cover.living_room_window" + # Turn the office light off first + await hass_hue.services.async_call( + cover.DOMAIN, const.SERVICE_CLOSE_COVER, + {const.ATTR_ENTITY_ID: COVER_ID}, + blocking=True) + + cover_test = hass_hue.states.get(COVER_ID) + assert cover_test.state == 'closing' + + for _ in range(7): + future = dt_util.utcnow() + timedelta(seconds=1) + async_fire_time_changed(hass_hue, future) + await hass_hue.async_block_till_done() + + cover_test = hass_hue.states.get(COVER_ID) + assert cover_test.state == 'closed' + + level = 20 + brightness = round(level/100*255) + + # Go through the API to open + cover_result = await perform_put_light_state( + hass_hue, hue_client, + COVER_ID, False, brightness) + + assert cover_result.status == 200 + assert 'application/json' in cover_result.headers['content-type'] + + cover_result_json = await cover_result.json() + + assert len(cover_result_json) == 2 + assert True, cover_result_json[0]['success'][ + '/lights/cover.living_room_window/state/on'] + assert cover_result_json[1]['success'][ + '/lights/cover.living_room_window/state/bri'] == level + + for _ in range(100): + future = dt_util.utcnow() + timedelta(seconds=1) + async_fire_time_changed(hass_hue, future) + await hass_hue.async_block_till_done() + + # Check to make sure the state changed + cover_test_2 = hass_hue.states.get(COVER_ID) + assert cover_test_2.state == 'open' + assert cover_test_2.attributes.get('current_position') == level + + @asyncio.coroutine def test_put_light_state_fan(hass_hue, hue_client): """Test turning on fan and setting speed.""" From 861d58f58fcea8d20f284f0f0908bc3e9a7181dd Mon Sep 17 00:00:00 2001 From: Ben Van Mechelen Date: Mon, 11 Feb 2019 20:00:37 +0100 Subject: [PATCH 146/242] Support for Multiple modbus hubs (#19726) * modbus: support multiple modbus hub * update data after entities added * pass hub object to each entity. and save hub to hass.data but not in module level * add hub_client setup log * don't update when adding device, because hub_client is not ready right now * support restore last state * remove useless func * compatible with python35 * removed unrelated style changes * Update flexit for multi-device modbus * change how hubs are referenced in the configuration * Also update climate/modbus.py * Remove unwanted whitescapce * Defined common constants centrally * Update DOMAIN in climate and switch components * Removed unnecessary vol.schema * Make hub name optional * Add name property to ModbusHub --- homeassistant/components/climate/flexit.py | 12 +- homeassistant/components/modbus/__init__.py | 124 +++++++++++------- .../components/modbus/binary_sensor.py | 17 ++- homeassistant/components/modbus/climate.py | 18 ++- homeassistant/components/modbus/sensor.py | 33 +++-- homeassistant/components/modbus/switch.py | 53 +++++--- 6 files changed, 163 insertions(+), 94 deletions(-) diff --git a/homeassistant/components/climate/flexit.py b/homeassistant/components/climate/flexit.py index de74d2facb5..e0453b8bf90 100644 --- a/homeassistant/components/climate/flexit.py +++ b/homeassistant/components/climate/flexit.py @@ -20,13 +20,15 @@ from homeassistant.const import ( from homeassistant.components.climate import ( ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE) -from homeassistant.components import modbus +from homeassistant.components.modbus import ( + CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN) import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['pyflexit==0.3'] DEPENDENCIES = ['modbus'] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string, vol.Required(CONF_SLAVE): vol.All(int, vol.Range(min=0, max=32)), vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): cv.string }) @@ -40,15 +42,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Flexit Platform.""" modbus_slave = config.get(CONF_SLAVE, None) name = config.get(CONF_NAME, None) - add_entities([Flexit(modbus_slave, name)], True) + hub = hass.data[MODBUS_DOMAIN][config.get(CONF_HUB)] + add_entities([Flexit(hub, modbus_slave, name)], True) class Flexit(ClimateDevice): """Representation of a Flexit AC unit.""" - def __init__(self, modbus_slave, name): + def __init__(self, hub, modbus_slave, name): """Initialize the unit.""" from pyflexit import pyflexit + self._hub = hub self._name = name self._slave = modbus_slave self._target_temperature = None @@ -64,7 +68,7 @@ class Flexit(ClimateDevice): self._heating = None self._cooling = None self._alarm = False - self.unit = pyflexit.pyflexit(modbus.HUB, modbus_slave) + self.unit = pyflexit.pyflexit(hub, modbus_slave) @property def supported_features(self): diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index 40ede019c10..77a62103f80 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -12,19 +12,27 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, - CONF_HOST, CONF_METHOD, CONF_PORT, CONF_TYPE, CONF_TIMEOUT, ATTR_STATE) + CONF_HOST, CONF_METHOD, CONF_NAME, CONF_PORT, CONF_TYPE, CONF_TIMEOUT, + ATTR_STATE) DOMAIN = 'modbus' REQUIREMENTS = ['pymodbus==1.5.2'] +CONF_HUB = 'hub' # Type of network CONF_BAUDRATE = 'baudrate' CONF_BYTESIZE = 'bytesize' CONF_STOPBITS = 'stopbits' CONF_PARITY = 'parity' -SERIAL_SCHEMA = { +DEFAULT_HUB = 'default' + +BASE_SCHEMA = vol.Schema({ + vol.Optional(CONF_NAME, default=DEFAULT_HUB): cv.string +}) + +SERIAL_SCHEMA = BASE_SCHEMA.extend({ vol.Required(CONF_BAUDRATE): cv.positive_int, vol.Required(CONF_BYTESIZE): vol.Any(5, 6, 7, 8), vol.Required(CONF_METHOD): vol.Any('rtu', 'ascii'), @@ -33,20 +41,18 @@ SERIAL_SCHEMA = { vol.Required(CONF_STOPBITS): vol.Any(1, 2), vol.Required(CONF_TYPE): 'serial', vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout, -} +}) -ETHERNET_SCHEMA = { +ETHERNET_SCHEMA = BASE_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PORT): cv.port, vol.Required(CONF_TYPE): vol.Any('tcp', 'udp', 'rtuovertcp'), vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout, -} - +}) CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA) -}, extra=vol.ALLOW_EXTRA) - + DOMAIN: vol.All(cv.ensure_list, [vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA)]) +}, extra=vol.ALLOW_EXTRA,) _LOGGER = logging.getLogger(__name__) @@ -54,71 +60,79 @@ SERVICE_WRITE_REGISTER = 'write_register' SERVICE_WRITE_COIL = 'write_coil' ATTR_ADDRESS = 'address' +ATTR_HUB = 'hub' ATTR_UNIT = 'unit' ATTR_VALUE = 'value' SERVICE_WRITE_REGISTER_SCHEMA = vol.Schema({ + vol.Optional(ATTR_HUB, default=DEFAULT_HUB): cv.string, vol.Required(ATTR_UNIT): cv.positive_int, vol.Required(ATTR_ADDRESS): cv.positive_int, vol.Required(ATTR_VALUE): vol.All(cv.ensure_list, [cv.positive_int]) }) SERVICE_WRITE_COIL_SCHEMA = vol.Schema({ + vol.Optional(ATTR_HUB, default=DEFAULT_HUB): cv.string, vol.Required(ATTR_UNIT): cv.positive_int, vol.Required(ATTR_ADDRESS): cv.positive_int, vol.Required(ATTR_STATE): cv.boolean }) -HUB = None + +def setup_client(client_config): + """Set up pymodbus client.""" + client_type = client_config[CONF_TYPE] + + if client_type == 'serial': + from pymodbus.client.sync import ModbusSerialClient as ModbusClient + return ModbusClient(method=client_config[CONF_METHOD], + port=client_config[CONF_PORT], + baudrate=client_config[CONF_BAUDRATE], + stopbits=client_config[CONF_STOPBITS], + bytesize=client_config[CONF_BYTESIZE], + parity=client_config[CONF_PARITY], + timeout=client_config[CONF_TIMEOUT]) + if client_type == 'rtuovertcp': + from pymodbus.client.sync import ModbusTcpClient as ModbusClient + from pymodbus.transaction import ModbusRtuFramer + return ModbusClient(host=client_config[CONF_HOST], + port=client_config[CONF_PORT], + framer=ModbusRtuFramer, + timeout=client_config[CONF_TIMEOUT]) + if client_type == 'tcp': + from pymodbus.client.sync import ModbusTcpClient as ModbusClient + return ModbusClient(host=client_config[CONF_HOST], + port=client_config[CONF_PORT], + timeout=client_config[CONF_TIMEOUT]) + if client_type == 'udp': + from pymodbus.client.sync import ModbusUdpClient as ModbusClient + return ModbusClient(host=client_config[CONF_HOST], + port=client_config[CONF_PORT], + timeout=client_config[CONF_TIMEOUT]) + assert False def setup(hass, config): """Set up Modbus component.""" # Modbus connection type - client_type = config[DOMAIN][CONF_TYPE] + hass.data[DOMAIN] = hub_collect = {} - # Connect to Modbus network - # pylint: disable=import-error - - if client_type == 'serial': - from pymodbus.client.sync import ModbusSerialClient as ModbusClient - client = ModbusClient(method=config[DOMAIN][CONF_METHOD], - port=config[DOMAIN][CONF_PORT], - baudrate=config[DOMAIN][CONF_BAUDRATE], - stopbits=config[DOMAIN][CONF_STOPBITS], - bytesize=config[DOMAIN][CONF_BYTESIZE], - parity=config[DOMAIN][CONF_PARITY], - timeout=config[DOMAIN][CONF_TIMEOUT]) - elif client_type == 'rtuovertcp': - from pymodbus.client.sync import ModbusTcpClient as ModbusClient - from pymodbus.transaction import ModbusRtuFramer as ModbusFramer - client = ModbusClient(host=config[DOMAIN][CONF_HOST], - port=config[DOMAIN][CONF_PORT], - framer=ModbusFramer, - timeout=config[DOMAIN][CONF_TIMEOUT]) - elif client_type == 'tcp': - from pymodbus.client.sync import ModbusTcpClient as ModbusClient - client = ModbusClient(host=config[DOMAIN][CONF_HOST], - port=config[DOMAIN][CONF_PORT], - timeout=config[DOMAIN][CONF_TIMEOUT]) - elif client_type == 'udp': - from pymodbus.client.sync import ModbusUdpClient as ModbusClient - client = ModbusClient(host=config[DOMAIN][CONF_HOST], - port=config[DOMAIN][CONF_PORT], - timeout=config[DOMAIN][CONF_TIMEOUT]) - else: - return False - - global HUB - HUB = ModbusHub(client) + for client_config in config[DOMAIN]: + client = setup_client(client_config) + name = client_config[CONF_NAME] + hub_collect[name] = ModbusHub(client, name) + _LOGGER.debug('Setting up hub: %s', client_config) def stop_modbus(event): """Stop Modbus service.""" - HUB.close() + for client in hub_collect.values(): + client.close() def start_modbus(event): """Start Modbus service.""" - HUB.connect() + for client in hub_collect.values(): + client.connect() + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus) # Register services for modbus @@ -134,13 +148,14 @@ def setup(hass, config): unit = int(float(service.data.get(ATTR_UNIT))) address = int(float(service.data.get(ATTR_ADDRESS))) value = service.data.get(ATTR_VALUE) + client_name = service.data.get(ATTR_HUB) if isinstance(value, list): - HUB.write_registers( + hub_collect[client_name].write_registers( unit, address, [int(float(i)) for i in value]) else: - HUB.write_register( + hub_collect[client_name].write_register( unit, address, int(float(value))) @@ -150,7 +165,8 @@ def setup(hass, config): unit = service.data.get(ATTR_UNIT) address = service.data.get(ATTR_ADDRESS) state = service.data.get(ATTR_STATE) - HUB.write_coil(unit, address, state) + client_name = service.data.get(ATTR_HUB) + hub_collect[client_name].write_coil(unit, address, state) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_modbus) @@ -160,10 +176,16 @@ def setup(hass, config): class ModbusHub: """Thread safe wrapper class for pymodbus.""" - def __init__(self, modbus_client): + def __init__(self, modbus_client, name): """Initialize the modbus hub.""" self._client = modbus_client self._lock = threading.Lock() + self._name = name + + @property + def name(self): + """Return the name of this hub.""" + return self._name def close(self): """Disconnect client.""" diff --git a/homeassistant/components/modbus/binary_sensor.py b/homeassistant/components/modbus/binary_sensor.py index f9f2597593e..7089439a7e1 100644 --- a/homeassistant/components/modbus/binary_sensor.py +++ b/homeassistant/components/modbus/binary_sensor.py @@ -7,7 +7,8 @@ https://home-assistant.io/components/binary_sensor.modbus/ import logging import voluptuous as vol -from homeassistant.components import modbus +from homeassistant.components.modbus import ( + CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN) from homeassistant.const import CONF_NAME, CONF_SLAVE from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.helpers import config_validation as cv @@ -21,6 +22,7 @@ CONF_COILS = 'coils' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_COILS): [{ + vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string, vol.Required(CONF_COIL): cv.positive_int, vol.Required(CONF_NAME): cv.string, vol.Optional(CONF_SLAVE): cv.positive_int @@ -32,7 +34,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Modbus binary sensors.""" sensors = [] for coil in config.get(CONF_COILS): + hub = hass.data[MODBUS_DOMAIN][coil.get(CONF_HUB)] sensors.append(ModbusCoilSensor( + hub, coil.get(CONF_NAME), coil.get(CONF_SLAVE), coil.get(CONF_COIL))) @@ -42,8 +46,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class ModbusCoilSensor(BinarySensorDevice): """Modbus coil sensor.""" - def __init__(self, name, slave, coil): + def __init__(self, hub, name, slave, coil): """Initialize the modbus coil sensor.""" + self._hub = hub self._name = name self._slave = int(slave) if slave else None self._coil = int(coil) @@ -61,11 +66,9 @@ class ModbusCoilSensor(BinarySensorDevice): def update(self): """Update the state of the sensor.""" - result = modbus.HUB.read_coils(self._slave, self._coil, 1) + result = self._hub.read_coils(self._slave, self._coil, 1) try: self._value = result.bits[0] except AttributeError: - _LOGGER.error( - 'No response from modbus slave %s coil %s', - self._slave, - self._coil) + _LOGGER.error('No response from hub %s, slave %s, coil %s', + self._hub.name, self._slave, self._coil) diff --git a/homeassistant/components/modbus/climate.py b/homeassistant/components/modbus/climate.py index 1c5c03e4502..23051898679 100644 --- a/homeassistant/components/modbus/climate.py +++ b/homeassistant/components/modbus/climate.py @@ -17,8 +17,8 @@ from homeassistant.const import ( CONF_NAME, CONF_SLAVE, ATTR_TEMPERATURE) from homeassistant.components.climate import ( ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE) - -from homeassistant.components import modbus +from homeassistant.components.modbus import ( + CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN) import homeassistant.helpers.config_validation as cv DEPENDENCIES = ['modbus'] @@ -35,6 +35,7 @@ DATA_TYPE_UINT = 'uint' DATA_TYPE_FLOAT = 'float' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string, vol.Required(CONF_NAME): cv.string, vol.Required(CONF_SLAVE): cv.positive_int, vol.Required(CONF_TARGET_TEMP): cv.positive_int, @@ -59,8 +60,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): data_type = config.get(CONF_DATA_TYPE) count = config.get(CONF_COUNT) precision = config.get(CONF_PRECISION) + hub_name = config.get(CONF_HUB) + hub = hass.data[MODBUS_DOMAIN][hub_name] - add_entities([ModbusThermostat(name, modbus_slave, + add_entities([ModbusThermostat(hub, name, modbus_slave, target_temp_register, current_temp_register, data_type, count, precision)], True) @@ -68,9 +71,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class ModbusThermostat(ClimateDevice): """Representation of a Modbus Thermostat.""" - def __init__(self, name, modbus_slave, target_temp_register, + def __init__(self, hub, name, modbus_slave, target_temp_register, current_temp_register, data_type, count, precision): """Initialize the unit.""" + self._hub = hub self._name = name self._slave = modbus_slave self._target_temperature_register = target_temp_register @@ -133,8 +137,8 @@ class ModbusThermostat(ClimateDevice): def read_register(self, register): """Read holding register using the modbus hub slave.""" try: - result = modbus.HUB.read_holding_registers(self._slave, register, - self._count) + result = self._hub.read_holding_registers(self._slave, register, + self._count) except AttributeError as ex: _LOGGER.error(ex) byte_string = b''.join( @@ -145,4 +149,4 @@ class ModbusThermostat(ClimateDevice): def write_register(self, register, value): """Write register using the modbus hub slave.""" - modbus.HUB.write_registers(self._slave, register, [value, 0]) + self._hub.write_registers(self._slave, register, [value, 0]) diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py index 833cb0c5a62..b263bad5318 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -9,11 +9,12 @@ import struct import voluptuous as vol -from homeassistant.components import modbus +from homeassistant.components.modbus import ( + CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN) from homeassistant.const import ( CONF_NAME, CONF_OFFSET, CONF_UNIT_OF_MEASUREMENT, CONF_SLAVE, CONF_STRUCTURE) -from homeassistant.helpers.entity import Entity +from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers import config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA @@ -40,6 +41,7 @@ DATA_TYPE_CUSTOM = 'custom' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_REGISTERS): [{ + vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string, vol.Required(CONF_NAME): cv.string, vol.Required(CONF_REGISTER): cv.positive_int, vol.Optional(CONF_REGISTER_TYPE, default=REGISTER_TYPE_HOLDING): @@ -70,8 +72,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): structure = '>i' if register.get(CONF_DATA_TYPE) != DATA_TYPE_CUSTOM: try: - structure = '>{}'.format(data_types[ - register.get(CONF_DATA_TYPE)][register.get(CONF_COUNT)]) + structure = '>{}'.format(data_types[register.get( + CONF_DATA_TYPE)][register.get(CONF_COUNT)]) except KeyError: _LOGGER.error("Unable to detect data type for %s sensor, " "try a custom type.", register.get(CONF_NAME)) @@ -93,7 +95,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): "(%d words)", size, register.get(CONF_COUNT)) continue + hub_name = register.get(CONF_HUB) + hub = hass.data[MODBUS_DOMAIN][hub_name] sensors.append(ModbusRegisterSensor( + hub, register.get(CONF_NAME), register.get(CONF_SLAVE), register.get(CONF_REGISTER), @@ -111,13 +116,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class ModbusRegisterSensor(Entity): +class ModbusRegisterSensor(RestoreEntity): """Modbus register sensor.""" - def __init__(self, name, slave, register, register_type, + def __init__(self, hub, name, slave, register, register_type, unit_of_measurement, count, reverse_order, scale, offset, structure, precision): """Initialize the modbus register sensor.""" + self._hub = hub self._name = name self._slave = int(slave) if slave else None self._register = int(register) @@ -131,6 +137,13 @@ class ModbusRegisterSensor(Entity): self._structure = structure self._value = None + async def async_added_to_hass(self): + """Handle entity which will be added.""" + state = await self.async_get_last_state() + if not state: + return + self._value = state.state + @property def state(self): """Return the state of the sensor.""" @@ -149,12 +162,12 @@ class ModbusRegisterSensor(Entity): def update(self): """Update the state of the sensor.""" if self._register_type == REGISTER_TYPE_INPUT: - result = modbus.HUB.read_input_registers( + result = self._hub.read_input_registers( self._slave, self._register, self._count) else: - result = modbus.HUB.read_holding_registers( + result = self._hub.read_holding_registers( self._slave, self._register, self._count) @@ -165,8 +178,8 @@ class ModbusRegisterSensor(Entity): if self._reverse_order: registers.reverse() except AttributeError: - _LOGGER.error("No response from modbus slave %s, register %s", - self._slave, self._register) + _LOGGER.error("No response from hub %s, slave %s, register %s", + self._hub.name, self._slave, self._register) return byte_string = b''.join( [x.to_bytes(2, byteorder='big') for x in registers] diff --git a/homeassistant/components/modbus/switch.py b/homeassistant/components/modbus/switch.py index a8c8358f0cf..04c73d7d372 100644 --- a/homeassistant/components/modbus/switch.py +++ b/homeassistant/components/modbus/switch.py @@ -7,10 +7,12 @@ https://home-assistant.io/components/switch.modbus/ import logging import voluptuous as vol -from homeassistant.components import modbus +from homeassistant.components.modbus import ( + CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN) from homeassistant.const import ( - CONF_NAME, CONF_SLAVE, CONF_COMMAND_ON, CONF_COMMAND_OFF) + CONF_NAME, CONF_SLAVE, CONF_COMMAND_ON, CONF_COMMAND_OFF, STATE_ON) from homeassistant.helpers.entity import ToggleEntity +from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers import config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA @@ -31,6 +33,7 @@ REGISTER_TYPE_HOLDING = 'holding' REGISTER_TYPE_INPUT = 'input' REGISTERS_SCHEMA = vol.Schema({ + vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string, vol.Required(CONF_NAME): cv.string, vol.Optional(CONF_SLAVE): cv.positive_int, vol.Required(CONF_REGISTER): cv.positive_int, @@ -46,6 +49,7 @@ REGISTERS_SCHEMA = vol.Schema({ }) COILS_SCHEMA = vol.Schema({ + vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string, vol.Required(CONF_COIL): cv.positive_int, vol.Required(CONF_NAME): cv.string, vol.Required(CONF_SLAVE): cv.positive_int, @@ -64,13 +68,20 @@ def setup_platform(hass, config, add_entities, discovery_info=None): switches = [] if CONF_COILS in config: for coil in config.get(CONF_COILS): + hub_name = coil.get(CONF_HUB) + hub = hass.data[MODBUS_DOMAIN][hub_name] switches.append(ModbusCoilSwitch( + hub, coil.get(CONF_NAME), coil.get(CONF_SLAVE), coil.get(CONF_COIL))) if CONF_REGISTERS in config: for register in config.get(CONF_REGISTERS): + hub_name = register.get(CONF_HUB) + hub = hass.data[MODBUS_DOMAIN][hub_name] + switches.append(ModbusRegisterSwitch( + hub, register.get(CONF_NAME), register.get(CONF_SLAVE), register.get(CONF_REGISTER), @@ -84,16 +95,24 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(switches) -class ModbusCoilSwitch(ToggleEntity): +class ModbusCoilSwitch(ToggleEntity, RestoreEntity): """Representation of a Modbus coil switch.""" - def __init__(self, name, slave, coil): + def __init__(self, hub, name, slave, coil): """Initialize the coil switch.""" + self._hub = hub self._name = name self._slave = int(slave) if slave else None self._coil = int(coil) self._is_on = None + async def async_added_to_hass(self): + """Handle entity which will be added.""" + state = await self.async_get_last_state() + if not state: + return + self._is_on = state.state == STATE_ON + @property def is_on(self): """Return true if switch is on.""" @@ -106,20 +125,21 @@ class ModbusCoilSwitch(ToggleEntity): def turn_on(self, **kwargs): """Set switch on.""" - modbus.HUB.write_coil(self._slave, self._coil, True) + self._hub.write_coil(self._slave, self._coil, True) def turn_off(self, **kwargs): """Set switch off.""" - modbus.HUB.write_coil(self._slave, self._coil, False) + self._hub.write_coil(self._slave, self._coil, False) def update(self): """Update the state of the switch.""" - result = modbus.HUB.read_coils(self._slave, self._coil, 1) + result = self._hub.read_coils(self._slave, self._coil, 1) try: self._is_on = bool(result.bits[0]) except AttributeError: _LOGGER.error( - 'No response from modbus slave %s coil %s', + 'No response from hub %s, slave %s, coil %s', + self._hub.name, self._slave, self._coil) @@ -128,10 +148,11 @@ class ModbusRegisterSwitch(ModbusCoilSwitch): """Representation of a Modbus register switch.""" # pylint: disable=super-init-not-called - def __init__(self, name, slave, register, command_on, + def __init__(self, hub, name, slave, register, command_on, command_off, verify_state, verify_register, register_type, state_on, state_off): """Initialize the register switch.""" + self._hub = hub self._name = name self._slave = slave self._register = register @@ -156,7 +177,7 @@ class ModbusRegisterSwitch(ModbusCoilSwitch): def turn_on(self, **kwargs): """Set switch on.""" - modbus.HUB.write_register( + self._hub.write_register( self._slave, self._register, self._command_on) @@ -165,7 +186,7 @@ class ModbusRegisterSwitch(ModbusCoilSwitch): def turn_off(self, **kwargs): """Set switch off.""" - modbus.HUB.write_register( + self._hub.write_register( self._slave, self._register, self._command_off) @@ -179,12 +200,12 @@ class ModbusRegisterSwitch(ModbusCoilSwitch): value = 0 if self._register_type == REGISTER_TYPE_INPUT: - result = modbus.HUB.read_input_registers( + result = self._hub.read_input_registers( self._slave, self._register, 1) else: - result = modbus.HUB.read_holding_registers( + result = self._hub.read_holding_registers( self._slave, self._register, 1) @@ -193,7 +214,8 @@ class ModbusRegisterSwitch(ModbusCoilSwitch): value = int(result.registers[0]) except AttributeError: _LOGGER.error( - 'No response from modbus slave %s register %s', + 'No response from hub %s, slave %s, register %s', + self._hub.name, self._slave, self._verify_register) @@ -203,8 +225,9 @@ class ModbusRegisterSwitch(ModbusCoilSwitch): self._is_on = False else: _LOGGER.error( - 'Unexpected response from modbus slave %s ' + 'Unexpected response from hub %s, slave %s ' 'register %s, got 0x%2x', + self._hub.name, self._slave, self._verify_register, value) From 868820c4240e70971da94df70f51fd031e59d74e Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Mon, 11 Feb 2019 14:38:04 -0500 Subject: [PATCH 147/242] add device info API (#20950) --- homeassistant/components/zha/api.py | 31 ++++++++++++++++++++++++++-- tests/components/zha/test_api.py | 32 +++++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index c412cb9fef0..75a099562a6 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -14,14 +14,13 @@ import homeassistant.helpers.config_validation as cv from .core.const import ( DOMAIN, ATTR_CLUSTER_ID, ATTR_CLUSTER_TYPE, ATTR_ATTRIBUTE, ATTR_VALUE, ATTR_MANUFACTURER, ATTR_COMMAND, ATTR_COMMAND_TYPE, ATTR_ARGS, IN, OUT, - CLIENT_COMMANDS, SERVER_COMMANDS, SERVER) + CLIENT_COMMANDS, SERVER_COMMANDS, SERVER, NAME) _LOGGER = logging.getLogger(__name__) TYPE = 'type' CLIENT = 'client' ID = 'id' -NAME = 'name' RESPONSE = 'response' DEVICE_INFO = 'device_info' @@ -74,6 +73,11 @@ SCHEMA_WS_RECONFIGURE_NODE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required(ATTR_IEEE): str }) +WS_DEVICES = 'zha/devices' +SCHEMA_WS_LIST_DEVICES = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required(TYPE): WS_DEVICES, +}) + WS_ENTITIES_BY_IEEE = 'zha/entities' SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required(TYPE): WS_ENTITIES_BY_IEEE, @@ -215,6 +219,29 @@ def async_load_api(hass, application_controller, zha_gateway): SERVICE_ISSUE_ZIGBEE_CLUSTER_COMMAND ]) + @websocket_api.async_response + async def websocket_get_devices(hass, connection, msg): + """Get ZHA devices.""" + devices = [ + { + **device.device_info, + 'entities': [{ + 'entity_id': entity_ref.reference_id, + NAME: entity_ref.device_info[NAME] + } for entity_ref in zha_gateway.device_registry[device.ieee]] + } for device in zha_gateway.devices.values() + ] + + connection.send_message(websocket_api.result_message( + msg[ID], + devices + )) + + hass.components.websocket_api.async_register_command( + WS_DEVICES, websocket_get_devices, + SCHEMA_WS_LIST_DEVICES + ) + @websocket_api.async_response async def websocket_reconfigure_node(hass, connection, msg): """Reconfigure a ZHA nodes entities by its ieee address.""" diff --git a/tests/components/zha/test_api.py b/tests/components/zha/test_api.py index 4a3201d7768..87621ea548b 100644 --- a/tests/components/zha/test_api.py +++ b/tests/components/zha/test_api.py @@ -5,10 +5,12 @@ from homeassistant.const import ATTR_ENTITY_ID from homeassistant.components.switch import DOMAIN from homeassistant.components.zha.api import ( async_load_api, WS_ENTITIES_BY_IEEE, WS_ENTITY_CLUSTERS, ATTR_IEEE, TYPE, - ID, NAME, WS_ENTITY_CLUSTER_ATTRIBUTES, WS_ENTITY_CLUSTER_COMMANDS + ID, WS_ENTITY_CLUSTER_ATTRIBUTES, WS_ENTITY_CLUSTER_COMMANDS, + WS_DEVICES ) from homeassistant.components.zha.core.const import ( - ATTR_CLUSTER_ID, ATTR_CLUSTER_TYPE, IN + ATTR_CLUSTER_ID, ATTR_CLUSTER_TYPE, IN, IEEE, MODEL, NAME, QUIRK_APPLIED, + ATTR_MANUFACTURER ) from .common import async_init_zigpy_device @@ -109,3 +111,29 @@ async def test_entity_cluster_commands( assert command[ID] is not None assert command[NAME] is not None assert command[TYPE] is not None + + +async def test_list_devices( + hass, config_entry, zha_gateway, zha_client): + """Test getting entity cluster commands.""" + await zha_client.send_json({ + ID: 5, + TYPE: WS_DEVICES + }) + + msg = await zha_client.receive_json() + + devices = msg['result'] + assert len(devices) == 1 + + for device in devices: + assert device[IEEE] is not None + assert device[ATTR_MANUFACTURER] is not None + assert device[MODEL] is not None + assert device[NAME] is not None + assert device[QUIRK_APPLIED] is not None + assert device['entities'] is not None + + for entity_reference in device['entities']: + assert entity_reference[NAME] is not None + assert entity_reference['entity_id'] is not None From 4cb408f8f93aca371419c06598c08e4ee3c59c55 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 11 Feb 2019 20:46:21 +0100 Subject: [PATCH 148/242] Sort imports (#20984) --- homeassistant/components/netgear_lte/__init__.py | 6 +++--- homeassistant/components/netgear_lte/notify.py | 9 ++++----- homeassistant/components/netgear_lte/sensor.py | 10 +++++----- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/netgear_lte/__init__.py b/homeassistant/components/netgear_lte/__init__.py index 7658015ea67..77b9783b239 100644 --- a/homeassistant/components/netgear_lte/__init__.py +++ b/homeassistant/components/netgear_lte/__init__.py @@ -8,9 +8,9 @@ import asyncio from datetime import timedelta import logging -import voluptuous as vol -import attr import aiohttp +import attr +import voluptuous as vol from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP) @@ -144,7 +144,7 @@ async def _retry_login(hass, modem_data, password): import eternalegypt _LOGGER.warning( - "Could not connect to %s. Will keep trying.", modem_data.host) + "Could not connect to %s. Will keep trying", modem_data.host) modem_data.connected = False delay = 15 diff --git a/homeassistant/components/netgear_lte/notify.py b/homeassistant/components/netgear_lte/notify.py index 9ba804e193d..3f39ccfb43f 100644 --- a/homeassistant/components/netgear_lte/notify.py +++ b/homeassistant/components/netgear_lte/notify.py @@ -1,22 +1,21 @@ -"""Netgear LTE platform for notify component. +""" +The Netgear LTE platform for notify component. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.netgear_lte/ """ - import logging -import voluptuous as vol import attr +import voluptuous as vol from homeassistant.components.notify import ( - BaseNotificationService, ATTR_TARGET, PLATFORM_SCHEMA) + ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import CONF_HOST import homeassistant.helpers.config_validation as cv from ..netgear_lte import DATA_KEY - DEPENDENCIES = ['netgear_lte'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/netgear_lte/sensor.py b/homeassistant/components/netgear_lte/sensor.py index 3c17750d6ad..99907a21b57 100644 --- a/homeassistant/components/netgear_lte/sensor.py +++ b/homeassistant/components/netgear_lte/sensor.py @@ -1,17 +1,17 @@ -"""Netgear LTE sensors. +""" +Support for Netgear LTE sensors. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.netgear_lte/ """ - -import voluptuous as vol import attr +import voluptuous as vol -from homeassistant.const import CONF_HOST, CONF_SENSORS from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import CONF_HOST, CONF_SENSORS from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity from ..netgear_lte import DATA_KEY From c3c92232da042392cb7a49c993ceed499260e0ca Mon Sep 17 00:00:00 2001 From: Brian Towles Date: Mon, 11 Feb 2019 13:48:02 -0600 Subject: [PATCH 149/242] Unique Ids for August entities to allow renames (#20887) * working unique ids for august * cleanup blank lines * cleanup blank lines * cleanup blank lines * rebase * get the oneline back in * blank line stuff * whitespace cleanup * Blank Line . * blank line again --- homeassistant/components/august/binary_sensor.py | 14 ++++++++++++++ homeassistant/components/august/camera.py | 5 +++++ homeassistant/components/august/lock.py | 5 +++++ 3 files changed, 24 insertions(+) diff --git a/homeassistant/components/august/binary_sensor.py b/homeassistant/components/august/binary_sensor.py index 581b7a3e0df..c059f4c020a 100644 --- a/homeassistant/components/august/binary_sensor.py +++ b/homeassistant/components/august/binary_sensor.py @@ -144,6 +144,13 @@ class AugustDoorBinarySensor(BinarySensorDevice): from august.lock import LockDoorStatus self._state = self._state == LockDoorStatus.OPEN + @property + def unique_id(self) -> str: + """Get the unique of the door open binary sensor.""" + return '{:s}_{:s}'.format(self._door.device_id, + SENSOR_TYPES_DOOR[self._sensor_type][0] + .lower()) + class AugustDoorbellBinarySensor(BinarySensorDevice): """Representation of an August binary sensor.""" @@ -182,3 +189,10 @@ class AugustDoorbellBinarySensor(BinarySensorDevice): state_provider = SENSOR_TYPES_DOORBELL[self._sensor_type][2] self._state = state_provider(self._data, self._doorbell) self._available = self._doorbell.is_online + + @property + def unique_id(self) -> str: + """Get the unique id of the doorbell sensor.""" + return '{:s}_{:s}'.format(self._doorbell.device_id, + SENSOR_TYPES_DOORBELL[self._sensor_type][0] + .lower()) diff --git a/homeassistant/components/august/camera.py b/homeassistant/components/august/camera.py index dcce5e13588..c62a32342dc 100644 --- a/homeassistant/components/august/camera.py +++ b/homeassistant/components/august/camera.py @@ -74,3 +74,8 @@ class AugustCamera(Camera): timeout=self._timeout).content return self._image_content + + @property + def unique_id(self) -> str: + """Get the unique id of the camera.""" + return '{:s}_camera'.format(self._doorbell.device_id) diff --git a/homeassistant/components/august/lock.py b/homeassistant/components/august/lock.py index ce6792ceb39..e8364170980 100644 --- a/homeassistant/components/august/lock.py +++ b/homeassistant/components/august/lock.py @@ -95,3 +95,8 @@ class AugustLock(LockDevice): return { ATTR_BATTERY_LEVEL: self._lock_detail.battery_level, } + + @property + def unique_id(self) -> str: + """Get the unique id of the lock.""" + return '{:s}_lock'.format(self._lock.device_id) From 277f37423e5ce5bd163edf7b06a335a45fad2782 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 11 Feb 2019 21:22:12 +0100 Subject: [PATCH 150/242] Sort imports (#20985) --- homeassistant/components/mythicbeastsdns/__init__.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/mythicbeastsdns/__init__.py b/homeassistant/components/mythicbeastsdns/__init__.py index d73e4619c78..6b5ce2e8597 100644 --- a/homeassistant/components/mythicbeastsdns/__init__.py +++ b/homeassistant/components/mythicbeastsdns/__init__.py @@ -6,13 +6,14 @@ https://home-assistant.io/components/mythicbeastsdns/ """ from datetime import timedelta import logging + import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.const import CONF_HOST, CONF_DOMAIN, CONF_PASSWORD, \ - CONF_UPDATE_INTERVAL -from homeassistant.helpers.event import async_track_time_interval +from homeassistant.const import ( + CONF_DOMAIN, CONF_HOST, CONF_PASSWORD, CONF_UPDATE_INTERVAL) from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import async_track_time_interval REQUIREMENTS = ['mbddns==0.1.2'] @@ -24,8 +25,8 @@ DEFAULT_INTERVAL = timedelta(minutes=10) CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ - vol.Required(CONF_HOST): cv.string, vol.Required(CONF_DOMAIN): cv.string, + vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Optional(CONF_UPDATE_INTERVAL, default=DEFAULT_INTERVAL): vol.All( cv.time_period, cv.positive_timedelta), From 55f9db6992c2e08c4d1c85a66b91f66ea9202005 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 11 Feb 2019 21:57:17 +0100 Subject: [PATCH 151/242] Bump aioesphomeapi to 1.5.0 (#20986) * Bump aioesphomeapi to 1.5.0 * Update requirements_all.txt * Fix editor line length setting --- homeassistant/components/esphome/__init__.py | 2 +- homeassistant/components/esphome/config_flow.py | 9 ++++----- requirements_all.txt | 2 +- tests/components/esphome/test_config_flow.py | 4 +++- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 1ff2c10c828..8d113b6ab9d 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -31,7 +31,7 @@ if TYPE_CHECKING: ServiceCall DOMAIN = 'esphome' -REQUIREMENTS = ['aioesphomeapi==1.4.2'] +REQUIREMENTS = ['aioesphomeapi==1.5.0'] DISPATCHER_UPDATE_ENTITY = 'esphome_{entry_id}_update_{component_key}_{key}' diff --git a/homeassistant/components/esphome/config_flow.py b/homeassistant/components/esphome/config_flow.py index 1f71d8d66b5..e509455c12e 100644 --- a/homeassistant/components/esphome/config_flow.py +++ b/homeassistant/components/esphome/config_flow.py @@ -55,11 +55,10 @@ class EsphomeFlowHandler(config_entries.ConfigFlow): async def async_step_discovery(self, user_input: ConfigType): """Handle discovery.""" - # mDNS hostname has additional '.' at end - hostname = user_input['hostname'][:-1] - hosts = (hostname, user_input['host']) + address = user_input['properties'].get( + 'address', user_input['hostname'][:-1]) for entry in self._async_current_entries(): - if entry.data['host'] in hosts: + if entry.data['host'] == address: return self.async_abort( reason='already_configured' ) @@ -67,7 +66,7 @@ class EsphomeFlowHandler(config_entries.ConfigFlow): # Prefer .local addresses (mDNS is available after all, otherwise # we wouldn't have received the discovery message) return await self.async_step_user(user_input={ - 'host': hostname, + 'host': address, 'port': user_input['port'], }) diff --git a/requirements_all.txt b/requirements_all.txt index 182268212be..cae373390ba 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -102,7 +102,7 @@ aioautomatic==0.6.5 aiodns==1.1.1 # homeassistant.components.esphome -aioesphomeapi==1.4.2 +aioesphomeapi==1.5.0 # homeassistant.components.freebox aiofreepybox==0.0.6 diff --git a/tests/components/esphome/test_config_flow.py b/tests/components/esphome/test_config_flow.py index 1291aa53123..8c870c6ad73 100644 --- a/tests/components/esphome/test_config_flow.py +++ b/tests/components/esphome/test_config_flow.py @@ -242,7 +242,9 @@ async def test_discovery_already_configured_ip(hass, mock_client): 'host': '192.168.43.183', 'port': 6053, 'hostname': 'test8266.local.', - 'properties': {} + 'properties': { + "address": "192.168.43.183" + } } result = await flow.async_step_discovery(user_input=service_info) assert result['type'] == 'abort' From b5b03f5b7fe7dc7c6c8a73a54e3eef51a31b7ac9 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 11 Feb 2019 15:31:49 -0700 Subject: [PATCH 152/242] Fix bug with monitored_conditions in Ambient PWS (#20837) * Make monitored_conditions more specific in Ambient PWS * Revert messing around with storing monitored_conditions elsewhere * Come on, Aaron * Fix bug with monitored_conditions in Ambient PWS --- .../components/ambient_station/__init__.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index cf5625620a2..bbde0c84972 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -28,6 +28,8 @@ from .const import ( REQUIREMENTS = ['aioambient==0.1.0'] _LOGGER = logging.getLogger(__name__) +DATA_CONFIG = 'config' + DEFAULT_SOCKET_MIN_RETRY = 15 TYPE_24HOURRAININ = '24hourrainin' @@ -234,12 +236,20 @@ async def async_setup(hass, config): conf = config[DOMAIN] + # Store config for use during entry setup: + hass.data[DOMAIN][DATA_CONFIG] = conf + if conf[CONF_APP_KEY] in configured_instances(hass): return True hass.async_create_task( hass.config_entries.flow.async_init( - DOMAIN, context={'source': SOURCE_IMPORT}, data=conf)) + DOMAIN, + context={'source': SOURCE_IMPORT}, + data={ + CONF_API_KEY: conf[CONF_API_KEY], + CONF_APP_KEY: conf[CONF_APP_KEY] + })) return True @@ -257,7 +267,7 @@ async def async_setup_entry(hass, config_entry): Client( config_entry.data[CONF_API_KEY], config_entry.data[CONF_APP_KEY], session), - config_entry.data.get(CONF_MONITORED_CONDITIONS, [])) + hass.data[DOMAIN][DATA_CONFIG].get(CONF_MONITORED_CONDITIONS, [])) hass.loop.create_task(ambient.ws_connect()) hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = ambient except WebsocketConnectionError as err: From 0d98f9783f63149ae78a73b5e609b5ed24f705db Mon Sep 17 00:00:00 2001 From: arigilder <43716164+arigilder@users.noreply.github.com> Date: Mon, 11 Feb 2019 17:34:48 -0500 Subject: [PATCH 153/242] Add lagging hdate for sensors that should lag to update (#20655) * Add lagging hdate for sensors that should lag to update * Fix indentation * Lint fix --- .../components/sensor/jewish_calendar.py | 37 ++++++++++++------- .../components/sensor/test_jewish_calendar.py | 9 +++++ 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/sensor/jewish_calendar.py b/homeassistant/components/sensor/jewish_calendar.py index cc226337f02..65aeaf7fba9 100644 --- a/homeassistant/components/sensor/jewish_calendar.py +++ b/homeassistant/components/sensor/jewish_calendar.py @@ -4,7 +4,6 @@ Platform to retrieve Jewish calendar information for Home Assistant. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.jewish_calendar/ """ -from datetime import timedelta import logging import voluptuous as vol @@ -140,12 +139,6 @@ class JewishCalSensor(Entity): _LOGGER.debug("Now: %s Sunset: %s", now, sunset) - if now > sunset: - today += timedelta(1) - - date = hdate.HDate( - today, diaspora=self.diaspora, hebrew=self._hebrew) - location = hdate.Location(latitude=self.latitude, longitude=self.longitude, timezone=self.timezone, @@ -158,27 +151,43 @@ class JewishCalSensor(Entity): candle_lighting_offset=self.candle_lighting_offset, havdalah_offset=self.havdalah_offset, hebrew=self._hebrew) + date = hdate.HDate( + today, diaspora=self.diaspora, hebrew=self._hebrew) + lagging_date = date + + # Advance Hebrew date if sunset has passed. + # Not all sensors should advance immediately when the Hebrew date + # officially changes (i.e. after sunset), hence lagging_date. + if now > sunset: + date = date.next_day + today_times = make_zmanim(today) + if today_times.havdalah and now > today_times.havdalah: + lagging_date = lagging_date.next_day + + # Terminology note: by convention in py-libhdate library, "upcoming" + # refers to "current" or "upcoming" dates. if self.type == 'date': self._state = date.hebrew_date elif self.type == 'weekly_portion': # Compute the weekly portion based on the upcoming shabbat. - self._state = date.upcoming_shabbat.parasha + self._state = lagging_date.upcoming_shabbat.parasha elif self.type == 'holiday_name': self._state = date.holiday_description elif self.type == 'holyness': self._state = date.holiday_type elif self.type == 'upcoming_shabbat_candle_lighting': - times = make_zmanim(date.upcoming_shabbat.previous_day.gdate) - self._state = times.candle_lighting - elif self.type == 'upcoming_candle_lighting': - times = make_zmanim(date.upcoming_shabbat_or_yom_tov.first_day + times = make_zmanim(lagging_date.upcoming_shabbat .previous_day.gdate) self._state = times.candle_lighting + elif self.type == 'upcoming_candle_lighting': + times = make_zmanim(lagging_date.upcoming_shabbat_or_yom_tov + .first_day.previous_day.gdate) + self._state = times.candle_lighting elif self.type == 'upcoming_shabbat_havdalah': - times = make_zmanim(date.upcoming_shabbat.gdate) + times = make_zmanim(lagging_date.upcoming_shabbat.gdate) self._state = times.havdalah elif self.type == 'upcoming_havdalah': - times = make_zmanim(date.upcoming_shabbat_or_yom_tov + times = make_zmanim(lagging_date.upcoming_shabbat_or_yom_tov .last_day.gdate) self._state = times.havdalah elif self.type == 'issur_melacha_in_effect': diff --git a/tests/components/sensor/test_jewish_calendar.py b/tests/components/sensor/test_jewish_calendar.py index 639364164e0..7243874a41d 100644 --- a/tests/components/sensor/test_jewish_calendar.py +++ b/tests/components/sensor/test_jewish_calendar.py @@ -158,6 +158,14 @@ class TestJewishCalenderSensor(): 'weekly_portion': 'Ki Tavo', 'hebrew_weekly_portion': 'כי תבוא'}, havdalah_offset=50), + make_nyc_test_params( + dt(2018, 9, 1, 20, 0), + {'upcoming_shabbat_candle_lighting': dt(2018, 8, 31, 19, 15), + 'upcoming_shabbat_havdalah': dt(2018, 9, 1, 20, 14), + 'upcoming_candle_lighting': dt(2018, 8, 31, 19, 15), + 'upcoming_havdalah': dt(2018, 9, 1, 20, 14), + 'weekly_portion': 'Ki Tavo', + 'hebrew_weekly_portion': 'כי תבוא'}), make_nyc_test_params( dt(2018, 9, 1, 20, 21), {'upcoming_shabbat_candle_lighting': dt(2018, 9, 7, 19, 4), @@ -317,6 +325,7 @@ class TestJewishCalenderSensor(): shabbat_test_ids = [ "currently_first_shabbat", "currently_first_shabbat_with_havdalah_offset", + "currently_first_shabbat_bein_hashmashot_lagging_date", "after_first_shabbat", "friday_upcoming_shabbat", "upcoming_rosh_hashana", From e8ed56ca52c00c4303000d58595e4f473875bf7c Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Tue, 12 Feb 2019 01:11:36 -0600 Subject: [PATCH 154/242] Add SmartThings Climate platform (#20963) * Add SmartThings Climate platform * Add SmartThings Climate platform --- .../components/smartthings/__init__.py | 2 +- .../components/smartthings/climate.py | 221 +++++++++++++++ homeassistant/components/smartthings/const.py | 1 + requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/smartthings/conftest.py | 3 +- tests/components/smartthings/test_climate.py | 266 ++++++++++++++++++ 7 files changed, 493 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/smartthings/climate.py create mode 100644 tests/components/smartthings/test_climate.py diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index b7b5436da3e..0d86289e11c 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -23,7 +23,7 @@ from .const import ( from .smartapp import ( setup_smartapp, setup_smartapp_endpoint, validate_installed_app) -REQUIREMENTS = ['pysmartapp==0.3.0', 'pysmartthings==0.6.0'] +REQUIREMENTS = ['pysmartapp==0.3.0', 'pysmartthings==0.6.1'] DEPENDENCIES = ['webhook'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py new file mode 100644 index 00000000000..5a79270307c --- /dev/null +++ b/homeassistant/components/smartthings/climate.py @@ -0,0 +1,221 @@ +""" +Support for climate entities/thermostats through the SmartThings cloud API. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/smartthings.climate/ +""" +import asyncio + +from homeassistant.components.climate import ( + ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, + ATTR_TEMPERATURE, STATE_AUTO, STATE_COOL, STATE_ECO, STATE_HEAT, STATE_OFF, + SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW, + ClimateDevice) +from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT + +from . import SmartThingsEntity +from .const import DATA_BROKERS, DOMAIN + +DEPENDENCIES = ['smartthings'] + +ATTR_OPERATION_STATE = 'operation_state' +MODE_TO_STATE = { + 'auto': STATE_AUTO, + 'cool': STATE_COOL, + 'eco': STATE_ECO, + 'rush hour': STATE_ECO, + 'emergency heat': STATE_HEAT, + 'heat': STATE_HEAT, + 'off': STATE_OFF +} +STATE_TO_MODE = { + STATE_AUTO: 'auto', + STATE_COOL: 'cool', + STATE_ECO: 'eco', + STATE_HEAT: 'heat', + STATE_OFF: 'off' +} +UNIT_MAP = { + 'C': TEMP_CELSIUS, + 'F': TEMP_FAHRENHEIT +} + + +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): + """Platform uses config entry setup.""" + pass + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Add climate entities for a config entry.""" + broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] + async_add_entities( + [SmartThingsThermostat(device) for device in broker.devices.values() + if is_climate(device)]) + + +def is_climate(device): + """Determine if the device should be represented as a climate entity.""" + from pysmartthings import Capability + + # Can have this legacy/deprecated capability + if Capability.thermostat in device.capabilities: + return True + # Or must have all of these + climate_capabilities = [ + Capability.temperature_measurement, + Capability.thermostat_cooling_setpoint, + Capability.thermostat_heating_setpoint, + Capability.thermostat_mode] + if all(capability in device.capabilities + for capability in climate_capabilities): + return True + # Optional capabilities: + # relative_humidity_measurement -> state attribs + # thermostat_operating_state -> state attribs + # thermostat_fan_mode -> SUPPORT_FAN_MODE + return False + + +class SmartThingsThermostat(SmartThingsEntity, ClimateDevice): + """Define a SmartThings climate entities.""" + + def __init__(self, device): + """Init the class.""" + super().__init__(device) + self._supported_features = self._determine_features() + + def _determine_features(self): + from pysmartthings import Capability + + flags = SUPPORT_OPERATION_MODE \ + | SUPPORT_TARGET_TEMPERATURE \ + | SUPPORT_TARGET_TEMPERATURE_LOW \ + | SUPPORT_TARGET_TEMPERATURE_HIGH + if self._device.get_capability( + Capability.thermostat_fan_mode, Capability.thermostat): + flags |= SUPPORT_FAN_MODE + return flags + + async def async_set_fan_mode(self, fan_mode): + """Set new target fan mode.""" + await self._device.set_thermostat_fan_mode(fan_mode, set_status=True) + + # State is set optimistically in the command above, therefore update + # the entity state ahead of receiving the confirming push updates + self.async_schedule_update_ha_state(True) + + async def async_set_operation_mode(self, operation_mode): + """Set new target operation mode.""" + mode = STATE_TO_MODE[operation_mode] + await self._device.set_thermostat_mode(mode, set_status=True) + + # State is set optimistically in the command above, therefore update + # the entity state ahead of receiving the confirming push updates + self.async_schedule_update_ha_state(True) + + async def async_set_temperature(self, **kwargs): + """Set new operation mode and target temperatures.""" + # Operation state + operation_state = kwargs.get(ATTR_OPERATION_MODE) + if operation_state: + mode = STATE_TO_MODE[operation_state] + await self._device.set_thermostat_mode(mode, set_status=True) + + # Heat/cool setpoint + heating_setpoint = None + cooling_setpoint = None + if self.current_operation == STATE_HEAT: + heating_setpoint = kwargs.get(ATTR_TEMPERATURE) + elif self.current_operation == STATE_COOL: + cooling_setpoint = kwargs.get(ATTR_TEMPERATURE) + else: + heating_setpoint = kwargs.get(ATTR_TARGET_TEMP_LOW) + cooling_setpoint = kwargs.get(ATTR_TARGET_TEMP_HIGH) + tasks = [] + if heating_setpoint is not None: + tasks.append(self._device.set_heating_setpoint( + round(heating_setpoint, 3), set_status=True)) + if cooling_setpoint is not None: + tasks.append(self._device.set_cooling_setpoint( + round(cooling_setpoint, 3), set_status=True)) + await asyncio.gather(*tasks) + + # State is set optimistically in the commands above, therefore update + # the entity state ahead of receiving the confirming push updates + self.async_schedule_update_ha_state(True) + + @property + def current_fan_mode(self): + """Return the fan setting.""" + return self._device.status.thermostat_fan_mode + + @property + def current_humidity(self): + """Return the current humidity.""" + return self._device.status.humidity + + @property + def current_operation(self): + """Return current operation ie. heat, cool, idle.""" + return MODE_TO_STATE[self._device.status.thermostat_mode] + + @property + def current_temperature(self): + """Return the current temperature.""" + return self._device.status.temperature + + @property + def device_state_attributes(self): + """Return device specific state attributes.""" + return { + ATTR_OPERATION_STATE: + self._device.status.thermostat_operating_state + } + + @property + def fan_list(self): + """Return the list of available fan modes.""" + return self._device.status.supported_thermostat_fan_modes + + @property + def operation_list(self): + """Return the list of available operation modes.""" + return {MODE_TO_STATE[mode] for mode in + self._device.status.supported_thermostat_modes} + + @property + def supported_features(self): + """Return the supported features.""" + return self._supported_features + + @property + def target_temperature(self): + """Return the temperature we try to reach.""" + if self.current_operation == STATE_COOL: + return self._device.status.cooling_setpoint + if self.current_operation == STATE_HEAT: + return self._device.status.heating_setpoint + return None + + @property + def target_temperature_high(self): + """Return the highbound target temperature we try to reach.""" + if self.current_operation == STATE_AUTO: + return self._device.status.cooling_setpoint + return None + + @property + def target_temperature_low(self): + """Return the lowbound target temperature we try to reach.""" + if self.current_operation == STATE_AUTO: + return self._device.status.heating_setpoint + return None + + @property + def temperature_unit(self): + """Return the unit of measurement.""" + return UNIT_MAP.get( + self._device.status.attributes['temperature'].unit) diff --git a/homeassistant/components/smartthings/const.py b/homeassistant/components/smartthings/const.py index 9391c871b25..df14ab68055 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -20,6 +20,7 @@ STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 SUPPORTED_PLATFORMS = [ 'binary_sensor', + 'climate', 'fan', 'light', 'sensor', diff --git a/requirements_all.txt b/requirements_all.txt index cae373390ba..a1a5cf3a2cc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1240,7 +1240,7 @@ pysma==0.3.1 pysmartapp==0.3.0 # homeassistant.components.smartthings -pysmartthings==0.6.0 +pysmartthings==0.6.1 # homeassistant.components.device_tracker.snmp # homeassistant.components.sensor.snmp diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ff63edada63..c70de8da50b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -217,7 +217,7 @@ pyqwikswitch==0.8 pysmartapp==0.3.0 # homeassistant.components.smartthings -pysmartthings==0.6.0 +pysmartthings==0.6.1 # homeassistant.components.sonos pysonos==0.0.6 diff --git a/tests/components/smartthings/conftest.py b/tests/components/smartthings/conftest.py index c1a1769f04c..ee892fb03b9 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -235,7 +235,8 @@ def config_entry_fixture(hass, installed_app, location): def device_factory_fixture(): """Fixture for creating mock devices.""" api = Mock(spec=Api) - api.post_device_command.return_value = mock_coro(return_value={}) + api.post_device_command.side_effect = \ + lambda *args, **kwargs: mock_coro(return_value={}) def _factory(label, capabilities, status: dict = None): device_data = { diff --git a/tests/components/smartthings/test_climate.py b/tests/components/smartthings/test_climate.py new file mode 100644 index 00000000000..0f1102e2ab1 --- /dev/null +++ b/tests/components/smartthings/test_climate.py @@ -0,0 +1,266 @@ +""" +Test for the SmartThings climate platform. + +The only mocking required is of the underlying SmartThings API object so +real HTTP calls are not initiated during testing. +""" +from pysmartthings import Attribute, Capability +from pysmartthings.device import Status +import pytest + +from homeassistant.components.climate import ( + ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, ATTR_FAN_LIST, + ATTR_FAN_MODE, ATTR_OPERATION_LIST, ATTR_OPERATION_MODE, + ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN as CLIMATE_DOMAIN, + SERVICE_SET_FAN_MODE, SERVICE_SET_OPERATION_MODE, SERVICE_SET_TEMPERATURE, + STATE_AUTO, STATE_COOL, STATE_ECO, STATE_HEAT, STATE_OFF, SUPPORT_FAN_MODE, + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW) +from homeassistant.components.smartthings import climate +from homeassistant.components.smartthings.const import DOMAIN +from homeassistant.const import ( + ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE) + +from .conftest import setup_platform + + +@pytest.fixture(name="legacy_thermostat") +def legacy_thermostat_fixture(device_factory): + """Fixture returns a legacy thermostat.""" + device = device_factory( + "Legacy Thermostat", + capabilities=[Capability.thermostat], + status={ + Attribute.cooling_setpoint: 74, + Attribute.heating_setpoint: 68, + Attribute.thermostat_fan_mode: 'auto', + Attribute.supported_thermostat_fan_modes: ['auto', 'on'], + Attribute.thermostat_mode: 'auto', + Attribute.supported_thermostat_modes: climate.MODE_TO_STATE.keys(), + Attribute.thermostat_operating_state: 'idle' + } + ) + device.status.attributes[Attribute.temperature] = Status(70, 'F', None) + return device + + +@pytest.fixture(name="basic_thermostat") +def basic_thermostat_fixture(device_factory): + """Fixture returns a basic thermostat.""" + device = device_factory( + "Basic Thermostat", + capabilities=[ + Capability.temperature_measurement, + Capability.thermostat_cooling_setpoint, + Capability.thermostat_heating_setpoint, + Capability.thermostat_mode], + status={ + Attribute.cooling_setpoint: 74, + Attribute.heating_setpoint: 68, + Attribute.thermostat_mode: 'off', + Attribute.supported_thermostat_modes: + ['off', 'auto', 'heat', 'cool'] + } + ) + device.status.attributes[Attribute.temperature] = Status(70, 'F', None) + return device + + +@pytest.fixture(name="thermostat") +def thermostat_fixture(device_factory): + """Fixture returns a fully-featured thermostat.""" + device = device_factory( + "Thermostat", + capabilities=[ + Capability.temperature_measurement, + Capability.relative_humidity_measurement, + Capability.thermostat_cooling_setpoint, + Capability.thermostat_heating_setpoint, + Capability.thermostat_mode, + Capability.thermostat_operating_state, + Capability.thermostat_fan_mode], + status={ + Attribute.cooling_setpoint: 74, + Attribute.heating_setpoint: 68, + Attribute.thermostat_fan_mode: 'on', + Attribute.supported_thermostat_fan_modes: ['auto', 'on'], + Attribute.thermostat_mode: 'heat', + Attribute.supported_thermostat_modes: + ['auto', 'heat', 'cool', 'off', 'eco'], + Attribute.thermostat_operating_state: 'fan only', + Attribute.humidity: 34 + } + ) + device.status.attributes[Attribute.temperature] = Status(70, 'F', None) + return device + + +async def test_async_setup_platform(): + """Test setup platform does nothing (it uses config entries).""" + await climate.async_setup_platform(None, None, None) + + +def test_is_climate(device_factory, legacy_thermostat, + basic_thermostat, thermostat): + """Test climate devices are correctly identified.""" + other_devices = [ + device_factory('Unknown', ['Unknown']), + device_factory("Switch 1", [Capability.switch]) + ] + for device in [legacy_thermostat, basic_thermostat, thermostat]: + assert climate.is_climate(device), device.name + for device in other_devices: + assert not climate.is_climate(device), device.name + + +async def test_legacy_thermostat_entity_state(hass, legacy_thermostat): + """Tests the state attributes properly match the thermostat type.""" + await setup_platform(hass, CLIMATE_DOMAIN, legacy_thermostat) + state = hass.states.get('climate.legacy_thermostat') + assert state.state == STATE_AUTO + assert state.attributes[ATTR_SUPPORTED_FEATURES] == \ + SUPPORT_OPERATION_MODE | SUPPORT_FAN_MODE | \ + SUPPORT_TARGET_TEMPERATURE_HIGH | SUPPORT_TARGET_TEMPERATURE_LOW | \ + SUPPORT_TARGET_TEMPERATURE + assert state.attributes[climate.ATTR_OPERATION_STATE] == 'idle' + assert state.attributes[ATTR_OPERATION_LIST] == { + STATE_AUTO, STATE_COOL, STATE_ECO, STATE_HEAT, STATE_OFF} + assert state.attributes[ATTR_FAN_MODE] == 'auto' + assert state.attributes[ATTR_FAN_LIST] == ['auto', 'on'] + assert state.attributes[ATTR_TARGET_TEMP_LOW] == 20 # celsius + assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 23.3 # celsius + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 21.1 # celsius + + +async def test_basic_thermostat_entity_state(hass, basic_thermostat): + """Tests the state attributes properly match the thermostat type.""" + await setup_platform(hass, CLIMATE_DOMAIN, basic_thermostat) + state = hass.states.get('climate.basic_thermostat') + assert state.state == STATE_OFF + assert state.attributes[ATTR_SUPPORTED_FEATURES] == \ + SUPPORT_OPERATION_MODE | SUPPORT_TARGET_TEMPERATURE_HIGH | \ + SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_TARGET_TEMPERATURE + assert state.attributes[climate.ATTR_OPERATION_STATE] is None + assert state.attributes[ATTR_OPERATION_LIST] == { + STATE_OFF, STATE_AUTO, STATE_HEAT, STATE_COOL} + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 21.1 # celsius + + +async def test_thermostat_entity_state(hass, thermostat): + """Tests the state attributes properly match the thermostat type.""" + await setup_platform(hass, CLIMATE_DOMAIN, thermostat) + state = hass.states.get('climate.thermostat') + assert state.state == STATE_HEAT + assert state.attributes[ATTR_SUPPORTED_FEATURES] == \ + SUPPORT_OPERATION_MODE | SUPPORT_FAN_MODE | \ + SUPPORT_TARGET_TEMPERATURE_HIGH | SUPPORT_TARGET_TEMPERATURE_LOW | \ + SUPPORT_TARGET_TEMPERATURE + assert state.attributes[climate.ATTR_OPERATION_STATE] == 'fan only' + assert state.attributes[ATTR_OPERATION_LIST] == { + STATE_AUTO, STATE_HEAT, STATE_COOL, STATE_OFF, STATE_ECO} + assert state.attributes[ATTR_FAN_MODE] == 'on' + assert state.attributes[ATTR_FAN_LIST] == ['auto', 'on'] + assert state.attributes[ATTR_TEMPERATURE] == 20 # celsius + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 21.1 # celsius + assert state.attributes[ATTR_CURRENT_HUMIDITY] == 34 + + +async def test_set_fan_mode(hass, thermostat): + """Test the fan mode is set successfully.""" + await setup_platform(hass, CLIMATE_DOMAIN, thermostat) + await hass.services.async_call( + CLIMATE_DOMAIN, SERVICE_SET_FAN_MODE, { + ATTR_ENTITY_ID: 'climate.thermostat', + ATTR_FAN_MODE: 'auto'}, + blocking=True) + state = hass.states.get('climate.thermostat') + assert state.attributes[ATTR_FAN_MODE] == 'auto' + + +async def test_set_operation_mode(hass, thermostat): + """Test the operation mode is set successfully.""" + await setup_platform(hass, CLIMATE_DOMAIN, thermostat) + await hass.services.async_call( + CLIMATE_DOMAIN, SERVICE_SET_OPERATION_MODE, { + ATTR_ENTITY_ID: 'climate.thermostat', + ATTR_OPERATION_MODE: STATE_ECO}, + blocking=True) + state = hass.states.get('climate.thermostat') + assert state.state == STATE_ECO + + +async def test_set_temperature_heat_mode(hass, thermostat): + """Test the temperature is set successfully when in heat mode.""" + thermostat.status.thermostat_mode = 'heat' + await setup_platform(hass, CLIMATE_DOMAIN, thermostat) + await hass.services.async_call( + CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, { + ATTR_ENTITY_ID: 'climate.thermostat', + ATTR_TEMPERATURE: 21}, + blocking=True) + state = hass.states.get('climate.thermostat') + assert state.attributes[ATTR_OPERATION_MODE] == STATE_HEAT + assert state.attributes[ATTR_TEMPERATURE] == 21 + assert thermostat.status.heating_setpoint == 69.8 + + +async def test_set_temperature_cool_mode(hass, thermostat): + """Test the temperature is set successfully when in cool mode.""" + thermostat.status.thermostat_mode = 'cool' + await setup_platform(hass, CLIMATE_DOMAIN, thermostat) + await hass.services.async_call( + CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, { + ATTR_ENTITY_ID: 'climate.thermostat', + ATTR_TEMPERATURE: 21}, + blocking=True) + state = hass.states.get('climate.thermostat') + assert state.attributes[ATTR_TEMPERATURE] == 21 + + +async def test_set_temperature(hass, thermostat): + """Test the temperature is set successfully.""" + thermostat.status.thermostat_mode = 'auto' + await setup_platform(hass, CLIMATE_DOMAIN, thermostat) + await hass.services.async_call( + CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, { + ATTR_ENTITY_ID: 'climate.thermostat', + ATTR_TARGET_TEMP_HIGH: 25.5, + ATTR_TARGET_TEMP_LOW: 22.2}, + blocking=True) + state = hass.states.get('climate.thermostat') + assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 25.5 + assert state.attributes[ATTR_TARGET_TEMP_LOW] == 22.2 + + +async def test_set_temperature_with_mode(hass, thermostat): + """Test the temperature and mode is set successfully.""" + await setup_platform(hass, CLIMATE_DOMAIN, thermostat) + await hass.services.async_call( + CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, { + ATTR_ENTITY_ID: 'climate.thermostat', + ATTR_TARGET_TEMP_HIGH: 25.5, + ATTR_TARGET_TEMP_LOW: 22.2, + ATTR_OPERATION_MODE: STATE_AUTO}, + blocking=True) + state = hass.states.get('climate.thermostat') + assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 25.5 + assert state.attributes[ATTR_TARGET_TEMP_LOW] == 22.2 + assert state.state == STATE_AUTO + + +async def test_entity_and_device_attributes(hass, thermostat): + """Test the attributes of the entries are correct.""" + await setup_platform(hass, CLIMATE_DOMAIN, thermostat) + entity_registry = await hass.helpers.entity_registry.async_get_registry() + device_registry = await hass.helpers.device_registry.async_get_registry() + + entry = entity_registry.async_get("climate.thermostat") + assert entry + assert entry.unique_id == thermostat.device_id + + entry = device_registry.async_get_device( + {(DOMAIN, thermostat.device_id)}, []) + assert entry + assert entry.name == thermostat.label + assert entry.model == thermostat.device_type_name + assert entry.manufacturer == 'Unavailable' From 69df620ad65842f2cd6543d27d75252229175401 Mon Sep 17 00:00:00 2001 From: Thomas Passer Jensen Date: Tue, 12 Feb 2019 09:26:46 +0100 Subject: [PATCH 155/242] Add Rejseplanen danish public transport sensor component (#19885) * Add Rejseplanen danish public transport sensor component * Removed commented out code and fixed style errors * Use rjpl pypi package for API calls. * Fix platform schema config and code cleanup. * Use updated rjpl library with specific exceptions * API error message is now logged, unknown state attributes excluded --- .coveragerc | 1 + .../components/sensor/rejseplanen.py | 228 ++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 232 insertions(+) create mode 100755 homeassistant/components/sensor/rejseplanen.py diff --git a/.coveragerc b/.coveragerc index 8ef830c8a8a..ad3189e5126 100644 --- a/.coveragerc +++ b/.coveragerc @@ -520,6 +520,7 @@ omit = homeassistant/components/sensor/radarr.py homeassistant/components/sensor/rainbird.py homeassistant/components/sensor/recollect_waste.py + homeassistant/components/sensor/rejseplanen.py homeassistant/components/sensor/ripple.py homeassistant/components/sensor/rova.py homeassistant/components/sensor/rtorrent.py diff --git a/homeassistant/components/sensor/rejseplanen.py b/homeassistant/components/sensor/rejseplanen.py new file mode 100755 index 00000000000..bade1bd6315 --- /dev/null +++ b/homeassistant/components/sensor/rejseplanen.py @@ -0,0 +1,228 @@ +""" +Support for Rejseplanen information from rejseplanen.dk. + +For more info on the API see: +https://help.rejseplanen.dk/hc/en-us/articles/214174465-Rejseplanen-s-API + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.rejseplanen/ +""" +import logging +from datetime import timedelta, datetime +from operator import itemgetter + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +import homeassistant.util.dt as dt_util +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION +from homeassistant.helpers.entity import Entity + +REQUIREMENTS = ['rjpl==0.3.5'] +_LOGGER = logging.getLogger(__name__) + +ATTR_STOP_ID = 'Stop ID' +ATTR_STOP_NAME = 'Stop' +ATTR_ROUTE = 'Route' +ATTR_TYPE = 'Type' +ATTR_DIRECTION = "Direction" +ATTR_DUE_IN = 'Due in' +ATTR_DUE_AT = 'Due at' +ATTR_NEXT_UP = 'Later departure' + +CONF_ATTRIBUTION = "Data provided by rejseplanen.dk" +CONF_STOP_ID = 'stop_id' +CONF_ROUTE = 'route' +CONF_DIRECTION = 'direction' +CONF_DEPARTURE_TYPE = 'departure_type' + +DEFAULT_NAME = 'Next departure' +ICON = 'mdi:bus' + +SCAN_INTERVAL = timedelta(minutes=1) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_STOP_ID): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_ROUTE, default=[]): + vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_DIRECTION, default=[]): + vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_DEPARTURE_TYPE, default=[]): + vol.All(cv.ensure_list, [vol.In(list(['BUS', 'EXB', 'M', + 'S', 'REG']))]) +}) + + +def due_in_minutes(timestamp): + """Get the time in minutes from a timestamp. + + The timestamp should be in the format day.month.year hour:minute + """ + diff = datetime.strptime( + timestamp, "%d.%m.%y %H:%M") - dt_util.now().replace(tzinfo=None) + + return int(diff.total_seconds() // 60) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Rejseplanen transport sensor.""" + name = config[CONF_NAME] + stop_id = config[CONF_STOP_ID] + route = config.get(CONF_ROUTE) + direction = config[CONF_DIRECTION] + departure_type = config[CONF_DEPARTURE_TYPE] + + data = PublicTransportData(stop_id, route, direction, departure_type) + add_devices([RejseplanenTransportSensor(data, + stop_id, + route, + direction, + name)], + True) + + +class RejseplanenTransportSensor(Entity): + """Implementation of Rejseplanen transport sensor.""" + + def __init__(self, data, stop_id, route, direction, name): + """Initialize the sensor.""" + self.data = data + self._name = name + self._stop_id = stop_id + self._route = route + self._direction = direction + self._times = self._state = None + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def device_state_attributes(self): + """Return the state attributes.""" + if self._times is not None: + next_up = None + if len(self._times) > 1: + next_up = ('{} towards ' + '{} in ' + '{} from ' + '{}'.format(self._times[1][ATTR_ROUTE], + self._times[1][ATTR_DIRECTION], + str(self._times[1][ATTR_DUE_IN]), + self._times[1][ATTR_STOP_NAME])) + params = { + ATTR_DUE_IN: str(self._times[0][ATTR_DUE_IN]), + ATTR_DUE_AT: self._times[0][ATTR_DUE_AT], + ATTR_TYPE: self._times[0][ATTR_TYPE], + ATTR_ROUTE: self._times[0][ATTR_ROUTE], + ATTR_DIRECTION: self._times[0][ATTR_DIRECTION], + ATTR_STOP_NAME: self._times[0][ATTR_STOP_NAME], + ATTR_STOP_ID: self._stop_id, + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, + ATTR_NEXT_UP: next_up + } + return {k: v for k, v in params.items() if v} + + @property + def unit_of_measurement(self): + """Return the unit this state is expressed in.""" + return 'min' + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return ICON + + def update(self): + """Get the latest data from rejseplanen.dk and update the states.""" + self.data.update() + self._times = self.data.info + try: + self._state = self._times[0][ATTR_DUE_IN] + except TypeError: + pass + + +class PublicTransportData(): + """The Class for handling the data retrieval.""" + + def __init__(self, stop_id, route, direction, departure_type): + """Initialize the data object.""" + self.stop_id = stop_id + self.route = route + self.direction = direction + self.departure_type = departure_type + self.info = self.empty_result() + + def empty_result(self): + """Object returned when no departures are found.""" + return [{ATTR_DUE_IN: 'n/a', + ATTR_DUE_AT: 'n/a', + ATTR_TYPE: 'n/a', + ATTR_ROUTE: self.route, + ATTR_DIRECTION: 'n/a', + ATTR_STOP_NAME: 'n/a'}] + + def update(self): + """Get the latest data from rejseplanen.""" + import rjpl + self.info = [] + + try: + results = rjpl.departureBoard(int(self.stop_id), timeout=5) + except rjpl.rjplAPIError as error: + _LOGGER.debug("API returned error: %s", error) + self.info = self.empty_result() + return + except (rjpl.rjplConnectionError, rjpl.rjplHTTPError): + _LOGGER.debug("Error occured while connecting to the API") + self.info = self.empty_result() + return + + # Filter result + results = [d for d in results if 'cancelled' not in d] + if self.route: + results = [d for d in results if d['name'] in self.route] + if self.direction: + results = [d for d in results if d['direction'] in self.direction] + if self.departure_type: + results = [d for d in results if d['type'] in self.departure_type] + + for item in results: + route = item.get('name') + + due_at_date = item.get('rtDate') + due_at_time = item.get('rtTime') + + if due_at_date is None: + due_at_date = item.get('date') # Scheduled date + if due_at_time is None: + due_at_time = item.get('time') # Scheduled time + + if (due_at_date is not None and + due_at_time is not None and + route is not None): + due_at = '{} {}'.format(due_at_date, due_at_time) + + departure_data = {ATTR_DUE_IN: due_in_minutes(due_at), + ATTR_DUE_AT: due_at, + ATTR_TYPE: item.get('type'), + ATTR_ROUTE: route, + ATTR_DIRECTION: item.get('direction'), + ATTR_STOP_NAME: item.get('stop')} + self.info.append(departure_data) + + if not self.info: + _LOGGER.debug("No departures with given parameters") + self.info = self.empty_result() + + # Sort the data by time + self.info = sorted(self.info, key=itemgetter(ATTR_DUE_IN)) diff --git a/requirements_all.txt b/requirements_all.txt index a1a5cf3a2cc..eeb216897fb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1482,6 +1482,9 @@ ring_doorbell==0.2.2 # homeassistant.components.device_tracker.ritassist ritassist==0.9.2 +# homeassistant.components.sensor.rejseplanen +rjpl==0.3.5 + # homeassistant.components.notify.rocketchat rocketchat-API==0.6.1 From 9cbb26bee2727ad0947cafb69af854d9718eb316 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 12 Feb 2019 01:30:09 -0800 Subject: [PATCH 156/242] Bump feedparser version to py3.7 compat (#20987) * Bump feedparser version to py3.7 compat * Update requirements_test_all.txt * Update gen_requirements_all.py --- homeassistant/components/feedreader/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- script/gen_requirements_all.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/feedreader/__init__.py b/homeassistant/components/feedreader/__init__.py index 7882cdc5a15..f6d653360a9 100644 --- a/homeassistant/components/feedreader/__init__.py +++ b/homeassistant/components/feedreader/__init__.py @@ -16,7 +16,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_START, CONF_SCAN_INTERVAL from homeassistant.helpers.event import track_time_interval import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['feedparser==5.2.1'] +REQUIREMENTS = ['feedparser-homeassistant==5.2.2.dev1'] _LOGGER = getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index eeb216897fb..dacfe4fd6d5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -411,7 +411,7 @@ fastdotcom==0.0.3 fedexdeliverymanager==1.0.6 # homeassistant.components.feedreader -feedparser==5.2.1 +feedparser-homeassistant==5.2.2.dev1 # homeassistant.components.fibaro fiblary3==0.1.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c70de8da50b..0d2e0cd88bf 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -84,7 +84,7 @@ ephem==3.7.6.0 evohomeclient==0.2.8 # homeassistant.components.feedreader -feedparser==5.2.1 +feedparser-homeassistant==5.2.2.dev1 # homeassistant.components.sensor.foobot foobot_async==0.3.1 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 4a99ef84bc9..47028ef3530 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -51,7 +51,7 @@ TEST_REQUIREMENTS = ( 'enturclient', 'ephem', 'evohomeclient', - 'feedparser', + 'feedparser-homeassistant', 'foobot_async', 'geojson_client', 'georss_client', From 5dfaec596791c2316bc63a81d6abb071e0cbb853 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 12 Feb 2019 01:33:03 -0800 Subject: [PATCH 157/242] Update to Python 3.7 (#20988) --- Dockerfile | 2 +- virtualization/Docker/Dockerfile.dev | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0dcd0f666c7..c863ff9433c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ # When updating this file, please also update virtualization/Docker/Dockerfile.dev # This way, the development image and the production image are kept in sync. -FROM python:3.6 +FROM python:3.7 LABEL maintainer="Paulus Schoutsen " # Uncomment any of the following lines to disable the installation. diff --git a/virtualization/Docker/Dockerfile.dev b/virtualization/Docker/Dockerfile.dev index c01706782a0..de460319bc2 100644 --- a/virtualization/Docker/Dockerfile.dev +++ b/virtualization/Docker/Dockerfile.dev @@ -2,7 +2,7 @@ # Based on the production Dockerfile, but with development additions. # Keep this file as close as possible to the production Dockerfile, so the environments match. -FROM python:3.6 +FROM python:3.7 LABEL maintainer="Paulus Schoutsen " # Uncomment any of the following lines to disable the installation. From 1e69848af4cbfc570e7a640d1d8b6cb0c30585ab Mon Sep 17 00:00:00 2001 From: carstenschroeder Date: Tue, 12 Feb 2019 12:12:44 +0100 Subject: [PATCH 158/242] Updates pyatmo to 1.8 and adds exception handling (#20938) * switch to pyatmo 1.7 & add exception handling * STATE_UNKNOWN => None * correct too long line * delete whitespace * remove fancy update logic --- homeassistant/components/netatmo/__init__.py | 2 +- homeassistant/components/netatmo/sensor.py | 298 ++++++++++--------- requirements_all.txt | 2 +- 3 files changed, 156 insertions(+), 146 deletions(-) diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index 50bd290797d..ce39ad9d55e 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -16,7 +16,7 @@ from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle -REQUIREMENTS = ['pyatmo==1.4'] +REQUIREMENTS = ['pyatmo==1.8'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index d593d93729b..f4dad6a3b6b 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -164,146 +164,152 @@ class NetAtmoSensor(Entity): self._state = None return - if self.type == 'temperature': - self._state = round(data['Temperature'], 1) - elif self.type == 'humidity': - self._state = data['Humidity'] - elif self.type == 'rain': - self._state = data['Rain'] - elif self.type == 'sum_rain_1': - self._state = data['sum_rain_1'] - elif self.type == 'sum_rain_24': - self._state = data['sum_rain_24'] - elif self.type == 'noise': - self._state = data['Noise'] - elif self.type == 'co2': - self._state = data['CO2'] - elif self.type == 'pressure': - self._state = round(data['Pressure'], 1) - elif self.type == 'battery_percent': - self._state = data['battery_percent'] - elif self.type == 'battery_lvl': - self._state = data['battery_vp'] - elif (self.type == 'battery_vp' and - self._module_type == MODULE_TYPE_WIND): - if data['battery_vp'] >= 5590: - self._state = "Full" - elif data['battery_vp'] >= 5180: - self._state = "High" - elif data['battery_vp'] >= 4770: - self._state = "Medium" - elif data['battery_vp'] >= 4360: - self._state = "Low" - elif data['battery_vp'] < 4360: - self._state = "Very Low" - elif (self.type == 'battery_vp' and - self._module_type == MODULE_TYPE_RAIN): - if data['battery_vp'] >= 5500: - self._state = "Full" - elif data['battery_vp'] >= 5000: - self._state = "High" - elif data['battery_vp'] >= 4500: - self._state = "Medium" - elif data['battery_vp'] >= 4000: - self._state = "Low" - elif data['battery_vp'] < 4000: - self._state = "Very Low" - elif (self.type == 'battery_vp' and - self._module_type == MODULE_TYPE_INDOOR): - if data['battery_vp'] >= 5640: - self._state = "Full" - elif data['battery_vp'] >= 5280: - self._state = "High" - elif data['battery_vp'] >= 4920: - self._state = "Medium" - elif data['battery_vp'] >= 4560: - self._state = "Low" - elif data['battery_vp'] < 4560: - self._state = "Very Low" - elif (self.type == 'battery_vp' and - self._module_type == MODULE_TYPE_OUTDOOR): - if data['battery_vp'] >= 5500: - self._state = "Full" - elif data['battery_vp'] >= 5000: - self._state = "High" - elif data['battery_vp'] >= 4500: - self._state = "Medium" - elif data['battery_vp'] >= 4000: - self._state = "Low" - elif data['battery_vp'] < 4000: - self._state = "Very Low" - elif self.type == 'min_temp': - self._state = data['min_temp'] - elif self.type == 'max_temp': - self._state = data['max_temp'] - elif self.type == 'windangle_value': - self._state = data['WindAngle'] - elif self.type == 'windangle': - if data['WindAngle'] >= 330: - self._state = "N (%d\xb0)" % data['WindAngle'] - elif data['WindAngle'] >= 300: - self._state = "NW (%d\xb0)" % data['WindAngle'] - elif data['WindAngle'] >= 240: - self._state = "W (%d\xb0)" % data['WindAngle'] - elif data['WindAngle'] >= 210: - self._state = "SW (%d\xb0)" % data['WindAngle'] - elif data['WindAngle'] >= 150: - self._state = "S (%d\xb0)" % data['WindAngle'] - elif data['WindAngle'] >= 120: - self._state = "SE (%d\xb0)" % data['WindAngle'] - elif data['WindAngle'] >= 60: - self._state = "E (%d\xb0)" % data['WindAngle'] - elif data['WindAngle'] >= 30: - self._state = "NE (%d\xb0)" % data['WindAngle'] - elif data['WindAngle'] >= 0: - self._state = "N (%d\xb0)" % data['WindAngle'] - elif self.type == 'windstrength': - self._state = data['WindStrength'] - elif self.type == 'gustangle_value': - self._state = data['GustAngle'] - elif self.type == 'gustangle': - if data['GustAngle'] >= 330: - self._state = "N (%d\xb0)" % data['GustAngle'] - elif data['GustAngle'] >= 300: - self._state = "NW (%d\xb0)" % data['GustAngle'] - elif data['GustAngle'] >= 240: - self._state = "W (%d\xb0)" % data['GustAngle'] - elif data['GustAngle'] >= 210: - self._state = "SW (%d\xb0)" % data['GustAngle'] - elif data['GustAngle'] >= 150: - self._state = "S (%d\xb0)" % data['GustAngle'] - elif data['GustAngle'] >= 120: - self._state = "SE (%d\xb0)" % data['GustAngle'] - elif data['GustAngle'] >= 60: - self._state = "E (%d\xb0)" % data['GustAngle'] - elif data['GustAngle'] >= 30: - self._state = "NE (%d\xb0)" % data['GustAngle'] - elif data['GustAngle'] >= 0: - self._state = "N (%d\xb0)" % data['GustAngle'] - elif self.type == 'guststrength': - self._state = data['GustStrength'] - elif self.type == 'rf_status_lvl': - self._state = data['rf_status'] - elif self.type == 'rf_status': - if data['rf_status'] >= 90: - self._state = "Low" - elif data['rf_status'] >= 76: - self._state = "Medium" - elif data['rf_status'] >= 60: - self._state = "High" - elif data['rf_status'] <= 59: - self._state = "Full" - elif self.type == 'wifi_status_lvl': - self._state = data['wifi_status'] - elif self.type == 'wifi_status': - if data['wifi_status'] >= 86: - self._state = "Low" - elif data['wifi_status'] >= 71: - self._state = "Medium" - elif data['wifi_status'] >= 56: - self._state = "High" - elif data['wifi_status'] <= 55: - self._state = "Full" + try: + if self.type == 'temperature': + self._state = round(data['Temperature'], 1) + elif self.type == 'humidity': + self._state = data['Humidity'] + elif self.type == 'rain': + self._state = data['Rain'] + elif self.type == 'sum_rain_1': + self._state = data['sum_rain_1'] + elif self.type == 'sum_rain_24': + self._state = data['sum_rain_24'] + elif self.type == 'noise': + self._state = data['Noise'] + elif self.type == 'co2': + self._state = data['CO2'] + elif self.type == 'pressure': + self._state = round(data['Pressure'], 1) + elif self.type == 'battery_percent': + self._state = data['battery_percent'] + elif self.type == 'battery_lvl': + self._state = data['battery_vp'] + elif (self.type == 'battery_vp' and + self._module_type == MODULE_TYPE_WIND): + if data['battery_vp'] >= 5590: + self._state = "Full" + elif data['battery_vp'] >= 5180: + self._state = "High" + elif data['battery_vp'] >= 4770: + self._state = "Medium" + elif data['battery_vp'] >= 4360: + self._state = "Low" + elif data['battery_vp'] < 4360: + self._state = "Very Low" + elif (self.type == 'battery_vp' and + self._module_type == MODULE_TYPE_RAIN): + if data['battery_vp'] >= 5500: + self._state = "Full" + elif data['battery_vp'] >= 5000: + self._state = "High" + elif data['battery_vp'] >= 4500: + self._state = "Medium" + elif data['battery_vp'] >= 4000: + self._state = "Low" + elif data['battery_vp'] < 4000: + self._state = "Very Low" + elif (self.type == 'battery_vp' and + self._module_type == MODULE_TYPE_INDOOR): + if data['battery_vp'] >= 5640: + self._state = "Full" + elif data['battery_vp'] >= 5280: + self._state = "High" + elif data['battery_vp'] >= 4920: + self._state = "Medium" + elif data['battery_vp'] >= 4560: + self._state = "Low" + elif data['battery_vp'] < 4560: + self._state = "Very Low" + elif (self.type == 'battery_vp' and + self._module_type == MODULE_TYPE_OUTDOOR): + if data['battery_vp'] >= 5500: + self._state = "Full" + elif data['battery_vp'] >= 5000: + self._state = "High" + elif data['battery_vp'] >= 4500: + self._state = "Medium" + elif data['battery_vp'] >= 4000: + self._state = "Low" + elif data['battery_vp'] < 4000: + self._state = "Very Low" + elif self.type == 'min_temp': + self._state = data['min_temp'] + elif self.type == 'max_temp': + self._state = data['max_temp'] + elif self.type == 'windangle_value': + self._state = data['WindAngle'] + elif self.type == 'windangle': + if data['WindAngle'] >= 330: + self._state = "N (%d\xb0)" % data['WindAngle'] + elif data['WindAngle'] >= 300: + self._state = "NW (%d\xb0)" % data['WindAngle'] + elif data['WindAngle'] >= 240: + self._state = "W (%d\xb0)" % data['WindAngle'] + elif data['WindAngle'] >= 210: + self._state = "SW (%d\xb0)" % data['WindAngle'] + elif data['WindAngle'] >= 150: + self._state = "S (%d\xb0)" % data['WindAngle'] + elif data['WindAngle'] >= 120: + self._state = "SE (%d\xb0)" % data['WindAngle'] + elif data['WindAngle'] >= 60: + self._state = "E (%d\xb0)" % data['WindAngle'] + elif data['WindAngle'] >= 30: + self._state = "NE (%d\xb0)" % data['WindAngle'] + elif data['WindAngle'] >= 0: + self._state = "N (%d\xb0)" % data['WindAngle'] + elif self.type == 'windstrength': + self._state = data['WindStrength'] + elif self.type == 'gustangle_value': + self._state = data['GustAngle'] + elif self.type == 'gustangle': + if data['GustAngle'] >= 330: + self._state = "N (%d\xb0)" % data['GustAngle'] + elif data['GustAngle'] >= 300: + self._state = "NW (%d\xb0)" % data['GustAngle'] + elif data['GustAngle'] >= 240: + self._state = "W (%d\xb0)" % data['GustAngle'] + elif data['GustAngle'] >= 210: + self._state = "SW (%d\xb0)" % data['GustAngle'] + elif data['GustAngle'] >= 150: + self._state = "S (%d\xb0)" % data['GustAngle'] + elif data['GustAngle'] >= 120: + self._state = "SE (%d\xb0)" % data['GustAngle'] + elif data['GustAngle'] >= 60: + self._state = "E (%d\xb0)" % data['GustAngle'] + elif data['GustAngle'] >= 30: + self._state = "NE (%d\xb0)" % data['GustAngle'] + elif data['GustAngle'] >= 0: + self._state = "N (%d\xb0)" % data['GustAngle'] + elif self.type == 'guststrength': + self._state = data['GustStrength'] + elif self.type == 'rf_status_lvl': + self._state = data['rf_status'] + elif self.type == 'rf_status': + if data['rf_status'] >= 90: + self._state = "Low" + elif data['rf_status'] >= 76: + self._state = "Medium" + elif data['rf_status'] >= 60: + self._state = "High" + elif data['rf_status'] <= 59: + self._state = "Full" + elif self.type == 'wifi_status_lvl': + self._state = data['wifi_status'] + elif self.type == 'wifi_status': + if data['wifi_status'] >= 86: + self._state = "Low" + elif data['wifi_status'] >= 71: + self._state = "Medium" + elif data['wifi_status'] >= 56: + self._state = "High" + elif data['wifi_status'] <= 55: + self._state = "Full" + except KeyError: + _LOGGER.error("No %s data found for %s", self.type, + self.module_name) + self._state = None + return class NetAtmoData: @@ -360,10 +366,14 @@ class NetAtmoData: self.data = self.station_data.lastData(exclude=3600) newinterval = 0 - for module in self.data: - if 'When' in self.data[module]: - newinterval = self.data[module]['When'] - break + try: + for module in self.data: + if 'When' in self.data[module]: + newinterval = self.data[module]['When'] + break + except TypeError: + _LOGGER.error("No modules found!") + if newinterval: # Try and estimate when fresh data will be available newinterval += NETATMO_UPDATE_INTERVAL - time() diff --git a/requirements_all.txt b/requirements_all.txt index dacfe4fd6d5..9f2df6894b3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -930,7 +930,7 @@ pyalarmdotcom==0.3.2 pyarlo==0.2.3 # homeassistant.components.netatmo -pyatmo==1.4 +pyatmo==1.8 # homeassistant.components.apple_tv pyatv==0.3.12 From 00b8d57cd0cf64daa0ddf59fa6cf9da1af824d8f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 12 Feb 2019 07:38:19 -0800 Subject: [PATCH 159/242] Add frontend storage (#20880) * Add frontend storage * Update storage.py --- homeassistant/components/frontend/__init__.py | 3 + homeassistant/components/frontend/storage.py | 79 ++++++++ tests/components/frontend/test_storage.py | 186 ++++++++++++++++++ 3 files changed, 268 insertions(+) create mode 100644 homeassistant/components/frontend/storage.py create mode 100644 tests/components/frontend/test_storage.py diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 46652b4d7b0..1efda81dfe0 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,6 +24,8 @@ from homeassistant.core import callback from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass +from .storage import async_setup_frontend_storage + REQUIREMENTS = ['home-assistant-frontend==20190203.0'] DOMAIN = 'frontend' @@ -195,6 +197,7 @@ def add_manifest_json_key(key, val): async def async_setup(hass, config): """Set up the serving of the frontend.""" + await async_setup_frontend_storage(hass) hass.components.websocket_api.async_register_command( WS_TYPE_GET_PANELS, websocket_get_panels, SCHEMA_GET_PANELS) hass.components.websocket_api.async_register_command( diff --git a/homeassistant/components/frontend/storage.py b/homeassistant/components/frontend/storage.py new file mode 100644 index 00000000000..f01abc79e8e --- /dev/null +++ b/homeassistant/components/frontend/storage.py @@ -0,0 +1,79 @@ +"""API for persistent storage for the frontend.""" +from functools import wraps +import voluptuous as vol + +from homeassistant.components import websocket_api + +DATA_STORAGE = 'frontend_storage' +STORAGE_VERSION_USER_DATA = 1 +STORAGE_KEY_USER_DATA = 'frontend.user_data_{}' + + +async def async_setup_frontend_storage(hass): + """Set up frontend storage.""" + hass.data[DATA_STORAGE] = {} + hass.components.websocket_api.async_register_command( + websocket_set_user_data + ) + hass.components.websocket_api.async_register_command( + websocket_get_user_data + ) + + +def with_store(orig_func): + """Decorate function to provide data.""" + @wraps(orig_func) + async def with_store_func(hass, connection, msg): + """Provide user specific data and store to function.""" + store = hass.helpers.storage.Store( + STORAGE_VERSION_USER_DATA, + STORAGE_KEY_USER_DATA.format(connection.user.id) + ) + data = hass.data[DATA_STORAGE] + user_id = connection.user.id + if user_id not in data: + data[user_id] = await store.async_load() or {} + + await orig_func( + hass, connection, msg, + store, + data[user_id], + ) + return with_store_func + + +@websocket_api.websocket_command({ + vol.Required('type'): 'frontend/set_user_data', + vol.Required('key'): str, + vol.Required('value'): vol.Any(bool, str, int, float, dict, list, None), +}) +@websocket_api.async_response +@with_store +async def websocket_set_user_data(hass, connection, msg, store, data): + """Handle set global data command. + + Async friendly. + """ + data[msg['key']] = msg['value'] + await store.async_save(data) + connection.send_message(websocket_api.result_message( + msg['id'], + )) + + +@websocket_api.websocket_command({ + vol.Required('type'): 'frontend/get_user_data', + vol.Optional('key'): str, +}) +@websocket_api.async_response +@with_store +async def websocket_get_user_data(hass, connection, msg, store, data): + """Handle get global data command. + + Async friendly. + """ + connection.send_message(websocket_api.result_message( + msg['id'], { + 'value': data.get(msg['key']) if 'key' in msg else data + } + )) diff --git a/tests/components/frontend/test_storage.py b/tests/components/frontend/test_storage.py new file mode 100644 index 00000000000..97b132cfd13 --- /dev/null +++ b/tests/components/frontend/test_storage.py @@ -0,0 +1,186 @@ +"""The tests for frontend storage.""" +import pytest + +from homeassistant.setup import async_setup_component +from homeassistant.components.frontend import storage + + +@pytest.fixture(autouse=True) +def setup_frontend(hass): + """Fixture to setup the frontend.""" + hass.loop.run_until_complete(async_setup_component(hass, 'frontend', {})) + + +async def test_get_user_data_empty(hass, hass_ws_client, hass_storage): + """Test get_user_data command.""" + client = await hass_ws_client(hass) + + await client.send_json({ + 'id': 5, + 'type': 'frontend/get_user_data', + 'key': 'non-existing-key', + }) + + res = await client.receive_json() + assert res['success'], res + assert res['result']['value'] is None + + +async def test_get_user_data(hass, hass_ws_client, hass_admin_user, + hass_storage): + """Test get_user_data command.""" + storage_key = storage.STORAGE_KEY_USER_DATA.format(hass_admin_user.id) + hass_storage[storage_key] = { + 'key': storage_key, + 'version': 1, + 'data': { + 'test-key': 'test-value', + 'test-complex': [{'foo': 'bar'}] + } + } + + client = await hass_ws_client(hass) + + # Get a simple string key + + await client.send_json({ + 'id': 6, + 'type': 'frontend/get_user_data', + 'key': 'test-key', + }) + + res = await client.receive_json() + assert res['success'], res + assert res['result']['value'] == 'test-value' + + # Get a more complex key + + await client.send_json({ + 'id': 7, + 'type': 'frontend/get_user_data', + 'key': 'test-complex', + }) + + res = await client.receive_json() + assert res['success'], res + assert res['result']['value'][0]['foo'] == 'bar' + + # Get all data (no key) + + await client.send_json({ + 'id': 8, + 'type': 'frontend/get_user_data', + }) + + res = await client.receive_json() + assert res['success'], res + assert res['result']['value']['test-key'] == 'test-value' + assert res['result']['value']['test-complex'][0]['foo'] == 'bar' + + +async def test_set_user_data_empty(hass, hass_ws_client, hass_storage): + """Test set_user_data command.""" + client = await hass_ws_client(hass) + + # test creating + + await client.send_json({ + 'id': 6, + 'type': 'frontend/get_user_data', + 'key': 'test-key', + }) + + res = await client.receive_json() + assert res['success'], res + assert res['result']['value'] is None + + await client.send_json({ + 'id': 7, + 'type': 'frontend/set_user_data', + 'key': 'test-key', + 'value': 'test-value' + }) + + res = await client.receive_json() + assert res['success'], res + + await client.send_json({ + 'id': 8, + 'type': 'frontend/get_user_data', + 'key': 'test-key', + }) + + res = await client.receive_json() + assert res['success'], res + assert res['result']['value'] == 'test-value' + + +async def test_set_user_data(hass, hass_ws_client, hass_storage, + hass_admin_user): + """Test set_user_data command with initial data.""" + storage_key = storage.STORAGE_KEY_USER_DATA.format(hass_admin_user.id) + hass_storage[storage_key] = { + 'version': 1, + 'data': { + 'test-key': 'test-value', + 'test-complex': 'string', + } + } + + client = await hass_ws_client(hass) + + # test creating + + await client.send_json({ + 'id': 5, + 'type': 'frontend/set_user_data', + 'key': 'test-non-existent-key', + 'value': 'test-value-new' + }) + + res = await client.receive_json() + assert res['success'], res + + await client.send_json({ + 'id': 6, + 'type': 'frontend/get_user_data', + 'key': 'test-non-existent-key', + }) + + res = await client.receive_json() + assert res['success'], res + assert res['result']['value'] == 'test-value-new' + + # test updating with complex data + + await client.send_json({ + 'id': 7, + 'type': 'frontend/set_user_data', + 'key': 'test-complex', + 'value': [{'foo': 'bar'}] + }) + + res = await client.receive_json() + assert res['success'], res + + await client.send_json({ + 'id': 8, + 'type': 'frontend/get_user_data', + 'key': 'test-complex', + }) + + res = await client.receive_json() + assert res['success'], res + assert res['result']['value'][0]['foo'] == 'bar' + + # ensure other existing key was not modified + + await client.send_json({ + 'id': 9, + 'type': 'frontend/get_user_data', + 'key': 'test-key', + }) + + res = await client.receive_json() + assert res['success'], res + assert res['result']['value'] == 'test-value' From 2702c75fb09734e0901309ffe992be529bf407d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Tue, 12 Feb 2019 16:40:22 +0100 Subject: [PATCH 160/242] Norway air quality (#20683) * Add norway air quality sensor * style * library * Refacotr to air_quality * fix norway air comments --- .coveragerc | 1 + .../components/air_quality/norway_air.py | 139 ++++++++++++++++++ homeassistant/components/weather/met.py | 2 +- requirements_all.txt | 3 +- 4 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/air_quality/norway_air.py diff --git a/.coveragerc b/.coveragerc index ad3189e5126..5931322f80b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -13,6 +13,7 @@ omit = homeassistant/components/abode/* homeassistant/components/ads/* homeassistant/components/air_quality/nilu.py + homeassistant/components/air_quality/norway_air.py homeassistant/components/air_quality/opensensemap.py homeassistant/components/alarm_control_panel/alarmdotcom.py homeassistant/components/alarm_control_panel/canary.py diff --git a/homeassistant/components/air_quality/norway_air.py b/homeassistant/components/air_quality/norway_air.py new file mode 100644 index 00000000000..d4bfbebe47a --- /dev/null +++ b/homeassistant/components/air_quality/norway_air.py @@ -0,0 +1,139 @@ +""" +Sensor for checking the air quality forecast around Norway. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/air_quality.norway_air/ +""" +import logging + +from datetime import timedelta +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.air_quality import ( + PLATFORM_SCHEMA, AirQualityEntity) +from homeassistant.const import (CONF_LATITUDE, CONF_LONGITUDE, + CONF_NAME) +from homeassistant.helpers.aiohttp_client import async_get_clientsession + + +REQUIREMENTS = ['pyMetno==0.4.5'] + +_LOGGER = logging.getLogger(__name__) + +ATTRIBUTION = "Air quality from " \ + "https://luftkvalitet.miljostatus.no/, " \ + "delivered by the Norwegian Meteorological Institute." +# https://api.met.no/license_data.html + +CONF_FORECAST = 'forecast' + +DEFAULT_FORECAST = 0 +DEFAULT_NAME = 'Air quality Norway' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_FORECAST, default=DEFAULT_FORECAST): vol.Coerce(int), + vol.Optional(CONF_LATITUDE): cv.latitude, + vol.Optional(CONF_LONGITUDE): cv.longitude, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, +}) + +SCAN_INTERVAL = timedelta(minutes=5) + + +async def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): + """Set up the air_quality norway sensor.""" + forecast = config.get(CONF_FORECAST) + latitude = config.get(CONF_LATITUDE, hass.config.latitude) + longitude = config.get(CONF_LONGITUDE, hass.config.longitude) + name = config.get(CONF_NAME) + + if None in (latitude, longitude): + _LOGGER.error("Latitude or longitude not set in Home Assistant config") + return False + + coordinates = { + 'lat': str(latitude), + 'lon': str(longitude), + } + + async_add_entities([AirSensor(name, coordinates, + forecast, async_get_clientsession(hass), + )], + True) + + +def round_state(func): + """Round state.""" + def _decorator(self): + res = func(self) + if isinstance(res, float): + return round(res, 2) + return res + return _decorator + + +class AirSensor(AirQualityEntity): + """Representation of an Yr.no sensor.""" + + def __init__(self, name, coordinates, forecast, session): + """Initialize the sensor.""" + import metno + self._name = name + self._api = metno.AirQualityData(coordinates, forecast, session) + self._attrs = {} + + @property + def attribution(self) -> str: + """Return the attribution.""" + return ATTRIBUTION + + @property + def device_state_attributes(self) -> dict: + """Return other details about the sensor state.""" + return {'level': self._api.data.get('level')} + + @property + def name(self) -> str: + """Return the name of the sensor.""" + return self._name + + @property + @round_state + def air_quality_index(self): + """Return the Air Quality Index (AQI).""" + return self._api.data.get('aqi') + + @property + @round_state + def nitrogen_dioxide(self): + """Return the NO2 (nitrogen dioxide) level.""" + return self._api.data.get('no2_concentration') + + @property + @round_state + def ozone(self): + """Return the O3 (ozone) level.""" + return self._api.data.get('o3_concentration') + + @property + @round_state + def particulate_matter_2_5(self): + """Return the particulate matter 2.5 level.""" + return self._api.data.get('pm25_concentration') + + @property + @round_state + def particulate_matter_10(self): + """Return the particulate matter 10 level.""" + return self._api.data.get('pm10_concentration') + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return self._api.units.get('pm25_concentration') + + async def async_update(self) -> None: + """Update the sensor.""" + await self._api.update() diff --git a/homeassistant/components/weather/met.py b/homeassistant/components/weather/met.py index bab6624e9d0..c905e6b6ce3 100644 --- a/homeassistant/components/weather/met.py +++ b/homeassistant/components/weather/met.py @@ -18,7 +18,7 @@ from homeassistant.helpers.event import (async_track_utc_time_change, async_call_later) import homeassistant.util.dt as dt_util -REQUIREMENTS = ['pyMetno==0.3.0'] +REQUIREMENTS = ['pyMetno==0.4.5'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 9f2df6894b3..271d0d5b70d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -893,8 +893,9 @@ pyCEC==0.4.13 # homeassistant.components.switch.tplink pyHS100==0.3.4 +# homeassistant.components.air_quality.norway_air # homeassistant.components.weather.met -pyMetno==0.3.0 +pyMetno==0.4.5 # homeassistant.components.rfxtrx pyRFXtrx==0.23 From d89c56829c7dcb73373c4e0aacd5f9327065ed60 Mon Sep 17 00:00:00 2001 From: Fredrik Erlandsson Date: Tue, 12 Feb 2019 17:15:02 +0100 Subject: [PATCH 161/242] Fix Point does I/O in event loop (#20939) * call I/O operations via hass.async_add_executor_job * asyncio fixes * Fixes from @amelchio * async _update_callback --- homeassistant/components/point/__init__.py | 20 ++++++++++--------- .../components/point/binary_sensor.py | 3 +-- homeassistant/components/point/sensor.py | 7 +++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/point/__init__.py b/homeassistant/components/point/__init__.py index 66044678e28..e0f1e6651c6 100644 --- a/homeassistant/components/point/__init__.py +++ b/homeassistant/components/point/__init__.py @@ -12,7 +12,6 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_TOKEN, CONF_WEBHOOK_ID -from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send) @@ -117,8 +116,11 @@ async def async_setup_webhook(hass: HomeAssistantType, entry: ConfigEntry, entry, data={ **entry.data, }) - session.update_webhook(entry.data[CONF_WEBHOOK_URL], - entry.data[CONF_WEBHOOK_ID], events=['*']) + await hass.async_add_executor_job( + session.update_webhook, + entry.data[CONF_WEBHOOK_URL], + entry.data[CONF_WEBHOOK_ID], + ['*']) hass.components.webhook.async_register( DOMAIN, 'Point', entry.data[CONF_WEBHOOK_ID], handle_webhook) @@ -127,8 +129,8 @@ async def async_setup_webhook(hass: HomeAssistantType, entry: ConfigEntry, async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): """Unload a config entry.""" hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID]) - client = hass.data[DOMAIN].pop(entry.entry_id) - client.remove_webhook() + session = hass.data[DOMAIN].pop(entry.entry_id) + await hass.async_add_executor_job(session.remove_webhook) if not hass.data[DOMAIN]: hass.data.pop(DOMAIN) @@ -174,7 +176,8 @@ class MinutPointClient(): async def _sync(self): """Update local list of devices.""" - if not self._client.update() and self._is_available: + if not await self._hass.async_add_executor_job( + self._client.update) and self._is_available: self._is_available = False _LOGGER.warning("Device is unavailable") return @@ -237,15 +240,14 @@ class MinutPointEntity(Entity): _LOGGER.debug('Created device %s', self) self._async_unsub_dispatcher_connect = async_dispatcher_connect( self.hass, SIGNAL_UPDATE_ENTITY, self._update_callback) - self._update_callback() + await self._update_callback() async def async_will_remove_from_hass(self): """Disconnect dispatcher listener when removed.""" if self._async_unsub_dispatcher_connect: self._async_unsub_dispatcher_connect() - @callback - def _update_callback(self): + async def _update_callback(self): """Update the value of the sensor.""" pass diff --git a/homeassistant/components/point/binary_sensor.py b/homeassistant/components/point/binary_sensor.py index 1bd97ce2747..5f4834894bc 100644 --- a/homeassistant/components/point/binary_sensor.py +++ b/homeassistant/components/point/binary_sensor.py @@ -75,8 +75,7 @@ class MinutPointBinarySensor(MinutPointEntity, BinarySensorDevice): if self._async_unsub_hook_dispatcher_connect: self._async_unsub_hook_dispatcher_connect() - @callback - def _update_callback(self): + async def _update_callback(self): """Update the value of the sensor.""" if not self.is_updated: return diff --git a/homeassistant/components/point/sensor.py b/homeassistant/components/point/sensor.py index eb320de0efd..902c1a6c981 100644 --- a/homeassistant/components/point/sensor.py +++ b/homeassistant/components/point/sensor.py @@ -13,7 +13,6 @@ from homeassistant.components.sensor import DOMAIN from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS) -from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.dt import parse_datetime @@ -50,12 +49,12 @@ class MinutPointSensor(MinutPointEntity): super().__init__(point_client, device_id, device_class) self._device_prop = SENSOR_TYPES[device_class] - @callback - def _update_callback(self): + async def _update_callback(self): """Update the value of the sensor.""" if self.is_updated: _LOGGER.debug('Update sensor value for %s', self) - self._value = self.device.sensor(self.device_class) + self._value = await self.hass.async_add_executor_job( + self.device.sensor, self.device_class) self._updated = parse_datetime(self.device.last_update) self.async_schedule_update_ha_state() From d1c8d39107fedb22ff487dd9b52c22f62392c01f Mon Sep 17 00:00:00 2001 From: carstenschroeder Date: Tue, 12 Feb 2019 18:42:09 +0100 Subject: [PATCH 162/242] Add unique id to ADS platforms (#20511) * Add friendly_name option * Correct hound findings * correct hound findings 2 * add unique id * add unique id to all ads platforms --- homeassistant/components/ads/binary_sensor.py | 6 ++++++ homeassistant/components/ads/light.py | 6 ++++++ homeassistant/components/ads/sensor.py | 6 ++++++ homeassistant/components/ads/switch.py | 6 ++++++ 4 files changed, 24 insertions(+) diff --git a/homeassistant/components/ads/binary_sensor.py b/homeassistant/components/ads/binary_sensor.py index 1ee56cac9d3..c83837dcd5f 100644 --- a/homeassistant/components/ads/binary_sensor.py +++ b/homeassistant/components/ads/binary_sensor.py @@ -44,6 +44,7 @@ class AdsBinarySensor(BinarySensorDevice): def __init__(self, ads_hub, name, ads_var, device_class): """Initialize ADS binary sensor.""" self._name = name + self._unique_id = ads_var self._state = False self._device_class = device_class or 'moving' self._ads_hub = ads_hub @@ -66,6 +67,11 @@ class AdsBinarySensor(BinarySensorDevice): """Return the default name of the binary sensor.""" return self._name + @property + def unique_id(self): + """Return an unique identifier for this entity.""" + return self._unique_id + @property def device_class(self): """Return the device class.""" diff --git a/homeassistant/components/ads/light.py b/homeassistant/components/ads/light.py index 10df4c0bf72..a2d54fe7d4a 100644 --- a/homeassistant/components/ads/light.py +++ b/homeassistant/components/ads/light.py @@ -46,6 +46,7 @@ class AdsLight(Light): self._on_state = False self._brightness = None self._name = name + self._unique_id = ads_var_enable self.ads_var_enable = ads_var_enable self.ads_var_brightness = ads_var_brightness @@ -79,6 +80,11 @@ class AdsLight(Light): """Return the name of the device if any.""" return self._name + @property + def unique_id(self): + """Return an unique identifier for this entity.""" + return self._unique_id + @property def brightness(self): """Return the brightness of the light (0..255).""" diff --git a/homeassistant/components/ads/sensor.py b/homeassistant/components/ads/sensor.py index 24515357f5e..50f3fd08d5c 100644 --- a/homeassistant/components/ads/sensor.py +++ b/homeassistant/components/ads/sensor.py @@ -55,6 +55,7 @@ class AdsSensor(Entity): """Initialize AdsSensor entity.""" self._ads_hub = ads_hub self._name = name + self._unique_id = ads_var self._value = None self._unit_of_measurement = unit_of_measurement self.ads_var = ads_var @@ -84,6 +85,11 @@ class AdsSensor(Entity): """Return the name of the entity.""" return self._name + @property + def unique_id(self): + """Return an unique identifier for this entity.""" + return self._unique_id + @property def state(self): """Return the state of the device.""" diff --git a/homeassistant/components/ads/switch.py b/homeassistant/components/ads/switch.py index ecd1e7edc31..ab9e54adaa7 100644 --- a/homeassistant/components/ads/switch.py +++ b/homeassistant/components/ads/switch.py @@ -44,6 +44,7 @@ class AdsSwitch(ToggleEntity): self._ads_hub = ads_hub self._on_state = False self._name = name + self._unique_id = ads_var self.ads_var = ads_var async def async_added_to_hass(self): @@ -68,6 +69,11 @@ class AdsSwitch(ToggleEntity): """Return the name of the entity.""" return self._name + @property + def unique_id(self): + """Return an unique identifier for this entity.""" + return self._unique_id + @property def should_poll(self): """Return False because entity pushes its state to HA.""" From 6b46ed850b48e4f9adfbfc396b59f1bebc2a538b Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Tue, 12 Feb 2019 10:52:24 -0800 Subject: [PATCH 163/242] Upgrade cryptography to 2.5 (#21011) --- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index b94e383ec9e..27bb10d99f5 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -6,7 +6,7 @@ bcrypt==3.1.5 certifi>=2018.04.16 jinja2>=2.10 PyJWT==1.6.4 -cryptography==2.3.1 +cryptography==2.5 pip>=8.0.3 python-slugify==1.2.6 pytz>=2018.07 diff --git a/requirements_all.txt b/requirements_all.txt index 271d0d5b70d..b2f6237406e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -7,7 +7,7 @@ bcrypt==3.1.5 certifi>=2018.04.16 jinja2>=2.10 PyJWT==1.6.4 -cryptography==2.3.1 +cryptography==2.5 pip>=8.0.3 python-slugify==1.2.6 pytz>=2018.07 diff --git a/setup.py b/setup.py index cdd377b7673..285757ce710 100755 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ REQUIRES = [ 'jinja2>=2.10', 'PyJWT==1.6.4', # PyJWT has loose dependency. We want the latest one. - 'cryptography==2.3.1', + 'cryptography==2.5', 'pip>=8.0.3', 'python-slugify==1.2.6', 'pytz>=2018.07', From 80442e655df6b286a109e0e37dc4ea489f2b4169 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Tue, 12 Feb 2019 15:05:02 -0500 Subject: [PATCH 164/242] Update ZHA API to be device oriented (#20990) * update cluster API * swap to device focused API * update test --- homeassistant/components/zha/api.py | 193 ++++++++------------ homeassistant/components/zha/core/device.py | 26 +-- tests/components/zha/test_api.py | 43 ++--- 3 files changed, 101 insertions(+), 161 deletions(-) diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index 75a099562a6..0dd6dd78400 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -9,12 +9,11 @@ import logging import voluptuous as vol from homeassistant.components import websocket_api -from homeassistant.const import ATTR_ENTITY_ID import homeassistant.helpers.config_validation as cv from .core.const import ( DOMAIN, ATTR_CLUSTER_ID, ATTR_CLUSTER_TYPE, ATTR_ATTRIBUTE, ATTR_VALUE, ATTR_MANUFACTURER, ATTR_COMMAND, ATTR_COMMAND_TYPE, ATTR_ARGS, IN, OUT, - CLIENT_COMMANDS, SERVER_COMMANDS, SERVER, NAME) + CLIENT_COMMANDS, SERVER_COMMANDS, SERVER, NAME, ATTR_ENDPOINT_ID) _LOGGER = logging.getLogger(__name__) @@ -32,7 +31,6 @@ SERVICE_PERMIT = 'permit' SERVICE_REMOVE = 'remove' SERVICE_SET_ZIGBEE_CLUSTER_ATTRIBUTE = 'set_zigbee_cluster_attribute' SERVICE_ISSUE_ZIGBEE_CLUSTER_COMMAND = 'issue_zigbee_cluster_command' -ZIGBEE_CLUSTER_SERVICE = 'zigbee_cluster_service' IEEE_SERVICE = 'ieee_based_service' SERVICE_SCHEMAS = { @@ -43,13 +41,9 @@ SERVICE_SCHEMAS = { IEEE_SERVICE: vol.Schema({ vol.Required(ATTR_IEEE_ADDRESS): cv.string, }), - ZIGBEE_CLUSTER_SERVICE: vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_id, - vol.Required(ATTR_CLUSTER_ID): cv.positive_int, - vol.Optional(ATTR_CLUSTER_TYPE, default=IN): cv.string - }), SERVICE_SET_ZIGBEE_CLUSTER_ATTRIBUTE: vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(ATTR_IEEE): cv.string, + vol.Required(ATTR_ENDPOINT_ID): cv.positive_int, vol.Required(ATTR_CLUSTER_ID): cv.positive_int, vol.Optional(ATTR_CLUSTER_TYPE, default=IN): cv.string, vol.Required(ATTR_ATTRIBUTE): cv.positive_int, @@ -57,7 +51,8 @@ SERVICE_SCHEMAS = { vol.Optional(ATTR_MANUFACTURER): cv.positive_int, }), SERVICE_ISSUE_ZIGBEE_CLUSTER_COMMAND: vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(ATTR_IEEE): cv.string, + vol.Required(ATTR_ENDPOINT_ID): cv.positive_int, vol.Required(ATTR_CLUSTER_ID): cv.positive_int, vol.Optional(ATTR_CLUSTER_TYPE, default=IN): cv.string, vol.Required(ATTR_COMMAND): cv.positive_int, @@ -67,7 +62,7 @@ SERVICE_SCHEMAS = { }), } -WS_RECONFIGURE_NODE = 'zha/nodes/reconfigure' +WS_RECONFIGURE_NODE = 'zha/devices/reconfigure' SCHEMA_WS_RECONFIGURE_NODE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required(TYPE): WS_RECONFIGURE_NODE, vol.Required(ATTR_IEEE): str @@ -78,44 +73,39 @@ SCHEMA_WS_LIST_DEVICES = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required(TYPE): WS_DEVICES, }) -WS_ENTITIES_BY_IEEE = 'zha/entities' -SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required(TYPE): WS_ENTITIES_BY_IEEE, -}) - -WS_ENTITY_CLUSTERS = 'zha/entities/clusters' +WS_DEVICE_CLUSTERS = 'zha/devices/clusters' SCHEMA_WS_CLUSTERS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required(TYPE): WS_ENTITY_CLUSTERS, - vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(TYPE): WS_DEVICE_CLUSTERS, vol.Required(ATTR_IEEE): str }) -WS_ENTITY_CLUSTER_ATTRIBUTES = 'zha/entities/clusters/attributes' +WS_DEVICE_CLUSTER_ATTRIBUTES = 'zha/devices/clusters/attributes' SCHEMA_WS_CLUSTER_ATTRIBUTES = \ websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required(TYPE): WS_ENTITY_CLUSTER_ATTRIBUTES, - vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(TYPE): WS_DEVICE_CLUSTER_ATTRIBUTES, vol.Required(ATTR_IEEE): str, + vol.Required(ATTR_ENDPOINT_ID): int, vol.Required(ATTR_CLUSTER_ID): int, vol.Required(ATTR_CLUSTER_TYPE): str }) -WS_READ_CLUSTER_ATTRIBUTE = 'zha/entities/clusters/attributes/value' +WS_READ_CLUSTER_ATTRIBUTE = 'zha/devices/clusters/attributes/value' SCHEMA_WS_READ_CLUSTER_ATTRIBUTE = \ websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required(TYPE): WS_READ_CLUSTER_ATTRIBUTE, - vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(ATTR_IEEE): str, + vol.Required(ATTR_ENDPOINT_ID): int, vol.Required(ATTR_CLUSTER_ID): int, vol.Required(ATTR_CLUSTER_TYPE): str, vol.Required(ATTR_ATTRIBUTE): int, vol.Optional(ATTR_MANUFACTURER): object, }) -WS_ENTITY_CLUSTER_COMMANDS = 'zha/entities/clusters/commands' +WS_DEVICE_CLUSTER_COMMANDS = 'zha/devices/clusters/commands' SCHEMA_WS_CLUSTER_COMMANDS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required(TYPE): WS_ENTITY_CLUSTER_COMMANDS, - vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(TYPE): WS_DEVICE_CLUSTER_COMMANDS, vol.Required(ATTR_IEEE): str, + vol.Required(ATTR_ENDPOINT_ID): int, vol.Required(ATTR_CLUSTER_ID): int, vol.Required(ATTR_CLUSTER_TYPE): str }) @@ -145,18 +135,18 @@ def async_load_api(hass, application_controller, zha_gateway): async def set_zigbee_cluster_attributes(service): """Set zigbee attribute for cluster on zha entity.""" - entity_id = service.data.get(ATTR_ENTITY_ID) + ieee = service.data.get(ATTR_IEEE) + endpoint_id = service.data.get(ATTR_ENDPOINT_ID) cluster_id = service.data.get(ATTR_CLUSTER_ID) cluster_type = service.data.get(ATTR_CLUSTER_TYPE) attribute = service.data.get(ATTR_ATTRIBUTE) value = service.data.get(ATTR_VALUE) manufacturer = service.data.get(ATTR_MANUFACTURER) or None - entity_ref = zha_gateway.get_entity_reference(entity_id) + zha_device = zha_gateway.get_device(ieee) response = None - if entity_ref is not None: - response = await entity_ref.zha_device.write_zigbee_attribute( - list(entity_ref.cluster_listeners.values())[ - 0].cluster.endpoint.endpoint_id, + if zha_device is not None: + response = await zha_device.write_zigbee_attribute( + endpoint_id, cluster_id, attribute, value, @@ -166,7 +156,7 @@ def async_load_api(hass, application_controller, zha_gateway): _LOGGER.debug("Set attribute for: %s %s %s %s %s %s %s", "{}: [{}]".format(ATTR_CLUSTER_ID, cluster_id), "{}: [{}]".format(ATTR_CLUSTER_TYPE, cluster_type), - "{}: [{}]".format(ATTR_ENTITY_ID, entity_id), + "{}: [{}]".format(ATTR_ENDPOINT_ID, endpoint_id), "{}: [{}]".format(ATTR_ATTRIBUTE, attribute), "{}: [{}]".format(ATTR_VALUE, value), "{}: [{}]".format(ATTR_MANUFACTURER, manufacturer), @@ -181,20 +171,19 @@ def async_load_api(hass, application_controller, zha_gateway): async def issue_zigbee_cluster_command(service): """Issue command on zigbee cluster on zha entity.""" - entity_id = service.data.get(ATTR_ENTITY_ID) + ieee = service.data.get(ATTR_IEEE) + endpoint_id = service.data.get(ATTR_ENDPOINT_ID) cluster_id = service.data.get(ATTR_CLUSTER_ID) cluster_type = service.data.get(ATTR_CLUSTER_TYPE) command = service.data.get(ATTR_COMMAND) command_type = service.data.get(ATTR_COMMAND_TYPE) args = service.data.get(ATTR_ARGS) manufacturer = service.data.get(ATTR_MANUFACTURER) or None - entity_ref = zha_gateway.get_entity_reference(entity_id) - zha_device = entity_ref.zha_device + zha_device = zha_gateway.get_device(ieee) response = None - if entity_ref is not None: + if zha_device is not None: response = await zha_device.issue_cluster_command( - list(entity_ref.cluster_listeners.values())[ - 0].cluster.endpoint.endpoint_id, + endpoint_id, cluster_id, command, command_type, @@ -205,7 +194,7 @@ def async_load_api(hass, application_controller, zha_gateway): _LOGGER.debug("Issue command for: %s %s %s %s %s %s %s %s", "{}: [{}]".format(ATTR_CLUSTER_ID, cluster_id), "{}: [{}]".format(ATTR_CLUSTER_TYPE, cluster_type), - "{}: [{}]".format(ATTR_ENTITY_ID, entity_id), + "{}: [{}]".format(ATTR_ENDPOINT_ID, endpoint_id), "{}: [{}]".format(ATTR_COMMAND, command), "{}: [{}]".format(ATTR_COMMAND_TYPE, command_type), "{}: [{}]".format(ATTR_ARGS, args), @@ -256,77 +245,52 @@ def async_load_api(hass, application_controller, zha_gateway): ) @websocket_api.async_response - async def websocket_entities_by_ieee(hass, connection, msg): - """Return a dict of all zha entities grouped by ieee.""" - entities_by_ieee = {} - for ieee, entities in zha_gateway.device_registry.items(): - ieee_string = str(ieee) - entities_by_ieee[ieee_string] = [] - for entity in entities: - entities_by_ieee[ieee_string].append({ - ATTR_ENTITY_ID: entity.reference_id, - DEVICE_INFO: entity.device_info - }) - - connection.send_message(websocket_api.result_message( - msg[ID], - entities_by_ieee - )) - - hass.components.websocket_api.async_register_command( - WS_ENTITIES_BY_IEEE, websocket_entities_by_ieee, - SCHEMA_WS_LIST - ) - - @websocket_api.async_response - async def websocket_entity_clusters(hass, connection, msg): - """Return a list of entity clusters.""" - entity_id = msg[ATTR_ENTITY_ID] - entity_ref = zha_gateway.get_entity_reference(entity_id) - clusters = [] - if entity_ref is not None: - for listener in entity_ref.cluster_listeners.values(): - cluster = listener.cluster - in_clusters = cluster.endpoint.in_clusters.values() - out_clusters = cluster.endpoint.out_clusters.values() - if cluster in in_clusters: - clusters.append({ + async def websocket_device_clusters(hass, connection, msg): + """Return a list of device clusters.""" + ieee = msg[ATTR_IEEE] + zha_device = zha_gateway.get_device(ieee) + response_clusters = [] + if zha_device is not None: + clusters_by_endpoint = await zha_device.get_clusters() + for ep_id, clusters in clusters_by_endpoint.items(): + for c_id, cluster in clusters[IN].items(): + response_clusters.append({ TYPE: IN, - ID: cluster.cluster_id, - NAME: cluster.__class__.__name__ + ID: c_id, + NAME: cluster.__class__.__name__, + 'endpoint_id': ep_id }) - elif cluster in out_clusters: - clusters.append({ + for c_id, cluster in clusters[OUT].items(): + response_clusters.append({ TYPE: OUT, - ID: cluster.cluster_id, - NAME: cluster.__class__.__name__ + ID: c_id, + NAME: cluster.__class__.__name__, + 'endpoint_id': ep_id }) connection.send_message(websocket_api.result_message( msg[ID], - clusters + response_clusters )) hass.components.websocket_api.async_register_command( - WS_ENTITY_CLUSTERS, websocket_entity_clusters, + WS_DEVICE_CLUSTERS, websocket_device_clusters, SCHEMA_WS_CLUSTERS ) @websocket_api.async_response - async def websocket_entity_cluster_attributes(hass, connection, msg): + async def websocket_device_cluster_attributes(hass, connection, msg): """Return a list of cluster attributes.""" - entity_id = msg[ATTR_ENTITY_ID] + ieee = msg[ATTR_IEEE] + endpoint_id = msg[ATTR_ENDPOINT_ID] cluster_id = msg[ATTR_CLUSTER_ID] cluster_type = msg[ATTR_CLUSTER_TYPE] - ieee = msg[ATTR_IEEE] cluster_attributes = [] - entity_ref = zha_gateway.get_entity_reference(entity_id) - device = zha_gateway.get_device(ieee) + zha_device = zha_gateway.get_device(ieee) attributes = None - if entity_ref is not None: - attributes = await device.get_cluster_attributes( - list(entity_ref.cluster_listeners.values())[ - 0].cluster.endpoint.endpoint_id, + if zha_device is not None: + attributes = await zha_device.get_cluster_attributes( + endpoint_id, cluster_id, cluster_type) if attributes is not None: @@ -340,7 +304,7 @@ def async_load_api(hass, application_controller, zha_gateway): _LOGGER.debug("Requested attributes for: %s %s %s %s", "{}: [{}]".format(ATTR_CLUSTER_ID, cluster_id), "{}: [{}]".format(ATTR_CLUSTER_TYPE, cluster_type), - "{}: [{}]".format(ATTR_ENTITY_ID, entity_id), + "{}: [{}]".format(ATTR_ENDPOINT_ID, endpoint_id), "{}: [{}]".format(RESPONSE, cluster_attributes) ) @@ -350,25 +314,23 @@ def async_load_api(hass, application_controller, zha_gateway): )) hass.components.websocket_api.async_register_command( - WS_ENTITY_CLUSTER_ATTRIBUTES, websocket_entity_cluster_attributes, + WS_DEVICE_CLUSTER_ATTRIBUTES, websocket_device_cluster_attributes, SCHEMA_WS_CLUSTER_ATTRIBUTES ) @websocket_api.async_response - async def websocket_entity_cluster_commands(hass, connection, msg): + async def websocket_device_cluster_commands(hass, connection, msg): """Return a list of cluster commands.""" - entity_id = msg[ATTR_ENTITY_ID] cluster_id = msg[ATTR_CLUSTER_ID] cluster_type = msg[ATTR_CLUSTER_TYPE] ieee = msg[ATTR_IEEE] - entity_ref = zha_gateway.get_entity_reference(entity_id) - device = zha_gateway.get_device(ieee) + endpoint_id = msg[ATTR_ENDPOINT_ID] + zha_device = zha_gateway.get_device(ieee) cluster_commands = [] commands = None - if entity_ref is not None: - commands = await device.get_cluster_commands( - list(entity_ref.cluster_listeners.values())[ - 0].cluster.endpoint.endpoint_id, + if zha_device is not None: + commands = await zha_device.get_cluster_commands( + endpoint_id, cluster_id, cluster_type) @@ -392,7 +354,7 @@ def async_load_api(hass, application_controller, zha_gateway): _LOGGER.debug("Requested commands for: %s %s %s %s", "{}: [{}]".format(ATTR_CLUSTER_ID, cluster_id), "{}: [{}]".format(ATTR_CLUSTER_TYPE, cluster_type), - "{}: [{}]".format(ATTR_ENTITY_ID, entity_id), + "{}: [{}]".format(ATTR_ENDPOINT_ID, endpoint_id), "{}: [{}]".format(RESPONSE, cluster_commands) ) @@ -402,31 +364,24 @@ def async_load_api(hass, application_controller, zha_gateway): )) hass.components.websocket_api.async_register_command( - WS_ENTITY_CLUSTER_COMMANDS, websocket_entity_cluster_commands, + WS_DEVICE_CLUSTER_COMMANDS, websocket_device_cluster_commands, SCHEMA_WS_CLUSTER_COMMANDS ) @websocket_api.async_response async def websocket_read_zigbee_cluster_attributes(hass, connection, msg): """Read zigbee attribute for cluster on zha entity.""" - entity_id = msg[ATTR_ENTITY_ID] + ieee = msg[ATTR_IEEE] + endpoint_id = msg[ATTR_ENDPOINT_ID] cluster_id = msg[ATTR_CLUSTER_ID] cluster_type = msg[ATTR_CLUSTER_TYPE] attribute = msg[ATTR_ATTRIBUTE] - entity_ref = zha_gateway.get_entity_reference(entity_id) manufacturer = msg.get(ATTR_MANUFACTURER) or None + zha_device = zha_gateway.get_device(ieee) success = failure = None - clusters = [] - if cluster_type == IN: - clusters = \ - list(entity_ref.cluster_listeners.values())[ - 0].cluster.endpoint.in_clusters - else: - clusters = \ - list(entity_ref.cluster_listeners.values())[ - 0].cluster.endpoint.out_clusters - cluster = clusters[cluster_id] - if entity_ref is not None: + if zha_device is not None: + cluster = await zha_device.get_cluster( + endpoint_id, cluster_id, cluster_type=cluster_type) success, failure = await cluster.read_attributes( [attribute], allow_cache=False, @@ -436,7 +391,7 @@ def async_load_api(hass, application_controller, zha_gateway): _LOGGER.debug("Read attribute for: %s %s %s %s %s %s %s", "{}: [{}]".format(ATTR_CLUSTER_ID, cluster_id), "{}: [{}]".format(ATTR_CLUSTER_TYPE, cluster_type), - "{}: [{}]".format(ATTR_ENTITY_ID, entity_id), + "{}: [{}]".format(ATTR_ENDPOINT_ID, endpoint_id), "{}: [{}]".format(ATTR_ATTRIBUTE, attribute), "{}: [{}]".format(ATTR_MANUFACTURER, manufacturer), "{}: [{}]".format(RESPONSE, str(success.get(attribute))), diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 6bbfcd43a94..0119b651675 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -220,24 +220,24 @@ class ZHADevice: if ep_id != 0 } - async def get_cluster(self, endpooint_id, cluster_id, cluster_type=IN): + async def get_cluster(self, endpoint_id, cluster_id, cluster_type=IN): """Get zigbee cluster from this entity.""" clusters = await self.get_clusters() - return clusters[endpooint_id][cluster_type][cluster_id] + return clusters[endpoint_id][cluster_type][cluster_id] - async def get_cluster_attributes(self, endpooint_id, cluster_id, + async def get_cluster_attributes(self, endpoint_id, cluster_id, cluster_type=IN): """Get zigbee attributes for specified cluster.""" - cluster = await self.get_cluster(endpooint_id, cluster_id, + cluster = await self.get_cluster(endpoint_id, cluster_id, cluster_type) if cluster is None: return None return cluster.attributes - async def get_cluster_commands(self, endpooint_id, cluster_id, + async def get_cluster_commands(self, endpoint_id, cluster_id, cluster_type=IN): """Get zigbee commands for specified cluster.""" - cluster = await self.get_cluster(endpooint_id, cluster_id, + cluster = await self.get_cluster(endpoint_id, cluster_id, cluster_type) if cluster is None: return None @@ -246,12 +246,12 @@ class ZHADevice: SERVER_COMMANDS: cluster.server_commands, } - async def write_zigbee_attribute(self, endpooint_id, cluster_id, + async def write_zigbee_attribute(self, endpoint_id, cluster_id, attribute, value, cluster_type=IN, manufacturer=None): """Write a value to a zigbee attribute for a cluster in this entity.""" cluster = await self.get_cluster( - endpooint_id, cluster_id, cluster_type) + endpoint_id, cluster_id, cluster_type) if cluster is None: return None @@ -266,7 +266,7 @@ class ZHADevice: value, attribute, cluster_id, - endpooint_id, + endpoint_id, response ) return response @@ -276,17 +276,17 @@ class ZHADevice: '{}: {}'.format(ATTR_VALUE, value), '{}: {}'.format(ATTR_ATTRIBUTE, attribute), '{}: {}'.format(ATTR_CLUSTER_ID, cluster_id), - '{}: {}'.format(ATTR_ENDPOINT_ID, endpooint_id), + '{}: {}'.format(ATTR_ENDPOINT_ID, endpoint_id), exc ) return None - async def issue_cluster_command(self, endpooint_id, cluster_id, command, + async def issue_cluster_command(self, endpoint_id, cluster_id, command, command_type, args, cluster_type=IN, manufacturer=None): """Issue a command against specified zigbee cluster on this entity.""" cluster = await self.get_cluster( - endpooint_id, cluster_id, cluster_type) + endpoint_id, cluster_id, cluster_type) if cluster is None: return None response = None @@ -305,6 +305,6 @@ class ZHADevice: '{}: {}'.format(ATTR_ARGS, args), '{}: {}'.format(ATTR_CLUSTER_ID, cluster_type), '{}: {}'.format(ATTR_MANUFACTURER, manufacturer), - '{}: {}'.format(ATTR_ENDPOINT_ID, endpooint_id) + '{}: {}'.format(ATTR_ENDPOINT_ID, endpoint_id) ) return response diff --git a/tests/components/zha/test_api.py b/tests/components/zha/test_api.py index 87621ea548b..ad139d81ddf 100644 --- a/tests/components/zha/test_api.py +++ b/tests/components/zha/test_api.py @@ -1,16 +1,15 @@ """Test ZHA API.""" from unittest.mock import Mock import pytest -from homeassistant.const import ATTR_ENTITY_ID from homeassistant.components.switch import DOMAIN from homeassistant.components.zha.api import ( - async_load_api, WS_ENTITIES_BY_IEEE, WS_ENTITY_CLUSTERS, ATTR_IEEE, TYPE, - ID, WS_ENTITY_CLUSTER_ATTRIBUTES, WS_ENTITY_CLUSTER_COMMANDS, + async_load_api, WS_DEVICE_CLUSTERS, ATTR_IEEE, TYPE, + ID, WS_DEVICE_CLUSTER_ATTRIBUTES, WS_DEVICE_CLUSTER_COMMANDS, WS_DEVICES ) from homeassistant.components.zha.core.const import ( ATTR_CLUSTER_ID, ATTR_CLUSTER_TYPE, IN, IEEE, MODEL, NAME, QUIRK_APPLIED, - ATTR_MANUFACTURER + ATTR_MANUFACTURER, ATTR_ENDPOINT_ID ) from .common import async_init_zigpy_device @@ -35,25 +34,11 @@ async def zha_client(hass, config_entry, zha_gateway, hass_ws_client): return await hass_ws_client(hass) -async def test_entities_by_ieee(hass, config_entry, zha_gateway, zha_client): - """Test getting entity refs by ieee address.""" +async def test_device_clusters(hass, config_entry, zha_gateway, zha_client): + """Test getting device cluster info.""" await zha_client.send_json({ ID: 5, - TYPE: WS_ENTITIES_BY_IEEE, - }) - - msg = await zha_client.receive_json() - - assert '00:0d:6f:00:0a:90:69:e7' in msg['result'] - assert len(msg['result']['00:0d:6f:00:0a:90:69:e7']) == 2 - - -async def test_entity_clusters(hass, config_entry, zha_gateway, zha_client): - """Test getting entity cluster info.""" - await zha_client.send_json({ - ID: 5, - TYPE: WS_ENTITY_CLUSTERS, - ATTR_ENTITY_ID: 'switch.fakemanufacturer_fakemodel_0a9069e7_1_6', + TYPE: WS_DEVICE_CLUSTERS, ATTR_IEEE: '00:0d:6f:00:0a:90:69:e7' }) @@ -68,13 +53,13 @@ async def test_entity_clusters(hass, config_entry, zha_gateway, zha_client): assert cluster_info[NAME] == 'OnOff' -async def test_entity_cluster_attributes( +async def test_device_cluster_attributes( hass, config_entry, zha_gateway, zha_client): - """Test getting entity cluster attributes.""" + """Test getting device cluster attributes.""" await zha_client.send_json({ ID: 5, - TYPE: WS_ENTITY_CLUSTER_ATTRIBUTES, - ATTR_ENTITY_ID: 'switch.fakemanufacturer_fakemodel_0a9069e7_1_6', + TYPE: WS_DEVICE_CLUSTER_ATTRIBUTES, + ATTR_ENDPOINT_ID: 1, ATTR_IEEE: '00:0d:6f:00:0a:90:69:e7', ATTR_CLUSTER_ID: 6, ATTR_CLUSTER_TYPE: IN @@ -90,13 +75,13 @@ async def test_entity_cluster_attributes( assert attribute[NAME] is not None -async def test_entity_cluster_commands( +async def test_device_cluster_commands( hass, config_entry, zha_gateway, zha_client): - """Test getting entity cluster commands.""" + """Test getting device cluster commands.""" await zha_client.send_json({ ID: 5, - TYPE: WS_ENTITY_CLUSTER_COMMANDS, - ATTR_ENTITY_ID: 'switch.fakemanufacturer_fakemodel_0a9069e7_1_6', + TYPE: WS_DEVICE_CLUSTER_COMMANDS, + ATTR_ENDPOINT_ID: 1, ATTR_IEEE: '00:0d:6f:00:0a:90:69:e7', ATTR_CLUSTER_ID: 6, ATTR_CLUSTER_TYPE: IN From d795410b27896acd5d0d15c6a23262b443774093 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 12 Feb 2019 21:44:30 +0100 Subject: [PATCH 165/242] Update ordering (#21013) --- .../components/system_health/__init__.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/system_health/__init__.py b/homeassistant/components/system_health/__init__.py index fca433550d7..05e5b76fb5b 100644 --- a/homeassistant/components/system_health/__init__.py +++ b/homeassistant/components/system_health/__init__.py @@ -1,4 +1,9 @@ -"""System health component.""" +""" +System health component. + +For more details about this component, please refer to the documentation at +https://www.home-assistant.io/components/system_health/ +""" import asyncio from collections import OrderedDict import logging @@ -7,15 +12,17 @@ from typing import Callable, Dict import async_timeout import voluptuous as vol -from homeassistant.core import callback -from homeassistant.loader import bind_hass -from homeassistant.helpers.typing import HomeAssistantType, ConfigType from homeassistant.components import websocket_api +from homeassistant.core import callback +from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.loader import bind_hass + +_LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['http'] DOMAIN = 'system_health' + INFO_CALLBACK_TIMEOUT = 5 -_LOGGER = logging.getLogger(__name__) @bind_hass From fe9800e784eae7f5edc20d30f73442d5335433b9 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 12 Feb 2019 22:34:06 +0100 Subject: [PATCH 166/242] Prevent OverflowError in ESPHome integration (#21014) --- homeassistant/components/esphome/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 8d113b6ab9d..1c7d713d827 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -325,6 +325,7 @@ async def _setup_auto_reconnect_logic(hass: HomeAssistantType, # In the future another API will be set up so that the ESP can # notify HA of connectivity directly, but for new we'll use a # really short reconnect interval. + tries = min(tries, 10) # prevent OverflowError wait_time = int(round(min(1.8**tries, 60.0))) _LOGGER.info("Trying to reconnect in %s seconds", wait_time) await asyncio.sleep(wait_time) From b6854a82cf5220a8ca5849efc3d73407b0fddb33 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 12 Feb 2019 22:44:32 +0100 Subject: [PATCH 167/242] Upgrade restrictedpython to 4.0b8 (#21015) --- homeassistant/components/python_script/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/python_script/__init__.py b/homeassistant/components/python_script/__init__.py index 3cfa0696644..3d0952b89fb 100644 --- a/homeassistant/components/python_script/__init__.py +++ b/homeassistant/components/python_script/__init__.py @@ -18,7 +18,7 @@ from homeassistant.loader import bind_hass from homeassistant.util import sanitize_filename import homeassistant.util.dt as dt_util -REQUIREMENTS = ['restrictedpython==4.0b7'] +REQUIREMENTS = ['restrictedpython==4.0b8'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index b2f6237406e..89ebd8da51d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1469,7 +1469,7 @@ recollect-waste==1.0.1 regenmaschine==1.1.0 # homeassistant.components.python_script -restrictedpython==4.0b7 +restrictedpython==4.0b8 # homeassistant.components.idteck_prox rfk101py==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0d2e0cd88bf..43365491182 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -251,7 +251,7 @@ pywebpush==1.6.0 regenmaschine==1.1.0 # homeassistant.components.python_script -restrictedpython==4.0b7 +restrictedpython==4.0b8 # homeassistant.components.rflink rflink==0.0.37 From 7b7720d0eacb17f15347d46ed8aeff4cabb68d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Tue, 12 Feb 2019 22:45:12 +0100 Subject: [PATCH 168/242] Norway air, minor fix (#21016) --- homeassistant/components/air_quality/norway_air.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/air_quality/norway_air.py b/homeassistant/components/air_quality/norway_air.py index d4bfbebe47a..372f3ec079d 100644 --- a/homeassistant/components/air_quality/norway_air.py +++ b/homeassistant/components/air_quality/norway_air.py @@ -51,7 +51,7 @@ async def async_setup_platform(hass, config, async_add_entities, if None in (latitude, longitude): _LOGGER.error("Latitude or longitude not set in Home Assistant config") - return False + return coordinates = { 'lat': str(latitude), @@ -82,7 +82,6 @@ class AirSensor(AirQualityEntity): import metno self._name = name self._api = metno.AirQualityData(coordinates, forecast, session) - self._attrs = {} @property def attribution(self) -> str: From 4d1d22070c86abe82286e79d95a97bcd6cd94a92 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 12 Feb 2019 13:48:11 -0800 Subject: [PATCH 169/242] Bump frontend to 20190212.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 1efda81dfe0..f89596c0c02 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -26,7 +26,7 @@ from homeassistant.loader import bind_hass from .storage import async_setup_frontend_storage -REQUIREMENTS = ['home-assistant-frontend==20190203.0'] +REQUIREMENTS = ['home-assistant-frontend==20190212.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index 89ebd8da51d..4d07cfd4733 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -535,7 +535,7 @@ hole==0.3.0 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190203.0 +home-assistant-frontend==20190212.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 43365491182..1daff29d915 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -116,7 +116,7 @@ hdate==0.8.7 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190203.0 +home-assistant-frontend==20190212.0 # homeassistant.components.homekit_controller homekit==0.12.2 From f1a00cc0f9590bcfad51e72afde3f4627bfc6098 Mon Sep 17 00:00:00 2001 From: rbflurry Date: Tue, 12 Feb 2019 17:18:45 -0500 Subject: [PATCH 170/242] Allow target all timer services using 'entity_id: all' (#21008) --- homeassistant/components/timer/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/timer/__init__.py b/homeassistant/components/timer/__init__.py index b898c577bb2..a4809369d9b 100644 --- a/homeassistant/components/timer/__init__.py +++ b/homeassistant/components/timer/__init__.py @@ -43,11 +43,11 @@ SERVICE_CANCEL = 'cancel' SERVICE_FINISH = 'finish' SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, }) SERVICE_SCHEMA_DURATION = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Optional(ATTR_DURATION, default=timedelta(DEFAULT_DURATION)): cv.time_period, }) From 6fad9e1a0a42785c8b4d0e3c53259289f62deacd Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 12 Feb 2019 14:57:13 -0800 Subject: [PATCH 171/242] RFC: Embed platforms without component for remote component. (#20809) * Embed platforms for remote component. * Update reqs --- homeassistant/components/{remote/demo.py => demo/remote.py} | 0 homeassistant/components/harmony/__init__.py | 5 +++++ .../components/{remote/harmony.py => harmony/remote.py} | 0 homeassistant/components/itach/__init__.py | 5 +++++ .../components/{remote/itach.py => itach/remote.py} | 0 requirements_all.txt | 4 ++-- .../components/{remote/test_demo.py => demo/test_remote.py} | 0 7 files changed, 12 insertions(+), 2 deletions(-) rename homeassistant/components/{remote/demo.py => demo/remote.py} (100%) create mode 100644 homeassistant/components/harmony/__init__.py rename homeassistant/components/{remote/harmony.py => harmony/remote.py} (100%) create mode 100644 homeassistant/components/itach/__init__.py rename homeassistant/components/{remote/itach.py => itach/remote.py} (100%) rename tests/components/{remote/test_demo.py => demo/test_remote.py} (100%) diff --git a/homeassistant/components/remote/demo.py b/homeassistant/components/demo/remote.py similarity index 100% rename from homeassistant/components/remote/demo.py rename to homeassistant/components/demo/remote.py diff --git a/homeassistant/components/harmony/__init__.py b/homeassistant/components/harmony/__init__.py new file mode 100644 index 00000000000..25a33929c1a --- /dev/null +++ b/homeassistant/components/harmony/__init__.py @@ -0,0 +1,5 @@ +"""The harmony component. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/harmony/ +""" diff --git a/homeassistant/components/remote/harmony.py b/homeassistant/components/harmony/remote.py similarity index 100% rename from homeassistant/components/remote/harmony.py rename to homeassistant/components/harmony/remote.py diff --git a/homeassistant/components/itach/__init__.py b/homeassistant/components/itach/__init__.py new file mode 100644 index 00000000000..267370dbcd7 --- /dev/null +++ b/homeassistant/components/itach/__init__.py @@ -0,0 +1,5 @@ +"""The itach component. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/itach/ +""" diff --git a/homeassistant/components/remote/itach.py b/homeassistant/components/itach/remote.py similarity index 100% rename from homeassistant/components/remote/itach.py rename to homeassistant/components/itach/remote.py diff --git a/requirements_all.txt b/requirements_all.txt index 4d07cfd4733..ea8caac47a8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -110,7 +110,7 @@ aiofreepybox==0.0.6 # homeassistant.components.camera.yi aioftp==0.12.0 -# homeassistant.components.remote.harmony +# homeassistant.components.harmony.remote aioharmony==0.1.5 # homeassistant.components.emulated_hue @@ -1082,7 +1082,7 @@ pyirishrail==0.0.2 # homeassistant.components.binary_sensor.iss pyiss==1.0.1 -# homeassistant.components.remote.itach +# homeassistant.components.itach.remote pyitachip2ir==0.0.7 # homeassistant.components.kira diff --git a/tests/components/remote/test_demo.py b/tests/components/demo/test_remote.py similarity index 100% rename from tests/components/remote/test_demo.py rename to tests/components/demo/test_remote.py From 888345e4ffd93424eb4ba14cd98eeb860f4ce98a Mon Sep 17 00:00:00 2001 From: emontnemery Date: Wed, 13 Feb 2019 00:00:54 +0100 Subject: [PATCH 172/242] Fix discovery of audio groups (#20947) * Fix discovery of audio groups * Fix tests * Re-discover * Review comments * Remove failing tests * Update dependencies * Fix test --- homeassistant/components/cast/__init__.py | 2 +- homeassistant/components/cast/media_player.py | 163 +++++++++++++++--- requirements_all.txt | 2 +- tests/components/cast/test_media_player.py | 67 +------ 4 files changed, 149 insertions(+), 85 deletions(-) diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py index 53f5e704019..bc2f52139e2 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -4,7 +4,7 @@ from homeassistant.helpers import config_entry_flow DOMAIN = 'cast' -REQUIREMENTS = ['pychromecast==2.1.0'] +REQUIREMENTS = ['pychromecast==2.5.0'] async def async_setup(hass, config): diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index b80a8ce5e0f..a58bab92e9d 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -57,6 +57,10 @@ ADDED_CAST_DEVICES_KEY = 'cast_added_cast_devices' # Chromecast or receive it through configuration SIGNAL_CAST_DISCOVERED = 'cast_discovered' +# Dispatcher signal fired with a ChromecastInfo every time a Chromecast is +# removed +SIGNAL_CAST_REMOVED = 'cast_removed' + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_HOST): cv.string, vol.Optional(CONF_IGNORE_CEC, default=[]): @@ -73,6 +77,7 @@ class ChromecastInfo: host = attr.ib(type=str) port = attr.ib(type=int) + service = attr.ib(type=Optional[str], default=None) uuid = attr.ib(type=Optional[str], converter=attr.converters.optional(str), default=None) # always convert UUID to string if not None manufacturer = attr.ib(type=str, default='') @@ -105,13 +110,15 @@ def _fill_out_missing_chromecast_info(info: ChromecastInfo) -> ChromecastInfo: # Fill out missing information via HTTP dial. from pychromecast import dial - http_device_status = dial.get_device_status(info.host) + http_device_status = dial.get_device_status( + info.host, services=[info.service], + zconf=ChromeCastZeroconf.get_zeroconf()) if http_device_status is None: # HTTP dial didn't give us any new information. return info return ChromecastInfo( - host=info.host, port=info.port, + service=info.service, host=info.host, port=info.port, uuid=(info.uuid or http_device_status.uuid), friendly_name=(info.friendly_name or http_device_status.friendly_name), manufacturer=(info.manufacturer or http_device_status.manufacturer), @@ -122,7 +129,6 @@ def _fill_out_missing_chromecast_info(info: ChromecastInfo) -> ChromecastInfo: def _discover_chromecast(hass: HomeAssistantType, info: ChromecastInfo): if info in hass.data[KNOWN_CHROMECAST_INFO_KEY]: _LOGGER.debug("Discovered previous chromecast %s", info) - return # Either discovered completely new chromecast or a "moved" one. info = _fill_out_missing_chromecast_info(info) @@ -138,6 +144,29 @@ def _discover_chromecast(hass: HomeAssistantType, info: ChromecastInfo): dispatcher_send(hass, SIGNAL_CAST_DISCOVERED, info) +def _remove_chromecast(hass: HomeAssistantType, info: ChromecastInfo): + # Removed chromecast + _LOGGER.debug("Removed chromecast %s", info) + + dispatcher_send(hass, SIGNAL_CAST_REMOVED, info) + + +class ChromeCastZeroconf: + """Class to hold a zeroconf instance.""" + + __zconf = None + + @classmethod + def set_zeroconf(cls, zconf): + """Set zeroconf.""" + cls.__zconf = zconf + + @classmethod + def get_zeroconf(cls): + """Get zeroconf.""" + return cls.__zconf + + def _setup_internal_discovery(hass: HomeAssistantType) -> None: """Set up the pychromecast internal discovery.""" if INTERNAL_DISCOVERY_RUNNING_KEY not in hass.data: @@ -149,10 +178,22 @@ def _setup_internal_discovery(hass: HomeAssistantType) -> None: import pychromecast - def internal_callback(name): + def internal_add_callback(name): """Handle zeroconf discovery of a new chromecast.""" mdns = listener.services[name] _discover_chromecast(hass, ChromecastInfo( + service=name, + host=mdns[0], + port=mdns[1], + uuid=mdns[2], + model_name=mdns[3], + friendly_name=mdns[4], + )) + + def internal_remove_callback(name, mdns): + """Handle zeroconf discovery of a removed chromecast.""" + _remove_chromecast(hass, ChromecastInfo( + service=name, host=mdns[0], port=mdns[1], uuid=mdns[2], @@ -161,7 +202,9 @@ def _setup_internal_discovery(hass: HomeAssistantType) -> None: )) _LOGGER.debug("Starting internal pychromecast discovery.") - listener, browser = pychromecast.start_discovery(internal_callback) + listener, browser = pychromecast.start_discovery(internal_add_callback, + internal_remove_callback) + ChromeCastZeroconf.set_zeroconf(browser.zc) def stop_discovery(event): """Stop discovery of new chromecasts.""" @@ -327,12 +370,18 @@ class CastDevice(MediaPlayerDevice): """Initialize the cast device.""" import pychromecast # noqa: pylint: disable=unused-import self._cast_info = cast_info # type: ChromecastInfo + self.services = None + if cast_info.service: + self.services = set() + self.services.add(cast_info.service) self._chromecast = None # type: Optional[pychromecast.Chromecast] self.cast_status = None self.media_status = None self.media_status_received = None self._available = False # type: bool self._status_listener = None # type: Optional[CastStatusListener] + self._add_remove_handler = None + self._del_remove_handler = None async def async_added_to_hass(self): """Create chromecast object when added to hass.""" @@ -345,15 +394,36 @@ class CastDevice(MediaPlayerDevice): if self._cast_info.uuid != discover.uuid: # Discovered is not our device. return + if self.services is None: + _LOGGER.warning( + "[%s %s (%s:%s)] Received update for manually added Cast", + self.entity_id, self._cast_info.friendly_name, + self._cast_info.host, self._cast_info.port) + return _LOGGER.debug("Discovered chromecast with same UUID: %s", discover) self.hass.async_create_task(self.async_set_cast_info(discover)) + def async_cast_removed(discover: ChromecastInfo): + """Handle removal of Chromecast.""" + if self._cast_info.uuid is None: + # We can't handle empty UUIDs + return + if self._cast_info.uuid != discover.uuid: + # Removed is not our device. + return + _LOGGER.debug("Removed chromecast with same UUID: %s", discover) + self.hass.async_create_task(self.async_del_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._add_remove_handler = async_dispatcher_connect( + self.hass, SIGNAL_CAST_DISCOVERED, + async_cast_discovered) + self._del_remove_handler = async_dispatcher_connect( + self.hass, SIGNAL_CAST_REMOVED, + async_cast_removed) self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_stop) self.hass.async_create_task(self.async_set_cast_info(self._cast_info)) @@ -364,27 +434,52 @@ class CastDevice(MediaPlayerDevice): # Remove the entity from the added casts so that it can dynamically # be re-added again. self.hass.data[ADDED_CAST_DEVICES_KEY].remove(self._cast_info.uuid) + if self._add_remove_handler: + self._add_remove_handler() + if self._del_remove_handler: + self._del_remove_handler() async def async_set_cast_info(self, cast_info): """Set the cast information and set up the chromecast object.""" import pychromecast - old_cast_info = self._cast_info self._cast_info = cast_info + if self.services is not None: + if cast_info.service not in self.services: + _LOGGER.debug("[%s %s (%s:%s)] Got new service: %s (%s)", + self.entity_id, self._cast_info.friendly_name, + self._cast_info.host, self._cast_info.port, + cast_info.service, self.services) + + self.services.add(cast_info.service) + if self._chromecast is not None: - if old_cast_info.host_port == cast_info.host_port: - _LOGGER.debug("No connection related update: %s", - cast_info.host_port) - return - await self._async_disconnect() + # Only setup the chromecast once, added elements to services + # will automatically be picked up. + return # pylint: disable=protected-access - _LOGGER.debug("Connecting to cast device %s", cast_info) - chromecast = await self.hass.async_add_job( - pychromecast._get_chromecast_from_host, ( - cast_info.host, cast_info.port, cast_info.uuid, - cast_info.model_name, cast_info.friendly_name - )) + if self.services is None: + _LOGGER.debug( + "[%s %s (%s:%s)] Connecting to cast device by host %s", + self.entity_id, self._cast_info.friendly_name, + self._cast_info.host, self._cast_info.port, cast_info) + chromecast = await self.hass.async_add_job( + pychromecast._get_chromecast_from_host, ( + cast_info.host, cast_info.port, cast_info.uuid, + cast_info.model_name, cast_info.friendly_name + )) + else: + _LOGGER.debug( + "[%s %s (%s:%s)] Connecting to cast device by service %s", + self.entity_id, self._cast_info.friendly_name, + self._cast_info.host, self._cast_info.port, self.services) + chromecast = await self.hass.async_add_job( + pychromecast._get_chromecast_from_service, ( + self.services, ChromeCastZeroconf.get_zeroconf(), + cast_info.uuid, cast_info.model_name, + cast_info.friendly_name + )) self._chromecast = chromecast self._status_listener = CastStatusListener(self, chromecast) # Initialise connection status as connected because we can only @@ -394,15 +489,27 @@ class CastDevice(MediaPlayerDevice): self._available = True self.cast_status = chromecast.status self.media_status = chromecast.media_controller.status - _LOGGER.debug("Connection successful!") + _LOGGER.debug("[%s %s (%s:%s)] Connection successful!", + self.entity_id, self._cast_info.friendly_name, + self._cast_info.host, self._cast_info.port) self.async_schedule_update_ha_state() + async def async_del_cast_info(self, cast_info): + """Remove the service.""" + self.services.discard(cast_info.service) + _LOGGER.debug("[%s %s (%s:%s)] Remove service: %s (%s)", + self.entity_id, self._cast_info.friendly_name, + self._cast_info.host, self._cast_info.port, + cast_info.service, self.services) + 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 chromecast socket.") + _LOGGER.debug("[%s %s (%s:%s)] Disconnecting from chromecast socket.", + self.entity_id, self._cast_info.friendly_name, + self._cast_info.host, self._cast_info.port) self._available = False self.async_schedule_update_ha_state() @@ -439,8 +546,11 @@ class CastDevice(MediaPlayerDevice): from pychromecast.socket_client import CONNECTION_STATUS_CONNECTED, \ CONNECTION_STATUS_DISCONNECTED - _LOGGER.debug("Received cast device connection status: %s", - connection_status.status) + _LOGGER.debug( + "[%s %s (%s:%s)] Received cast device connection status: %s", + self.entity_id, self._cast_info.friendly_name, + self._cast_info.host, self._cast_info.port, + connection_status.status) if connection_status.status == CONNECTION_STATUS_DISCONNECTED: self._available = False self._invalidate() @@ -452,8 +562,11 @@ class CastDevice(MediaPlayerDevice): # Connection status callbacks happen often when disconnected. # Only update state when availability changed to put less pressure # on state machine. - _LOGGER.debug("Cast device availability changed: %s", - connection_status.status) + _LOGGER.debug( + "[%s %s (%s:%s)] Cast device availability changed: %s", + self.entity_id, self._cast_info.friendly_name, + self._cast_info.host, self._cast_info.port, + connection_status.status) self._available = new_available self.schedule_update_ha_state() diff --git a/requirements_all.txt b/requirements_all.txt index ea8caac47a8..ba09538df88 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -956,7 +956,7 @@ pycfdns==0.0.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==2.1.0 +pychromecast==2.5.0 # homeassistant.components.media_player.cmus pycmus==0.1.1 diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 2e0fe9d1529..b5d6220904f 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -12,8 +12,7 @@ from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.typing import HomeAssistantType from homeassistant.components.cast.media_player import ChromecastInfo from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.helpers.dispatcher import async_dispatcher_connect, \ - async_dispatcher_send +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.components.cast import media_player as cast from homeassistant.setup import async_setup_component @@ -44,7 +43,7 @@ def get_fake_chromecast_info(host='192.168.178.42', port=8009, uuid: Optional[UUID] = FakeUUID): """Generate a Fake ChromecastInfo with the specified arguments.""" return ChromecastInfo(host=host, port=port, uuid=uuid, - friendly_name="Speaker") + friendly_name="Speaker", service='the-service') async def async_setup_cast(hass, config=None, discovery_info=None): @@ -64,9 +63,10 @@ async def async_setup_cast_internal_discovery(hass, config=None, discovery_info=None): """Set up the cast platform and the discovery.""" listener = MagicMock(services={}) + browser = MagicMock(zc={}) with patch('pychromecast.start_discovery', - return_value=(listener, None)) as start_discovery: + return_value=(listener, browser)) as start_discovery: add_entities = await async_setup_cast(hass, config, discovery_info) await hass.async_block_till_done() await hass.async_block_till_done() @@ -120,8 +120,10 @@ def test_start_discovery_called_once(hass): @asyncio.coroutine def test_stop_discovery_called_on_stop(hass): """Test pychromecast.stop_discovery called on shutdown.""" + browser = MagicMock(zc={}) + with patch('pychromecast.start_discovery', - return_value=(None, 'the-browser')) as start_discovery: + return_value=(None, browser)) as start_discovery: # start_discovery should be called with empty config yield from async_setup_cast(hass, {}) @@ -132,38 +134,16 @@ def test_stop_discovery_called_on_stop(hass): hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) yield from hass.async_block_till_done() - stop_discovery.assert_called_once_with('the-browser') + stop_discovery.assert_called_once_with(browser) with patch('pychromecast.start_discovery', - return_value=(None, 'the-browser')) as start_discovery: + return_value=(None, browser)) as start_discovery: # start_discovery should be called again on re-startup yield from async_setup_cast(hass) assert start_discovery.call_count == 1 -async def test_internal_discovery_callback_only_generates_once(hass): - """Test discovery only called once per device.""" - discover_cast, _ = await async_setup_cast_internal_discovery(hass) - info = get_fake_chromecast_info() - - signal = MagicMock() - async_dispatcher_connect(hass, 'cast_discovered', signal) - - with patch('pychromecast.dial.get_device_status', return_value=None): - # discovering a cast device should call the dispatcher - discover_cast('the-service', info) - await hass.async_block_till_done() - discover = signal.mock_calls[0][1][0] - assert discover == info - signal.reset_mock() - - # discovering it a second time shouldn't - discover_cast('the-service', info) - await hass.async_block_till_done() - assert signal.call_count == 0 - - async def test_internal_discovery_callback_fill_out(hass): """Test internal discovery automatically filling out information.""" import pychromecast # imports mock pychromecast @@ -323,35 +303,6 @@ async def test_entity_media_states(hass: HomeAssistantType): assert state.state == 'unknown' -async def test_switched_host(hass: HomeAssistantType): - """Test cast device listens for changed hosts and disconnects old cast.""" - 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, _ = await async_setup_media_player_cast(hass, full_info) - - chromecast2 = get_fake_chromecast(info) - with patch('pychromecast._get_chromecast_from_host', - return_value=chromecast2) as get_chromecast: - async_dispatcher_send(hass, cast.SIGNAL_CAST_DISCOVERED, full_info) - await hass.async_block_till_done() - assert get_chromecast.call_count == 0 - - changed = attr.evolve(full_info, friendly_name='Speaker 2') - async_dispatcher_send(hass, cast.SIGNAL_CAST_DISCOVERED, changed) - await hass.async_block_till_done() - assert get_chromecast.call_count == 0 - - changed = attr.evolve(changed, host='host2') - async_dispatcher_send(hass, cast.SIGNAL_CAST_DISCOVERED, changed) - await hass.async_block_till_done() - assert get_chromecast.call_count == 1 - assert chromecast.disconnect.call_count == 1 - - async def test_disconnect_on_stop(hass: HomeAssistantType): """Test cast device disconnects socket on stop.""" info = get_fake_chromecast_info() From 561ff33641169c220efd42cd96a77e85accde620 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Tue, 12 Feb 2019 20:37:39 -0500 Subject: [PATCH 173/242] Update entity state when ZHA device becomes available (#20993) * correctly update device entity state * update state when device becomes available * constants * review comments --- homeassistant/components/zha/core/device.py | 2 +- homeassistant/components/zha/core/gateway.py | 2 +- homeassistant/components/zha/device_entity.py | 15 +++++++++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 0119b651675..7c972988e9c 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -173,7 +173,7 @@ class ZHADevice: await self._execute_listener_tasks('async_configure') _LOGGER.debug('%s: completed configuration', self.name) - async def async_initialize(self, from_cache): + async def async_initialize(self, from_cache=False): """Initialize listeners.""" _LOGGER.debug('%s: started initialization', self.name) await self._execute_listener_tasks('async_initialize', from_cache) diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index dca6b60ccc5..16d834bdecb 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -138,7 +138,7 @@ class ZHAGateway: )) await asyncio.gather(*endpoint_tasks) - await zha_device.async_initialize(not is_new_join) + await zha_device.async_initialize(from_cache=(not is_new_join)) discovery_tasks = [] for discovery_info in discovery_infos: diff --git a/homeassistant/components/zha/device_entity.py b/homeassistant/components/zha/device_entity.py index cf2156b76c3..e8b765a07a6 100644 --- a/homeassistant/components/zha/device_entity.py +++ b/homeassistant/components/zha/device_entity.py @@ -8,6 +8,7 @@ https://home-assistant.io/components/zha/ import logging import time +from homeassistant.core import callback from homeassistant.util import slugify from .entity import ZhaEntity from .const import LISTENER_BATTERY, SIGNAL_STATE_ATTR @@ -30,6 +31,9 @@ BATTERY_SIZES = { 255: 'Unknown' } +STATE_ONLINE = 'online' +STATE_OFFLINE = 'offline' + class ZhaDeviceEntity(ZhaEntity): """A base class for ZHA devices.""" @@ -108,13 +112,20 @@ class ZhaDeviceEntity(ZhaEntity): difference = time.time() - self._zha_device.last_seen if difference > self._keepalive_interval: self._zha_device.update_available(False) - self._state = None else: self._zha_device.update_available(True) - self._state = 'online' if self._battery_listener: await self.async_get_latest_battery_reading() + @callback + def async_set_available(self, available): + """Set entity availability.""" + if available: + self._state = STATE_ONLINE + else: + self._state = STATE_OFFLINE + super().async_set_available(available) + async def _async_init_battery_values(self): """Get initial battery level and battery info from listener cache.""" battery_size = await self._battery_listener.get_attribute_value( From d1950cd75cc70c36ad4228c6ca4ad5f64b67aa7b Mon Sep 17 00:00:00 2001 From: Jef D Date: Wed, 13 Feb 2019 03:51:10 +0100 Subject: [PATCH 174/242] Update co2signal==0.4.2 to fix #20805 (#21022) Update co2signal==0.4.2 to fix #20805 --- homeassistant/components/sensor/co2signal.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/co2signal.py b/homeassistant/components/sensor/co2signal.py index ad46f3b494f..7b4cd67bd70 100644 --- a/homeassistant/components/sensor/co2signal.py +++ b/homeassistant/components/sensor/co2signal.py @@ -15,7 +15,7 @@ from homeassistant.helpers.entity import Entity CONF_COUNTRY_CODE = "country_code" -REQUIREMENTS = ['co2signal==0.4.1'] +REQUIREMENTS = ['co2signal==0.4.2'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index ba09538df88..f4ac7f34125 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -262,7 +262,7 @@ caldav==0.5.0 ciscosparkapi==0.4.2 # homeassistant.components.sensor.co2signal -co2signal==0.4.1 +co2signal==0.4.2 # homeassistant.components.coinbase coinbase==2.1.0 From 6bbc663d0bc4cec676fc40b4813b42481b18e25c Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 13 Feb 2019 03:52:02 +0100 Subject: [PATCH 175/242] Add missing helpers (#21021) --- docs/source/api/helpers.rst | 175 +++++++++++++++++++++++++++++++++++- requirements_docs.txt | 2 +- 2 files changed, 173 insertions(+), 4 deletions(-) diff --git a/docs/source/api/helpers.rst b/docs/source/api/helpers.rst index af186fb1341..28f4059d60d 100644 --- a/docs/source/api/helpers.rst +++ b/docs/source/api/helpers.rst @@ -4,6 +4,23 @@ homeassistant.helpers package Submodules ---------- +homeassistant.helpers.aiohttp_client module +------------------------------------------- + +.. automodule:: homeassistant.helpers.aiohttp_client + :members: + :undoc-members: + :show-inheritance: + + +homeassistant.helpers.area_registry module +------------------------------------------ + +.. automodule:: homeassistant.helpers.area_registry + :members: + :undoc-members: + :show-inheritance: + homeassistant.helpers.condition module -------------------------------------- @@ -12,6 +29,14 @@ homeassistant.helpers.condition module :undoc-members: :show-inheritance: +homeassistant.helpers.config_entry_flow module +---------------------------------------------- + +.. automodule:: homeassistant.helpers.config_entry_flow + :members: + :undoc-members: + :show-inheritance: + homeassistant.helpers.config_validation module ---------------------------------------------- @@ -20,6 +45,30 @@ homeassistant.helpers.config_validation module :undoc-members: :show-inheritance: +homeassistant.helpers.data_entry_flow module +-------------------------------------------- + +.. automodule:: homeassistant.helpers.data_entry_flow + :members: + :undoc-members: + :show-inheritance: + +homeassistant.helpers.deprecation module +---------------------------------------- + +.. automodule:: homeassistant.helpers.depracation + :members: + :undoc-members: + :show-inheritance: + +homeassistant.helpers.device_registry module +-------------------------------------------- + +.. automodule:: homeassistant.helpers.device_registry + :members: + :undoc-members: + :show-inheritance: + homeassistant.helpers.discovery module -------------------------------------- @@ -28,6 +77,14 @@ homeassistant.helpers.discovery module :undoc-members: :show-inheritance: +homeassistant.helpers.dispatcher module +--------------------------------------- + +.. automodule:: homeassistant.helpers.dispatcher + :members: + :undoc-members: + :show-inheritance: + homeassistant.helpers.entity module ----------------------------------- @@ -44,6 +101,38 @@ homeassistant.helpers.entity_component module :undoc-members: :show-inheritance: +homeassistant.helpers.entity_platform module +-------------------------------------------- + +.. automodule:: homeassistant.helpers.entity_platform + :members: + :undoc-members: + :show-inheritance: + +homeassistant.helpers.entity_registry module +-------------------------------------------- + +.. automodule:: homeassistant.helpers.entity_registry + :members: + :undoc-members: + :show-inheritance: + +homeassistant.helpers.entity_values module +------------------------------------------ + +.. automodule:: homeassistant.helpers.entity_values + :members: + :undoc-members: + :show-inheritance: + +homeassistant.helpers.entityfilter module +----------------------------------------- + +.. automodule:: homeassistant.helpers.entityfilter + :members: + :undoc-members: + :show-inheritance: + homeassistant.helpers.event module ---------------------------------- @@ -52,10 +141,26 @@ homeassistant.helpers.event module :undoc-members: :show-inheritance: -homeassistant.helpers.event_decorators module ---------------------------------------------- +homeassistant.helpers.icon module +--------------------------------- -.. automodule:: homeassistant.helpers.event_decorators +.. automodule:: homeassistant.helpers.icon + :members: + :undoc-members: + :show-inheritance: + +homeassistant.helpers.intent module +----------------------------------- + +.. automodule:: homeassistant.helpers.intent + :members: + :undoc-members: + :show-inheritance: + +homeassistant.helpers.json module +--------------------------------- + +.. automodule:: homeassistant.helpers.json :members: :undoc-members: :show-inheritance: @@ -68,6 +173,22 @@ homeassistant.helpers.location module :undoc-members: :show-inheritance: +homeassistant.helpers.logging module +------------------------------------ + +.. automodule:: homeassistant.helpers.logging + :members: + :undoc-members: + :show-inheritance: + +homeassistant.helpers.restore_state module +------------------------------------------ + +.. automodule:: homeassistant.helpers.restore_state + :members: + :undoc-members: + :show-inheritance: + homeassistant.helpers.script module ----------------------------------- @@ -84,6 +205,14 @@ homeassistant.helpers.service module :undoc-members: :show-inheritance: +homeassistant.helpers.signal module +----------------------------------- + +.. automodule:: homeassistant.helpers.signal + :members: + :undoc-members: + :show-inheritance: + homeassistant.helpers.state module ---------------------------------- @@ -92,6 +221,38 @@ homeassistant.helpers.state module :undoc-members: :show-inheritance: +homeassistant.helpers.storage module +------------------------------------ + +.. automodule:: homeassistant.helpers.storage + :members: + :undoc-members: + :show-inheritance: + +homeassistant.helpers.sun module +-------------------------------- + +.. automodule:: homeassistant.helpers.sun + :members: + :undoc-members: + :show-inheritance: + +homeassistant.helpers.system_info module +---------------------------------------- + +.. automodule:: homeassistant.helpers.system_info + :members: + :undoc-members: + :show-inheritance: + +homeassistant.helpers.temperature module +---------------------------------------- + +.. automodule:: homeassistant.helpers.temperature + :members: + :undoc-members: + :show-inheritance: + homeassistant.helpers.template module ------------------------------------- @@ -100,6 +261,14 @@ homeassistant.helpers.template module :undoc-members: :show-inheritance: +homeassistant.helpers.translation module +----------------------------------------- + +.. automodule:: homeassistant.helpers.translation + :members: + :undoc-members: + :show-inheritance: + homeassistant.helpers.typing module ----------------------------------- diff --git a/requirements_docs.txt b/requirements_docs.txt index 73a104b1ffe..1efe929d666 100644 --- a/requirements_docs.txt +++ b/requirements_docs.txt @@ -1,3 +1,3 @@ -Sphinx==1.8.3 +Sphinx==1.8.4 sphinx-autodoc-typehints==1.6.0 sphinx-autodoc-annotation==1.0.post1 \ No newline at end of file From d037359bda92c721d7ecdab36241782fdb936752 Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Tue, 12 Feb 2019 22:35:20 -0600 Subject: [PATCH 176/242] Add lock config entry unload support. (#21025) --- homeassistant/components/lock/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py index c5c1ce1339d..71c838679fb 100644 --- a/homeassistant/components/lock/__init__.py +++ b/homeassistant/components/lock/__init__.py @@ -85,6 +85,11 @@ async def async_setup_entry(hass, entry): return await hass.data[DOMAIN].async_setup_entry(entry) +async def async_unload_entry(hass, entry): + """Unload a config entry.""" + return await hass.data[DOMAIN].async_unload_entry(entry) + + class LockDevice(Entity): """Representation of a lock.""" From 8a6235fdacf03b74e4754ec0969d4c6bc0866d61 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 12 Feb 2019 22:57:53 -0700 Subject: [PATCH 177/242] Bump aioambient to 0.1.1 (#21024) * Bump aioambient to 0.1.1 * Requirements --- homeassistant/components/ambient_station/__init__.py | 6 +++--- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index bbde0c84972..71d946b4bf5 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -25,7 +25,7 @@ from .const import ( ATTR_LAST_DATA, CONF_APP_KEY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE, TYPE_BINARY_SENSOR, TYPE_SENSOR) -REQUIREMENTS = ['aioambient==0.1.0'] +REQUIREMENTS = ['aioambient==0.1.1'] _LOGGER = logging.getLogger(__name__) DATA_CONFIG = 'config' @@ -257,7 +257,7 @@ async def async_setup(hass, config): async def async_setup_entry(hass, config_entry): """Set up the Ambient PWS as config entry.""" from aioambient import Client - from aioambient.errors import WebsocketConnectionError + from aioambient.errors import WebsocketError session = aiohttp_client.async_get_clientsession(hass) @@ -270,7 +270,7 @@ async def async_setup_entry(hass, config_entry): hass.data[DOMAIN][DATA_CONFIG].get(CONF_MONITORED_CONDITIONS, [])) hass.loop.create_task(ambient.ws_connect()) hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = ambient - except WebsocketConnectionError as err: + except WebsocketError as err: _LOGGER.error('Config entry failed: %s', err) raise ConfigEntryNotReady diff --git a/requirements_all.txt b/requirements_all.txt index f4ac7f34125..2d660e732a1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -90,7 +90,7 @@ abodepy==0.15.0 afsapi==0.0.4 # homeassistant.components.ambient_station -aioambient==0.1.0 +aioambient==0.1.1 # homeassistant.components.asuswrt aioasuswrt==1.1.20 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1daff29d915..9c7bce22a86 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -31,7 +31,7 @@ PyTransportNSW==0.1.1 YesssSMS==0.2.3 # homeassistant.components.ambient_station -aioambient==0.1.0 +aioambient==0.1.1 # homeassistant.components.device_tracker.automatic aioautomatic==0.6.5 From 8db8a587631c8e8f0c40f81a0e4a3135c4d72720 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 13 Feb 2019 12:30:37 +0100 Subject: [PATCH 178/242] Upgrade sqlalchemy to 1.2.17 (#21020) * Upgrade sqlalchemy to 1.2.17 * Update requirements_all.txt * Update requirements_test_all.txt * Run script again --- homeassistant/components/recorder/__init__.py | 2 +- homeassistant/components/sensor/sql.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index eb97d197e3e..cafae2b0b08 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -33,7 +33,7 @@ from . import migration, purge from .const import DATA_INSTANCE from .util import session_scope -REQUIREMENTS = ['sqlalchemy==1.2.16'] +REQUIREMENTS = ['sqlalchemy==1.2.17'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/sql.py b/homeassistant/components/sensor/sql.py index 136e3cac23b..f780158dd4e 100644 --- a/homeassistant/components/sensor/sql.py +++ b/homeassistant/components/sensor/sql.py @@ -20,7 +20,7 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['sqlalchemy==1.2.16'] +REQUIREMENTS = ['sqlalchemy==1.2.17'] CONF_COLUMN_NAME = 'column' CONF_QUERIES = 'queries' diff --git a/requirements_all.txt b/requirements_all.txt index 2d660e732a1..c9f6b7858b3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1596,7 +1596,7 @@ spotipy-homeassistant==2.4.4.dev1 # homeassistant.components.recorder # homeassistant.components.sensor.sql -sqlalchemy==1.2.16 +sqlalchemy==1.2.17 # homeassistant.components.sensor.srp_energy srpenergy==1.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9c7bce22a86..726d471f7bc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -276,7 +276,7 @@ somecomfort==0.5.2 # homeassistant.components.recorder # homeassistant.components.sensor.sql -sqlalchemy==1.2.16 +sqlalchemy==1.2.17 # homeassistant.components.sensor.srp_energy srpenergy==1.0.5 From d692251e62449840a2a5f31f6aee983198bbbcc2 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Wed, 13 Feb 2019 08:52:29 -0500 Subject: [PATCH 179/242] Run tasks when ZHA devices become available (#20998) * use tasks for message interception * update available handling * review comments and cleaned up check * review comments --- homeassistant/components/zha/__init__.py | 12 ++++++++---- homeassistant/components/zha/core/gateway.py | 12 ++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index ae08b2cac40..b8ef5c40838 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -152,10 +152,14 @@ async def async_setup_entry(hass, config_entry): def handle_message(sender, is_reply, profile, cluster, src_ep, dst_ep, tsn, command_id, args): """Handle message from a device.""" - if sender.last_seen is None and not sender.initializing: - if sender.ieee in zha_gateway.devices: - device = zha_gateway.devices[sender.ieee] - device.update_available(True) + if not sender.initializing and sender.ieee in zha_gateway.devices and \ + not zha_gateway.devices[sender.ieee].available: + hass.async_create_task( + zha_gateway.async_device_became_available( + sender, is_reply, profile, cluster, src_ep, dst_ep, tsn, + command_id, args + ) + ) return sender.handle_message( is_reply, profile, cluster, src_ep, dst_ep, tsn, command_id, args) diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 16d834bdecb..02ed1d73699 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -126,6 +126,18 @@ class ZHAGateway: self._devices[zigpy_device.ieee] = zha_device return zha_device + async def async_device_became_available( + self, sender, is_reply, profile, cluster, src_ep, dst_ep, tsn, + command_id, args): + """Handle tasks when a device becomes available.""" + self.async_update_device(sender) + + def async_update_device(self, sender): + """Update device that has just become available.""" + if sender.ieee in self.devices: + device = self.devices[sender.ieee] + device.update_available(True) + async def async_device_initialized(self, device, is_new_join): """Handle device joined and basic information discovered (async).""" zha_device = await self._get_or_create_device(device) From 136b1e1f6cf164a5ed40890a1c280a14d3cf9baa Mon Sep 17 00:00:00 2001 From: Colby Rome Date: Wed, 13 Feb 2019 11:14:59 -0500 Subject: [PATCH 180/242] Fix broken links to code examples (#21039) --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3bc284627fc..53cc6960fc3 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -27,5 +27,5 @@ If the code communicates with devices, web services, or third-party tools: If the code does not interact with devices: - [ ] Tests have been added to verify that the new code works. -[ex-requir]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/keyboard.py#L14 -[ex-import]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/keyboard.py#L54 +[ex-requir]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/keyboard/__init__.py#L14 +[ex-import]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/keyboard/__init__.py#L23 From 67780dfb4ec1a752ad187663a1a6a73be3b0664e Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Wed, 13 Feb 2019 08:47:38 -0800 Subject: [PATCH 181/242] Update scan interval to 5 minutes. (#21041) --- homeassistant/components/blink/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/blink/__init__.py b/homeassistant/components/blink/__init__.py index 82815d11a6e..bcf95ee2055 100644 --- a/homeassistant/components/blink/__init__.py +++ b/homeassistant/components/blink/__init__.py @@ -29,7 +29,7 @@ DEFAULT_BRAND = 'Blink' DEFAULT_ATTRIBUTION = "Data provided by immedia-semi.com" SIGNAL_UPDATE_BLINK = "blink_update" -DEFAULT_SCAN_INTERVAL = timedelta(seconds=60) +DEFAULT_SCAN_INTERVAL = timedelta(seconds=300) TYPE_CAMERA_ARMED = 'motion_enabled' TYPE_MOTION_DETECTED = 'motion_detected' From 22af9707ada33eae69c7a93cea09f0e3b83375f0 Mon Sep 17 00:00:00 2001 From: emontnemery Date: Wed, 13 Feb 2019 20:58:46 +0100 Subject: [PATCH 182/242] Add support for device_class to MQTT cover (#21044) --- homeassistant/components/mqtt/cover.py | 14 +++++++++---- tests/components/mqtt/test_cover.py | 28 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 75fae0e9c15..829be266b09 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -10,8 +10,8 @@ import voluptuous as vol from homeassistant.components import cover, mqtt from homeassistant.components.cover import ( - ATTR_POSITION, ATTR_TILT_POSITION, SUPPORT_CLOSE, SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, SUPPORT_OPEN_TILT, SUPPORT_SET_POSITION, + ATTR_POSITION, ATTR_TILT_POSITION, DEVICE_CLASSES_SCHEMA, SUPPORT_CLOSE, + SUPPORT_CLOSE_TILT, SUPPORT_OPEN, SUPPORT_OPEN_TILT, SUPPORT_SET_POSITION, SUPPORT_SET_TILT_POSITION, SUPPORT_STOP, SUPPORT_STOP_TILT, CoverDevice) from homeassistant.components.mqtt import ( ATTR_DISCOVERY_HASH, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, @@ -20,8 +20,8 @@ from homeassistant.components.mqtt import ( from homeassistant.components.mqtt.discovery import ( MQTT_DISCOVERY_NEW, clear_discovery_hash) from homeassistant.const import ( - CONF_DEVICE, CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE, STATE_CLOSED, - STATE_OPEN, STATE_UNKNOWN) + CONF_DEVICE, CONF_DEVICE_CLASS, CONF_NAME, CONF_OPTIMISTIC, + CONF_VALUE_TEMPLATE, STATE_CLOSED, STATE_OPEN, STATE_UNKNOWN) from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv @@ -120,6 +120,7 @@ PLATFORM_SCHEMA = vol.All(mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({ default=DEFAULT_TILT_INVERT_STATE): cv.boolean, vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, }).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema).extend( mqtt.MQTT_JSON_ATTRS_SCHEMA.schema), validate_options) @@ -328,6 +329,11 @@ class MqttCover(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, """Return current position of cover tilt.""" return self._tilt_value + @property + def device_class(self): + """Return the class of this sensor.""" + return self._config.get(CONF_DEVICE_CLASS) + @property def supported_features(self): """Flag supported features.""" diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index 343bb3643c6..47681e0de10 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -1051,6 +1051,34 @@ class TestCoverMQTT(unittest.TestCase): state = self.hass.states.get('cover.test') assert STATE_UNAVAILABLE == state.state + def test_valid_device_class(self): + """Test the setting of a valid sensor class.""" + assert setup_component(self.hass, cover.DOMAIN, { + cover.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'device_class': 'garage', + 'state_topic': 'test-topic', + } + }) + + state = self.hass.states.get('cover.test') + assert 'garage' == state.attributes.get('device_class') + + def test_invalid_device_class(self): + """Test the setting of an invalid sensor class.""" + assert setup_component(self.hass, cover.DOMAIN, { + cover.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'device_class': 'abc123', + 'state_topic': 'test-topic', + } + }) + + state = self.hass.states.get('cover.test') + assert state is None + async def test_setting_attribute_via_mqtt_json_message(hass, mqtt_mock): """Test the setting of attribute via MQTT with JSON payload.""" From 127c55e0c196dc6ffd321c1ad28b533f636faf79 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 13 Feb 2019 21:21:14 +0100 Subject: [PATCH 183/242] Update file header (#21023) * Update file header * Update file header * Update file header * Update file header * Update file header * Fix lint issues --- homeassistant/components/abode/__init__.py | 7 +- .../components/abode/alarm_control_panel.py | 7 +- .../components/abode/binary_sensor.py | 10 +-- homeassistant/components/abode/camera.py | 8 +-- homeassistant/components/abode/cover.py | 8 +-- homeassistant/components/abode/light.py | 7 +- homeassistant/components/abode/lock.py | 8 +-- homeassistant/components/abode/sensor.py | 7 +- homeassistant/components/abode/switch.py | 10 +-- homeassistant/components/ads/__init__.py | 7 +- homeassistant/components/ads/binary_sensor.py | 7 +- homeassistant/components/ads/light.py | 8 +-- homeassistant/components/ads/sensor.py | 7 +- homeassistant/components/ads/switch.py | 11 +--- .../components/alarmdecoder/__init__.py | 7 +- .../alarmdecoder/alarm_control_panel.py | 7 +- .../components/alarmdecoder/binary_sensor.py | 7 +- .../components/alarmdecoder/sensor.py | 7 +- homeassistant/components/alert/__init__.py | 7 +- homeassistant/components/alexa/__init__.py | 9 +-- homeassistant/components/alexa/auth.py | 1 - .../components/alexa/flash_briefings.py | 7 +- homeassistant/components/alexa/intent.py | 7 +- homeassistant/components/alexa/smart_home.py | 10 +-- .../components/ambient_station/__init__.py | 8 +-- .../ambient_station/binary_sensor.py | 10 +-- .../components/ambient_station/config_flow.py | 1 - .../components/ambient_station/sensor.py | 10 +-- homeassistant/components/amcrest/__init__.py | 7 +- homeassistant/components/amcrest/camera.py | 7 +- homeassistant/components/amcrest/sensor.py | 15 ++--- homeassistant/components/amcrest/switch.py | 11 +--- .../components/android_ip_webcam/__init__.py | 7 +- .../android_ip_webcam/binary_sensor.py | 11 +--- .../components/android_ip_webcam/sensor.py | 12 +--- .../components/android_ip_webcam/switch.py | 12 +--- homeassistant/components/apcupsd/__init__.py | 7 +- .../components/apcupsd/binary_sensor.py | 7 +- homeassistant/components/apcupsd/sensor.py | 7 +- homeassistant/components/api/__init__.py | 7 +- homeassistant/components/apple_tv/__init__.py | 7 +- .../components/apple_tv/media_player.py | 7 +- homeassistant/components/apple_tv/remote.py | 13 +--- .../components/aqualogic/__init__.py | 24 +++---- homeassistant/components/aqualogic/sensor.py | 11 +--- homeassistant/components/aqualogic/switch.py | 11 +--- homeassistant/components/arduino/__init__.py | 7 +- homeassistant/components/arduino/sensor.py | 9 +-- homeassistant/components/arduino/switch.py | 9 +-- homeassistant/components/arlo/__init__.py | 7 +- .../components/arlo/alarm_control_panel.py | 7 +- homeassistant/components/arlo/camera.py | 7 +- homeassistant/components/arlo/sensor.py | 7 +- .../components/asterisk_mbox/__init__.py | 16 ++--- .../components/asterisk_mbox/mailbox.py | 7 +- homeassistant/components/asuswrt/__init__.py | 18 ++--- homeassistant/components/august/__init__.py | 7 +- .../components/august/binary_sensor.py | 7 +- homeassistant/components/august/camera.py | 7 +- homeassistant/components/august/lock.py | 7 +- .../components/automation/__init__.py | 7 +- homeassistant/components/automation/event.py | 7 +- .../components/automation/geo_location.py | 8 +-- .../components/automation/homeassistant.py | 7 +- .../components/automation/litejet.py | 7 +- homeassistant/components/automation/mqtt.py | 7 +- .../components/automation/numeric_state.py | 7 +- homeassistant/components/automation/state.py | 7 +- homeassistant/components/automation/sun.py | 7 +- .../components/automation/template.py | 8 +-- homeassistant/components/automation/time.py | 7 +- .../components/automation/time_pattern.py | 7 +- .../components/automation/webhook.py | 7 +- homeassistant/components/automation/zone.py | 7 +- homeassistant/components/axis/__init__.py | 7 +- .../components/axis/binary_sensor.py | 7 +- homeassistant/components/axis/camera.py | 7 +- homeassistant/components/bbb_gpio/__init__.py | 7 +- .../components/bbb_gpio/binary_sensor.py | 7 +- homeassistant/components/bbb_gpio/switch.py | 10 +-- homeassistant/components/blink/__init__.py | 7 +- .../components/blink/alarm_control_panel.py | 7 +- .../components/blink/binary_sensor.py | 7 +- homeassistant/components/blink/camera.py | 7 +- homeassistant/components/blink/sensor.py | 7 +- homeassistant/components/bloomsky/__init__.py | 7 +- .../components/bloomsky/binary_sensor.py | 7 +- homeassistant/components/bloomsky/camera.py | 7 +- homeassistant/components/bloomsky/sensor.py | 7 +- .../bmw_connected_drive/__init__.py | 25 +++---- .../bmw_connected_drive/binary_sensor.py | 15 ++--- .../bmw_connected_drive/device_tracker.py | 6 +- .../components/bmw_connected_drive/lock.py | 7 +- .../components/bmw_connected_drive/sensor.py | 28 ++++---- homeassistant/components/browser/__init__.py | 14 ++-- homeassistant/components/calendar/__init__.py | 7 +- homeassistant/components/canary/__init__.py | 7 +- homeassistant/components/cast/__init__.py | 2 +- homeassistant/components/cast/media_player.py | 7 +- homeassistant/components/cloud/__init__.py | 7 +- .../components/cloudflare/__init__.py | 7 +- homeassistant/components/coinbase/__init__.py | 7 +- .../components/comfoconnect/__init__.py | 7 +- homeassistant/components/comfoconnect/fan.py | 7 +- .../components/comfoconnect/sensor.py | 7 +- homeassistant/components/config/automation.py | 1 - homeassistant/components/config/customize.py | 1 - homeassistant/components/config/script.py | 1 - homeassistant/components/config/zwave.py | 11 ++-- .../components/conversation/__init__.py | 7 +- homeassistant/components/counter/__init__.py | 7 +- homeassistant/components/daikin/__init__.py | 7 +- homeassistant/components/daikin/climate.py | 8 +-- homeassistant/components/daikin/sensor.py | 7 +- .../components/danfoss_air/__init__.py | 7 +- homeassistant/components/datadog/__init__.py | 7 +- homeassistant/components/deconz/__init__.py | 7 +- .../components/deconz/binary_sensor.py | 11 +--- .../components/deconz/config_flow.py | 1 - homeassistant/components/deconz/cover.py | 11 +--- homeassistant/components/deconz/light.py | 11 +--- homeassistant/components/deconz/scene.py | 11 +--- homeassistant/components/deconz/sensor.py | 11 +--- homeassistant/components/deconz/switch.py | 12 +--- homeassistant/components/demo/__init__.py | 7 +- .../components/dialogflow/__init__.py | 14 ++-- .../components/digital_ocean/__init__.py | 7 +- .../components/digital_ocean/binary_sensor.py | 7 +- .../components/digital_ocean/switch.py | 7 +- .../components/discovery/__init__.py | 36 +++++----- homeassistant/components/dominos/__init__.py | 18 ++--- homeassistant/components/doorbird/__init__.py | 17 ++--- homeassistant/components/doorbird/camera.py | 7 +- homeassistant/components/dovado/__init__.py | 16 ++--- homeassistant/components/dovado/notify.py | 7 +- homeassistant/components/dovado/sensor.py | 18 ++--- .../components/downloader/__init__.py | 7 +- homeassistant/components/duckdns/__init__.py | 7 +- homeassistant/components/dweet/__init__.py | 7 +- homeassistant/components/dweet/sensor.py | 15 ++--- homeassistant/components/dyson/__init__.py | 22 +++---- homeassistant/components/ebusd/__init__.py | 14 ++-- homeassistant/components/ebusd/sensor.py | 8 +-- .../components/ecoal_boiler/__init__.py | 15 ++--- .../components/ecoal_boiler/sensor.py | 7 +- .../components/ecoal_boiler/switch.py | 7 +- homeassistant/components/ecobee/__init__.py | 9 +-- .../components/ecobee/binary_sensor.py | 9 +-- homeassistant/components/ecobee/climate.py | 7 +- homeassistant/components/ecobee/notify.py | 7 +- homeassistant/components/ecobee/sensor.py | 7 +- homeassistant/components/ecobee/weather.py | 9 +-- homeassistant/components/ecovacs/__init__.py | 13 ++-- homeassistant/components/ecovacs/vacuum.py | 7 +- homeassistant/components/edp_redy/__init__.py | 16 ++--- homeassistant/components/edp_redy/sensor.py | 4 +- homeassistant/components/edp_redy/switch.py | 4 +- homeassistant/components/egardia/__init__.py | 39 ++++++----- .../components/egardia/alarm_control_panel.py | 26 +++----- .../components/egardia/binary_sensor.py | 24 +++---- .../components/eight_sleep/__init__.py | 7 +- .../components/eight_sleep/binary_sensor.py | 7 +- .../components/eight_sleep/sensor.py | 7 +- homeassistant/components/elkm1/__init__.py | 16 ++--- .../components/elkm1/alarm_control_panel.py | 8 +-- homeassistant/components/elkm1/climate.py | 7 +- homeassistant/components/elkm1/light.py | 16 ++--- homeassistant/components/elkm1/scene.py | 13 +--- homeassistant/components/elkm1/sensor.py | 11 +--- homeassistant/components/elkm1/switch.py | 13 +--- .../components/emoncms_history/__init__.py | 7 +- .../components/emulated_hue/__init__.py | 21 +++--- .../components/emulated_hue/hue_api.py | 3 +- homeassistant/components/emulated_hue/upnp.py | 2 +- .../components/emulated_roku/__init__.py | 7 +- .../components/emulated_roku/config_flow.py | 1 - .../components/emulated_roku/const.py | 1 - homeassistant/components/enocean/__init__.py | 7 +- .../components/enocean/binary_sensor.py | 7 +- homeassistant/components/enocean/light.py | 7 +- homeassistant/components/enocean/sensor.py | 7 +- homeassistant/components/enocean/switch.py | 7 +- .../components/envisalink/__init__.py | 7 +- .../envisalink/alarm_control_panel.py | 22 ++----- .../components/envisalink/binary_sensor.py | 7 +- homeassistant/components/envisalink/sensor.py | 16 ++--- homeassistant/components/esphome/__init__.py | 13 ++-- homeassistant/components/eufy/__init__.py | 18 ++--- homeassistant/components/eufy/light.py | 7 +- homeassistant/components/eufy/switch.py | 7 +- homeassistant/components/evohome/__init__.py | 27 ++------ homeassistant/components/evohome/climate.py | 58 +++++----------- .../components/fastdotcom/__init__.py | 8 +-- homeassistant/components/fastdotcom/sensor.py | 7 +- .../components/feedreader/__init__.py | 7 +- homeassistant/components/ffmpeg/__init__.py | 7 +- homeassistant/components/fibaro/__init__.py | 66 ++++++++++--------- .../components/fibaro/binary_sensor.py | 7 +- homeassistant/components/fibaro/cover.py | 7 +- homeassistant/components/fibaro/light.py | 8 +-- homeassistant/components/fibaro/scene.py | 7 +- homeassistant/components/fibaro/sensor.py | 7 +- homeassistant/components/fibaro/switch.py | 7 +- .../components/folder_watcher/__init__.py | 12 ++-- .../components/foursquare/__init__.py | 7 +- homeassistant/components/freebox/__init__.py | 9 +-- .../components/freebox/device_tracker.py | 7 +- homeassistant/components/freebox/sensor.py | 12 +--- homeassistant/components/fritzbox/__init__.py | 7 +- .../components/fritzbox/binary_sensor.py | 7 +- homeassistant/components/fritzbox/climate.py | 8 +-- homeassistant/components/fritzbox/sensor.py | 7 +- homeassistant/components/fritzbox/switch.py | 7 +- homeassistant/components/frontend/__init__.py | 7 +- homeassistant/components/gc100/__init__.py | 7 +- .../components/rainmachine/__init__.py | 7 +- .../components/raspihats/__init__.py | 7 +- .../components/raspihats/binary_sensor.py | 9 +-- homeassistant/components/raspihats/switch.py | 7 +- homeassistant/components/recorder/__init__.py | 10 +-- .../components/remember_the_milk/__init__.py | 7 +- homeassistant/components/remote/__init__.py | 7 +- .../components/rest_command/__init__.py | 7 +- homeassistant/components/rflink/__init__.py | 7 +- homeassistant/components/rfxtrx/__init__.py | 7 +- .../components/rfxtrx/binary_sensor.py | 9 +-- homeassistant/components/rfxtrx/cover.py | 7 +- homeassistant/components/rfxtrx/light.py | 7 +- homeassistant/components/rfxtrx/sensor.py | 7 +- homeassistant/components/rfxtrx/switch.py | 7 +- homeassistant/components/ring/__init__.py | 7 +- homeassistant/components/roku/__init__.py | 7 +- homeassistant/components/roku/media_player.py | 7 +- homeassistant/components/roku/remote.py | 12 +--- homeassistant/components/route53/__init__.py | 7 +- homeassistant/components/rpi_gpio/__init__.py | 7 +- .../components/rpi_gpio/binary_sensor.py | 7 +- homeassistant/components/rpi_gpio/cover.py | 10 +-- homeassistant/components/rpi_gpio/switch.py | 7 +- homeassistant/components/rpi_pfio/__init__.py | 7 +- .../components/rpi_pfio/binary_sensor.py | 7 +- homeassistant/components/rpi_pfio/switch.py | 7 +- .../components/rss_feed_template/__init__.py | 8 +-- homeassistant/components/sabnzbd/__init__.py | 7 +- homeassistant/components/sabnzbd/sensor.py | 15 ++--- .../components/satel_integra/__init__.py | 8 +-- .../satel_integra/alarm_control_panel.py | 11 +--- .../components/satel_integra/binary_sensor.py | 19 ++---- homeassistant/components/scene/__init__.py | 10 +-- .../components/scene/homeassistant.py | 7 +- .../scene/hunterdouglas_powerview.py | 7 +- homeassistant/components/scene/lifx_cloud.py | 7 +- homeassistant/components/scene/litejet.py | 7 +- homeassistant/components/script/__init__.py | 10 +-- homeassistant/components/scsgate/cover.py | 7 +- homeassistant/components/scsgate/light.py | 7 +- homeassistant/components/scsgate/switch.py | 7 +- homeassistant/components/sense/__init__.py | 7 +- .../components/sense/binary_sensor.py | 7 +- homeassistant/components/sense/sensor.py | 7 +- .../components/shell_command/__init__.py | 8 +-- homeassistant/components/shiftr/__init__.py | 7 +- .../components/shopping_list/__init__.py | 2 +- .../components/simplisafe/__init__.py | 9 +-- .../simplisafe/alarm_control_panel.py | 7 +- .../components/simplisafe/config_flow.py | 1 - homeassistant/components/sisyphus/__init__.py | 7 +- homeassistant/components/sisyphus/light.py | 16 +---- .../components/sisyphus/media_player.py | 7 +- homeassistant/components/skybell/__init__.py | 7 +- .../components/skybell/binary_sensor.py | 7 +- homeassistant/components/skybell/camera.py | 7 +- homeassistant/components/skybell/light.py | 7 +- homeassistant/components/skybell/sensor.py | 7 +- homeassistant/components/skybell/switch.py | 11 +--- homeassistant/components/sleepiq/__init__.py | 7 +- homeassistant/components/smappee/__init__.py | 7 +- homeassistant/components/smappee/sensor.py | 7 +- homeassistant/components/smappee/switch.py | 20 ++---- .../components/smartthings/__init__.py | 3 +- .../components/smartthings/binary_sensor.py | 11 +--- .../components/smartthings/climate.py | 7 +- homeassistant/components/smartthings/fan.py | 8 +-- homeassistant/components/smartthings/light.py | 7 +- .../components/smartthings/sensor.py | 7 +- .../components/smartthings/switch.py | 11 +--- homeassistant/components/smhi/__init__.py | 7 +- homeassistant/components/smhi/weather.py | 7 +- homeassistant/components/snips/__init__.py | 7 +- homeassistant/components/sonos/__init__.py | 2 +- .../components/sonos/media_player.py | 7 +- homeassistant/components/spaceapi/__init__.py | 7 +- homeassistant/components/spc/__init__.py | 7 +- .../components/speedtestdotnet/__init__.py | 7 +- .../components/speedtestdotnet/sensor.py | 11 +--- homeassistant/components/spider/__init__.py | 7 +- homeassistant/components/spider/climate.py | 11 +--- homeassistant/components/spider/switch.py | 8 +-- homeassistant/components/splunk/__init__.py | 7 +- homeassistant/components/statsd/__init__.py | 7 +- homeassistant/components/sun/__init__.py | 7 +- .../components/system_health/__init__.py | 7 +- .../components/system_log/__init__.py | 7 +- homeassistant/components/tado/__init__.py | 9 +-- homeassistant/components/tado/climate.py | 7 +- .../components/tado/device_tracker.py | 9 +-- homeassistant/components/tado/sensor.py | 7 +- homeassistant/components/tahoma/__init__.py | 7 +- .../components/tahoma/binary_sensor.py | 8 +-- homeassistant/components/tahoma/cover.py | 7 +- homeassistant/components/tahoma/scene.py | 7 +- homeassistant/components/tahoma/sensor.py | 8 +-- homeassistant/components/tahoma/switch.py | 10 +-- .../components/telegram_bot/__init__.py | 7 +- .../components/telegram_bot/broadcast.py | 9 +-- .../components/telegram_bot/polling.py | 7 +- .../components/telegram_bot/webhooks.py | 7 +- .../components/tellduslive/__init__.py | 10 +-- .../components/tellduslive/binary_sensor.py | 14 +--- homeassistant/components/tellduslive/cover.py | 9 +-- homeassistant/components/tellduslive/entry.py | 2 +- homeassistant/components/tellduslive/light.py | 9 +-- .../components/tellduslive/sensor.py | 7 +- .../components/tellduslive/switch.py | 10 +-- .../components/tellstick/__init__.py | 7 +- homeassistant/components/tellstick/cover.py | 9 +-- homeassistant/components/tellstick/light.py | 8 +-- homeassistant/components/tellstick/sensor.py | 7 +- homeassistant/components/tellstick/switch.py | 7 +- homeassistant/components/tesla/__init__.py | 7 +- .../components/tesla/binary_sensor.py | 7 +- homeassistant/components/tesla/climate.py | 7 +- .../components/tesla/device_tracker.py | 7 +- homeassistant/components/tesla/lock.py | 7 +- homeassistant/components/tesla/sensor.py | 7 +- homeassistant/components/tesla/switch.py | 7 +- .../components/thethingsnetwork/__init__.py | 7 +- .../components/thethingsnetwork/sensor.py | 11 +--- .../components/thingspeak/__init__.py | 13 ++-- .../components/thinkingcleaner/__init__.py | 2 +- .../components/thinkingcleaner/sensor.py | 7 +- .../components/thinkingcleaner/switch.py | 11 +--- homeassistant/components/tibber/__init__.py | 7 +- homeassistant/components/tibber/notify.py | 8 +-- homeassistant/components/tibber/sensor.py | 11 +--- homeassistant/components/timer/__init__.py | 14 ++-- homeassistant/components/toon/__init__.py | 7 +- homeassistant/components/toon/climate.py | 10 +-- homeassistant/components/toon/sensor.py | 7 +- homeassistant/components/toon/switch.py | 7 +- .../components/tplink_lte/__init__.py | 12 +--- homeassistant/components/tplink_lte/notify.py | 7 +- homeassistant/components/tradfri/__init__.py | 12 ++-- homeassistant/components/tradfri/light.py | 7 +- homeassistant/components/tradfri/sensor.py | 7 +- homeassistant/components/tradfri/switch.py | 7 +- .../components/transmission/__init__.py | 23 ++----- .../components/transmission/sensor.py | 28 ++------ .../components/transmission/switch.py | 12 +--- homeassistant/components/tuya/__init__.py | 7 +- homeassistant/components/tuya/climate.py | 8 +-- homeassistant/components/tuya/cover.py | 7 +- homeassistant/components/tuya/fan.py | 8 +-- homeassistant/components/tuya/light.py | 7 +- homeassistant/components/tuya/scene.py | 7 +- homeassistant/components/tuya/switch.py | 7 +- homeassistant/components/twilio/__init__.py | 7 +- homeassistant/components/unifi/__init__.py | 8 +-- homeassistant/components/unifi/const.py | 1 - homeassistant/components/unifi/controller.py | 1 - homeassistant/components/unifi/switch.py | 10 +-- homeassistant/components/upcloud/__init__.py | 15 ++--- .../components/upcloud/binary_sensor.py | 7 +- homeassistant/components/upcloud/switch.py | 7 +- homeassistant/components/updater/__init__.py | 7 +- homeassistant/components/upnp/__init__.py | 11 +--- homeassistant/components/usps/__init__.py | 9 +-- homeassistant/components/usps/camera.py | 7 +- homeassistant/components/usps/sensor.py | 7 +- .../components/utility_meter/__init__.py | 10 +-- .../components/utility_meter/sensor.py | 29 ++++---- homeassistant/components/velbus/__init__.py | 8 +-- .../components/velbus/binary_sensor.py | 11 +--- homeassistant/components/velbus/climate.py | 7 +- homeassistant/components/velbus/cover.py | 7 +- homeassistant/components/velbus/sensor.py | 11 +--- homeassistant/components/velbus/switch.py | 11 +--- homeassistant/components/velux/__init__.py | 7 +- homeassistant/components/velux/cover.py | 12 +--- homeassistant/components/velux/scene.py | 19 ++---- homeassistant/components/vera/__init__.py | 9 +-- .../components/vera/binary_sensor.py | 7 +- homeassistant/components/vera/climate.py | 7 +- homeassistant/components/vera/cover.py | 7 +- homeassistant/components/vera/light.py | 7 +- homeassistant/components/vera/lock.py | 7 +- homeassistant/components/vera/scene.py | 11 +--- homeassistant/components/vera/sensor.py | 11 +--- homeassistant/components/vera/switch.py | 11 +--- homeassistant/components/verisure/__init__.py | 7 +- .../verisure/alarm_control_panel.py | 7 +- .../components/verisure/binary_sensor.py | 7 +- homeassistant/components/verisure/camera.py | 7 +- homeassistant/components/verisure/lock.py | 9 +-- homeassistant/components/verisure/sensor.py | 7 +- homeassistant/components/verisure/switch.py | 7 +- .../components/volvooncall/__init__.py | 9 +-- .../components/volvooncall/binary_sensor.py | 11 +--- .../components/volvooncall/device_tracker.py | 7 +- homeassistant/components/volvooncall/lock.py | 11 +--- .../components/volvooncall/sensor.py | 12 +--- .../components/volvooncall/switch.py | 13 +--- homeassistant/components/vultr/__init__.py | 7 +- homeassistant/components/w800rf32/__init__.py | 13 ++-- .../components/w800rf32/binary_sensor.py | 14 ++-- .../components/wake_on_lan/__init__.py | 7 +- .../components/water_heater/__init__.py | 7 +- homeassistant/components/water_heater/demo.py | 16 ++--- .../components/water_heater/econet.py | 7 +- .../components/waterfurnace/__init__.py | 14 ++-- .../components/watson_iot/__init__.py | 9 +-- homeassistant/components/webhook/__init__.py | 13 ++-- homeassistant/components/weblink/__init__.py | 7 +- homeassistant/components/webostv/__init__.py | 2 +- .../components/webostv/media_player.py | 7 +- homeassistant/components/webostv/notify.py | 13 ++-- .../components/websocket_api/__init__.py | 7 +- .../components/websocket_api/commands.py | 1 - homeassistant/components/wemo/__init__.py | 31 ++++----- .../components/wemo/binary_sensor.py | 7 +- homeassistant/components/wemo/fan.py | 7 +- homeassistant/components/wemo/light.py | 7 +- homeassistant/components/wemo/switch.py | 9 +-- homeassistant/components/wink/__init__.py | 29 ++++---- .../components/wink/alarm_control_panel.py | 7 +- .../components/wink/binary_sensor.py | 7 +- homeassistant/components/wink/climate.py | 7 +- homeassistant/components/wink/cover.py | 7 +- homeassistant/components/wink/fan.py | 7 +- homeassistant/components/wink/light.py | 8 +-- homeassistant/components/wink/lock.py | 7 +- homeassistant/components/wink/scene.py | 7 +- homeassistant/components/wink/sensor.py | 7 +- homeassistant/components/wink/switch.py | 7 +- homeassistant/components/wink/water_heater.py | 7 +- .../components/wirelesstag/__init__.py | 18 ++--- .../components/wirelesstag/binary_sensor.py | 7 +- .../components/wirelesstag/sensor.py | 20 ++---- .../components/wirelesstag/switch.py | 11 +--- .../components/wunderlist/__init__.py | 11 +--- .../components/xiaomi_aqara/__init__.py | 7 +- homeassistant/components/xiaomi_aqara/lock.py | 11 +--- .../components/xiaomi_aqara/sensor.py | 2 +- .../components/xiaomi_aqara/switch.py | 52 +++++++-------- .../components/xiaomi_miio/__init__.py | 2 +- .../components/xiaomi_miio/device_tracker.py | 7 +- homeassistant/components/xiaomi_miio/fan.py | 7 +- homeassistant/components/xiaomi_miio/light.py | 9 +-- .../components/xiaomi_miio/remote.py | 10 +-- .../components/xiaomi_miio/sensor.py | 7 +- .../components/xiaomi_miio/switch.py | 7 +- .../components/xiaomi_miio/vacuum.py | 7 +- homeassistant/components/xs1/__init__.py | 27 +++----- homeassistant/components/xs1/climate.py | 12 +--- homeassistant/components/xs1/sensor.py | 12 +--- homeassistant/components/xs1/switch.py | 14 ++-- homeassistant/components/zabbix/__init__.py | 13 +--- homeassistant/components/zabbix/sensor.py | 7 +- homeassistant/components/zeroconf/__init__.py | 7 +- homeassistant/components/zigbee/__init__.py | 10 +-- .../components/zigbee/binary_sensor.py | 7 +- homeassistant/components/zigbee/light.py | 7 +- homeassistant/components/zigbee/sensor.py | 7 +- homeassistant/components/zigbee/switch.py | 7 +- homeassistant/components/zone/__init__.py | 8 +-- homeassistant/components/zone/zone.py | 3 +- .../components/zoneminder/__init__.py | 7 +- .../components/zoneminder/binary_sensor.py | 11 +--- homeassistant/components/zoneminder/camera.py | 7 +- homeassistant/components/zoneminder/sensor.py | 7 +- homeassistant/components/zoneminder/switch.py | 9 +-- homeassistant/components/zwave/__init__.py | 7 +- .../components/zwave/binary_sensor.py | 16 ++--- homeassistant/components/zwave/climate.py | 11 +--- homeassistant/components/zwave/cover.py | 11 +--- .../components/zwave/discovery_schemas.py | 2 +- homeassistant/components/zwave/fan.py | 11 +--- homeassistant/components/zwave/light.py | 11 +--- homeassistant/components/zwave/lock.py | 7 +- homeassistant/components/zwave/sensor.py | 11 +--- homeassistant/components/zwave/switch.py | 7 +- homeassistant/components/zwave/workaround.py | 2 +- 492 files changed, 1015 insertions(+), 3497 deletions(-) diff --git a/homeassistant/components/abode/__init__.py b/homeassistant/components/abode/__init__.py index 8a1a39a726f..71a1dcdd590 100644 --- a/homeassistant/components/abode/__init__.py +++ b/homeassistant/components/abode/__init__.py @@ -1,9 +1,4 @@ -""" -This component provides basic support for Abode Home Security system. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/abode/ -""" +"""Support for Abode Home Security system.""" import logging from functools import partial from requests.exceptions import HTTPError, ConnectTimeout diff --git a/homeassistant/components/abode/alarm_control_panel.py b/homeassistant/components/abode/alarm_control_panel.py index 947e2916300..ec5038a7a84 100644 --- a/homeassistant/components/abode/alarm_control_panel.py +++ b/homeassistant/components/abode/alarm_control_panel.py @@ -1,9 +1,4 @@ -""" -This component provides HA alarm_control_panel support for Abode System. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/alarm_control_panel.abode/ -""" +"""Support for Abode Security System alarm control panels.""" import logging import homeassistant.components.alarm_control_panel as alarm diff --git a/homeassistant/components/abode/binary_sensor.py b/homeassistant/components/abode/binary_sensor.py index a821abf445b..47baef1d7e5 100644 --- a/homeassistant/components/abode/binary_sensor.py +++ b/homeassistant/components/abode/binary_sensor.py @@ -1,20 +1,14 @@ -""" -This component provides HA binary_sensor support for Abode Security System. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.abode/ -""" +"""Support for Abode Security System binary sensors.""" import logging from homeassistant.components.abode import (AbodeDevice, AbodeAutomation, DOMAIN as ABODE_DOMAIN) from homeassistant.components.binary_sensor import BinarySensorDevice +_LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['abode'] -_LOGGER = logging.getLogger(__name__) - def setup_platform(hass, config, add_entities, discovery_info=None): """Set up a sensor for an Abode device.""" diff --git a/homeassistant/components/abode/camera.py b/homeassistant/components/abode/camera.py index 39681760d4d..99613d07c47 100644 --- a/homeassistant/components/abode/camera.py +++ b/homeassistant/components/abode/camera.py @@ -1,9 +1,4 @@ -""" -This component provides HA camera support for Abode Security System. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/camera.abode/ -""" +"""Support for Abode Security System cameras.""" import logging from datetime import timedelta @@ -13,7 +8,6 @@ from homeassistant.components.abode import AbodeDevice, DOMAIN as ABODE_DOMAIN from homeassistant.components.camera import Camera from homeassistant.util import Throttle - DEPENDENCIES = ['abode'] MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90) diff --git a/homeassistant/components/abode/cover.py b/homeassistant/components/abode/cover.py index 3ba3fb118f3..03d6219ebce 100644 --- a/homeassistant/components/abode/cover.py +++ b/homeassistant/components/abode/cover.py @@ -1,15 +1,9 @@ -""" -This component provides HA cover support for Abode Security System. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/cover.abode/ -""" +"""Support for Abode Security System covers.""" import logging from homeassistant.components.abode import AbodeDevice, DOMAIN as ABODE_DOMAIN from homeassistant.components.cover import CoverDevice - DEPENDENCIES = ['abode'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/abode/light.py b/homeassistant/components/abode/light.py index 397d61f3073..aabf5fbccdc 100644 --- a/homeassistant/components/abode/light.py +++ b/homeassistant/components/abode/light.py @@ -1,9 +1,4 @@ -""" -This component provides HA light support for Abode Security System. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.abode/ -""" +"""Support for Abode Security System lights.""" import logging from math import ceil from homeassistant.components.abode import AbodeDevice, DOMAIN as ABODE_DOMAIN diff --git a/homeassistant/components/abode/lock.py b/homeassistant/components/abode/lock.py index a8777ccb503..ce6634268e9 100644 --- a/homeassistant/components/abode/lock.py +++ b/homeassistant/components/abode/lock.py @@ -1,15 +1,9 @@ -""" -This component provides HA lock support for Abode Security System. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/lock.abode/ -""" +"""Support for Abode Security System locks.""" import logging from homeassistant.components.abode import AbodeDevice, DOMAIN as ABODE_DOMAIN from homeassistant.components.lock import LockDevice - DEPENDENCIES = ['abode'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/abode/sensor.py b/homeassistant/components/abode/sensor.py index 4695a5f0471..fa6cb9323bf 100644 --- a/homeassistant/components/abode/sensor.py +++ b/homeassistant/components/abode/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Abode Security System sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.abode/ -""" +"""Support for Abode Security System sensors.""" import logging from homeassistant.components.abode import AbodeDevice, DOMAIN as ABODE_DOMAIN diff --git a/homeassistant/components/abode/switch.py b/homeassistant/components/abode/switch.py index e3f993e5413..d5303a27cd2 100644 --- a/homeassistant/components/abode/switch.py +++ b/homeassistant/components/abode/switch.py @@ -1,20 +1,14 @@ -""" -This component provides HA switch support for Abode Security System. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.abode/ -""" +"""Support for Abode Security System switches.""" import logging from homeassistant.components.abode import (AbodeDevice, AbodeAutomation, DOMAIN as ABODE_DOMAIN) from homeassistant.components.switch import SwitchDevice +_LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['abode'] -_LOGGER = logging.getLogger(__name__) - def setup_platform(hass, config, add_entities, discovery_info=None): """Set up Abode switch devices.""" diff --git a/homeassistant/components/ads/__init__.py b/homeassistant/components/ads/__init__.py index 360236790f8..48b5ea21cbc 100644 --- a/homeassistant/components/ads/__init__.py +++ b/homeassistant/components/ads/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Automation Device Specification (ADS). - -For more details about this component, please refer to the documentation. -https://home-assistant.io/components/ads/ -""" +"""Support for Automation Device Specification (ADS).""" import threading import struct import logging diff --git a/homeassistant/components/ads/binary_sensor.py b/homeassistant/components/ads/binary_sensor.py index c83837dcd5f..6771e99cd77 100644 --- a/homeassistant/components/ads/binary_sensor.py +++ b/homeassistant/components/ads/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for ADS binary sensors. - -For more details about this platform, please refer to the documentation. -https://home-assistant.io/components/binary_sensor.ads/ -""" +"""Support for ADS binary sensors.""" import logging import voluptuous as vol diff --git a/homeassistant/components/ads/light.py b/homeassistant/components/ads/light.py index a2d54fe7d4a..e5299821e39 100644 --- a/homeassistant/components/ads/light.py +++ b/homeassistant/components/ads/light.py @@ -1,10 +1,4 @@ -""" -Support for ADS light sources. - -For more details about this platform, please refer to the documentation. -https://home-assistant.io/components/light.ads/ - -""" +"""Support for ADS light sources.""" import logging import voluptuous as vol from homeassistant.components.light import Light, ATTR_BRIGHTNESS, \ diff --git a/homeassistant/components/ads/sensor.py b/homeassistant/components/ads/sensor.py index 50f3fd08d5c..2972f50d804 100644 --- a/homeassistant/components/ads/sensor.py +++ b/homeassistant/components/ads/sensor.py @@ -1,9 +1,4 @@ -""" -Support for ADS sensors. - -For more details about this platform, please refer to the documentation. -https://home-assistant.io/components/sensor.ads/ -""" +"""Support for ADS sensors.""" import logging import voluptuous as vol diff --git a/homeassistant/components/ads/switch.py b/homeassistant/components/ads/switch.py index ab9e54adaa7..e3aee023f21 100644 --- a/homeassistant/components/ads/switch.py +++ b/homeassistant/components/ads/switch.py @@ -1,9 +1,4 @@ -""" -Support for ADS switch platform. - -For more details about this platform, please refer to the documentation. -https://home-assistant.io/components/switch.ads/ -""" +"""Support for ADS switch platform.""" import logging import voluptuous as vol @@ -37,7 +32,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class AdsSwitch(ToggleEntity): - """Representation of an Ads switch device.""" + """Representation of an ADS switch device.""" def __init__(self, ads_hub, name, ads_var): """Initialize the AdsSwitch entity.""" @@ -51,7 +46,7 @@ class AdsSwitch(ToggleEntity): """Register device notification.""" def update(name, value): """Handle device notification.""" - _LOGGER.debug('Variable %s changed its value to %d', name, value) + _LOGGER.debug("Variable %s changed its value to %d", name, value) self._on_state = value self.schedule_update_ha_state() diff --git a/homeassistant/components/alarmdecoder/__init__.py b/homeassistant/components/alarmdecoder/__init__.py index 92eab728210..1f74d72809b 100644 --- a/homeassistant/components/alarmdecoder/__init__.py +++ b/homeassistant/components/alarmdecoder/__init__.py @@ -1,9 +1,4 @@ -""" -Support for AlarmDecoder devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/alarmdecoder/ -""" +"""Support for AlarmDecoder devices.""" import logging from datetime import timedelta diff --git a/homeassistant/components/alarmdecoder/alarm_control_panel.py b/homeassistant/components/alarmdecoder/alarm_control_panel.py index 16e82280433..986907622b1 100644 --- a/homeassistant/components/alarmdecoder/alarm_control_panel.py +++ b/homeassistant/components/alarmdecoder/alarm_control_panel.py @@ -1,9 +1,4 @@ -""" -Support for AlarmDecoder-based alarm control panels (Honeywell/DSC). - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/alarm_control_panel.alarmdecoder/ -""" +"""Support for AlarmDecoder-based alarm control panels (Honeywell/DSC).""" import logging import voluptuous as vol diff --git a/homeassistant/components/alarmdecoder/binary_sensor.py b/homeassistant/components/alarmdecoder/binary_sensor.py index d8fddeaa540..c5af6ea79cb 100644 --- a/homeassistant/components/alarmdecoder/binary_sensor.py +++ b/homeassistant/components/alarmdecoder/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for AlarmDecoder zone states- represented as binary sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.alarmdecoder/ -""" +"""Support for AlarmDecoder zone states- represented as binary sensors.""" import logging from homeassistant.components.binary_sensor import BinarySensorDevice diff --git a/homeassistant/components/alarmdecoder/sensor.py b/homeassistant/components/alarmdecoder/sensor.py index 546d09299dc..b2f697ea83f 100644 --- a/homeassistant/components/alarmdecoder/sensor.py +++ b/homeassistant/components/alarmdecoder/sensor.py @@ -1,9 +1,4 @@ -""" -Support for AlarmDecoder Sensors (Shows Panel Display). - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.alarmdecoder/ -""" +"""Support for AlarmDecoder sensors (Shows Panel Display).""" import logging from homeassistant.helpers.entity import Entity diff --git a/homeassistant/components/alert/__init__.py b/homeassistant/components/alert/__init__.py index 579a19c1b52..f92fd6b187b 100644 --- a/homeassistant/components/alert/__init__.py +++ b/homeassistant/components/alert/__init__.py @@ -1,9 +1,4 @@ -""" -Support for repeating alerts when conditions are met. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/alert/ -""" +"""Support for repeating alerts when conditions are met.""" import asyncio import logging from datetime import datetime, timedelta diff --git a/homeassistant/components/alexa/__init__.py b/homeassistant/components/alexa/__init__.py index 8491268dfd6..062d698d512 100644 --- a/homeassistant/components/alexa/__init__.py +++ b/homeassistant/components/alexa/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Alexa skill service end point. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/alexa/ -""" +"""Support for Alexa skill service end point.""" import logging import voluptuous as vol @@ -57,7 +52,7 @@ CONFIG_SCHEMA = vol.Schema({ async def async_setup(hass, config): - """Activate Alexa component.""" + """Activate the Alexa component.""" config = config.get(DOMAIN, {}) flash_briefings_config = config.get(CONF_FLASH_BRIEFINGS) diff --git a/homeassistant/components/alexa/auth.py b/homeassistant/components/alexa/auth.py index 978cb611895..6918ec1e54f 100644 --- a/homeassistant/components/alexa/auth.py +++ b/homeassistant/components/alexa/auth.py @@ -1,5 +1,4 @@ """Support for Alexa skill auth.""" - import asyncio import json import logging diff --git a/homeassistant/components/alexa/flash_briefings.py b/homeassistant/components/alexa/flash_briefings.py index 02f47b05617..537f04b20be 100644 --- a/homeassistant/components/alexa/flash_briefings.py +++ b/homeassistant/components/alexa/flash_briefings.py @@ -1,9 +1,4 @@ -""" -Support for Alexa skill service end point. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/alexa/ -""" +"""Support for Alexa skill service end point.""" import copy from datetime import datetime import logging diff --git a/homeassistant/components/alexa/intent.py b/homeassistant/components/alexa/intent.py index 85cb4f105cd..b30a7238b3e 100644 --- a/homeassistant/components/alexa/intent.py +++ b/homeassistant/components/alexa/intent.py @@ -1,9 +1,4 @@ -""" -Support for Alexa skill service end point. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/alexa/ -""" +"""Support for Alexa skill service end point.""" import enum import logging diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index acb23fe4278..4e2383bb43d 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -1,10 +1,4 @@ -"""Support for alexa Smart Home Skill API. - -API documentation: -https://developer.amazon.com/docs/smarthome/understand-the-smart-home-skill-api.html -https://developer.amazon.com/docs/device-apis/message-guide.html -""" - +"""Support for alexa Smart Home Skill API.""" import asyncio from collections import OrderedDict from datetime import datetime @@ -67,7 +61,7 @@ API_THERMOSTAT_MODES = OrderedDict([ (climate.STATE_OFF, 'OFF'), (climate.STATE_IDLE, 'OFF'), (climate.STATE_FAN_ONLY, 'OFF'), - (climate.STATE_DRY, 'OFF') + (climate.STATE_DRY, 'OFF'), ]) SMART_HOME_HTTP_ENDPOINT = '/api/alexa/smart_home' diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index 71d946b4bf5..5972660c6e6 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Ambient Weather Station Service. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/ambient_station/ -""" +"""Support for Ambient Weather Station Service.""" import logging import voluptuous as vol @@ -26,6 +21,7 @@ from .const import ( TYPE_BINARY_SENSOR, TYPE_SENSOR) REQUIREMENTS = ['aioambient==0.1.1'] + _LOGGER = logging.getLogger(__name__) DATA_CONFIG = 'config' diff --git a/homeassistant/components/ambient_station/binary_sensor.py b/homeassistant/components/ambient_station/binary_sensor.py index 9d3b90a08a1..2defa032809 100644 --- a/homeassistant/components/ambient_station/binary_sensor.py +++ b/homeassistant/components/ambient_station/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for Ambient Weather Station binary sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.ambient_station/ -""" +"""Support for Ambient Weather Station binary sensors.""" import logging from homeassistant.components.ambient_station import ( @@ -15,9 +10,10 @@ from homeassistant.const import ATTR_NAME from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TYPE_BINARY_SENSOR -DEPENDENCIES = ['ambient_station'] _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['ambient_station'] + async def async_setup_platform( hass, config, async_add_entities, discovery_info=None): diff --git a/homeassistant/components/ambient_station/config_flow.py b/homeassistant/components/ambient_station/config_flow.py index 56e747ce5e0..f01bfd8f791 100644 --- a/homeassistant/components/ambient_station/config_flow.py +++ b/homeassistant/components/ambient_station/config_flow.py @@ -1,5 +1,4 @@ """Config flow to configure the Ambient PWS component.""" - import voluptuous as vol from homeassistant import config_entries diff --git a/homeassistant/components/ambient_station/sensor.py b/homeassistant/components/ambient_station/sensor.py index 2699975cfb5..fa3222bf0e4 100644 --- a/homeassistant/components/ambient_station/sensor.py +++ b/homeassistant/components/ambient_station/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Ambient Weather Station sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.ambient_station/ -""" +"""Support for Ambient Weather Station sensors.""" import logging from homeassistant.components.ambient_station import ( @@ -12,9 +7,10 @@ from homeassistant.const import ATTR_NAME from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TYPE_SENSOR -DEPENDENCIES = ['ambient_station'] _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['ambient_station'] + async def async_setup_platform( hass, config, async_add_entities, discovery_info=None): diff --git a/homeassistant/components/amcrest/__init__.py b/homeassistant/components/amcrest/__init__.py index bcd0c38c3bd..49f11570b21 100644 --- a/homeassistant/components/amcrest/__init__.py +++ b/homeassistant/components/amcrest/__init__.py @@ -1,9 +1,4 @@ -""" -This component provides basic support for Amcrest IP cameras. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/amcrest/ -""" +"""Support for Amcrest IP cameras.""" import logging from datetime import timedelta diff --git a/homeassistant/components/amcrest/camera.py b/homeassistant/components/amcrest/camera.py index 3b3368c2f5c..7c943b89734 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -1,9 +1,4 @@ -""" -This component provides basic support for Amcrest IP cameras. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/camera.amcrest/ -""" +"""Support for Amcrest IP cameras.""" import logging from homeassistant.components.amcrest import ( diff --git a/homeassistant/components/amcrest/sensor.py b/homeassistant/components/amcrest/sensor.py index 22e13d05e20..4869dfffa6e 100644 --- a/homeassistant/components/amcrest/sensor.py +++ b/homeassistant/components/amcrest/sensor.py @@ -1,9 +1,4 @@ -""" -This component provides HA sensor support for Amcrest IP cameras. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.amcrest/ -""" +"""Suppoort for Amcrest IP camera sensors.""" from datetime import timedelta import logging @@ -18,8 +13,8 @@ _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=10) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up a sensor for an Amcrest IP Camera.""" if discovery_info is None: return @@ -45,8 +40,8 @@ class AmcrestSensor(Entity): self._attrs = {} self._camera = camera self._sensor_type = sensor_type - self._name = '{0}_{1}'.format(name, - SENSORS.get(self._sensor_type)[0]) + self._name = '{0}_{1}'.format( + name, SENSORS.get(self._sensor_type)[0]) self._icon = 'mdi:{}'.format(SENSORS.get(self._sensor_type)[2]) self._state = None diff --git a/homeassistant/components/amcrest/switch.py b/homeassistant/components/amcrest/switch.py index 4eb20308850..3c1f03f0145 100644 --- a/homeassistant/components/amcrest/switch.py +++ b/homeassistant/components/amcrest/switch.py @@ -1,9 +1,4 @@ -""" -Support for toggling Amcrest IP camera settings. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.amcrest/ -""" +"""Support for toggling Amcrest IP camera settings.""" import logging from homeassistant.components.amcrest import DATA_AMCREST, SWITCHES @@ -16,8 +11,8 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['amcrest'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the IP Amcrest camera switch platform.""" if discovery_info is None: return diff --git a/homeassistant/components/android_ip_webcam/__init__.py b/homeassistant/components/android_ip_webcam/__init__.py index 1cf46174371..c5424b3d0fa 100644 --- a/homeassistant/components/android_ip_webcam/__init__.py +++ b/homeassistant/components/android_ip_webcam/__init__.py @@ -1,9 +1,4 @@ -""" -Support for IP Webcam, an Android app that acts as a full-featured webcam. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/android_ip_webcam/ -""" +"""Support for Android IP Webcam.""" import asyncio import logging from datetime import timedelta diff --git a/homeassistant/components/android_ip_webcam/binary_sensor.py b/homeassistant/components/android_ip_webcam/binary_sensor.py index 085bafd3ae3..e33e22f3778 100644 --- a/homeassistant/components/android_ip_webcam/binary_sensor.py +++ b/homeassistant/components/android_ip_webcam/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for IP Webcam binary sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.android_ip_webcam/ -""" +"""Support for Android IP Webcam binary sensors.""" from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.android_ip_webcam import ( KEY_MAP, DATA_IP_WEBCAM, AndroidIPCamEntity, CONF_HOST, CONF_NAME) @@ -11,8 +6,8 @@ from homeassistant.components.android_ip_webcam import ( DEPENDENCIES = ['android_ip_webcam'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the IP Webcam binary sensors.""" if discovery_info is None: return diff --git a/homeassistant/components/android_ip_webcam/sensor.py b/homeassistant/components/android_ip_webcam/sensor.py index 0f795f85dcd..e98ce7951b8 100644 --- a/homeassistant/components/android_ip_webcam/sensor.py +++ b/homeassistant/components/android_ip_webcam/sensor.py @@ -1,10 +1,4 @@ -""" -Support for IP Webcam sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.android_ip_webcam/ -""" - +"""Support for Android IP Webcam sensors.""" from homeassistant.components.android_ip_webcam import ( KEY_MAP, ICON_MAP, DATA_IP_WEBCAM, AndroidIPCamEntity, CONF_HOST, CONF_NAME, CONF_SENSORS) @@ -13,8 +7,8 @@ from homeassistant.helpers.icon import icon_for_battery_level DEPENDENCIES = ['android_ip_webcam'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the IP Webcam Sensor.""" if discovery_info is None: return diff --git a/homeassistant/components/android_ip_webcam/switch.py b/homeassistant/components/android_ip_webcam/switch.py index f770b9d5ebf..73a94acbcdd 100644 --- a/homeassistant/components/android_ip_webcam/switch.py +++ b/homeassistant/components/android_ip_webcam/switch.py @@ -1,10 +1,4 @@ -""" -Support for IP Webcam settings. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.android_ip_webcam/ -""" - +"""Support for Android IP Webcam settings.""" from homeassistant.components.switch import SwitchDevice from homeassistant.components.android_ip_webcam import ( KEY_MAP, ICON_MAP, DATA_IP_WEBCAM, AndroidIPCamEntity, CONF_HOST, @@ -13,8 +7,8 @@ from homeassistant.components.android_ip_webcam import ( DEPENDENCIES = ['android_ip_webcam'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the IP Webcam switch platform.""" if discovery_info is None: return diff --git a/homeassistant/components/apcupsd/__init__.py b/homeassistant/components/apcupsd/__init__.py index 79b88378169..aab6f6dda01 100644 --- a/homeassistant/components/apcupsd/__init__.py +++ b/homeassistant/components/apcupsd/__init__.py @@ -1,9 +1,4 @@ -""" -Support for status output of APCUPSd via its Network Information Server (NIS). - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/apcupsd/ -""" +"""Support for APCUPSd via its Network Information Server (NIS).""" import logging from datetime import timedelta diff --git a/homeassistant/components/apcupsd/binary_sensor.py b/homeassistant/components/apcupsd/binary_sensor.py index f876b8cc34b..445dab9b074 100644 --- a/homeassistant/components/apcupsd/binary_sensor.py +++ b/homeassistant/components/apcupsd/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for tracking the online status of a UPS. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.apcupsd/ -""" +"""Support for tracking the online status of a UPS.""" import voluptuous as vol from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/apcupsd/sensor.py b/homeassistant/components/apcupsd/sensor.py index 90c1f2e6795..4ebe0ac8aaf 100644 --- a/homeassistant/components/apcupsd/sensor.py +++ b/homeassistant/components/apcupsd/sensor.py @@ -1,9 +1,4 @@ -""" -Provides a sensor to track various status aspects of a UPS. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.apcupsd/ -""" +"""Support for APCUPSd sensors.""" import logging import voluptuous as vol diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index 961350bfa89..7639ac621fe 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -1,9 +1,4 @@ -""" -Rest API for Home Assistant. - -For more details about the RESTful API, please refer to the documentation at -https://developers.home-assistant.io/docs/en/external_api_rest.html -""" +"""Rest API for Home Assistant.""" import asyncio import json import logging diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index 73cabdfbae6..b265dc533eb 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Apple TV. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/apple_tv/ -""" +"""Support for Apple TV.""" import asyncio import logging from typing import Sequence, TypeVar, Union diff --git a/homeassistant/components/apple_tv/media_player.py b/homeassistant/components/apple_tv/media_player.py index 0b38a256e40..03ac5bd2549 100644 --- a/homeassistant/components/apple_tv/media_player.py +++ b/homeassistant/components/apple_tv/media_player.py @@ -1,9 +1,4 @@ -""" -Support for Apple TV. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/media_player.apple_tv/ -""" +"""Support for Apple TV media player.""" import logging from homeassistant.components.apple_tv import ( diff --git a/homeassistant/components/apple_tv/remote.py b/homeassistant/components/apple_tv/remote.py index 72696143bfe..2d80ded6861 100644 --- a/homeassistant/components/apple_tv/remote.py +++ b/homeassistant/components/apple_tv/remote.py @@ -1,21 +1,14 @@ -""" -Remote control support for Apple TV. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/remote.apple_tv/ -""" - +"""Remote control support for Apple TV.""" from homeassistant.components.apple_tv import ( ATTR_ATV, ATTR_POWER, DATA_APPLE_TV) from homeassistant.components import remote from homeassistant.const import (CONF_NAME, CONF_HOST) - DEPENDENCIES = ['apple_tv'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the Apple TV remote platform.""" if not discovery_info: return diff --git a/homeassistant/components/aqualogic/__init__.py b/homeassistant/components/aqualogic/__init__.py index abb61d42ca3..a4f83b573f7 100644 --- a/homeassistant/components/aqualogic/__init__.py +++ b/homeassistant/components/aqualogic/__init__.py @@ -1,9 +1,4 @@ -""" -Support for AquaLogic component. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/aqualogic/ -""" +"""Support for AquaLogic devices.""" from datetime import timedelta import logging import time @@ -20,15 +15,15 @@ REQUIREMENTS = ["aqualogic==1.0"] _LOGGER = logging.getLogger(__name__) -DOMAIN = "aqualogic" -UPDATE_TOPIC = DOMAIN + "_update" -CONF_UNIT = "unit" +DOMAIN = 'aqualogic' +UPDATE_TOPIC = DOMAIN + '_update' +CONF_UNIT = 'unit' RECONNECT_INTERVAL = timedelta(seconds=10) CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PORT): cv.port + vol.Required(CONF_PORT): cv.port, }), }, extra=vol.ALLOW_EXTRA) @@ -39,10 +34,8 @@ def setup(hass, config): port = config[DOMAIN][CONF_PORT] processor = AquaLogicProcessor(hass, host, port) hass.data[DOMAIN] = processor - hass.bus.listen_once(EVENT_HOMEASSISTANT_START, - processor.start_listen) - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, - processor.shutdown) + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, processor.start_listen) + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, processor.shutdown) _LOGGER.debug("AquaLogicProcessor %s:%i initialized", host, port) return True @@ -85,8 +78,7 @@ class AquaLogicProcessor(threading.Thread): if self._shutdown: return - _LOGGER.error("Connection to %s:%d lost", - self._host, self._port) + _LOGGER.error("Connection to %s:%d lost", self._host, self._port) time.sleep(RECONNECT_INTERVAL.seconds) @property diff --git a/homeassistant/components/aqualogic/sensor.py b/homeassistant/components/aqualogic/sensor.py index f10fd05b83f..9e061ba91bf 100644 --- a/homeassistant/components/aqualogic/sensor.py +++ b/homeassistant/components/aqualogic/sensor.py @@ -1,9 +1,4 @@ -""" -Support for AquaLogic sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.aqualogic/ -""" +"""Support for AquaLogic sensors.""" import logging import voluptuous as vol @@ -46,8 +41,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the sensor platform.""" sensors = [] diff --git a/homeassistant/components/aqualogic/switch.py b/homeassistant/components/aqualogic/switch.py index 48c4702aca0..ee040fa1ba5 100644 --- a/homeassistant/components/aqualogic/switch.py +++ b/homeassistant/components/aqualogic/switch.py @@ -1,9 +1,4 @@ -""" -Support for AquaLogic switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.aqualogic/ -""" +"""Support for AquaLogic switches.""" import logging import voluptuous as vol @@ -37,8 +32,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the switch platform.""" switches = [] diff --git a/homeassistant/components/arduino/__init__.py b/homeassistant/components/arduino/__init__.py index 785f8c57f94..351122e74f0 100644 --- a/homeassistant/components/arduino/__init__.py +++ b/homeassistant/components/arduino/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Arduino boards running with the Firmata firmware. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/arduino/ -""" +"""Support for Arduino boards running with the Firmata firmware.""" import logging import voluptuous as vol diff --git a/homeassistant/components/arduino/sensor.py b/homeassistant/components/arduino/sensor.py index f46eebce1b2..ff758ea5847 100644 --- a/homeassistant/components/arduino/sensor.py +++ b/homeassistant/components/arduino/sensor.py @@ -1,11 +1,4 @@ -""" -Support for getting information from Arduino pins. - -Only analog pins are supported. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.arduino/ -""" +"""Support for getting information from Arduino pins.""" import logging import voluptuous as vol diff --git a/homeassistant/components/arduino/switch.py b/homeassistant/components/arduino/switch.py index ee8f0e878a3..947c5188766 100644 --- a/homeassistant/components/arduino/switch.py +++ b/homeassistant/components/arduino/switch.py @@ -1,11 +1,4 @@ -""" -Support for switching Arduino pins on and off. - -So far only digital pins are supported. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.arduino/ -""" +"""Support for switching Arduino pins on and off.""" import logging import voluptuous as vol diff --git a/homeassistant/components/arlo/__init__.py b/homeassistant/components/arlo/__init__.py index aebd57098b5..7e81836e522 100644 --- a/homeassistant/components/arlo/__init__.py +++ b/homeassistant/components/arlo/__init__.py @@ -1,9 +1,4 @@ -""" -This component provides support for Netgear Arlo IP cameras. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/arlo/ -""" +"""Support for Netgear Arlo IP cameras.""" import logging from datetime import timedelta diff --git a/homeassistant/components/arlo/alarm_control_panel.py b/homeassistant/components/arlo/alarm_control_panel.py index 66f11fab83f..8c21a448a23 100644 --- a/homeassistant/components/arlo/alarm_control_panel.py +++ b/homeassistant/components/arlo/alarm_control_panel.py @@ -1,9 +1,4 @@ -""" -Support for Arlo Alarm Control Panels. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/alarm_control_panel.arlo/ -""" +"""Support for Arlo Alarm Control Panels.""" import logging import voluptuous as vol diff --git a/homeassistant/components/arlo/camera.py b/homeassistant/components/arlo/camera.py index 7857995b4af..6f20ecdadcd 100644 --- a/homeassistant/components/arlo/camera.py +++ b/homeassistant/components/arlo/camera.py @@ -1,9 +1,4 @@ -""" -Support for Netgear Arlo IP cameras. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/camera.arlo/ -""" +"""Support for Netgear Arlo IP cameras.""" import logging import voluptuous as vol diff --git a/homeassistant/components/arlo/sensor.py b/homeassistant/components/arlo/sensor.py index be940cc4f51..3ad7b70a947 100644 --- a/homeassistant/components/arlo/sensor.py +++ b/homeassistant/components/arlo/sensor.py @@ -1,9 +1,4 @@ -""" -This component provides HA sensor for Netgear Arlo IP cameras. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.arlo/ -""" +"""Sensor support for Netgear Arlo IP cameras.""" import logging import voluptuous as vol diff --git a/homeassistant/components/asterisk_mbox/__init__.py b/homeassistant/components/asterisk_mbox/__init__.py index 406774d5fad..d8d3b194cd7 100644 --- a/homeassistant/components/asterisk_mbox/__init__.py +++ b/homeassistant/components/asterisk_mbox/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Asterisk Voicemail interface. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/asterisk_mbox/ -""" +"""Support for Asterisk Voicemail interface.""" import logging import voluptuous as vol @@ -78,9 +73,8 @@ class AsteriskData: @callback def handle_data(self, command, msg): """Handle changes to the mailbox.""" - from asterisk_mbox.commands import (CMD_MESSAGE_LIST, - CMD_MESSAGE_CDR_AVAILABLE, - CMD_MESSAGE_CDR) + from asterisk_mbox.commands import ( + CMD_MESSAGE_LIST, CMD_MESSAGE_CDR_AVAILABLE, CMD_MESSAGE_CDR) if command == CMD_MESSAGE_LIST: _LOGGER.debug("AsteriskVM sent updated message list: Len %d", @@ -89,8 +83,8 @@ class AsteriskData: self.messages = sorted( msg, key=lambda item: item['info']['origtime'], reverse=True) if not isinstance(old_messages, list): - async_dispatcher_send(self.hass, SIGNAL_DISCOVER_PLATFORM, - DOMAIN) + async_dispatcher_send( + self.hass, SIGNAL_DISCOVER_PLATFORM, DOMAIN) async_dispatcher_send(self.hass, SIGNAL_MESSAGE_UPDATE, self.messages) elif command == CMD_MESSAGE_CDR: diff --git a/homeassistant/components/asterisk_mbox/mailbox.py b/homeassistant/components/asterisk_mbox/mailbox.py index 087018084f2..9da4bd1a21a 100644 --- a/homeassistant/components/asterisk_mbox/mailbox.py +++ b/homeassistant/components/asterisk_mbox/mailbox.py @@ -1,9 +1,4 @@ -""" -Asterisk Voicemail interface. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/mailbox.asteriskvm/ -""" +"""Support for the Asterisk Voicemail interface.""" import logging from homeassistant.components.asterisk_mbox import DOMAIN as ASTERISK_DOMAIN diff --git a/homeassistant/components/asuswrt/__init__.py b/homeassistant/components/asuswrt/__init__.py index 0069b3c0d73..3fc0e9d6476 100644 --- a/homeassistant/components/asuswrt/__init__.py +++ b/homeassistant/components/asuswrt/__init__.py @@ -1,9 +1,4 @@ -""" -Support for ASUSWRT devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/asuswrt/ -""" +"""Support for ASUSWRT devices.""" import logging import voluptuous as vol @@ -18,15 +13,16 @@ REQUIREMENTS = ['aioasuswrt==1.1.20'] _LOGGER = logging.getLogger(__name__) +CONF_PUB_KEY = 'pub_key' +CONF_REQUIRE_IP = 'require_ip' +CONF_SENSORS = 'sensors' +CONF_SSH_KEY = 'ssh_key' + DOMAIN = "asuswrt" DATA_ASUSWRT = DOMAIN - -CONF_PUB_KEY = 'pub_key' -CONF_SSH_KEY = 'ssh_key' -CONF_REQUIRE_IP = 'require_ip' DEFAULT_SSH_PORT = 22 + SECRET_GROUP = 'Password or SSH Key' -CONF_SENSORS = 'sensors' SENSOR_TYPES = ['upload_speed', 'download_speed', 'download', 'upload'] CONFIG_SCHEMA = vol.Schema({ diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index 2073f680e00..8e749dca46e 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -1,9 +1,4 @@ -""" -Support for August devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/august/ -""" +"""Support for August devices.""" import logging from datetime import timedelta diff --git a/homeassistant/components/august/binary_sensor.py b/homeassistant/components/august/binary_sensor.py index c059f4c020a..1ad2d80cea8 100644 --- a/homeassistant/components/august/binary_sensor.py +++ b/homeassistant/components/august/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for August binary sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.august/ -""" +"""Support for August binary sensors.""" import logging from datetime import timedelta, datetime diff --git a/homeassistant/components/august/camera.py b/homeassistant/components/august/camera.py index c62a32342dc..7420bb04067 100644 --- a/homeassistant/components/august/camera.py +++ b/homeassistant/components/august/camera.py @@ -1,9 +1,4 @@ -""" -Support for August camera. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/camera.august/ -""" +"""Support for August camera.""" from datetime import timedelta import requests diff --git a/homeassistant/components/august/lock.py b/homeassistant/components/august/lock.py index e8364170980..ff355bbf87b 100644 --- a/homeassistant/components/august/lock.py +++ b/homeassistant/components/august/lock.py @@ -1,9 +1,4 @@ -""" -Support for August lock. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/lock.august/ -""" +"""Support for August lock.""" import logging from datetime import timedelta diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 836901cde30..35cf695f1e3 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -1,9 +1,4 @@ -""" -Allow to set up simple automation rules via the config file. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/automation/ -""" +"""Allow to set up simple automation rules via the config file.""" import asyncio from functools import partial import importlib diff --git a/homeassistant/components/automation/event.py b/homeassistant/components/automation/event.py index ec47479eac8..6cc7e3dae7d 100644 --- a/homeassistant/components/automation/event.py +++ b/homeassistant/components/automation/event.py @@ -1,9 +1,4 @@ -""" -Offer event listening automation rules. - -For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/docs/automation/trigger/#event-trigger -""" +"""Offer event listening automation rules.""" import logging import voluptuous as vol diff --git a/homeassistant/components/automation/geo_location.py b/homeassistant/components/automation/geo_location.py index 33ef00da380..8f838ea6d6b 100644 --- a/homeassistant/components/automation/geo_location.py +++ b/homeassistant/components/automation/geo_location.py @@ -1,10 +1,4 @@ -""" -Offer geolocation automation rules. - -For more details about this automation trigger, please refer to the -documentation at -https://home-assistant.io/docs/automation/trigger/#geolocation-trigger -""" +"""Offer geolocation automation rules.""" import voluptuous as vol from homeassistant.components.geo_location import DOMAIN diff --git a/homeassistant/components/automation/homeassistant.py b/homeassistant/components/automation/homeassistant.py index 6d7a44291c9..1b022316676 100644 --- a/homeassistant/components/automation/homeassistant.py +++ b/homeassistant/components/automation/homeassistant.py @@ -1,9 +1,4 @@ -""" -Offer Home Assistant core automation rules. - -For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/components/automation/#homeassistant-trigger -""" +"""Offer Home Assistant core automation rules.""" import logging import voluptuous as vol diff --git a/homeassistant/components/automation/litejet.py b/homeassistant/components/automation/litejet.py index 70e01174078..20c689d74cf 100644 --- a/homeassistant/components/automation/litejet.py +++ b/homeassistant/components/automation/litejet.py @@ -1,9 +1,4 @@ -""" -Trigger an automation when a LiteJet switch is released. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/automation.litejet/ -""" +"""Trigger an automation when a LiteJet switch is released.""" import logging import voluptuous as vol diff --git a/homeassistant/components/automation/mqtt.py b/homeassistant/components/automation/mqtt.py index 1fa0d540610..5f52da745ee 100644 --- a/homeassistant/components/automation/mqtt.py +++ b/homeassistant/components/automation/mqtt.py @@ -1,9 +1,4 @@ -""" -Offer MQTT listening automation rules. - -For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/docs/automation/trigger/#mqtt-trigger -""" +"""Offer MQTT listening automation rules.""" import json import voluptuous as vol diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index aa51e631026..bf45abb88f0 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -1,9 +1,4 @@ -""" -Offer numeric state listening automation rules. - -For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/docs/automation/trigger/#numeric-state-trigger -""" +"""Offer numeric state listening automation rules.""" import logging import voluptuous as vol diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index 4e47026d8d1..f4d7f69c07a 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -1,9 +1,4 @@ -""" -Offer state listening automation rules. - -For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/docs/automation/trigger/#state-trigger -""" +"""Offer state listening automation rules.""" import voluptuous as vol from homeassistant.core import callback diff --git a/homeassistant/components/automation/sun.py b/homeassistant/components/automation/sun.py index 509195689a1..07fbf716e1c 100644 --- a/homeassistant/components/automation/sun.py +++ b/homeassistant/components/automation/sun.py @@ -1,9 +1,4 @@ -""" -Offer sun based automation rules. - -For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/docs/automation/trigger/#sun-trigger -""" +"""Offer sun based automation rules.""" from datetime import timedelta import logging diff --git a/homeassistant/components/automation/template.py b/homeassistant/components/automation/template.py index 347b3f94e7d..ed86d52584f 100644 --- a/homeassistant/components/automation/template.py +++ b/homeassistant/components/automation/template.py @@ -1,9 +1,4 @@ -""" -Offer template automation rules. - -For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/docs/automation/trigger/#template-trigger -""" +"""Offer template automation rules.""" import logging import voluptuous as vol @@ -13,7 +8,6 @@ from homeassistant.const import CONF_VALUE_TEMPLATE, CONF_PLATFORM from homeassistant.helpers.event import async_track_template import homeassistant.helpers.config_validation as cv - _LOGGER = logging.getLogger(__name__) TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({ diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py index d57e190490f..ce6d6eb4446 100644 --- a/homeassistant/components/automation/time.py +++ b/homeassistant/components/automation/time.py @@ -1,9 +1,4 @@ -""" -Offer time listening automation rules. - -For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/docs/automation/trigger/#time-trigger -""" +"""Offer time listening automation rules.""" import logging import voluptuous as vol diff --git a/homeassistant/components/automation/time_pattern.py b/homeassistant/components/automation/time_pattern.py index 8b6e907f7b8..da8bc9f8629 100644 --- a/homeassistant/components/automation/time_pattern.py +++ b/homeassistant/components/automation/time_pattern.py @@ -1,9 +1,4 @@ -""" -Offer time listening automation rules. - -For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/docs/automation/trigger/#time-trigger -""" +"""Offer time listening automation rules.""" import logging import voluptuous as vol diff --git a/homeassistant/components/automation/webhook.py b/homeassistant/components/automation/webhook.py index f4afc8a601a..f65b5cf885c 100644 --- a/homeassistant/components/automation/webhook.py +++ b/homeassistant/components/automation/webhook.py @@ -1,9 +1,4 @@ -""" -Offer webhook triggered automation rules. - -For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/docs/automation/trigger/#webhook-trigger -""" +"""Offer webhook triggered automation rules.""" from functools import partial import logging diff --git a/homeassistant/components/automation/zone.py b/homeassistant/components/automation/zone.py index 0c3c0941a9e..e2d79eede8d 100644 --- a/homeassistant/components/automation/zone.py +++ b/homeassistant/components/automation/zone.py @@ -1,9 +1,4 @@ -""" -Offer zone automation rules. - -For more details about this automation rule, please refer to the documentation -at https://home-assistant.io/docs/automation/trigger/#zone-trigger -""" +"""Offer zone automation rules.""" import voluptuous as vol from homeassistant.core import callback diff --git a/homeassistant/components/axis/__init__.py b/homeassistant/components/axis/__init__.py index fd2e603445c..df723272a7a 100644 --- a/homeassistant/components/axis/__init__.py +++ b/homeassistant/components/axis/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Axis devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/axis/ -""" +"""Support for Axis devices.""" import logging import voluptuous as vol diff --git a/homeassistant/components/axis/binary_sensor.py b/homeassistant/components/axis/binary_sensor.py index 671bbc730d0..11014dc4bc9 100644 --- a/homeassistant/components/axis/binary_sensor.py +++ b/homeassistant/components/axis/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for Axis binary sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.axis/ -""" +"""Support for Axis binary sensors.""" from datetime import timedelta import logging diff --git a/homeassistant/components/axis/camera.py b/homeassistant/components/axis/camera.py index 9846ae85fb2..b9e969efec1 100644 --- a/homeassistant/components/axis/camera.py +++ b/homeassistant/components/axis/camera.py @@ -1,9 +1,4 @@ -""" -Support for Axis camera streaming. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/camera.axis/ -""" +"""Support for Axis camera streaming.""" import logging from homeassistant.components.camera.mjpeg import ( diff --git a/homeassistant/components/bbb_gpio/__init__.py b/homeassistant/components/bbb_gpio/__init__.py index e3f327f1d5c..7749af8f335 100644 --- a/homeassistant/components/bbb_gpio/__init__.py +++ b/homeassistant/components/bbb_gpio/__init__.py @@ -1,9 +1,4 @@ -""" -Support for controlling GPIO pins of a Beaglebone Black. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/bbb_gpio/ -""" +"""Support for controlling GPIO pins of a Beaglebone Black.""" import logging from homeassistant.const import ( diff --git a/homeassistant/components/bbb_gpio/binary_sensor.py b/homeassistant/components/bbb_gpio/binary_sensor.py index 8968b680369..1ee371dcc2a 100644 --- a/homeassistant/components/bbb_gpio/binary_sensor.py +++ b/homeassistant/components/bbb_gpio/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for binary sensor using Beaglebone Black GPIO. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.bbb_gpio/ -""" +"""Support for binary sensor using Beaglebone Black GPIO.""" import logging import voluptuous as vol diff --git a/homeassistant/components/bbb_gpio/switch.py b/homeassistant/components/bbb_gpio/switch.py index 9e120beb442..3ad46fd61ae 100644 --- a/homeassistant/components/bbb_gpio/switch.py +++ b/homeassistant/components/bbb_gpio/switch.py @@ -1,9 +1,4 @@ -""" -Allows to configure a switch using BeagleBone Black GPIO. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/switch.bbb_gpio/ -""" +"""Allows to configure a switch using BeagleBone Black GPIO.""" import logging import voluptuous as vol @@ -29,8 +24,7 @@ PIN_SCHEMA = vol.Schema({ }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_PINS, default={}): - vol.Schema({cv.string: PIN_SCHEMA}), + vol.Required(CONF_PINS, default={}): vol.Schema({cv.string: PIN_SCHEMA}), }) diff --git a/homeassistant/components/blink/__init__.py b/homeassistant/components/blink/__init__.py index bcf95ee2055..8e95f967396 100644 --- a/homeassistant/components/blink/__init__.py +++ b/homeassistant/components/blink/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Blink Home Camera System. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/blink/ -""" +"""Support for Blink Home Camera System.""" import logging from datetime import timedelta import voluptuous as vol diff --git a/homeassistant/components/blink/alarm_control_panel.py b/homeassistant/components/blink/alarm_control_panel.py index 77267fd7516..b9bf4a5250f 100644 --- a/homeassistant/components/blink/alarm_control_panel.py +++ b/homeassistant/components/blink/alarm_control_panel.py @@ -1,9 +1,4 @@ -""" -Support for Blink Alarm Control Panel. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/alarm_control_panel.blink/ -""" +"""Support for Blink Alarm Control Panel.""" import logging from homeassistant.components.alarm_control_panel import AlarmControlPanel diff --git a/homeassistant/components/blink/binary_sensor.py b/homeassistant/components/blink/binary_sensor.py index cd558f03684..fe0b95b1517 100644 --- a/homeassistant/components/blink/binary_sensor.py +++ b/homeassistant/components/blink/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for Blink system camera control. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.blink. -""" +"""Support for Blink system camera control.""" from homeassistant.components.blink import BLINK_DATA, BINARY_SENSORS from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.const import CONF_MONITORED_CONDITIONS diff --git a/homeassistant/components/blink/camera.py b/homeassistant/components/blink/camera.py index e9047914456..2e5c024d6e5 100644 --- a/homeassistant/components/blink/camera.py +++ b/homeassistant/components/blink/camera.py @@ -1,9 +1,4 @@ -""" -Support for Blink system camera. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/camera.blink/ -""" +"""Support for Blink system camera.""" import logging from homeassistant.components.blink import BLINK_DATA, DEFAULT_BRAND diff --git a/homeassistant/components/blink/sensor.py b/homeassistant/components/blink/sensor.py index a1a07cb2a73..c1fdf9252dd 100644 --- a/homeassistant/components/blink/sensor.py +++ b/homeassistant/components/blink/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Blink system camera sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.blink/ -""" +"""Support for Blink system camera sensors.""" import logging from homeassistant.components.blink import BLINK_DATA, SENSORS diff --git a/homeassistant/components/bloomsky/__init__.py b/homeassistant/components/bloomsky/__init__.py index 55fb15c686d..a42eb34004b 100644 --- a/homeassistant/components/bloomsky/__init__.py +++ b/homeassistant/components/bloomsky/__init__.py @@ -1,9 +1,4 @@ -""" -Support for BloomSky weather station. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/bloomsky/ -""" +"""Support for BloomSky weather station.""" from datetime import timedelta import logging diff --git a/homeassistant/components/bloomsky/binary_sensor.py b/homeassistant/components/bloomsky/binary_sensor.py index 971941f4dd6..c8763524de7 100644 --- a/homeassistant/components/bloomsky/binary_sensor.py +++ b/homeassistant/components/bloomsky/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support the binary sensors of a BloomSky weather station. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.bloomsky/ -""" +"""Support the binary sensors of a BloomSky weather station.""" import logging import voluptuous as vol diff --git a/homeassistant/components/bloomsky/camera.py b/homeassistant/components/bloomsky/camera.py index 1c9266ca3a7..5cb2e1adfe1 100644 --- a/homeassistant/components/bloomsky/camera.py +++ b/homeassistant/components/bloomsky/camera.py @@ -1,9 +1,4 @@ -""" -Support for a camera of a BloomSky weather station. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/camera.bloomsky/ -""" +"""Support for a camera of a BloomSky weather station.""" import logging import requests diff --git a/homeassistant/components/bloomsky/sensor.py b/homeassistant/components/bloomsky/sensor.py index 02cd456107f..7e6847f0e7e 100644 --- a/homeassistant/components/bloomsky/sensor.py +++ b/homeassistant/components/bloomsky/sensor.py @@ -1,9 +1,4 @@ -""" -Support the sensor of a BloomSky weather station. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/sensor.bloomsky/ -""" +"""Support the sensor of a BloomSky weather station.""" import logging import voluptuous as vol diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index 40f2b91045a..e1ac30120d2 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -1,9 +1,4 @@ -""" -Reads vehicle status from BMW connected drive portal. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/bmw_connected_drive/ -""" +"""Reads vehicle status from BMW connected drive portal.""" import datetime import logging @@ -87,8 +82,8 @@ def setup_account(account_config: dict, hass, name: str) \ read_only = account_config[CONF_READ_ONLY] _LOGGER.debug('Adding new account %s', name) - cd_account = BMWConnectedDriveAccount(username, password, region, name, - read_only) + cd_account = BMWConnectedDriveAccount( + username, password, region, name, read_only) def execute_service(call): """Execute a service for a vehicle. @@ -99,7 +94,7 @@ def setup_account(account_config: dict, hass, name: str) \ vin = call.data[ATTR_VIN] vehicle = cd_account.account.get_vehicle(vin) if not vehicle: - _LOGGER.error('Could not find a vehicle for VIN "%s"!', vin) + _LOGGER.error("Could not find a vehicle for VIN %s", vin) return function_name = _SERVICE_MAP[call.service] function_call = getattr(vehicle.remote_services, function_name) @@ -108,9 +103,7 @@ def setup_account(account_config: dict, hass, name: str) \ # register the remote services for service in _SERVICE_MAP: hass.services.register( - DOMAIN, service, - execute_service, - schema=SERVICE_SCHEMA) + DOMAIN, service, execute_service, schema=SERVICE_SCHEMA) # update every UPDATE_INTERVAL minutes, starting now # this should even out the load on the servers @@ -144,15 +137,15 @@ class BMWConnectedDriveAccount: Notify all listeners about the update. """ - _LOGGER.debug('Updating vehicle state for account %s, ' - 'notifying %d listeners', - self.name, len(self._update_listeners)) + _LOGGER.debug( + "Updating vehicle state for account %s, notifying %d listeners", + self.name, len(self._update_listeners)) try: self.account.update_vehicle_states() for listener in self._update_listeners: listener() except IOError as exception: - _LOGGER.error('Error updating the vehicle state.') + _LOGGER.error("Error updating the vehicle state") _LOGGER.exception(exception) def add_update_listener(self, listener): diff --git a/homeassistant/components/bmw_connected_drive/binary_sensor.py b/homeassistant/components/bmw_connected_drive/binary_sensor.py index f8855b2e28b..1843f647df8 100644 --- a/homeassistant/components/bmw_connected_drive/binary_sensor.py +++ b/homeassistant/components/bmw_connected_drive/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Reads vehicle status from BMW connected drive portal. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.bmw_connected_drive/ -""" +"""Reads vehicle status from BMW connected drive portal.""" import logging from homeassistant.components.binary_sensor import BinarySensorDevice @@ -42,14 +37,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if vehicle.has_hv_battery: _LOGGER.debug('BMW with a high voltage battery') for key, value in sorted(SENSOR_TYPES_ELEC.items()): - device = BMWConnectedDriveSensor(account, vehicle, key, - value[0], value[1]) + device = BMWConnectedDriveSensor( + account, vehicle, key, value[0], value[1]) devices.append(device) elif vehicle.has_internal_combustion_engine: _LOGGER.debug('BMW with an internal combustion engine') for key, value in sorted(SENSOR_TYPES.items()): - device = BMWConnectedDriveSensor(account, vehicle, key, - value[0], value[1]) + device = BMWConnectedDriveSensor( + account, vehicle, key, value[0], value[1]) devices.append(device) add_entities(devices, True) diff --git a/homeassistant/components/bmw_connected_drive/device_tracker.py b/homeassistant/components/bmw_connected_drive/device_tracker.py index 02a12653180..21121b069af 100644 --- a/homeassistant/components/bmw_connected_drive/device_tracker.py +++ b/homeassistant/components/bmw_connected_drive/device_tracker.py @@ -1,8 +1,4 @@ -"""Device tracker for BMW Connected Drive vehicles. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/device_tracker.bmw_connected_drive/ -""" +"""Device tracker for BMW Connected Drive vehicles.""" import logging from homeassistant.components.bmw_connected_drive import DOMAIN \ diff --git a/homeassistant/components/bmw_connected_drive/lock.py b/homeassistant/components/bmw_connected_drive/lock.py index b13665610d8..8a5eddaa86a 100644 --- a/homeassistant/components/bmw_connected_drive/lock.py +++ b/homeassistant/components/bmw_connected_drive/lock.py @@ -1,9 +1,4 @@ -""" -Support for BMW cars with BMW ConnectedDrive. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/lock.bmw_connected_drive/ -""" +"""Support for BMW car locks with BMW ConnectedDrive.""" import logging from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN diff --git a/homeassistant/components/bmw_connected_drive/sensor.py b/homeassistant/components/bmw_connected_drive/sensor.py index a7ee5724d19..a01142c53ed 100644 --- a/homeassistant/components/bmw_connected_drive/sensor.py +++ b/homeassistant/components/bmw_connected_drive/sensor.py @@ -1,9 +1,4 @@ -""" -Reads vehicle status from BMW connected drive portal. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.bmw_connected_drive/ -""" +"""Support for reading vehicle status from BMW connected drive portal.""" import logging from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN @@ -25,7 +20,7 @@ ATTR_TO_HA_METRIC = { 'max_range_electric': ['mdi:ruler', LENGTH_KILOMETERS], 'remaining_fuel': ['mdi:gas-station', VOLUME_LITERS], 'charging_time_remaining': ['mdi:update', 'h'], - 'charging_status': ['mdi:battery-charging', None] + 'charging_status': ['mdi:battery-charging', None], } ATTR_TO_HA_IMPERIAL = { @@ -36,7 +31,7 @@ ATTR_TO_HA_IMPERIAL = { 'max_range_electric': ['mdi:ruler', LENGTH_MILES], 'remaining_fuel': ['mdi:gas-station', VOLUME_GALLONS], 'charging_time_remaining': ['mdi:update', 'h'], - 'charging_status': ['mdi:battery-charging', None] + 'charging_status': ['mdi:battery-charging', None], } @@ -54,12 +49,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for account in accounts: for vehicle in account.account.vehicles: for attribute_name in vehicle.drive_train_attributes: - device = BMWConnectedDriveSensor(account, vehicle, - attribute_name, - attribute_info) + device = BMWConnectedDriveSensor( + account, vehicle, attribute_name, attribute_info) devices.append(device) - device = BMWConnectedDriveSensor(account, vehicle, 'mileage', - attribute_info) + device = BMWConnectedDriveSensor( + account, vehicle, 'mileage', attribute_info) devices.append(device) add_entities(devices, True) @@ -140,13 +134,13 @@ class BMWConnectedDriveSensor(Entity): self._state = getattr(vehicle_state, self._attribute).value elif self.unit_of_measurement == VOLUME_GALLONS: value = getattr(vehicle_state, self._attribute) - value_converted = self.hass.config.units.volume(value, - VOLUME_LITERS) + value_converted = self.hass.config.units.volume( + value, VOLUME_LITERS) self._state = round(value_converted) elif self.unit_of_measurement == LENGTH_MILES: value = getattr(vehicle_state, self._attribute) - value_converted = self.hass.config.units.length(value, - LENGTH_KILOMETERS) + value_converted = self.hass.config.units.length( + value, LENGTH_KILOMETERS) self._state = round(value_converted) else: self._state = getattr(vehicle_state, self._attribute) diff --git a/homeassistant/components/browser/__init__.py b/homeassistant/components/browser/__init__.py index 041a0f9cdc6..1c002f21f5f 100644 --- a/homeassistant/components/browser/__init__.py +++ b/homeassistant/components/browser/__init__.py @@ -1,17 +1,13 @@ -""" -Provides functionality to launch a web browser on the host machine. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/browser/ -""" +"""Support for launching a web browser on the host machine.""" import voluptuous as vol -DOMAIN = "browser" -SERVICE_BROWSE_URL = "browse_url" - ATTR_URL = 'url' ATTR_URL_DEFAULT = 'https://www.google.com' +DOMAIN = 'browser' + +SERVICE_BROWSE_URL = 'browse_url' + SERVICE_BROWSE_URL_SCHEMA = vol.Schema({ # pylint: disable=no-value-for-parameter vol.Required(ATTR_URL, default=ATTR_URL_DEFAULT): vol.Url(), diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index 024dc1ac9de..aa9e3153fe5 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Google Calendar event device sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/calendar/ -""" +"""Support for Google Calendar event device sensors.""" import logging from datetime import timedelta import re diff --git a/homeassistant/components/canary/__init__.py b/homeassistant/components/canary/__init__.py index 04c33d83f3d..e53c7e22d2d 100644 --- a/homeassistant/components/canary/__init__.py +++ b/homeassistant/components/canary/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Canary. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/canary/ -""" +"""Support for Canary devices.""" import logging from datetime import timedelta diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py index bc2f52139e2..94b926795e7 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -2,9 +2,9 @@ from homeassistant import config_entries from homeassistant.helpers import config_entry_flow +REQUIREMENTS = ['pychromecast==2.5.0'] DOMAIN = 'cast' -REQUIREMENTS = ['pychromecast==2.5.0'] async def async_setup(hass, config): diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index a58bab92e9d..432290482f1 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -1,9 +1,4 @@ -""" -Provide functionality to interact with Cast devices on the network. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/media_player.cast/ -""" +"""Provide functionality to interact with Cast devices on the network.""" import asyncio import logging import threading diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 98e649e1742..c427657c76d 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -1,9 +1,4 @@ -""" -Component to integrate the Home Assistant cloud. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/cloud/ -""" +"""Component to integrate the Home Assistant cloud.""" from datetime import datetime, timedelta import json import logging diff --git a/homeassistant/components/cloudflare/__init__.py b/homeassistant/components/cloudflare/__init__.py index ae400ca6385..363e7c5eeb1 100644 --- a/homeassistant/components/cloudflare/__init__.py +++ b/homeassistant/components/cloudflare/__init__.py @@ -1,9 +1,4 @@ -""" -Update the IP addresses of your Cloudflare DNS records. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/cloudflare/ -""" +"""Update the IP addresses of your Cloudflare DNS records.""" from datetime import timedelta import logging diff --git a/homeassistant/components/coinbase/__init__.py b/homeassistant/components/coinbase/__init__.py index 98c321b9f5a..40d04eadb3a 100644 --- a/homeassistant/components/coinbase/__init__.py +++ b/homeassistant/components/coinbase/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Coinbase. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/coinbase/ -""" +"""Support for Coinbase.""" from datetime import timedelta import logging diff --git a/homeassistant/components/comfoconnect/__init__.py b/homeassistant/components/comfoconnect/__init__.py index 69d88274f29..64ebec18545 100644 --- a/homeassistant/components/comfoconnect/__init__.py +++ b/homeassistant/components/comfoconnect/__init__.py @@ -1,9 +1,4 @@ -""" -Support to control a Zehnder ComfoAir Q350/450/600 ventilation unit. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/comfoconnect/ -""" +"""Support to control a Zehnder ComfoAir Q350/450/600 ventilation unit.""" import logging import voluptuous as vol diff --git a/homeassistant/components/comfoconnect/fan.py b/homeassistant/components/comfoconnect/fan.py index 0e0ac8c80b6..a396dd276a5 100644 --- a/homeassistant/components/comfoconnect/fan.py +++ b/homeassistant/components/comfoconnect/fan.py @@ -1,9 +1,4 @@ -""" -Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/fan.comfoconnect/ -""" +"""Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit.""" import logging from homeassistant.components.comfoconnect import ( diff --git a/homeassistant/components/comfoconnect/sensor.py b/homeassistant/components/comfoconnect/sensor.py index 9ae6a4de091..ac5de866cfb 100644 --- a/homeassistant/components/comfoconnect/sensor.py +++ b/homeassistant/components/comfoconnect/sensor.py @@ -1,9 +1,4 @@ -""" -Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/sensor.comfoconnect/ -""" +"""Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit.""" import logging from homeassistant.components.comfoconnect import ( diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index 7836cba6cf9..47ac9d3a4b2 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -7,7 +7,6 @@ from homeassistant.const import CONF_ID, SERVICE_RELOAD from homeassistant.components.automation import DOMAIN, PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv - CONFIG_PATH = 'automations.yaml' diff --git a/homeassistant/components/config/customize.py b/homeassistant/components/config/customize.py index b7a8c9c070a..ec9d9d0ff27 100644 --- a/homeassistant/components/config/customize.py +++ b/homeassistant/components/config/customize.py @@ -1,5 +1,4 @@ """Provide configuration end points for Customize.""" - from homeassistant.components.config import EditKeyBasedConfigView from homeassistant.components import SERVICE_RELOAD_CORE_CONFIG from homeassistant.config import DATA_CUSTOMIZE diff --git a/homeassistant/components/config/script.py b/homeassistant/components/config/script.py index 3adc6f14233..640e267d066 100644 --- a/homeassistant/components/config/script.py +++ b/homeassistant/components/config/script.py @@ -1,5 +1,4 @@ """Provide configuration end points for scripts.""" - from homeassistant.components.config import EditKeyBasedConfigView from homeassistant.components.script import DOMAIN, SCRIPT_ENTRY_SCHEMA from homeassistant.const import SERVICE_RELOAD diff --git a/homeassistant/components/config/zwave.py b/homeassistant/components/config/zwave.py index 57123ee12de..f29dde4594c 100644 --- a/homeassistant/components/config/zwave.py +++ b/homeassistant/components/config/zwave.py @@ -1,13 +1,14 @@ """Provide configuration end points for Z-Wave.""" +from collections import deque import logging -from collections import deque from aiohttp.web import Response -import homeassistant.core as ha -from homeassistant.const import HTTP_NOT_FOUND, HTTP_OK -from homeassistant.components.http import HomeAssistantView + from homeassistant.components.config import EditKeyBasedConfigView -from homeassistant.components.zwave import const, DEVICE_CONFIG_SCHEMA_ENTRY +from homeassistant.components.http import HomeAssistantView +from homeassistant.components.zwave import DEVICE_CONFIG_SCHEMA_ENTRY, const +from homeassistant.const import HTTP_NOT_FOUND, HTTP_OK +import homeassistant.core as ha import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/conversation/__init__.py b/homeassistant/components/conversation/__init__.py index d8d386f5ca0..7082cb36726 100644 --- a/homeassistant/components/conversation/__init__.py +++ b/homeassistant/components/conversation/__init__.py @@ -1,9 +1,4 @@ -""" -Support for functionality to have conversations with Home Assistant. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/conversation/ -""" +"""Support for functionality to have conversations with Home Assistant.""" import logging import re diff --git a/homeassistant/components/counter/__init__.py b/homeassistant/components/counter/__init__.py index aeef2818f63..ab7ada618fe 100644 --- a/homeassistant/components/counter/__init__.py +++ b/homeassistant/components/counter/__init__.py @@ -1,9 +1,4 @@ -""" -Component to count within automations. - -For more details about this component, please refer to the documentation -at https://home-assistant.io/components/counter/ -""" +"""Component to count within automations.""" import logging import voluptuous as vol diff --git a/homeassistant/components/daikin/__init__.py b/homeassistant/components/daikin/__init__.py index f16d6a87d55..ce4a58162c7 100644 --- a/homeassistant/components/daikin/__init__.py +++ b/homeassistant/components/daikin/__init__.py @@ -1,9 +1,4 @@ -""" -Platform for the Daikin AC. - -For more details about this component, please refer to the documentation -https://home-assistant.io/components/daikin/ -""" +"""Platform for the Daikin AC.""" import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/daikin/climate.py b/homeassistant/components/daikin/climate.py index 45589c12864..d97b506e273 100644 --- a/homeassistant/components/daikin/climate.py +++ b/homeassistant/components/daikin/climate.py @@ -1,9 +1,4 @@ -""" -Support for the Daikin HVAC. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/climate.daikin/ -""" +"""Support for the Daikin HVAC.""" import logging import re @@ -22,7 +17,6 @@ from homeassistant.const import ( ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, TEMP_CELSIUS) import homeassistant.helpers.config_validation as cv - _LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ diff --git a/homeassistant/components/daikin/sensor.py b/homeassistant/components/daikin/sensor.py index f887ccf64a0..6065a182274 100644 --- a/homeassistant/components/daikin/sensor.py +++ b/homeassistant/components/daikin/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Daikin AC Sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.daikin/ -""" +"""Support for Daikin AC sensors.""" import logging from homeassistant.components.daikin import DOMAIN as DAIKIN_DOMAIN diff --git a/homeassistant/components/danfoss_air/__init__.py b/homeassistant/components/danfoss_air/__init__.py index 80c36b6f0c6..d6123a25f23 100644 --- a/homeassistant/components/danfoss_air/__init__.py +++ b/homeassistant/components/danfoss_air/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Danfoss Air HRV. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/danfoss_air/ -""" +"""Support for Danfoss Air HRV.""" from datetime import timedelta import logging diff --git a/homeassistant/components/datadog/__init__.py b/homeassistant/components/datadog/__init__.py index 58503d7187b..3b519514d17 100644 --- a/homeassistant/components/datadog/__init__.py +++ b/homeassistant/components/datadog/__init__.py @@ -1,9 +1,4 @@ -""" -A component which allows you to send data to Datadog. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/datadog/ -""" +"""Support for sending data to Datadog.""" import logging import voluptuous as vol diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 4d3e2cbc6a9..8015324be13 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -1,9 +1,4 @@ -""" -Support for deCONZ devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/deconz/ -""" +"""Support for deCONZ devices.""" import voluptuous as vol from homeassistant import config_entries diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index 286b310c1a9..77d01c5c40b 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for deCONZ binary sensor. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.deconz/ -""" +"""Support for deCONZ binary sensors.""" from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.const import ATTR_BATTERY_LEVEL from homeassistant.core import callback @@ -16,8 +11,8 @@ from .deconz_device import DeconzDevice DEPENDENCIES = ['deconz'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Old way of setting up deCONZ binary sensors.""" pass diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index f7bc71a2398..8f90f303fca 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -1,5 +1,4 @@ """Config flow to configure deCONZ component.""" - import voluptuous as vol from homeassistant import config_entries diff --git a/homeassistant/components/deconz/cover.py b/homeassistant/components/deconz/cover.py index 99bdd20a295..48f06a894bb 100644 --- a/homeassistant/components/deconz/cover.py +++ b/homeassistant/components/deconz/cover.py @@ -1,9 +1,4 @@ -""" -Support for deCONZ covers. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/cover.deconz/ -""" +"""Support for deCONZ covers.""" from homeassistant.components.cover import ( ATTR_POSITION, CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_STOP, SUPPORT_SET_POSITION) @@ -18,8 +13,8 @@ DEPENDENCIES = ['deconz'] ZIGBEE_SPEC = ['lumi.curtain'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Unsupported way of setting up deCONZ covers.""" pass diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index f7c777b8100..50e22c84d6f 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -1,9 +1,4 @@ -""" -Support for deCONZ light. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/light.deconz/ -""" +"""Support for deCONZ lights.""" from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_HS_COLOR, ATTR_TRANSITION, EFFECT_COLORLOOP, FLASH_LONG, FLASH_SHORT, @@ -21,8 +16,8 @@ from .deconz_device import DeconzDevice DEPENDENCIES = ['deconz'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Old way of setting up deCONZ lights and group.""" pass diff --git a/homeassistant/components/deconz/scene.py b/homeassistant/components/deconz/scene.py index 05845a02288..d3a6df810ba 100644 --- a/homeassistant/components/deconz/scene.py +++ b/homeassistant/components/deconz/scene.py @@ -1,9 +1,4 @@ -""" -Support for deCONZ scenes. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/scene.deconz/ -""" +"""Support for deCONZ scenes.""" from homeassistant.components.deconz import DOMAIN as DECONZ_DOMAIN from homeassistant.components.scene import Scene from homeassistant.core import callback @@ -12,8 +7,8 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect DEPENDENCIES = ['deconz'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Old way of setting up deCONZ scenes.""" pass diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 1913e3d5087..3083f0c6732 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -1,9 +1,4 @@ -""" -Support for deCONZ sensor. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/sensor.deconz/ -""" +"""Support for deCONZ sensors.""" from homeassistant.const import ( ATTR_BATTERY_LEVEL, ATTR_VOLTAGE, DEVICE_CLASS_BATTERY) from homeassistant.core import callback @@ -21,8 +16,8 @@ ATTR_DAYLIGHT = 'daylight' ATTR_EVENT_ID = 'event_id' -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Old way of setting up deCONZ sensors.""" pass diff --git a/homeassistant/components/deconz/switch.py b/homeassistant/components/deconz/switch.py index 64d93389670..c48c7205e01 100644 --- a/homeassistant/components/deconz/switch.py +++ b/homeassistant/components/deconz/switch.py @@ -1,9 +1,4 @@ -""" -Support for deCONZ switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.deconz/ -""" +"""Support for deCONZ switches.""" from homeassistant.components.switch import SwitchDevice from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -11,12 +6,11 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import DOMAIN as DECONZ_DOMAIN, POWER_PLUGS, SIRENS from .deconz_device import DeconzDevice - DEPENDENCIES = ['deconz'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Old way of setting up deCONZ switches.""" pass diff --git a/homeassistant/components/demo/__init__.py b/homeassistant/components/demo/__init__.py index 2b9854fbcc7..2699ade0b1d 100644 --- a/homeassistant/components/demo/__init__.py +++ b/homeassistant/components/demo/__init__.py @@ -1,9 +1,4 @@ -""" -Set up the demo environment that mimics interaction with devices. - -For more details about this component, please refer to the documentation -https://home-assistant.io/components/demo/ -""" +"""Set up the demo environment that mimics interaction with devices.""" import asyncio import time diff --git a/homeassistant/components/dialogflow/__init__.py b/homeassistant/components/dialogflow/__init__.py index 3f3fbe7c14e..210aebe80d5 100644 --- a/homeassistant/components/dialogflow/__init__.py +++ b/homeassistant/components/dialogflow/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Dialogflow webhook. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/dialogflow/ -""" +"""Support for Dialogflow webhook.""" import logging import voluptuous as vol @@ -12,6 +7,7 @@ from aiohttp import web from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import intent, template, config_entry_flow + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['webhook'] @@ -29,7 +25,7 @@ class DialogFlowError(HomeAssistantError): async def async_setup(hass, config): - """Set up Dialogflow component.""" + """Set up the Dialogflow component.""" return True @@ -45,9 +41,7 @@ async def handle_webhook(hass, webhook_id, request): except DialogFlowError as err: _LOGGER.warning(str(err)) - return web.json_response( - dialogflow_error_response(message, str(err)) - ) + return web.json_response(dialogflow_error_response(message, str(err))) except intent.UnknownIntent as err: _LOGGER.warning(str(err)) diff --git a/homeassistant/components/digital_ocean/__init__.py b/homeassistant/components/digital_ocean/__init__.py index c0c9d95586c..d061dad6726 100644 --- a/homeassistant/components/digital_ocean/__init__.py +++ b/homeassistant/components/digital_ocean/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Digital Ocean. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/digital_ocean/ -""" +"""Support for Digital Ocean.""" import logging from datetime import timedelta diff --git a/homeassistant/components/digital_ocean/binary_sensor.py b/homeassistant/components/digital_ocean/binary_sensor.py index 0f604c525e0..255f43b67ba 100644 --- a/homeassistant/components/digital_ocean/binary_sensor.py +++ b/homeassistant/components/digital_ocean/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for monitoring the state of Digital Ocean droplets. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.digital_ocean/ -""" +"""Support for monitoring the state of Digital Ocean droplets.""" import logging import voluptuous as vol diff --git a/homeassistant/components/digital_ocean/switch.py b/homeassistant/components/digital_ocean/switch.py index c17df81abba..a10c961b8e4 100644 --- a/homeassistant/components/digital_ocean/switch.py +++ b/homeassistant/components/digital_ocean/switch.py @@ -1,9 +1,4 @@ -""" -Support for interacting with Digital Ocean droplets. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/switch.digital_ocean/ -""" +"""Support for interacting with Digital Ocean droplets.""" import logging import voluptuous as vol diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py index 87b89ddb44c..85e3164d08b 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -26,28 +26,28 @@ REQUIREMENTS = ['netdisco==2.3.0'] DOMAIN = 'discovery' SCAN_INTERVAL = timedelta(seconds=300) -SERVICE_NETGEAR = 'netgear_router' -SERVICE_WEMO = 'belkin_wemo' -SERVICE_HASS_IOS_APP = 'hass_ios' -SERVICE_IKEA_TRADFRI = 'ikea_tradfri' -SERVICE_HASSIO = 'hassio' -SERVICE_AXIS = 'axis' SERVICE_APPLE_TV = 'apple_tv' -SERVICE_WINK = 'wink' -SERVICE_XIAOMI_GW = 'xiaomi_gw' -SERVICE_TELLDUSLIVE = 'tellstick' -SERVICE_HUE = 'philips_hue' -SERVICE_KONNECTED = 'konnected' -SERVICE_DECONZ = 'deconz' +SERVICE_AXIS = 'axis' SERVICE_DAIKIN = 'daikin' +SERVICE_DECONZ = 'deconz' +SERVICE_DLNA_DMR = 'dlna_dmr' +SERVICE_FREEBOX = 'freebox' +SERVICE_HASS_IOS_APP = 'hass_ios' +SERVICE_HASSIO = 'hassio' +SERVICE_HOMEKIT = 'homekit' +SERVICE_HUE = 'philips_hue' +SERVICE_IGD = 'igd' +SERVICE_IKEA_TRADFRI = 'ikea_tradfri' +SERVICE_KONNECTED = 'konnected' +SERVICE_NETGEAR = 'netgear_router' +SERVICE_OCTOPRINT = 'octoprint' +SERVICE_ROKU = 'roku' SERVICE_SABNZBD = 'sabnzbd' SERVICE_SAMSUNG_PRINTER = 'samsung_printer' -SERVICE_HOMEKIT = 'homekit' -SERVICE_OCTOPRINT = 'octoprint' -SERVICE_FREEBOX = 'freebox' -SERVICE_IGD = 'igd' -SERVICE_DLNA_DMR = 'dlna_dmr' -SERVICE_ROKU = 'roku' +SERVICE_TELLDUSLIVE = 'tellstick' +SERVICE_WEMO = 'belkin_wemo' +SERVICE_WINK = 'wink' +SERVICE_XIAOMI_GW = 'xiaomi_gw' CONFIG_ENTRY_HANDLERS = { SERVICE_DAIKIN: 'daikin', diff --git a/homeassistant/components/dominos/__init__.py b/homeassistant/components/dominos/__init__.py index 2c9f763aaa8..1c8966f3b4b 100644 --- a/homeassistant/components/dominos/__init__.py +++ b/homeassistant/components/dominos/__init__.py @@ -1,24 +1,18 @@ -""" -Support for Dominos Pizza ordering. - -The Dominos Pizza component creates a service which can be invoked to order -from their menu - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/dominos/. -""" -import logging +"""Support for Dominos Pizza ordering.""" from datetime import timedelta +import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components import http from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.util import Throttle +REQUIREMENTS = ['pizzapi==0.0.3'] + _LOGGER = logging.getLogger(__name__) # The domain of your component. Should be equal to the name of your component. @@ -40,8 +34,6 @@ ATTR_ORDER_CODES = 'codes' MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10) MIN_TIME_BETWEEN_STORE_UPDATES = timedelta(minutes=3330) -REQUIREMENTS = ['pizzapi==0.0.3'] - DEPENDENCIES = ['http'] _ORDERS_SCHEMA = vol.Schema({ diff --git a/homeassistant/components/doorbird/__init__.py b/homeassistant/components/doorbird/__init__.py index 28747bbe8be..d477836425d 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -1,19 +1,15 @@ -""" -Support for DoorBird device. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/doorbird/ -""" +"""Support for DoorBird devices.""" import logging - from urllib.error import HTTPError + import voluptuous as vol from homeassistant.components.http import HomeAssistantView -from homeassistant.const import CONF_HOST, CONF_USERNAME, \ - CONF_PASSWORD, CONF_NAME, CONF_DEVICES, CONF_MONITORED_CONDITIONS +from homeassistant.const import ( + CONF_DEVICES, CONF_HOST, CONF_MONITORED_CONDITIONS, CONF_NAME, + CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME) import homeassistant.helpers.config_validation as cv -from homeassistant.util import slugify, dt as dt_util +from homeassistant.util import dt as dt_util, slugify REQUIREMENTS = ['doorbirdpy==2.0.6'] @@ -28,7 +24,6 @@ CONF_DOORBELL_EVENTS = 'doorbell_events' CONF_DOORBELL_NUMS = 'doorbell_numbers' CONF_RELAY_NUMS = 'relay_numbers' CONF_MOTION_EVENTS = 'motion_events' -CONF_TOKEN = 'token' SENSOR_TYPES = { 'doorbell': { diff --git a/homeassistant/components/doorbird/camera.py b/homeassistant/components/doorbird/camera.py index 8982e6d0847..e201837aaf6 100644 --- a/homeassistant/components/doorbird/camera.py +++ b/homeassistant/components/doorbird/camera.py @@ -1,9 +1,4 @@ -""" -Support for viewing the camera feed from a DoorBird video doorbell. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/camera.doorbird/ -""" +"""Support for viewing the camera feed from a DoorBird video doorbell.""" import asyncio import datetime import logging diff --git a/homeassistant/components/dovado/__init__.py b/homeassistant/components/dovado/__init__.py index 7a50ac815b1..df2eed3011a 100644 --- a/homeassistant/components/dovado/__init__.py +++ b/homeassistant/components/dovado/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Dovado router. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/dovado/ -""" +"""Support for Dovado router.""" import logging from datetime import timedelta @@ -37,17 +32,14 @@ def setup(hass, config): hass.data[DOMAIN] = DovadoData( dovado.Dovado( - config[CONF_USERNAME], - config[CONF_PASSWORD], - config.get(CONF_HOST), - config.get(CONF_PORT) - ) + config[CONF_USERNAME], config[CONF_PASSWORD], + config.get(CONF_HOST), config.get(CONF_PORT)) ) return True class DovadoData: - """Maintains a connection to the router.""" + """Maintain a connection to the router.""" def __init__(self, client): """Set up a new Dovado connection.""" diff --git a/homeassistant/components/dovado/notify.py b/homeassistant/components/dovado/notify.py index 00036378a78..ea6ba2b455f 100644 --- a/homeassistant/components/dovado/notify.py +++ b/homeassistant/components/dovado/notify.py @@ -1,9 +1,4 @@ -""" -Support for SMS notifications from the Dovado router. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/notify.dovado/ -""" +"""Support for SMS notifications from the Dovado router.""" import logging from homeassistant.components.dovado import DOMAIN as DOVADO_DOMAIN diff --git a/homeassistant/components/dovado/sensor.py b/homeassistant/components/dovado/sensor.py index b89275b1795..eb0016ed298 100644 --- a/homeassistant/components/dovado/sensor.py +++ b/homeassistant/components/dovado/sensor.py @@ -1,9 +1,4 @@ -""" -Support for sensors from the Dovado router. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.dovado/ -""" +"""Support for sensors from the Dovado router.""" import logging import re from datetime import timedelta @@ -31,20 +26,16 @@ SENSOR_SMS_UNREAD = 'sms' SENSORS = { SENSOR_NETWORK: ('signal strength', 'Network', None, 'mdi:access-point-network'), - SENSOR_SIGNAL: ('signal strength', 'Signal Strength', '%', - 'mdi:signal'), + SENSOR_SIGNAL: ('signal strength', 'Signal Strength', '%', 'mdi:signal'), SENSOR_SMS_UNREAD: ('sms unread', 'SMS unread', '', 'mdi:message-text-outline'), - SENSOR_UPLOAD: ('traffic modem tx', 'Sent', 'GB', - 'mdi:cloud-upload'), + SENSOR_UPLOAD: ('traffic modem tx', 'Sent', 'GB', 'mdi:cloud-upload'), SENSOR_DOWNLOAD: ('traffic modem rx', 'Received', 'GB', 'mdi:cloud-download'), } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SENSORS): vol.All( - cv.ensure_list, [vol.In(SENSORS)] - ), + vol.Required(CONF_SENSORS): vol.All(cv.ensure_list, [vol.In(SENSORS)]), }) @@ -69,6 +60,7 @@ class DovadoSensor(Entity): self._state = self._compute_state() def _compute_state(self): + """Compute the state of the sensor.""" state = self._data.state.get(SENSORS[self._sensor][0]) if self._sensor == SENSOR_NETWORK: match = re.search(r"\((.+)\)", state) diff --git a/homeassistant/components/downloader/__init__.py b/homeassistant/components/downloader/__init__.py index 0d57740a83d..5af367ef92d 100644 --- a/homeassistant/components/downloader/__init__.py +++ b/homeassistant/components/downloader/__init__.py @@ -1,9 +1,4 @@ -""" -Support for functionality to download files. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/downloader/ -""" +"""Support for functionality to download files.""" import logging import os import re diff --git a/homeassistant/components/duckdns/__init__.py b/homeassistant/components/duckdns/__init__.py index 3420bbed1bc..9899a0af98e 100644 --- a/homeassistant/components/duckdns/__init__.py +++ b/homeassistant/components/duckdns/__init__.py @@ -1,9 +1,4 @@ -""" -Integrate with DuckDNS. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/duckdns/ -""" +"""Integrate with DuckDNS.""" from datetime import timedelta import logging diff --git a/homeassistant/components/dweet/__init__.py b/homeassistant/components/dweet/__init__.py index d5f94bb2c7b..f8e5b181163 100644 --- a/homeassistant/components/dweet/__init__.py +++ b/homeassistant/components/dweet/__init__.py @@ -1,9 +1,4 @@ -""" -A component which allows you to send data to Dweet.io. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/dweet/ -""" +"""Support for sending data to Dweet.io.""" import logging from datetime import timedelta diff --git a/homeassistant/components/dweet/sensor.py b/homeassistant/components/dweet/sensor.py index 5f85164f35d..d1a64201e6d 100644 --- a/homeassistant/components/dweet/sensor.py +++ b/homeassistant/components/dweet/sensor.py @@ -1,9 +1,4 @@ -""" -Support for showing values from Dweet.io. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.dweet/ -""" +"""Support for showing values from Dweet.io.""" import json import logging from datetime import timedelta @@ -13,15 +8,13 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_VALUE_TEMPLATE, CONF_UNIT_OF_MEASUREMENT) + CONF_NAME, CONF_VALUE_TEMPLATE, CONF_UNIT_OF_MEASUREMENT, CONF_DEVICE) from homeassistant.helpers.entity import Entity REQUIREMENTS = ['dweepy==0.3.0'] _LOGGER = logging.getLogger(__name__) -CONF_DEVICE = 'device' - DEFAULT_NAME = 'Dweet.io Sensor' SCAN_INTERVAL = timedelta(minutes=1) @@ -49,11 +42,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): content = json.dumps(dweepy.get_latest_dweet_for(device)[0]['content']) except dweepy.DweepyError: _LOGGER.error("Device/thing %s could not be found", device) - return False + return if value_template.render_with_possible_json_value(content) == '': _LOGGER.error("%s was not found", value_template) - return False + return dweet = DweetData(device) diff --git a/homeassistant/components/dyson/__init__.py b/homeassistant/components/dyson/__init__.py index 791f990d9ad..c2e56436bd8 100644 --- a/homeassistant/components/dyson/__init__.py +++ b/homeassistant/components/dyson/__init__.py @@ -1,29 +1,25 @@ -"""Parent component for Dyson Pure Cool Link devices. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/dyson/ -""" - +"""Support for Dyson Pure Cool Link devices.""" import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv +from homeassistant.const import ( + CONF_DEVICES, CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME) from homeassistant.helpers import discovery -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT, \ - CONF_DEVICES +import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['libpurecoollink==0.4.2'] _LOGGER = logging.getLogger(__name__) -CONF_LANGUAGE = "language" -CONF_RETRY = "retry" +CONF_LANGUAGE = 'language' +CONF_RETRY = 'retry' DEFAULT_TIMEOUT = 5 DEFAULT_RETRY = 10 +DYSON_DEVICES = 'dyson_devices' -DOMAIN = "dyson" +DOMAIN = 'dyson' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -37,8 +33,6 @@ CONFIG_SCHEMA = vol.Schema({ }) }, extra=vol.ALLOW_EXTRA) -DYSON_DEVICES = "dyson_devices" - def setup(hass, config): """Set up the Dyson parent component.""" diff --git a/homeassistant/components/ebusd/__init__.py b/homeassistant/components/ebusd/__init__.py index 951e6f13b91..bc1b3aa9595 100644 --- a/homeassistant/components/ebusd/__init__.py +++ b/homeassistant/components/ebusd/__init__.py @@ -1,10 +1,4 @@ -""" -Support for Ebusd daemon for communication with eBUS heating systems. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/ebus/ -""" - +"""Support for Ebusd daemon for communication with eBUS heating systems.""" from datetime import timedelta import logging import socket @@ -53,13 +47,13 @@ def setup(hass, config): conf.get(CONF_HOST), conf.get(CONF_PORT)) try: - _LOGGER.debug("Ebusd component setup started.") + _LOGGER.debug("Ebusd component setup started") import ebusdpy ebusdpy.init(server_address) hass.data[DOMAIN] = EbusdData(server_address, circuit) sensor_config = { - 'monitored_conditions': monitored_conditions, + CONF_MONITORED_CONDITIONS: monitored_conditions, 'client_name': name, 'sensor_types': SENSOR_TYPES[circuit] } @@ -68,7 +62,7 @@ def setup(hass, config): hass.services.register( DOMAIN, SERVICE_EBUSD_WRITE, hass.data[DOMAIN].write) - _LOGGER.debug("Ebusd component setup completed.") + _LOGGER.debug("Ebusd component setup completed") return True except (socket.timeout, socket.error): return False diff --git a/homeassistant/components/ebusd/sensor.py b/homeassistant/components/ebusd/sensor.py index 24ca263898c..942ba107509 100644 --- a/homeassistant/components/ebusd/sensor.py +++ b/homeassistant/components/ebusd/sensor.py @@ -1,10 +1,4 @@ -""" -Support for Ebusd daemon for communication with eBUS heating systems. - -For more details about ebusd deamon, please refer to the documentation at -https://github.com/john30/ebusd -""" - +"""Support for Ebusd sensors.""" import logging import datetime diff --git a/homeassistant/components/ecoal_boiler/__init__.py b/homeassistant/components/ecoal_boiler/__init__.py index bd08024e64a..6ab9fc3181c 100644 --- a/homeassistant/components/ecoal_boiler/__init__.py +++ b/homeassistant/components/ecoal_boiler/__init__.py @@ -1,9 +1,4 @@ -""" -Component to control ecoal/esterownik.pl coal/wood boiler controller. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/ecoal_boiler/ -""" +"""Support to control ecoal/esterownik.pl coal/wood boiler controller.""" import logging import voluptuous as vol @@ -61,12 +56,10 @@ SENSOR_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_USERNAME, - default=DEFAULT_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD, - default=DEFAULT_PASSWORD): cv.string, - vol.Optional(CONF_SWITCHES, default={}): SWITCH_SCHEMA, + vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA, + vol.Optional(CONF_SWITCHES, default={}): SWITCH_SCHEMA, + vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, }) }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/ecoal_boiler/sensor.py b/homeassistant/components/ecoal_boiler/sensor.py index de81d16470c..47ed2d6ebdf 100644 --- a/homeassistant/components/ecoal_boiler/sensor.py +++ b/homeassistant/components/ecoal_boiler/sensor.py @@ -1,9 +1,4 @@ -""" -Allows reading temperatures from ecoal/esterownik.pl controller. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.ecoal_boiler/ -""" +"""Allows reading temperatures from ecoal/esterownik.pl controller.""" import logging from homeassistant.components.ecoal_boiler import ( diff --git a/homeassistant/components/ecoal_boiler/switch.py b/homeassistant/components/ecoal_boiler/switch.py index d8d6c98bb8b..f113125194a 100644 --- a/homeassistant/components/ecoal_boiler/switch.py +++ b/homeassistant/components/ecoal_boiler/switch.py @@ -1,9 +1,4 @@ -""" -Allows to configuration ecoal (esterownik.pl) pumps as switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.ecoal_boiler/ -""" +"""Allows to configuration ecoal (esterownik.pl) pumps as switches.""" import logging from typing import Optional diff --git a/homeassistant/components/ecobee/__init__.py b/homeassistant/components/ecobee/__init__.py index 3829c2caebd..167132a5f41 100644 --- a/homeassistant/components/ecobee/__init__.py +++ b/homeassistant/components/ecobee/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Ecobee. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/ecobee/ -""" +"""Support for Ecobee devices.""" import logging import os from datetime import timedelta @@ -34,7 +29,7 @@ NETWORK = None CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Optional(CONF_API_KEY): cv.string, - vol.Optional(CONF_HOLD_TEMP, default=False): cv.boolean + vol.Optional(CONF_HOLD_TEMP, default=False): cv.boolean, }) }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/ecobee/binary_sensor.py b/homeassistant/components/ecobee/binary_sensor.py index 37f25476bd0..ca8e551bf5e 100644 --- a/homeassistant/components/ecobee/binary_sensor.py +++ b/homeassistant/components/ecobee/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for Ecobee sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.ecobee/ -""" +"""Support for Ecobee binary sensors.""" from homeassistant.components import ecobee from homeassistant.components.binary_sensor import BinarySensorDevice @@ -33,7 +28,7 @@ class EcobeeBinarySensor(BinarySensorDevice): """Representation of an Ecobee sensor.""" def __init__(self, sensor_name, sensor_index): - """Initialize the sensor.""" + """Initialize the Ecobee sensor.""" self._name = sensor_name + ' Occupancy' self.sensor_name = sensor_name self.index = sensor_index diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index 46fc5c29752..aa6440894e1 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -1,9 +1,4 @@ -""" -Platform for Ecobee Thermostats. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/climate.ecobee/ -""" +"""Support for Ecobee Thermostats.""" import logging import voluptuous as vol diff --git a/homeassistant/components/ecobee/notify.py b/homeassistant/components/ecobee/notify.py index 31e4c4751c8..9824d20b85e 100644 --- a/homeassistant/components/ecobee/notify.py +++ b/homeassistant/components/ecobee/notify.py @@ -1,9 +1,4 @@ -""" -Support for ecobee Send Message service. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/notify.ecobee/ -""" +"""Support for Ecobee Send Message service.""" import logging import voluptuous as vol diff --git a/homeassistant/components/ecobee/sensor.py b/homeassistant/components/ecobee/sensor.py index ae22401a618..1f9fd5cbde8 100644 --- a/homeassistant/components/ecobee/sensor.py +++ b/homeassistant/components/ecobee/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Ecobee sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.ecobee/ -""" +"""Support for Ecobee sensors.""" from homeassistant.components import ecobee from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, TEMP_FAHRENHEIT) diff --git a/homeassistant/components/ecobee/weather.py b/homeassistant/components/ecobee/weather.py index 7382e5c1815..2ba5f362b7d 100644 --- a/homeassistant/components/ecobee/weather.py +++ b/homeassistant/components/ecobee/weather.py @@ -1,9 +1,4 @@ -""" -Support for displaying weather info from Ecobee API. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/weather.ecobee/ -""" +"""Support for displaying weather info from Ecobee API.""" from datetime import datetime from homeassistant.components import ecobee @@ -23,7 +18,7 @@ MISSING_DATA = -5002 def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Ecobee weather component.""" + """Set up the Ecobee weather platform.""" if discovery_info is None: return dev = list() diff --git a/homeassistant/components/ecovacs/__init__.py b/homeassistant/components/ecovacs/__init__.py index 8cbe95ee685..124cae3ca47 100644 --- a/homeassistant/components/ecovacs/__init__.py +++ b/homeassistant/components/ecovacs/__init__.py @@ -1,19 +1,14 @@ -"""Parent component for Ecovacs Deebot vacuums. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/ecovacs/ -""" - +"""Support for Ecovacs Deebot vacuums.""" import logging import random import string import voluptuous as vol -import homeassistant.helpers.config_validation as cv +from homeassistant.const import ( + CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers import discovery -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, \ - EVENT_HOMEASSISTANT_STOP +import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['sucks==0.9.3'] diff --git a/homeassistant/components/ecovacs/vacuum.py b/homeassistant/components/ecovacs/vacuum.py index 7f05554c496..9d2af730315 100644 --- a/homeassistant/components/ecovacs/vacuum.py +++ b/homeassistant/components/ecovacs/vacuum.py @@ -1,9 +1,4 @@ -""" -Support for Ecovacs Ecovacs Vaccums. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/vacuum.ecovacs/ -""" +"""Support for Ecovacs Ecovacs Vaccums.""" import logging from homeassistant.components.vacuum import ( diff --git a/homeassistant/components/edp_redy/__init__.py b/homeassistant/components/edp_redy/__init__.py index 10780103613..9b8bfaa437a 100644 --- a/homeassistant/components/edp_redy/__init__.py +++ b/homeassistant/components/edp_redy/__init__.py @@ -1,19 +1,13 @@ -""" -Support for EDP re:dy. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/edp_redy/ -""" - -import logging +"""Support for EDP re:dy.""" from datetime import timedelta +import logging import voluptuous as vol -from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, - EVENT_HOMEASSISTANT_START) +from homeassistant.const import ( + CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_START) from homeassistant.core import callback -from homeassistant.helpers import discovery, dispatcher, aiohttp_client +from homeassistant.helpers import aiohttp_client, discovery, dispatcher import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_point_in_time diff --git a/homeassistant/components/edp_redy/sensor.py b/homeassistant/components/edp_redy/sensor.py index 0f259ec673a..926a073832c 100644 --- a/homeassistant/components/edp_redy/sensor.py +++ b/homeassistant/components/edp_redy/sensor.py @@ -13,8 +13,8 @@ DEPENDENCIES = ['edp_redy'] ATTR_ACTIVE_POWER = 'active_power' -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Perform the setup for re:dy devices.""" from edp_redy.session import ACTIVE_POWER_ID diff --git a/homeassistant/components/edp_redy/switch.py b/homeassistant/components/edp_redy/switch.py index 1576361da33..ad4ce8fe728 100644 --- a/homeassistant/components/edp_redy/switch.py +++ b/homeassistant/components/edp_redy/switch.py @@ -12,8 +12,8 @@ DEPENDENCIES = ['edp_redy'] ATTR_ACTIVE_POWER = 'active_power' -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Perform the setup for re:dy devices.""" session = hass.data[EDP_REDY] devices = [] diff --git a/homeassistant/components/egardia/__init__.py b/homeassistant/components/egardia/__init__.py index 3547f4fc76e..fe613824c95 100644 --- a/homeassistant/components/egardia/__init__.py +++ b/homeassistant/components/egardia/__init__.py @@ -1,28 +1,24 @@ -""" -Interfaces with Egardia/Woonveilig alarm control panel. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/egardia/ -""" +"""Interfaces with Egardia/Woonveilig alarm control panel.""" import logging import requests import voluptuous as vol +from homeassistant.const import ( + CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_USERNAME, + EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv -from homeassistant.const import ( - CONF_PORT, CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_NAME, - EVENT_HOMEASSISTANT_STOP) REQUIREMENTS = ['pythonegardia==1.0.39'] _LOGGER = logging.getLogger(__name__) +ATTR_DISCOVER_DEVICES = 'egardia_sensor' + CONF_REPORT_SERVER_CODES = 'report_server_codes' CONF_REPORT_SERVER_ENABLED = 'report_server_enabled' CONF_REPORT_SERVER_PORT = 'report_server_port' -REPORT_SERVER_CODES_IGNORE = 'ignore' CONF_VERSION = 'version' DEFAULT_NAME = 'Egardia' @@ -31,21 +27,24 @@ DEFAULT_REPORT_SERVER_ENABLED = False DEFAULT_REPORT_SERVER_PORT = 52010 DEFAULT_VERSION = 'GATE-01' DOMAIN = 'egardia' -EGARDIA_SERVER = 'egardia_server' + EGARDIA_DEVICE = 'egardiadevice' EGARDIA_NAME = 'egardianame' -EGARDIA_REPORT_SERVER_ENABLED = 'egardia_rs_enabled' EGARDIA_REPORT_SERVER_CODES = 'egardia_rs_codes' +EGARDIA_REPORT_SERVER_ENABLED = 'egardia_rs_enabled' +EGARDIA_SERVER = 'egardia_server' + NOTIFICATION_ID = 'egardia_notification' NOTIFICATION_TITLE = 'Egardia' -ATTR_DISCOVER_DEVICES = 'egardia_sensor' + +REPORT_SERVER_CODES_IGNORE = 'ignore' SERVER_CODE_SCHEMA = vol.Schema({ vol.Optional('arm'): vol.All(cv.ensure_list_csv, [cv.string]), vol.Optional('disarm'): vol.All(cv.ensure_list_csv, [cv.string]), vol.Optional('armhome'): vol.All(cv.ensure_list_csv, [cv.string]), vol.Optional('triggered'): vol.All(cv.ensure_list_csv, [cv.string]), - vol.Optional('ignore'): vol.All(cv.ensure_list_csv, [cv.string]) + vol.Optional('ignore'): vol.All(cv.ensure_list_csv, [cv.string]), }) CONFIG_SCHEMA = vol.Schema({ @@ -82,10 +81,10 @@ def setup(hass, config): host, port, username, password, '', version) except requests.exceptions.RequestException: _LOGGER.error("An error occurred accessing your Egardia device. " - "Please check config.") + "Please check configuration") return False except egardiadevice.UnauthorizedError: - _LOGGER.error("Unable to authorize. Wrong password or username.") + _LOGGER.error("Unable to authorize. Wrong password or username") return False # Set up the egardia server if enabled if rs_enabled: @@ -101,21 +100,21 @@ def setup(hass, config): server.start() def handle_stop_event(event): - """Handle HA stop event.""" + """Handle Home Assistant stop event.""" server.stop() # listen to home assistant stop event hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop_event) except IOError: - _LOGGER.error("Binding error occurred while starting " - "EgardiaServer.") + _LOGGER.error( + "Binding error occurred while starting EgardiaServer") return False discovery.load_platform(hass, 'alarm_control_panel', DOMAIN, discovered=conf, hass_config=config) - # get the sensors from the device and add those + # Get the sensors from the device and add those sensors = device.getsensors() discovery.load_platform(hass, 'binary_sensor', DOMAIN, {ATTR_DISCOVER_DEVICES: sensors}, config) diff --git a/homeassistant/components/egardia/alarm_control_panel.py b/homeassistant/components/egardia/alarm_control_panel.py index dfd60c4abde..e202a46f9f1 100644 --- a/homeassistant/components/egardia/alarm_control_panel.py +++ b/homeassistant/components/egardia/alarm_control_panel.py @@ -1,23 +1,17 @@ -""" -Interfaces with Egardia/Woonveilig alarm control panel. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/alarm_control_panel.egardia/ -""" +"""Interfaces with Egardia/Woonveilig alarm control panel.""" import logging import requests import homeassistant.components.alarm_control_panel as alarm -from homeassistant.const import ( - STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, - STATE_ALARM_ARMED_AWAY, STATE_ALARM_TRIGGERED, - STATE_ALARM_ARMED_NIGHT) from homeassistant.components.egardia import ( - EGARDIA_DEVICE, EGARDIA_SERVER, - REPORT_SERVER_CODES_IGNORE, CONF_REPORT_SERVER_CODES, - CONF_REPORT_SERVER_ENABLED, CONF_REPORT_SERVER_PORT - ) + CONF_REPORT_SERVER_CODES, CONF_REPORT_SERVER_ENABLED, + CONF_REPORT_SERVER_PORT, EGARDIA_DEVICE, EGARDIA_SERVER, + REPORT_SERVER_CODES_IGNORE) +from homeassistant.const import ( + STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED) + DEPENDENCIES = ['egardia'] _LOGGER = logging.getLogger(__name__) @@ -34,7 +28,7 @@ STATES = { def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Egardia platform.""" + """Set up the Egardia Alarm Control Panael platform.""" if discovery_info is None: return device = EgardiaAlarm( @@ -43,7 +37,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): discovery_info[CONF_REPORT_SERVER_ENABLED], discovery_info.get(CONF_REPORT_SERVER_CODES), discovery_info[CONF_REPORT_SERVER_PORT]) - # add egardia alarm device + add_entities([device], True) diff --git a/homeassistant/components/egardia/binary_sensor.py b/homeassistant/components/egardia/binary_sensor.py index 56d7dda17ba..74a048a86c0 100644 --- a/homeassistant/components/egardia/binary_sensor.py +++ b/homeassistant/components/egardia/binary_sensor.py @@ -1,20 +1,20 @@ -""" -Interfaces with Egardia/Woonveilig alarm control panel. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.egardia/ -""" +"""Interfaces with Egardia/Woonveilig alarm control panel.""" import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.components.egardia import ( - EGARDIA_DEVICE, ATTR_DISCOVER_DEVICES) + ATTR_DISCOVER_DEVICES, EGARDIA_DEVICE) +from homeassistant.const import STATE_OFF, STATE_ON + _LOGGER = logging.getLogger(__name__) + DEPENDENCIES = ['egardia'] -EGARDIA_TYPE_TO_DEVICE_CLASS = {'IR Sensor': 'motion', - 'Door Contact': 'opening', - 'IR': 'motion'} + +EGARDIA_TYPE_TO_DEVICE_CLASS = { + 'IR Sensor': 'motion', + 'Door Contact': 'opening', + 'IR': 'motion', +} async def async_setup_platform(hass, config, async_add_entities, @@ -25,7 +25,7 @@ async def async_setup_platform(hass, config, async_add_entities, return disc_info = discovery_info[ATTR_DISCOVER_DEVICES] - # multiple devices here! + async_add_entities( ( EgardiaBinarySensor( diff --git a/homeassistant/components/eight_sleep/__init__.py b/homeassistant/components/eight_sleep/__init__.py index 8c36830817e..851fd3d1c31 100644 --- a/homeassistant/components/eight_sleep/__init__.py +++ b/homeassistant/components/eight_sleep/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Eight smart mattress covers and mattresses. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/eight_sleep/ -""" +"""Support for Eight smart mattress covers and mattresses.""" import logging from datetime import timedelta diff --git a/homeassistant/components/eight_sleep/binary_sensor.py b/homeassistant/components/eight_sleep/binary_sensor.py index 34d3a7a13ca..2a9cb19a327 100644 --- a/homeassistant/components/eight_sleep/binary_sensor.py +++ b/homeassistant/components/eight_sleep/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for Eight Sleep binary sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.eight_sleep/ -""" +"""Support for Eight Sleep binary sensors.""" import logging from homeassistant.components.binary_sensor import BinarySensorDevice diff --git a/homeassistant/components/eight_sleep/sensor.py b/homeassistant/components/eight_sleep/sensor.py index 0fc793d31ca..2bb03c8d4f2 100644 --- a/homeassistant/components/eight_sleep/sensor.py +++ b/homeassistant/components/eight_sleep/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Eight Sleep sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.eight_sleep/ -""" +"""Support for Eight Sleep sensors.""" import logging from homeassistant.components.eight_sleep import ( diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index 8ac3cec6411..a0c08bf5429 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -1,10 +1,4 @@ -""" -Support the ElkM1 Gold and ElkM1 EZ8 alarm / integration panels. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/elkm1/ -""" - +"""Support the ElkM1 Gold and ElkM1 EZ8 alarm/integration panels.""" import logging import re @@ -18,20 +12,20 @@ from homeassistant.helpers import discovery from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType # noqa -DOMAIN = "elkm1" - REQUIREMENTS = ['elkm1-lib==0.7.13'] +DOMAIN = 'elkm1' + CONF_AREA = 'area' CONF_COUNTER = 'counter' +CONF_ENABLED = 'enabled' CONF_KEYPAD = 'keypad' CONF_OUTPUT = 'output' +CONF_PLC = 'plc' CONF_SETTING = 'setting' CONF_TASK = 'task' CONF_THERMOSTAT = 'thermostat' -CONF_PLC = 'plc' CONF_ZONE = 'zone' -CONF_ENABLED = 'enabled' _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/elkm1/alarm_control_panel.py b/homeassistant/components/elkm1/alarm_control_panel.py index c6405f953fd..63b38c1d321 100644 --- a/homeassistant/components/elkm1/alarm_control_panel.py +++ b/homeassistant/components/elkm1/alarm_control_panel.py @@ -1,10 +1,4 @@ -""" -Each ElkM1 area will be created as a separate alarm_control_panel in HASS. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/alarm_control_panel.elkm1/ -""" - +"""Each ElkM1 area will be created as a separate alarm_control_panel.""" import voluptuous as vol import homeassistant.components.alarm_control_panel as alarm from homeassistant.const import ( diff --git a/homeassistant/components/elkm1/climate.py b/homeassistant/components/elkm1/climate.py index 6bd33b382dc..467d542ee6d 100644 --- a/homeassistant/components/elkm1/climate.py +++ b/homeassistant/components/elkm1/climate.py @@ -1,9 +1,4 @@ -""" -Support for control of Elk-M1 connected thermostats. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/climate.elkm1/ -""" +"""Support for control of Elk-M1 connected thermostats.""" from homeassistant.components.climate import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, PRECISION_WHOLE, STATE_AUTO, STATE_COOL, STATE_FAN_ONLY, STATE_HEAT, STATE_IDLE, SUPPORT_AUX_HEAT, diff --git a/homeassistant/components/elkm1/light.py b/homeassistant/components/elkm1/light.py index 707aedbb161..3a282595d58 100644 --- a/homeassistant/components/elkm1/light.py +++ b/homeassistant/components/elkm1/light.py @@ -1,10 +1,4 @@ -""" -Support for control of ElkM1 lighting (X10, UPB, etc). - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.elkm1/ -""" - +"""Support for control of ElkM1 lighting (X10, UPB, etc).""" from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) from homeassistant.components.elkm1 import ( @@ -13,8 +7,8 @@ from homeassistant.components.elkm1 import ( DEPENDENCIES = [ELK_DOMAIN] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the Elk light platform.""" if discovery_info is None: return @@ -24,10 +18,10 @@ async def async_setup_platform(hass, config, async_add_entities, class ElkLight(ElkEntity, Light): - """Elk lighting device.""" + """Representation of an Elk lighting device.""" def __init__(self, element, elk, elk_data): - """Initialize light.""" + """Initialize the Elk light.""" super().__init__(element, elk, elk_data) self._brightness = self._element.status diff --git a/homeassistant/components/elkm1/scene.py b/homeassistant/components/elkm1/scene.py index 47dd17a56ae..c8583b1d8bf 100644 --- a/homeassistant/components/elkm1/scene.py +++ b/homeassistant/components/elkm1/scene.py @@ -1,11 +1,4 @@ -""" -Support for control of ElkM1 tasks ("macros"). - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/scene.elkm1/ -""" - - +"""Support for control of ElkM1 tasks ("macros").""" from homeassistant.components.elkm1 import ( DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities) from homeassistant.components.scene import Scene @@ -13,8 +6,8 @@ from homeassistant.components.scene import Scene DEPENDENCIES = [ELK_DOMAIN] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Create the Elk-M1 scene platform.""" if discovery_info is None: return diff --git a/homeassistant/components/elkm1/sensor.py b/homeassistant/components/elkm1/sensor.py index 3fd57b190a6..63da6ea5376 100644 --- a/homeassistant/components/elkm1/sensor.py +++ b/homeassistant/components/elkm1/sensor.py @@ -1,17 +1,12 @@ -""" -Support for control of ElkM1 sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.elkm1/ -""" +"""Support for control of ElkM1 sensors.""" from homeassistant.components.elkm1 import ( DOMAIN as ELK_DOMAIN, create_elk_entities, ElkEntity) DEPENDENCIES = [ELK_DOMAIN] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Create the Elk-M1 sensor platform.""" if discovery_info is None: return diff --git a/homeassistant/components/elkm1/switch.py b/homeassistant/components/elkm1/switch.py index a838e1b948e..7badd6ee5dc 100644 --- a/homeassistant/components/elkm1/switch.py +++ b/homeassistant/components/elkm1/switch.py @@ -1,11 +1,4 @@ -""" -Support for control of ElkM1 outputs (relays). - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.elkm1/ -""" - - +"""Support for control of ElkM1 outputs (relays).""" from homeassistant.components.elkm1 import ( DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities) from homeassistant.components.switch import SwitchDevice @@ -13,8 +6,8 @@ from homeassistant.components.switch import SwitchDevice DEPENDENCIES = [ELK_DOMAIN] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Create the Elk-M1 switch platform.""" if discovery_info is None: return diff --git a/homeassistant/components/emoncms_history/__init__.py b/homeassistant/components/emoncms_history/__init__.py index 6a92ab64044..45fb358cecc 100644 --- a/homeassistant/components/emoncms_history/__init__.py +++ b/homeassistant/components/emoncms_history/__init__.py @@ -1,9 +1,4 @@ -""" -A component which allows you to send data to Emoncms. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/emoncms_history/ -""" +"""Support for sending data to Emoncms.""" import logging from datetime import timedelta diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index 07ecb9d265a..c8ed263a2dc 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -1,9 +1,4 @@ -""" -Support for local control of entities by emulating the Phillips Hue bridge. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/emulated_hue/ -""" +"""Support for local control of entities by emulating a Phillips Hue bridge.""" import logging from aiohttp import web @@ -31,18 +26,18 @@ _LOGGER = logging.getLogger(__name__) NUMBERS_FILE = 'emulated_hue_ids.json' -CONF_HOST_IP = 'host_ip' -CONF_LISTEN_PORT = 'listen_port' CONF_ADVERTISE_IP = 'advertise_ip' CONF_ADVERTISE_PORT = 'advertise_port' -CONF_UPNP_BIND_MULTICAST = 'upnp_bind_multicast' -CONF_OFF_MAPS_TO_ON_DOMAINS = 'off_maps_to_on_domains' +CONF_ENTITIES = 'entities' +CONF_ENTITY_HIDDEN = 'hidden' +CONF_ENTITY_NAME = 'name' CONF_EXPOSE_BY_DEFAULT = 'expose_by_default' CONF_EXPOSED_DOMAINS = 'exposed_domains' +CONF_HOST_IP = 'host_ip' +CONF_LISTEN_PORT = 'listen_port' +CONF_OFF_MAPS_TO_ON_DOMAINS = 'off_maps_to_on_domains' CONF_TYPE = 'type' -CONF_ENTITIES = 'entities' -CONF_ENTITY_NAME = 'name' -CONF_ENTITY_HIDDEN = 'hidden' +CONF_UPNP_BIND_MULTICAST = 'upnp_bind_multicast' TYPE_ALEXA = 'alexa' TYPE_GOOGLE = 'google_home' diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index fdf1d35201e..95b3c470d9e 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -1,4 +1,4 @@ -"""Provides a Hue API to control Home Assistant.""" +"""Support for a Hue API to control Home Assistant.""" import logging from aiohttp import web @@ -33,7 +33,6 @@ from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.const import KEY_REAL_IP from homeassistant.util.network import is_local - _LOGGER = logging.getLogger(__name__) HUE_API_STATE_ON = 'on' diff --git a/homeassistant/components/emulated_hue/upnp.py b/homeassistant/components/emulated_hue/upnp.py index 548b6f3d771..a163d4b2e91 100644 --- a/homeassistant/components/emulated_hue/upnp.py +++ b/homeassistant/components/emulated_hue/upnp.py @@ -1,4 +1,4 @@ -"""Provides a UPNP discovery method that mimics Hue hubs.""" +"""Support UPNP discovery method that mimics Hue hubs.""" import threading import socket import logging diff --git a/homeassistant/components/emulated_roku/__init__.py b/homeassistant/components/emulated_roku/__init__.py index 4dec1d5602a..ef87e14ec43 100644 --- a/homeassistant/components/emulated_roku/__init__.py +++ b/homeassistant/components/emulated_roku/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Roku API emulation. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/emulated_roku/ -""" +"""Support for Roku API emulation.""" import voluptuous as vol from homeassistant import config_entries, util diff --git a/homeassistant/components/emulated_roku/config_flow.py b/homeassistant/components/emulated_roku/config_flow.py index f2d56f84681..d08ad09f1c0 100644 --- a/homeassistant/components/emulated_roku/config_flow.py +++ b/homeassistant/components/emulated_roku/config_flow.py @@ -1,5 +1,4 @@ """Config flow to configure emulated_roku component.""" - import voluptuous as vol from homeassistant import config_entries diff --git a/homeassistant/components/emulated_roku/const.py b/homeassistant/components/emulated_roku/const.py index f4a034e31ac..25ea3adaa84 100644 --- a/homeassistant/components/emulated_roku/const.py +++ b/homeassistant/components/emulated_roku/const.py @@ -1,5 +1,4 @@ """Constants for the emulated_roku component.""" - DOMAIN = 'emulated_roku' CONF_SERVERS = 'servers' diff --git a/homeassistant/components/enocean/__init__.py b/homeassistant/components/enocean/__init__.py index 75e456f62bd..8b3c27025cd 100644 --- a/homeassistant/components/enocean/__init__.py +++ b/homeassistant/components/enocean/__init__.py @@ -1,9 +1,4 @@ -""" -EnOcean Component. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/EnOcean/ -""" +"""Support for EnOcean devices.""" import logging import voluptuous as vol diff --git a/homeassistant/components/enocean/binary_sensor.py b/homeassistant/components/enocean/binary_sensor.py index c883897c2ea..1fde8c79e40 100644 --- a/homeassistant/components/enocean/binary_sensor.py +++ b/homeassistant/components/enocean/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for EnOcean binary sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.enocean/ -""" +"""Support for EnOcean binary sensors.""" import logging import voluptuous as vol diff --git a/homeassistant/components/enocean/light.py b/homeassistant/components/enocean/light.py index ebe2c409796..f574f89f951 100644 --- a/homeassistant/components/enocean/light.py +++ b/homeassistant/components/enocean/light.py @@ -1,9 +1,4 @@ -""" -Support for EnOcean light sources. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.enocean/ -""" +"""Support for EnOcean light sources.""" import logging import math diff --git a/homeassistant/components/enocean/sensor.py b/homeassistant/components/enocean/sensor.py index 02e6812d5d5..d2e88ed3825 100644 --- a/homeassistant/components/enocean/sensor.py +++ b/homeassistant/components/enocean/sensor.py @@ -1,9 +1,4 @@ -""" -Support for EnOcean sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.enocean/ -""" +"""Support for EnOcean sensors.""" import logging import voluptuous as vol diff --git a/homeassistant/components/enocean/switch.py b/homeassistant/components/enocean/switch.py index ab979604f50..4dfbafd36b1 100644 --- a/homeassistant/components/enocean/switch.py +++ b/homeassistant/components/enocean/switch.py @@ -1,9 +1,4 @@ -""" -Support for EnOcean switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.enocean/ -""" +"""Support for EnOcean switches.""" import logging import voluptuous as vol diff --git a/homeassistant/components/envisalink/__init__.py b/homeassistant/components/envisalink/__init__.py index 8b89b307db9..b7590341f78 100644 --- a/homeassistant/components/envisalink/__init__.py +++ b/homeassistant/components/envisalink/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Envisalink devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/envisalink/ -""" +"""Support for Envisalink devices.""" import asyncio import logging diff --git a/homeassistant/components/envisalink/alarm_control_panel.py b/homeassistant/components/envisalink/alarm_control_panel.py index 9b772d9bdf0..a4cc5864fc4 100644 --- a/homeassistant/components/envisalink/alarm_control_panel.py +++ b/homeassistant/components/envisalink/alarm_control_panel.py @@ -1,9 +1,4 @@ -""" -Support for Envisalink-based alarm control panels (Honeywell/DSC). - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/alarm_control_panel.envisalink/ -""" +"""Support for Envisalink-based alarm control panels (Honeywell/DSC).""" import logging import voluptuous as vol @@ -31,8 +26,8 @@ ALARM_KEYPRESS_SCHEMA = vol.Schema({ }) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Perform the setup for Envisalink alarm panels.""" configured_partitions = discovery_info['partitions'] code = discovery_info[CONF_CODE] @@ -42,14 +37,9 @@ async def async_setup_platform(hass, config, async_add_entities, for part_num in configured_partitions: device_config_data = PARTITION_SCHEMA(configured_partitions[part_num]) device = EnvisalinkAlarm( - hass, - part_num, - device_config_data[CONF_PARTITIONNAME], - code, - panic_type, - hass.data[DATA_EVL].alarm_state['partition'][part_num], - hass.data[DATA_EVL] - ) + hass, part_num, device_config_data[CONF_PARTITIONNAME], code, + panic_type, hass.data[DATA_EVL].alarm_state['partition'][part_num], + hass.data[DATA_EVL]) devices.append(device) async_add_entities(devices) diff --git a/homeassistant/components/envisalink/binary_sensor.py b/homeassistant/components/envisalink/binary_sensor.py index 276ace8dd51..26b54e16cc8 100644 --- a/homeassistant/components/envisalink/binary_sensor.py +++ b/homeassistant/components/envisalink/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for Envisalink zone states- represented as binary sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.envisalink/ -""" +"""Support for Envisalink zone states- represented as binary sensors.""" import logging import datetime diff --git a/homeassistant/components/envisalink/sensor.py b/homeassistant/components/envisalink/sensor.py index aed2b056d2f..cc6a8b87232 100644 --- a/homeassistant/components/envisalink/sensor.py +++ b/homeassistant/components/envisalink/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Envisalink sensors (shows panel info). - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.envisalink/ -""" +"""Support for Envisalink sensors (shows panel info).""" import logging from homeassistant.core import callback @@ -18,8 +13,8 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['envisalink'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Perform the setup for Envisalink sensor devices.""" configured_partitions = discovery_info['partitions'] @@ -27,11 +22,10 @@ async def async_setup_platform(hass, config, async_add_entities, for part_num in configured_partitions: device_config_data = PARTITION_SCHEMA(configured_partitions[part_num]) device = EnvisalinkSensor( - hass, - device_config_data[CONF_PARTITIONNAME], - part_num, + hass, device_config_data[CONF_PARTITIONNAME], part_num, hass.data[DATA_EVL].alarm_state['partition'][part_num], hass.data[DATA_EVL]) + devices.append(device) async_add_entities(devices) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 1c7d713d827..4710967a625 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -1,4 +1,9 @@ -"""Support for esphome devices.""" +""" +Support for esphome devices. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/esphome/ +""" import asyncio import logging from typing import Any, Dict, List, Optional, TYPE_CHECKING, Callable @@ -30,9 +35,11 @@ if TYPE_CHECKING: from aioesphomeapi import APIClient, EntityInfo, EntityState, DeviceInfo, \ ServiceCall -DOMAIN = 'esphome' REQUIREMENTS = ['aioesphomeapi==1.5.0'] +_LOGGER = logging.getLogger(__name__) + +DOMAIN = 'esphome' DISPATCHER_UPDATE_ENTITY = 'esphome_{entry_id}_update_{component_key}_{key}' DISPATCHER_REMOVE_ENTITY = 'esphome_{entry_id}_remove_{component_key}_{key}' @@ -53,8 +60,6 @@ HA_COMPONENTS = [ 'switch', ] -_LOGGER = logging.getLogger(__name__) - # No config schema - only configuration entry CONFIG_SCHEMA = vol.Schema({}, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/eufy/__init__.py b/homeassistant/components/eufy/__init__.py index c1166f8cf7b..d5a0938bf66 100644 --- a/homeassistant/components/eufy/__init__.py +++ b/homeassistant/components/eufy/__init__.py @@ -1,20 +1,14 @@ -""" -Support for Eufy devices. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/eufy/ -""" +"""Support for Eufy devices.""" import logging import voluptuous as vol -from homeassistant.const import CONF_ACCESS_TOKEN, CONF_ADDRESS, \ - CONF_DEVICES, CONF_USERNAME, CONF_PASSWORD, CONF_TYPE, CONF_NAME +from homeassistant.const import ( + CONF_ACCESS_TOKEN, CONF_ADDRESS, CONF_DEVICES, CONF_NAME, CONF_PASSWORD, + CONF_TYPE, CONF_USERNAME) from homeassistant.helpers import discovery - import homeassistant.helpers.config_validation as cv - REQUIREMENTS = ['lakeside==0.11'] _LOGGER = logging.getLogger(__name__) @@ -30,8 +24,8 @@ DEVICE_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ - vol.Optional(CONF_DEVICES, default=[]): vol.All(cv.ensure_list, - [DEVICE_SCHEMA]), + vol.Optional(CONF_DEVICES, default=[]): + vol.All(cv.ensure_list, [DEVICE_SCHEMA]), vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string, vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string, }), diff --git a/homeassistant/components/eufy/light.py b/homeassistant/components/eufy/light.py index 7a44a58cd81..62bc058f155 100644 --- a/homeassistant/components/eufy/light.py +++ b/homeassistant/components/eufy/light.py @@ -1,9 +1,4 @@ -""" -Support for Eufy lights. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.eufy/ -""" +"""Support for Eufy lights.""" import logging from homeassistant.components.light import ( diff --git a/homeassistant/components/eufy/switch.py b/homeassistant/components/eufy/switch.py index a226797c0f8..96d68194107 100644 --- a/homeassistant/components/eufy/switch.py +++ b/homeassistant/components/eufy/switch.py @@ -1,9 +1,4 @@ -""" -Support for Eufy switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.eufy/ -""" +"""Support for Eufy switches.""" import logging from homeassistant.components.switch import SwitchDevice diff --git a/homeassistant/components/evohome/__init__.py b/homeassistant/components/evohome/__init__.py index 40ba5b9b70f..52bb77516e6 100644 --- a/homeassistant/components/evohome/__init__.py +++ b/homeassistant/components/evohome/__init__.py @@ -1,19 +1,10 @@ -"""Support for (EMEA/EU-based) Honeywell evohome systems. - -Support for a temperature control system (TCS, controller) with 0+ heating -zones (e.g. TRVs, relays) and, optionally, a DHW controller. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/evohome/ -""" - +"""Support for (EMEA/EU-based) Honeywell evohome systems.""" # Glossary: # TCS - temperature control system (a.k.a. Controller, Parent), which can # have up to 13 Children: # 0-12 Heating zones (a.k.a. Zone), and # 0-1 DHW controller, (a.k.a. Boiler) # The TCS & Zones are implemented as Climate devices, Boiler as a WaterHeater - from datetime import timedelta import logging @@ -46,8 +37,7 @@ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_LOCATION_IDX, default=0): - cv.positive_int, + vol.Optional(CONF_LOCATION_IDX, default=0): cv.positive_int, vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL_DEFAULT): vol.All(cv.time_period, vol.Range(min=SCAN_INTERVAL_MINIMUM)), }), @@ -109,9 +99,9 @@ def setup(hass, hass_config): ) else: - raise # we dont expect/handle any other HTTPErrors + raise # We don't expect/handle any other HTTPErrors - return False # unable to continue + return False finally: # Redact username, password as no longer needed evo_data['params'][CONF_USERNAME] = 'REDACTED' @@ -136,11 +126,8 @@ def setup(hass, hass_config): except IndexError: _LOGGER.warning( "setup(): Parameter '%s'=%s, is outside its range (0-%s)", - CONF_LOCATION_IDX, - loc_idx, - len(client.installation_info) - 1 - ) - return False # unable to continue + CONF_LOCATION_IDX, loc_idx, len(client.installation_info) - 1) + return False if _LOGGER.isEnabledFor(logging.DEBUG): tmp_loc = dict(evo_data['config']) @@ -154,7 +141,7 @@ def setup(hass, hass_config): @callback def _first_update(event): - # When HA has started, the hub knows to retreive it's first update + """When HA has started, the hub knows to retrieve it's first update.""" pkt = {'sender': 'setup()', 'signal': 'refresh', 'to': EVO_PARENT} async_dispatcher_send(hass, DISPATCHER_EVOHOME, pkt) diff --git a/homeassistant/components/evohome/climate.py b/homeassistant/components/evohome/climate.py index fd58e6c01e8..ef82a3dc81c 100644 --- a/homeassistant/components/evohome/climate.py +++ b/homeassistant/components/evohome/climate.py @@ -1,12 +1,4 @@ -"""Support for Climate devices of (EMEA/EU-based) Honeywell evohome systems. - -Support for a temperature control system (TCS, controller) with 0+ heating -zones (e.g. TRVs, relays). - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/climate.evohome/ -""" - +"""Support for Climate devices of (EMEA/EU-based) Honeywell evohome systems.""" from datetime import datetime, timedelta import logging @@ -40,7 +32,7 @@ from homeassistant.helpers.dispatcher import ( _LOGGER = logging.getLogger(__name__) -# the Controller's opmode/state and the zone's (inherited) state +# The Controller's opmode/state and the zone's (inherited) state EVO_RESET = 'AutoWithReset' EVO_AUTO = 'Auto' EVO_AUTOECO = 'AutoWithEco' @@ -49,12 +41,12 @@ EVO_DAYOFF = 'DayOff' EVO_CUSTOM = 'Custom' EVO_HEATOFF = 'HeatingOff' -# these are for Zones' opmode, and state +# These are for Zones' opmode, and state EVO_FOLLOW = 'FollowSchedule' EVO_TEMPOVER = 'TemporaryOverride' EVO_PERMOVER = 'PermanentOverride' -# for the Controller. NB: evohome treats Away mode as a mode in/of itself, +# For the Controller. NB: evohome treats Away mode as a mode in/of itself, # where HA considers it to 'override' the exising operating mode TCS_STATE_TO_HA = { EVO_RESET: STATE_AUTO, @@ -100,16 +92,12 @@ async def async_setup_platform(hass, hass_config, async_add_entities, # evohomeclient has exposed no means of accessing non-default location # (i.e. loc_idx > 0) other than using a protected member, such as below - tcs_obj_ref = client.locations[loc_idx]._gateways[0]._control_systems[0] # noqa E501; pylint: disable=protected-access + tcs_obj_ref = client.locations[loc_idx]._gateways[0]._control_systems[0] # noqa E501; pylint: disable=protected-access _LOGGER.debug( - "setup_platform(): Found Controller, id=%s [%s], " - "name=%s (location_idx=%s)", - tcs_obj_ref.systemId, - tcs_obj_ref.modelType, - tcs_obj_ref.location.name, - loc_idx - ) + "Found Controller, id=%s [%s], name=%s (location_idx=%s)", + tcs_obj_ref.systemId, tcs_obj_ref.modelType, tcs_obj_ref.location.name, + loc_idx) controller = EvoController(evo_data, client, tcs_obj_ref) zones = [] @@ -117,12 +105,8 @@ async def async_setup_platform(hass, hass_config, async_add_entities, for zone_idx in tcs_obj_ref.zones: zone_obj_ref = tcs_obj_ref.zones[zone_idx] _LOGGER.debug( - "setup_platform(): Found Zone, id=%s [%s], " - "name=%s", - zone_obj_ref.zoneId, - zone_obj_ref.zone_type, - zone_obj_ref.name - ) + "Found Zone, id=%s [%s], name=%s", + zone_obj_ref.zoneId, zone_obj_ref.zone_type, zone_obj_ref.name) zones.append(EvoZone(evo_data, client, zone_obj_ref)) entities = [controller] + zones @@ -164,12 +148,9 @@ class EvoClimateDevice(ClimateDevice): _LOGGER.warning( "API rate limit has been exceeded. Suspending polling for %s " - "seconds, and increasing '%s' from %s to %s seconds.", - new_interval * 3, - CONF_SCAN_INTERVAL, - old_interval, - new_interval, - ) + "seconds, and increasing '%s' from %s to %s seconds", + new_interval * 3, CONF_SCAN_INTERVAL, old_interval, + new_interval) self._timers['statusUpdated'] = datetime.now() + new_interval * 3 @@ -316,7 +297,7 @@ class EvoZone(EvoClimateDevice): try: self._obj.set_temperature(temperature, until) except HTTPError as err: - self._handle_exception("HTTPError", str(err)) # noqa: E501; pylint: disable=no-member + self._handle_exception("HTTPError", str(err)) # noqa: E501; pylint: disable=no-member def set_temperature(self, **kwargs): """Set new target temperature, indefinitely.""" @@ -366,7 +347,7 @@ class EvoZone(EvoClimateDevice): try: self._obj.cancel_temp_override(self._obj) except HTTPError as err: - self._handle_exception("HTTPError", str(err)) # noqa: E501; pylint: disable=no-member + self._handle_exception("HTTPError", str(err)) # noqa: E501; pylint: disable=no-member elif operation_mode == EVO_TEMPOVER: _LOGGER.error( @@ -526,7 +507,7 @@ class EvoController(EvoClimateDevice): def _set_operation_mode(self, operation_mode): try: - self._obj._set_status(operation_mode) # noqa: E501; pylint: disable=protected-access + self._obj._set_status(operation_mode) # noqa: E501; pylint: disable=protected-access except HTTPError as err: self._handle_requests_exceptions(err) @@ -558,7 +539,7 @@ class EvoController(EvoClimateDevice): if not expired: return - # Retreive the latest state data via the client api + # Retrieve the latest state data via the client API loc_idx = self._params[CONF_LOCATION_IDX] try: @@ -570,10 +551,7 @@ class EvoController(EvoClimateDevice): self._timers['statusUpdated'] = datetime.now() self._available = True - _LOGGER.debug( - "_update_state_data(): self._status = %s", - self._status - ) + _LOGGER.debug("Status = %s", self._status) # inform the child devices that state data has been updated pkt = {'sender': 'controller', 'signal': 'refresh', 'to': EVO_CHILD} diff --git a/homeassistant/components/fastdotcom/__init__.py b/homeassistant/components/fastdotcom/__init__.py index aef8e4c48f9..a63fab76861 100644 --- a/homeassistant/components/fastdotcom/__init__.py +++ b/homeassistant/components/fastdotcom/__init__.py @@ -1,10 +1,4 @@ -""" -Support for testing internet speed via Fast.com. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/fastdotcom/ -""" - +"""Support for testing internet speed via Fast.com.""" import logging from datetime import timedelta diff --git a/homeassistant/components/fastdotcom/sensor.py b/homeassistant/components/fastdotcom/sensor.py index 4d105cebf44..0f17179f918 100644 --- a/homeassistant/components/fastdotcom/sensor.py +++ b/homeassistant/components/fastdotcom/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Fast.com internet speed testing sensor. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.fastdotcom/ -""" +"""Support for Fast.com internet speed testing sensor.""" import logging from homeassistant.components.fastdotcom import DOMAIN as FASTDOTCOM_DOMAIN, \ diff --git a/homeassistant/components/feedreader/__init__.py b/homeassistant/components/feedreader/__init__.py index f6d653360a9..86744bfd39c 100644 --- a/homeassistant/components/feedreader/__init__.py +++ b/homeassistant/components/feedreader/__init__.py @@ -1,9 +1,4 @@ -""" -Support for RSS/Atom feeds. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/feedreader/ -""" +"""Support for RSS/Atom feeds.""" from datetime import datetime, timedelta from logging import getLogger from os.path import exists diff --git a/homeassistant/components/ffmpeg/__init__.py b/homeassistant/components/ffmpeg/__init__.py index 3184b5a5d54..7b7e3a81294 100644 --- a/homeassistant/components/ffmpeg/__init__.py +++ b/homeassistant/components/ffmpeg/__init__.py @@ -1,9 +1,4 @@ -""" -Component that will help set the FFmpeg component. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/ffmpeg/ -""" +"""Support for FFmpeg.""" import logging import re diff --git a/homeassistant/components/fibaro/__init__.py b/homeassistant/components/fibaro/__init__.py index 715cc036265..9a6ccccb5e3 100644 --- a/homeassistant/components/fibaro/__init__.py +++ b/homeassistant/components/fibaro/__init__.py @@ -1,10 +1,4 @@ -""" -Support for the Fibaro devices. - -For more details about this platform, please refer to the documentation. -https://home-assistant.io/components/fibaro/ -""" - +"""Support for the Fibaro devices.""" import logging from collections import defaultdict from typing import Optional @@ -22,20 +16,30 @@ from homeassistant.util import convert, slugify REQUIREMENTS = ['fiblary3==0.1.7'] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'fibaro' -FIBARO_DEVICES = 'fibaro_devices' -FIBARO_CONTROLLERS = 'fibaro_controllers' -ATTR_CURRENT_POWER_W = "current_power_w" -ATTR_CURRENT_ENERGY_KWH = "current_energy_kwh" -CONF_PLUGINS = "plugins" -CONF_GATEWAYS = 'gateways' -CONF_DIMMING = "dimming" -CONF_COLOR = "color" -CONF_RESET_COLOR = "reset_color" -CONF_DEVICE_CONFIG = "device_config" -FIBARO_COMPONENTS = ['binary_sensor', 'cover', 'light', - 'scene', 'sensor', 'switch'] +ATTR_CURRENT_ENERGY_KWH = 'current_energy_kwh' +ATTR_CURRENT_POWER_W = 'current_power_w' + +CONF_COLOR = 'color' +CONF_DEVICE_CONFIG = 'device_config' +CONF_DIMMING = 'dimming' +CONF_GATEWAYS = 'gateways' +CONF_PLUGINS = 'plugins' +CONF_RESET_COLOR = 'reset_color' + +DOMAIN = 'fibaro' + +FIBARO_CONTROLLERS = 'fibaro_controllers' +FIBARO_DEVICES = 'fibaro_devices' + +FIBARO_COMPONENTS = [ + 'binary_sensor', + 'cover', + 'light', + 'scene', + 'sensor', + 'switch', +] FIBARO_TYPEMAP = { 'com.fibaro.multilevelSensor': "sensor", @@ -78,8 +82,7 @@ GATEWAY_CONFIG = vol.Schema({ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ - vol.Required(CONF_GATEWAYS): - vol.All(cv.ensure_list, [GATEWAY_CONFIG]) + vol.Required(CONF_GATEWAYS): vol.All(cv.ensure_list, [GATEWAY_CONFIG]), }) }, extra=vol.ALLOW_EXTRA) @@ -91,20 +94,19 @@ class FibaroController(): """Initialize the Fibaro controller.""" from fiblary3.client.v4.client import Client as FibaroClient - self._client = FibaroClient(config[CONF_URL], - config[CONF_USERNAME], - config[CONF_PASSWORD]) + self._client = FibaroClient( + config[CONF_URL], config[CONF_USERNAME], config[CONF_PASSWORD]) self._scene_map = None # Whether to import devices from plugins self._import_plugins = config[CONF_PLUGINS] self._device_config = config[CONF_DEVICE_CONFIG] - self._room_map = None # Mapping roomId to room object - self._device_map = None # Mapping deviceId to device object - self.fibaro_devices = None # List of devices by type - self._callbacks = {} # Update value callbacks by deviceId - self._state_handler = None # Fiblary's StateHandler object + self._room_map = None # Mapping roomId to room object + self._device_map = None # Mapping deviceId to device object + self.fibaro_devices = None # List of devices by type + self._callbacks = {} # Update value callbacks by deviceId + self._state_handler = None # Fiblary's StateHandler object self._excluded_devices = config[CONF_EXCLUDE] - self.hub_serial = None # Unique serial number of the hub + self.hub_serial = None # Unique serial number of the hub def connect(self): """Start the communication with the Fibaro controller.""" @@ -118,7 +120,7 @@ class FibaroController(): return False if login is None or login.status is False: _LOGGER.error("Invalid login for Fibaro HC. " - "Please check username and password.") + "Please check username and password") return False self._room_map = {room.id: room for room in self._client.rooms.list()} diff --git a/homeassistant/components/fibaro/binary_sensor.py b/homeassistant/components/fibaro/binary_sensor.py index 1934580c58e..2c2d9c30a79 100644 --- a/homeassistant/components/fibaro/binary_sensor.py +++ b/homeassistant/components/fibaro/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for Fibaro binary sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.fibaro/ -""" +"""Support for Fibaro binary sensors.""" import logging from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/fibaro/cover.py b/homeassistant/components/fibaro/cover.py index d47dbb20315..aa34fcc36a9 100644 --- a/homeassistant/components/fibaro/cover.py +++ b/homeassistant/components/fibaro/cover.py @@ -1,9 +1,4 @@ -""" -Support for Fibaro cover - curtains, rollershutters etc. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/cover.fibaro/ -""" +"""Support for Fibaro cover - curtains, rollershutters etc.""" import logging from homeassistant.components.cover import ( diff --git a/homeassistant/components/fibaro/light.py b/homeassistant/components/fibaro/light.py index 9b3b3850f39..5ee3e83b95f 100644 --- a/homeassistant/components/fibaro/light.py +++ b/homeassistant/components/fibaro/light.py @@ -1,10 +1,4 @@ -""" -Support for Fibaro lights. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.fibaro/ -""" - +"""Support for Fibaro lights.""" import logging import asyncio from functools import partial diff --git a/homeassistant/components/fibaro/scene.py b/homeassistant/components/fibaro/scene.py index a0bd4e7ff40..620f095b733 100644 --- a/homeassistant/components/fibaro/scene.py +++ b/homeassistant/components/fibaro/scene.py @@ -1,9 +1,4 @@ -""" -Support for Fibaro scenes. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/scene.fibaro/ -""" +"""Support for Fibaro scenes.""" import logging from homeassistant.components.scene import ( diff --git a/homeassistant/components/fibaro/sensor.py b/homeassistant/components/fibaro/sensor.py index e437ef8710d..01452d8b394 100644 --- a/homeassistant/components/fibaro/sensor.py +++ b/homeassistant/components/fibaro/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Fibaro sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.fibaro/ -""" +"""Support for Fibaro sensors.""" import logging from homeassistant.const import ( diff --git a/homeassistant/components/fibaro/switch.py b/homeassistant/components/fibaro/switch.py index 8b59aabec72..04b8aba1cf4 100644 --- a/homeassistant/components/fibaro/switch.py +++ b/homeassistant/components/fibaro/switch.py @@ -1,9 +1,4 @@ -""" -Support for Fibaro switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.fibaro/ -""" +"""Support for Fibaro switches.""" import logging from homeassistant.util import convert diff --git a/homeassistant/components/folder_watcher/__init__.py b/homeassistant/components/folder_watcher/__init__.py index 098b34ac948..babfbd9e9aa 100644 --- a/homeassistant/components/folder_watcher/__init__.py +++ b/homeassistant/components/folder_watcher/__init__.py @@ -1,17 +1,15 @@ -""" -Component for monitoring activity on a folder. - -For more details about this platform, refer to the documentation at -https://home-assistant.io/components/folder_watcher/ -""" -import os +"""Component for monitoring activity on a folder.""" import logging +import os + import voluptuous as vol + from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['watchdog==0.8.3'] + _LOGGER = logging.getLogger(__name__) CONF_FOLDER = 'folder' diff --git a/homeassistant/components/foursquare/__init__.py b/homeassistant/components/foursquare/__init__.py index a4a7395adc4..0c5a48049ec 100644 --- a/homeassistant/components/foursquare/__init__.py +++ b/homeassistant/components/foursquare/__init__.py @@ -1,9 +1,4 @@ -""" -Support for the Foursquare (Swarm) API. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/foursquare/ -""" +"""Support for the Foursquare (Swarm) API.""" import logging import requests diff --git a/homeassistant/components/freebox/__init__.py b/homeassistant/components/freebox/__init__.py index 99efbae0984..41e60d884ce 100644 --- a/homeassistant/components/freebox/__init__.py +++ b/homeassistant/components/freebox/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Freebox devices (Freebox v6 and Freebox mini 4K). - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/freebox/ -""" +"""Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" import logging import socket @@ -26,7 +21,7 @@ FREEBOX_CONFIG_FILE = 'freebox.conf' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PORT): cv.port + vol.Required(CONF_PORT): cv.port, }) }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/freebox/device_tracker.py b/homeassistant/components/freebox/device_tracker.py index f4e1ce5bd8a..fb94f7f56f5 100644 --- a/homeassistant/components/freebox/device_tracker.py +++ b/homeassistant/components/freebox/device_tracker.py @@ -1,9 +1,4 @@ -""" -Support for Freebox devices (Freebox v6 and Freebox mini 4K). - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/device_tracker.freebox/ -""" +"""Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" from collections import namedtuple import logging diff --git a/homeassistant/components/freebox/sensor.py b/homeassistant/components/freebox/sensor.py index 2f8ccbc745d..49e68dc2c41 100644 --- a/homeassistant/components/freebox/sensor.py +++ b/homeassistant/components/freebox/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Freebox devices (Freebox v6 and Freebox mini 4K). - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/sensor.freebox/ -""" +"""Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" import logging from homeassistant.components.freebox import DATA_FREEBOX @@ -18,10 +13,7 @@ async def async_setup_platform( hass, config, async_add_entities, discovery_info=None): """Set up the sensors.""" fbx = hass.data[DATA_FREEBOX] - async_add_entities([ - FbxRXSensor(fbx), - FbxTXSensor(fbx) - ], True) + async_add_entities([FbxRXSensor(fbx), FbxTXSensor(fbx)], True) class FbxSensor(Entity): diff --git a/homeassistant/components/fritzbox/__init__.py b/homeassistant/components/fritzbox/__init__.py index ad3c7bc1929..81ba019acbc 100644 --- a/homeassistant/components/fritzbox/__init__.py +++ b/homeassistant/components/fritzbox/__init__.py @@ -1,9 +1,4 @@ -""" -Support for AVM Fritz!Box smarthome devices. - -For more details about this component, please refer to the documentation at -http://home-assistant.io/components/fritzbox/ -""" +"""Support for AVM Fritz!Box smarthome devices.""" import logging import voluptuous as vol diff --git a/homeassistant/components/fritzbox/binary_sensor.py b/homeassistant/components/fritzbox/binary_sensor.py index ab58e6e84bc..c68c79f1e77 100644 --- a/homeassistant/components/fritzbox/binary_sensor.py +++ b/homeassistant/components/fritzbox/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for Fritzbox binary sensors. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.fritzbox/ -""" +"""Support for Fritzbox binary sensors.""" import logging import requests diff --git a/homeassistant/components/fritzbox/climate.py b/homeassistant/components/fritzbox/climate.py index f2d13ee92f6..64d99ebf133 100644 --- a/homeassistant/components/fritzbox/climate.py +++ b/homeassistant/components/fritzbox/climate.py @@ -1,9 +1,4 @@ -""" -Support for AVM Fritz!Box smarthome thermostate devices. - -For more details about this component, please refer to the documentation at -http://home-assistant.io/components/climate.fritzbox/ -""" +"""Support for AVM Fritz!Box smarthome thermostate devices.""" import logging import requests @@ -19,6 +14,7 @@ from homeassistant.components.climate import ( SUPPORT_TARGET_TEMPERATURE) from homeassistant.const import ( ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS) + DEPENDENCIES = ['fritzbox'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/fritzbox/sensor.py b/homeassistant/components/fritzbox/sensor.py index 66c515c2bfd..a1736fb9857 100644 --- a/homeassistant/components/fritzbox/sensor.py +++ b/homeassistant/components/fritzbox/sensor.py @@ -1,9 +1,4 @@ -""" -Support for AVM Fritz!Box smarthome temperature sensor only devices. - -For more details about this component, please refer to the documentation at -http://home-assistant.io/components/sensor.fritzbox/ -""" +"""Support for AVM Fritz!Box smarthome temperature sensor only devices.""" import logging import requests diff --git a/homeassistant/components/fritzbox/switch.py b/homeassistant/components/fritzbox/switch.py index 55fa8a04796..be9212793ab 100644 --- a/homeassistant/components/fritzbox/switch.py +++ b/homeassistant/components/fritzbox/switch.py @@ -1,9 +1,4 @@ -""" -Support for AVM Fritz!Box smarthome switch devices. - -For more details about this component, please refer to the documentation at -http://home-assistant.io/components/switch.fritzbox/ -""" +"""Support for AVM Fritz!Box smarthome switch devices.""" import logging import requests diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index f89596c0c02..bf4df366ae3 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -1,9 +1,4 @@ -""" -Handle the frontend for Home Assistant. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/frontend/ -""" +"""Handle the frontend for Home Assistant.""" import asyncio import json import logging diff --git a/homeassistant/components/gc100/__init__.py b/homeassistant/components/gc100/__init__.py index 0d4b19da030..36e9c61b1ba 100644 --- a/homeassistant/components/gc100/__init__.py +++ b/homeassistant/components/gc100/__init__.py @@ -1,9 +1,4 @@ -""" -Support for controlling Global Cache gc100. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/gc100/ -""" +"""Support for controlling Global Cache gc100.""" import logging import voluptuous as vol diff --git a/homeassistant/components/rainmachine/__init__.py b/homeassistant/components/rainmachine/__init__.py index 4eb9e390d71..0591af8acfa 100644 --- a/homeassistant/components/rainmachine/__init__.py +++ b/homeassistant/components/rainmachine/__init__.py @@ -1,9 +1,4 @@ -""" -Support for RainMachine devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/rainmachine/ -""" +"""Support for RainMachine devices.""" import logging from datetime import timedelta diff --git a/homeassistant/components/raspihats/__init__.py b/homeassistant/components/raspihats/__init__.py index 5e834cdf7ec..69b03a36769 100644 --- a/homeassistant/components/raspihats/__init__.py +++ b/homeassistant/components/raspihats/__init__.py @@ -1,9 +1,4 @@ -""" -Support for controlling raspihats boards. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/raspihats/ -""" +"""Support for controlling raspihats boards.""" import logging import threading import time diff --git a/homeassistant/components/raspihats/binary_sensor.py b/homeassistant/components/raspihats/binary_sensor.py index feef5396d88..04885402e72 100644 --- a/homeassistant/components/raspihats/binary_sensor.py +++ b/homeassistant/components/raspihats/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Configure a binary_sensor using a digital input from a raspihats board. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.raspihats/ -""" +"""Support for raspihats board binary sensors.""" import logging import voluptuous as vol @@ -34,7 +29,7 @@ _CHANNELS_SCHEMA = vol.Schema([{ _I2C_HATS_SCHEMA = vol.Schema([{ vol.Required(CONF_BOARD): vol.In(I2C_HAT_NAMES), vol.Required(CONF_ADDRESS): vol.Coerce(int), - vol.Required(CONF_CHANNELS): _CHANNELS_SCHEMA + vol.Required(CONF_CHANNELS): _CHANNELS_SCHEMA, }]) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ diff --git a/homeassistant/components/raspihats/switch.py b/homeassistant/components/raspihats/switch.py index b422efea2ff..10bb2f748c4 100644 --- a/homeassistant/components/raspihats/switch.py +++ b/homeassistant/components/raspihats/switch.py @@ -1,9 +1,4 @@ -""" -Configure a switch using a digital output from a raspihats board. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.raspihats/ -""" +"""Support for raspihats board switches.""" import logging import voluptuous as vol diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index cafae2b0b08..9b852b4a00a 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -1,12 +1,4 @@ -""" -Support for recording details. - -Component that records all events and state changes. Allows other components -to query this database. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/recorder/ -""" +"""Support for recording details.""" import asyncio from collections import namedtuple import concurrent.futures diff --git a/homeassistant/components/remember_the_milk/__init__.py b/homeassistant/components/remember_the_milk/__init__.py index a94e8e95c6f..82619e35a0e 100644 --- a/homeassistant/components/remember_the_milk/__init__.py +++ b/homeassistant/components/remember_the_milk/__init__.py @@ -1,9 +1,4 @@ -""" -Component to interact with Remember The Milk. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/remember_the_milk/ -""" +"""Support to interact with Remember The Milk.""" import json import logging import os diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index e04b0ef2758..b7923596039 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -1,9 +1,4 @@ -""" -Component to interface with universal remote control devices. - -For more details about this component, please refer to the documentation -at https://home-assistant.io/components/remote/ -""" +"""Support to interface with universal remote control devices.""" from datetime import timedelta import functools as ft import logging diff --git a/homeassistant/components/rest_command/__init__.py b/homeassistant/components/rest_command/__init__.py index c1ccc73b81c..01c5d837ca9 100644 --- a/homeassistant/components/rest_command/__init__.py +++ b/homeassistant/components/rest_command/__init__.py @@ -1,9 +1,4 @@ -""" -Exposes regular rest commands as services. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/rest_command/ -""" +"""Support for exposing regular REST commands as services.""" import asyncio import logging diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py index ce9777151cf..98e80580fea 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Rflink components. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/rflink/ -""" +"""Support for Rflink devices.""" import asyncio from collections import defaultdict import logging diff --git a/homeassistant/components/rfxtrx/__init__.py b/homeassistant/components/rfxtrx/__init__.py index f2c82842bc1..a7b703ef2ab 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -1,9 +1,4 @@ -""" -Support for RFXtrx components. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/rfxtrx/ -""" +"""Support for RFXtrx devices.""" from collections import OrderedDict import logging diff --git a/homeassistant/components/rfxtrx/binary_sensor.py b/homeassistant/components/rfxtrx/binary_sensor.py index 1e88c72e19d..9a49bd02b97 100644 --- a/homeassistant/components/rfxtrx/binary_sensor.py +++ b/homeassistant/components/rfxtrx/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for RFXtrx binary sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.rfxtrx/ -""" +"""Support for RFXtrx binary sensors.""" import logging import voluptuous as vol @@ -35,7 +30,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Any(cv.time_period, cv.positive_timedelta), vol.Optional(CONF_DATA_BITS): cv.positive_int, vol.Optional(CONF_COMMAND_ON): cv.byte, - vol.Optional(CONF_COMMAND_OFF): cv.byte + vol.Optional(CONF_COMMAND_OFF): cv.byte, }) }, vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean, diff --git a/homeassistant/components/rfxtrx/cover.py b/homeassistant/components/rfxtrx/cover.py index d486b601977..5a657923683 100644 --- a/homeassistant/components/rfxtrx/cover.py +++ b/homeassistant/components/rfxtrx/cover.py @@ -1,9 +1,4 @@ -""" -Support for RFXtrx cover components. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/cover.rfxtrx/ -""" +"""Support for RFXtrx covers.""" import voluptuous as vol from homeassistant.components import rfxtrx diff --git a/homeassistant/components/rfxtrx/light.py b/homeassistant/components/rfxtrx/light.py index 10288773486..d0b75c2f962 100644 --- a/homeassistant/components/rfxtrx/light.py +++ b/homeassistant/components/rfxtrx/light.py @@ -1,9 +1,4 @@ -""" -Support for RFXtrx lights. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.rfxtrx/ -""" +"""Support for RFXtrx lights.""" import logging import voluptuous as vol diff --git a/homeassistant/components/rfxtrx/sensor.py b/homeassistant/components/rfxtrx/sensor.py index c2eff8d7c5d..74c64635563 100644 --- a/homeassistant/components/rfxtrx/sensor.py +++ b/homeassistant/components/rfxtrx/sensor.py @@ -1,9 +1,4 @@ -""" -Support for RFXtrx sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.rfxtrx/ -""" +"""Support for RFXtrx sensors.""" import logging import voluptuous as vol diff --git a/homeassistant/components/rfxtrx/switch.py b/homeassistant/components/rfxtrx/switch.py index c0f057da138..141cf2c2c1a 100644 --- a/homeassistant/components/rfxtrx/switch.py +++ b/homeassistant/components/rfxtrx/switch.py @@ -1,9 +1,4 @@ -""" -Support for RFXtrx switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.rfxtrx/ -""" +"""Support for RFXtrx switches.""" import logging import voluptuous as vol diff --git a/homeassistant/components/ring/__init__.py b/homeassistant/components/ring/__init__.py index 2e048caa52f..526388a0918 100644 --- a/homeassistant/components/ring/__init__.py +++ b/homeassistant/components/ring/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Ring Doorbell/Chimes. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/ring/ -""" +"""Support for Ring Doorbell/Chimes.""" import logging from requests.exceptions import HTTPError, ConnectTimeout diff --git a/homeassistant/components/roku/__init__.py b/homeassistant/components/roku/__init__.py index 5ceebb3dee5..89bb1a9acb8 100644 --- a/homeassistant/components/roku/__init__.py +++ b/homeassistant/components/roku/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Roku platform. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/roku/ -""" +"""Support for Roku.""" import logging import voluptuous as vol diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 7d1977aadc1..3cf27af0674 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -1,9 +1,4 @@ -""" -Support for the Roku media player. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/media_player.roku/ -""" +"""Support for the Roku media player.""" import logging import requests.exceptions diff --git a/homeassistant/components/roku/remote.py b/homeassistant/components/roku/remote.py index 86a7105dafe..5529918010c 100644 --- a/homeassistant/components/roku/remote.py +++ b/homeassistant/components/roku/remote.py @@ -1,20 +1,14 @@ -""" -Support for the Roku remote. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/remote.roku/ -""" +"""Support for the Roku remote.""" import requests.exceptions from homeassistant.components import remote from homeassistant.const import (CONF_HOST) - DEPENDENCIES = ['roku'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the Roku remote platform.""" if not discovery_info: return diff --git a/homeassistant/components/route53/__init__.py b/homeassistant/components/route53/__init__.py index 4c35983feed..725dec8b8e5 100644 --- a/homeassistant/components/route53/__init__.py +++ b/homeassistant/components/route53/__init__.py @@ -1,9 +1,4 @@ -""" -Update the IP addresses of your Route53 DNS records. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/route53/ -""" +"""Update the IP addresses of your Route53 DNS records.""" from datetime import timedelta import logging from typing import List diff --git a/homeassistant/components/rpi_gpio/__init__.py b/homeassistant/components/rpi_gpio/__init__.py index 5f52341f1cb..b5bd0796f16 100644 --- a/homeassistant/components/rpi_gpio/__init__.py +++ b/homeassistant/components/rpi_gpio/__init__.py @@ -1,9 +1,4 @@ -""" -Support for controlling GPIO pins of a Raspberry Pi. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/rpi_gpio/ -""" +"""Support for controlling GPIO pins of a Raspberry Pi.""" import logging from homeassistant.const import ( diff --git a/homeassistant/components/rpi_gpio/binary_sensor.py b/homeassistant/components/rpi_gpio/binary_sensor.py index 2fe4e0766ed..559ae958404 100644 --- a/homeassistant/components/rpi_gpio/binary_sensor.py +++ b/homeassistant/components/rpi_gpio/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for binary sensor using RPi GPIO. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.rpi_gpio/ -""" +"""Support for binary sensor using RPi GPIO.""" import logging import voluptuous as vol diff --git a/homeassistant/components/rpi_gpio/cover.py b/homeassistant/components/rpi_gpio/cover.py index 828f5e8e0fe..403f7ec6867 100644 --- a/homeassistant/components/rpi_gpio/cover.py +++ b/homeassistant/components/rpi_gpio/cover.py @@ -1,12 +1,4 @@ -""" -Support for controlling a Raspberry Pi cover. - -Instructions for building the controller can be found here -https://github.com/andrewshilliday/garage-door-controller - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/cover.rpi_gpio/ -""" +"""Support for controlling a Raspberry Pi cover.""" import logging from time import sleep diff --git a/homeassistant/components/rpi_gpio/switch.py b/homeassistant/components/rpi_gpio/switch.py index 3dec1135ec8..bdb79d03eec 100644 --- a/homeassistant/components/rpi_gpio/switch.py +++ b/homeassistant/components/rpi_gpio/switch.py @@ -1,9 +1,4 @@ -""" -Allows to configure a switch using RPi GPIO. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.rpi_gpio/ -""" +"""Allows to configure a switch using RPi GPIO.""" import logging import voluptuous as vol diff --git a/homeassistant/components/rpi_pfio/__init__.py b/homeassistant/components/rpi_pfio/__init__.py index 286be87bce9..b096d9fe98a 100644 --- a/homeassistant/components/rpi_pfio/__init__.py +++ b/homeassistant/components/rpi_pfio/__init__.py @@ -1,9 +1,4 @@ -""" -Support for controlling the PiFace Digital I/O module on a RPi. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/rpi_pfio/ -""" +"""Support for controlling the PiFace Digital I/O module on a RPi.""" import logging from homeassistant.const import ( diff --git a/homeassistant/components/rpi_pfio/binary_sensor.py b/homeassistant/components/rpi_pfio/binary_sensor.py index 61d1f8ac285..677ec3bb16f 100644 --- a/homeassistant/components/rpi_pfio/binary_sensor.py +++ b/homeassistant/components/rpi_pfio/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for binary sensor using the PiFace Digital I/O module on a RPi. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.rpi_pfio/ -""" +"""Support for binary sensor using the PiFace Digital I/O module on a RPi.""" import logging import voluptuous as vol diff --git a/homeassistant/components/rpi_pfio/switch.py b/homeassistant/components/rpi_pfio/switch.py index 8b0871ca800..fc158bd666f 100644 --- a/homeassistant/components/rpi_pfio/switch.py +++ b/homeassistant/components/rpi_pfio/switch.py @@ -1,9 +1,4 @@ -""" -Allows to configure a switch using the PiFace Digital I/O module on a RPi. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.rpi_pfio/ -""" +"""Support for switches using the PiFace Digital I/O module on a RPi.""" import logging import voluptuous as vol diff --git a/homeassistant/components/rss_feed_template/__init__.py b/homeassistant/components/rss_feed_template/__init__.py index 34bee1ec5fc..3c93fe2ac83 100644 --- a/homeassistant/components/rss_feed_template/__init__.py +++ b/homeassistant/components/rss_feed_template/__init__.py @@ -1,10 +1,4 @@ -""" -Exports sensor values via RSS feed. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/rss_feed_template/ -""" - +"""Support to export sensor values via RSS feed.""" from html import escape from aiohttp import web diff --git a/homeassistant/components/sabnzbd/__init__.py b/homeassistant/components/sabnzbd/__init__.py index 25fce22c641..d070872f85c 100644 --- a/homeassistant/components/sabnzbd/__init__.py +++ b/homeassistant/components/sabnzbd/__init__.py @@ -1,9 +1,4 @@ -""" -Support for monitoring an SABnzbd NZB client. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sabnzbd/ -""" +"""Support for monitoring an SABnzbd NZB client.""" import logging from datetime import timedelta diff --git a/homeassistant/components/sabnzbd/sensor.py b/homeassistant/components/sabnzbd/sensor.py index 7e94f9026a8..ca8fc64eea4 100644 --- a/homeassistant/components/sabnzbd/sensor.py +++ b/homeassistant/components/sabnzbd/sensor.py @@ -1,9 +1,4 @@ -""" -Support for monitoring an SABnzbd NZB client. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.sabnzbd/ -""" +"""Support for monitoring an SABnzbd NZB client.""" import logging from homeassistant.components.sabnzbd import DATA_SABNZBD, \ @@ -16,8 +11,8 @@ DEPENDENCIES = ['sabnzbd'] _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the SABnzbd sensors.""" if discovery_info is None: return @@ -44,8 +39,8 @@ class SabnzbdSensor(Entity): async def async_added_to_hass(self): """Call when entity about to be added to hass.""" - async_dispatcher_connect(self.hass, SIGNAL_SABNZBD_UPDATED, - self.update_state) + async_dispatcher_connect( + self.hass, SIGNAL_SABNZBD_UPDATED, self.update_state) @property def name(self): diff --git a/homeassistant/components/satel_integra/__init__.py b/homeassistant/components/satel_integra/__init__.py index 25295c6f2ce..bff365a079f 100644 --- a/homeassistant/components/satel_integra/__init__.py +++ b/homeassistant/components/satel_integra/__init__.py @@ -1,10 +1,4 @@ -""" -Support for Satel Integra devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/satel_integra/ -""" - +"""Support for Satel Integra devices.""" import asyncio import logging diff --git a/homeassistant/components/satel_integra/alarm_control_panel.py b/homeassistant/components/satel_integra/alarm_control_panel.py index b704677800f..360acdb2497 100644 --- a/homeassistant/components/satel_integra/alarm_control_panel.py +++ b/homeassistant/components/satel_integra/alarm_control_panel.py @@ -1,9 +1,4 @@ -""" -Support for Satel Integra alarm, using ETHM module: https://www.satel.pl/en/ . - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/alarm_control_panel.satel_integra/ -""" +"""Support for Satel Integra alarm, using ETHM module.""" import logging import homeassistant.components.alarm_control_panel as alarm @@ -17,8 +12,8 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['satel_integra'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up for Satel Integra alarm panels.""" if not discovery_info: return diff --git a/homeassistant/components/satel_integra/binary_sensor.py b/homeassistant/components/satel_integra/binary_sensor.py index b4541cf2c44..34ced628712 100644 --- a/homeassistant/components/satel_integra/binary_sensor.py +++ b/homeassistant/components/satel_integra/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for Satel Integra zone states- represented as binary sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.satel_integra/ -""" +"""Support for Satel Integra zone states- represented as binary sensors.""" import logging from homeassistant.components.binary_sensor import BinarySensorDevice @@ -21,8 +16,8 @@ DEPENDENCIES = ['satel_integra'] _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the Satel Integra binary sensor devices.""" if not discovery_info: return @@ -34,8 +29,8 @@ async def async_setup_platform(hass, config, async_add_entities, for zone_num, device_config_data in configured_zones.items(): zone_type = device_config_data[CONF_ZONE_TYPE] zone_name = device_config_data[CONF_ZONE_NAME] - device = SatelIntegraBinarySensor(zone_num, zone_name, zone_type, - SIGNAL_ZONES_UPDATED) + device = SatelIntegraBinarySensor( + zone_num, zone_name, zone_type, SIGNAL_ZONES_UPDATED) devices.append(device) configured_outputs = discovery_info[CONF_OUTPUTS] @@ -43,8 +38,8 @@ async def async_setup_platform(hass, config, async_add_entities, for zone_num, device_config_data in configured_outputs.items(): zone_type = device_config_data[CONF_ZONE_TYPE] zone_name = device_config_data[CONF_ZONE_NAME] - device = SatelIntegraBinarySensor(zone_num, zone_name, zone_type, - SIGNAL_OUTPUTS_UPDATED) + device = SatelIntegraBinarySensor( + zone_num, zone_name, zone_type, SIGNAL_OUTPUTS_UPDATED) devices.append(device) async_add_entities(devices) diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index b3ab5228875..802512dbf5d 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -1,9 +1,4 @@ -""" -Allow users to set and activate scenes. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/scene/ -""" +"""Allow users to set and activate scenes.""" import asyncio import importlib import logging @@ -25,8 +20,7 @@ STATES = 'states' def _hass_domain_validator(config): """Validate platform in config for homeassistant domain.""" if CONF_PLATFORM not in config: - config = { - CONF_PLATFORM: HASS_DOMAIN, STATES: config} + config = {CONF_PLATFORM: HASS_DOMAIN, STATES: config} return config diff --git a/homeassistant/components/scene/homeassistant.py b/homeassistant/components/scene/homeassistant.py index 5812512ccef..96e24138b4a 100644 --- a/homeassistant/components/scene/homeassistant.py +++ b/homeassistant/components/scene/homeassistant.py @@ -1,9 +1,4 @@ -""" -Allow users to set and activate scenes. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/scene/ -""" +"""Allow users to set and activate scenes.""" from collections import namedtuple import voluptuous as vol diff --git a/homeassistant/components/scene/hunterdouglas_powerview.py b/homeassistant/components/scene/hunterdouglas_powerview.py index 7676deb1a9c..7f0709aa6c1 100644 --- a/homeassistant/components/scene/hunterdouglas_powerview.py +++ b/homeassistant/components/scene/hunterdouglas_powerview.py @@ -1,9 +1,4 @@ -""" -Support for Powerview scenes from a Powerview hub. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/scene.hunterdouglas_powerview/ -""" +"""Support for Powerview scenes from a Powerview hub.""" import logging import voluptuous as vol diff --git a/homeassistant/components/scene/lifx_cloud.py b/homeassistant/components/scene/lifx_cloud.py index c1dda86343d..c877bddbe53 100644 --- a/homeassistant/components/scene/lifx_cloud.py +++ b/homeassistant/components/scene/lifx_cloud.py @@ -1,9 +1,4 @@ -""" -Support for LIFX Cloud scenes. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/scene.lifx_cloud/ -""" +"""Support for LIFX Cloud scenes.""" import asyncio import logging diff --git a/homeassistant/components/scene/litejet.py b/homeassistant/components/scene/litejet.py index e12643fa651..2563c9ceb0c 100644 --- a/homeassistant/components/scene/litejet.py +++ b/homeassistant/components/scene/litejet.py @@ -1,9 +1,4 @@ -""" -Support for LiteJet scenes. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/scene.litejet/ -""" +"""Support for LiteJet scenes.""" import logging from homeassistant.components import litejet diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index e337a2ec251..fceedb57428 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -1,12 +1,4 @@ -""" -Support for scripts. - -Scripts are a sequence of actions that can be triggered manually -by the user or automatically based upon automation events, etc. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/script/ -""" +"""Support for scripts.""" import asyncio import logging diff --git a/homeassistant/components/scsgate/cover.py b/homeassistant/components/scsgate/cover.py index 2d85c1fe3c3..fc1c16e1ff3 100644 --- a/homeassistant/components/scsgate/cover.py +++ b/homeassistant/components/scsgate/cover.py @@ -1,9 +1,4 @@ -""" -Allow to configure a SCSGate cover. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/cover.scsgate/ -""" +"""Support for SCSGate covers.""" import logging import voluptuous as vol diff --git a/homeassistant/components/scsgate/light.py b/homeassistant/components/scsgate/light.py index c218e194791..87d7e02b383 100644 --- a/homeassistant/components/scsgate/light.py +++ b/homeassistant/components/scsgate/light.py @@ -1,9 +1,4 @@ -""" -Support for SCSGate lights. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.scsgate/ -""" +"""Support for SCSGate lights.""" import logging import voluptuous as vol diff --git a/homeassistant/components/scsgate/switch.py b/homeassistant/components/scsgate/switch.py index 9344aeab7ed..2b2bf2de94f 100644 --- a/homeassistant/components/scsgate/switch.py +++ b/homeassistant/components/scsgate/switch.py @@ -1,9 +1,4 @@ -""" -Support for SCSGate switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.scsgate/ -""" +"""Support for SCSGate switches.""" import logging import voluptuous as vol diff --git a/homeassistant/components/sense/__init__.py b/homeassistant/components/sense/__init__.py index 2fac2820230..11c45991400 100644 --- a/homeassistant/components/sense/__init__.py +++ b/homeassistant/components/sense/__init__.py @@ -1,9 +1,4 @@ -""" -Support for monitoring a Sense energy sensor. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sense/ -""" +"""Support for monitoring a Sense energy sensor.""" import logging import voluptuous as vol diff --git a/homeassistant/components/sense/binary_sensor.py b/homeassistant/components/sense/binary_sensor.py index a85a0c889d1..80fb8f2634d 100644 --- a/homeassistant/components/sense/binary_sensor.py +++ b/homeassistant/components/sense/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for monitoring a Sense energy sensor device. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.sense/ -""" +"""Support for monitoring a Sense energy sensor device.""" import logging from homeassistant.components.binary_sensor import BinarySensorDevice diff --git a/homeassistant/components/sense/sensor.py b/homeassistant/components/sense/sensor.py index 769b3a9e148..2995b860e5b 100644 --- a/homeassistant/components/sense/sensor.py +++ b/homeassistant/components/sense/sensor.py @@ -1,9 +1,4 @@ -""" -Support for monitoring a Sense energy sensor. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.sense/ -""" +"""Support for monitoring a Sense energy sensor.""" from datetime import timedelta import logging diff --git a/homeassistant/components/shell_command/__init__.py b/homeassistant/components/shell_command/__init__.py index f9ec8da54e3..e27870e2d86 100644 --- a/homeassistant/components/shell_command/__init__.py +++ b/homeassistant/components/shell_command/__init__.py @@ -1,9 +1,4 @@ -""" -Exposes regular shell commands as services. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/shell_command/ -""" +"""Expose regular shell commands as services.""" import asyncio import logging import shlex @@ -15,7 +10,6 @@ from homeassistant.core import ServiceCall from homeassistant.helpers import config_validation as cv, template from homeassistant.helpers.typing import ConfigType, HomeAssistantType - DOMAIN = 'shell_command' _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/shiftr/__init__.py b/homeassistant/components/shiftr/__init__.py index 17a46be4734..438bc36b1bf 100644 --- a/homeassistant/components/shiftr/__init__.py +++ b/homeassistant/components/shiftr/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Shiftr.io. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/shiftr/ -""" +"""Support for Shiftr.io.""" import logging import voluptuous as vol diff --git a/homeassistant/components/shopping_list/__init__.py b/homeassistant/components/shopping_list/__init__.py index 2ebd80c3de0..1a036f3661a 100644 --- a/homeassistant/components/shopping_list/__init__.py +++ b/homeassistant/components/shopping_list/__init__.py @@ -1,4 +1,4 @@ -"""Component to manage a shopping list.""" +"""Support to manage a shopping list.""" import asyncio import logging import uuid diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 7f1f8f539eb..fcd9d15839b 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -1,9 +1,4 @@ -""" -Support for SimpliSafe alarm systems. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/simplisafe/ -""" +"""Support for SimpliSafe alarm systems.""" import logging from datetime import timedelta @@ -36,7 +31,7 @@ ACCOUNT_CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_PASSWORD): cv.string, vol.Optional(CONF_CODE): cv.string, vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): - cv.time_period + cv.time_period, }) CONFIG_SCHEMA = vol.Schema({ diff --git a/homeassistant/components/simplisafe/alarm_control_panel.py b/homeassistant/components/simplisafe/alarm_control_panel.py index 626a819b0b9..9fdeea73da8 100644 --- a/homeassistant/components/simplisafe/alarm_control_panel.py +++ b/homeassistant/components/simplisafe/alarm_control_panel.py @@ -1,9 +1,4 @@ -""" -This platform provides alarm control functionality for SimpliSafe. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/alarm_control_panel.simplisafe/ -""" +"""Support for SimpliSafe alarm control panels.""" import logging import re diff --git a/homeassistant/components/simplisafe/config_flow.py b/homeassistant/components/simplisafe/config_flow.py index 0a59dcb3e1d..66e26bd5204 100644 --- a/homeassistant/components/simplisafe/config_flow.py +++ b/homeassistant/components/simplisafe/config_flow.py @@ -1,5 +1,4 @@ """Config flow to configure the SimpliSafe component.""" - from collections import OrderedDict import voluptuous as vol diff --git a/homeassistant/components/sisyphus/__init__.py b/homeassistant/components/sisyphus/__init__.py index f875e3a91c7..b1bec15d40e 100644 --- a/homeassistant/components/sisyphus/__init__.py +++ b/homeassistant/components/sisyphus/__init__.py @@ -1,9 +1,4 @@ -""" -Support for controlling Sisyphus Kinetic Art Tables. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/sisyphus/ -""" +"""Support for controlling Sisyphus Kinetic Art Tables.""" import asyncio import logging diff --git a/homeassistant/components/sisyphus/light.py b/homeassistant/components/sisyphus/light.py index 75cc86a0154..c9d20959696 100644 --- a/homeassistant/components/sisyphus/light.py +++ b/homeassistant/components/sisyphus/light.py @@ -1,9 +1,4 @@ -""" -Support for the light on the Sisyphus Kinetic Art Table. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.sisyphus/ -""" +"""Support for the light on the Sisyphus Kinetic Art Table.""" import logging from homeassistant.const import CONF_NAME @@ -26,15 +21,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class SisyphusLight(Light): - """Represents a Sisyphus table as a light.""" + """Representation of a Sisyphus table as a light.""" def __init__(self, name, table): - """ - Constructor. - - :param name: name of the table - :param table: sisyphus-control Table object - """ + """Initialize the Sisyphus table.""" self._name = name self._table = table diff --git a/homeassistant/components/sisyphus/media_player.py b/homeassistant/components/sisyphus/media_player.py index fd8c228d396..463ac2b6cd1 100644 --- a/homeassistant/components/sisyphus/media_player.py +++ b/homeassistant/components/sisyphus/media_player.py @@ -1,9 +1,4 @@ -""" -Support for track controls on the Sisyphus Kinetic Art Table. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/media_player.sisyphus/ -""" +"""Support for track controls on the Sisyphus Kinetic Art Table.""" import logging from homeassistant.components.media_player import MediaPlayerDevice diff --git a/homeassistant/components/skybell/__init__.py b/homeassistant/components/skybell/__init__.py index ee384fd7094..8724f7d3d66 100644 --- a/homeassistant/components/skybell/__init__.py +++ b/homeassistant/components/skybell/__init__.py @@ -1,9 +1,4 @@ -""" -Support for the Skybell HD Doorbell. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/skybell/ -""" +"""Support for the Skybell HD Doorbell.""" import logging from requests.exceptions import HTTPError, ConnectTimeout diff --git a/homeassistant/components/skybell/binary_sensor.py b/homeassistant/components/skybell/binary_sensor.py index 7d8b3a84a2a..169e1b51a4e 100644 --- a/homeassistant/components/skybell/binary_sensor.py +++ b/homeassistant/components/skybell/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Binary sensor support for the Skybell HD Doorbell. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.skybell/ -""" +"""Binary sensor support for the Skybell HD Doorbell.""" from datetime import timedelta import logging diff --git a/homeassistant/components/skybell/camera.py b/homeassistant/components/skybell/camera.py index 3ad95e40d62..c22489aa654 100644 --- a/homeassistant/components/skybell/camera.py +++ b/homeassistant/components/skybell/camera.py @@ -1,9 +1,4 @@ -""" -Camera support for the Skybell HD Doorbell. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/camera.skybell/ -""" +"""Camera support for the Skybell HD Doorbell.""" from datetime import timedelta import logging diff --git a/homeassistant/components/skybell/light.py b/homeassistant/components/skybell/light.py index ecb240f2ef3..02be279f609 100644 --- a/homeassistant/components/skybell/light.py +++ b/homeassistant/components/skybell/light.py @@ -1,9 +1,4 @@ -""" -Light/LED support for the Skybell HD Doorbell. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.skybell/ -""" +"""Light/LED support for the Skybell HD Doorbell.""" import logging diff --git a/homeassistant/components/skybell/sensor.py b/homeassistant/components/skybell/sensor.py index de8c3a5694d..89841ae74ef 100644 --- a/homeassistant/components/skybell/sensor.py +++ b/homeassistant/components/skybell/sensor.py @@ -1,9 +1,4 @@ -""" -Sensor support for Skybell Doorbells. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.skybell/ -""" +"""Sensor support for Skybell Doorbells.""" from datetime import timedelta import logging diff --git a/homeassistant/components/skybell/switch.py b/homeassistant/components/skybell/switch.py index 9d791aa3df3..32f1d7f9392 100644 --- a/homeassistant/components/skybell/switch.py +++ b/homeassistant/components/skybell/switch.py @@ -1,9 +1,4 @@ -""" -Switch support for the Skybell HD Doorbell. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.skybell/ -""" +"""Switch support for the Skybell HD Doorbell.""" import logging import voluptuous as vol @@ -53,8 +48,8 @@ class SkybellSwitch(SkybellDevice, SwitchDevice): """Initialize a light for a Skybell device.""" super().__init__(device) self._switch_type = switch_type - self._name = "{0} {1}".format(self._device.name, - SWITCH_TYPES[self._switch_type][0]) + self._name = "{0} {1}".format( + self._device.name, SWITCH_TYPES[self._switch_type][0]) @property def name(self): diff --git a/homeassistant/components/sleepiq/__init__.py b/homeassistant/components/sleepiq/__init__.py index 4d4ecf0160b..7a23c6c4609 100644 --- a/homeassistant/components/sleepiq/__init__.py +++ b/homeassistant/components/sleepiq/__init__.py @@ -1,9 +1,4 @@ -""" -Support for SleepIQ from SleepNumber. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sleepiq/ -""" +"""Support for SleepIQ from SleepNumber.""" import logging from datetime import timedelta from requests.exceptions import HTTPError diff --git a/homeassistant/components/smappee/__init__.py b/homeassistant/components/smappee/__init__.py index d8b7a68a506..7a495d7b89a 100644 --- a/homeassistant/components/smappee/__init__.py +++ b/homeassistant/components/smappee/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Smappee energy monitor. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/smappee/ -""" +"""Support for Smappee energy monitor.""" import logging from datetime import datetime, timedelta import re diff --git a/homeassistant/components/smappee/sensor.py b/homeassistant/components/smappee/sensor.py index 65f21815960..67213ab15bf 100644 --- a/homeassistant/components/smappee/sensor.py +++ b/homeassistant/components/smappee/sensor.py @@ -1,9 +1,4 @@ -""" -Support for monitoring a Smappee energy sensor. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.smappee/ -""" +"""Support for monitoring a Smappee energy sensor.""" import logging from datetime import timedelta diff --git a/homeassistant/components/smappee/switch.py b/homeassistant/components/smappee/switch.py index fc2716b8019..3b9bee081f7 100644 --- a/homeassistant/components/smappee/switch.py +++ b/homeassistant/components/smappee/switch.py @@ -1,9 +1,4 @@ -""" -Support for interacting with Smappee Comport Plugs. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/switch.smappee/ -""" +"""Support for interacting with Smappee Comport Plugs.""" import logging from homeassistant.components.smappee import DATA_SMAPPEE @@ -26,17 +21,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for items in smappee.info[location_id].get('actuators'): if items.get('name') != '': _LOGGER.debug("Remote actuator %s", items) - dev.append(SmappeeSwitch(smappee, - items.get('name'), - location_id, - items.get('id'))) + dev.append(SmappeeSwitch( + smappee, items.get('name'), location_id, + items.get('id'))) elif smappee.is_local_active: for items in smappee.local_devices: _LOGGER.debug("Local actuator %s", items) - dev.append(SmappeeSwitch(smappee, - items.get('value'), - None, - items.get('key'))) + dev.append(SmappeeSwitch( + smappee, items.get('value'), None, items.get('key'))) add_entities(dev) diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index 0d86289e11c..04da29aa55e 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -1,5 +1,4 @@ -"""SmartThings Cloud integration for Home Assistant.""" - +"""Support for SmartThings Cloud.""" import asyncio import logging from typing import Iterable diff --git a/homeassistant/components/smartthings/binary_sensor.py b/homeassistant/components/smartthings/binary_sensor.py index 045944ccfa9..2fbb6f719da 100644 --- a/homeassistant/components/smartthings/binary_sensor.py +++ b/homeassistant/components/smartthings/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for binary sensors through the SmartThings cloud API. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/smartthings.binary_sensor/ -""" +"""Support for binary sensors through the SmartThings cloud API.""" from homeassistant.components.binary_sensor import BinarySensorDevice from . import SmartThingsEntity @@ -20,7 +15,7 @@ CAPABILITY_TO_ATTRIB = { 'soundSensor': 'sound', 'tamperAlert': 'tamper', 'valve': 'valve', - 'waterSensor': 'water' + 'waterSensor': 'water', } ATTRIB_TO_CLASS = { 'acceleration': 'moving', @@ -31,7 +26,7 @@ ATTRIB_TO_CLASS = { 'sound': 'sound', 'tamper': 'problem', 'valve': 'opening', - 'water': 'moisture' + 'water': 'moisture', } diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py index 5a79270307c..9340bcef337 100644 --- a/homeassistant/components/smartthings/climate.py +++ b/homeassistant/components/smartthings/climate.py @@ -1,9 +1,4 @@ -""" -Support for climate entities/thermostats through the SmartThings cloud API. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/smartthings.climate/ -""" +"""Support for climate devices through the SmartThings cloud API.""" import asyncio from homeassistant.components.climate import ( diff --git a/homeassistant/components/smartthings/fan.py b/homeassistant/components/smartthings/fan.py index 7862736e60b..4de1744c9b8 100644 --- a/homeassistant/components/smartthings/fan.py +++ b/homeassistant/components/smartthings/fan.py @@ -1,10 +1,4 @@ -""" -Support for fans through the SmartThings cloud API. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/smartthings.fan/ -""" - +"""Support for fans through the SmartThings cloud API.""" from homeassistant.components.fan import ( SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, SUPPORT_SET_SPEED, FanEntity) diff --git a/homeassistant/components/smartthings/light.py b/homeassistant/components/smartthings/light.py index 8495be62a73..ce4b00ca1fe 100644 --- a/homeassistant/components/smartthings/light.py +++ b/homeassistant/components/smartthings/light.py @@ -1,9 +1,4 @@ -""" -Support for lights through the SmartThings cloud API. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/smartthings.light/ -""" +"""Support for lights through the SmartThings cloud API.""" import asyncio from homeassistant.components.light import ( diff --git a/homeassistant/components/smartthings/sensor.py b/homeassistant/components/smartthings/sensor.py index 5539703e77e..eb83334c6b3 100644 --- a/homeassistant/components/smartthings/sensor.py +++ b/homeassistant/components/smartthings/sensor.py @@ -1,9 +1,4 @@ -""" -Support for sensors through the SmartThings cloud API. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/smartthings.sensor/ -""" +"""Support for sensors through the SmartThings cloud API.""" from collections import namedtuple from homeassistant.const import ( diff --git a/homeassistant/components/smartthings/switch.py b/homeassistant/components/smartthings/switch.py index 1fccfcd3619..08cdb74ed77 100644 --- a/homeassistant/components/smartthings/switch.py +++ b/homeassistant/components/smartthings/switch.py @@ -1,9 +1,4 @@ -""" -Support for switches through the SmartThings cloud API. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/smartthings.switch/ -""" +"""Support for switches through the SmartThings cloud API.""" from homeassistant.components.switch import SwitchDevice from . import SmartThingsEntity @@ -12,8 +7,8 @@ from .const import DATA_BROKERS, DOMAIN DEPENDENCIES = ['smartthings'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Platform uses config entry setup.""" pass diff --git a/homeassistant/components/smhi/__init__.py b/homeassistant/components/smhi/__init__.py index ba7ccc2f682..6af8c14843b 100644 --- a/homeassistant/components/smhi/__init__.py +++ b/homeassistant/components/smhi/__init__.py @@ -1,9 +1,4 @@ -""" -Component for the Swedish weather institute weather service. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/smhi/ -""" +"""Support for the Swedish weather institute weather service.""" from homeassistant.config_entries import ConfigEntry from homeassistant.core import Config, HomeAssistant diff --git a/homeassistant/components/smhi/weather.py b/homeassistant/components/smhi/weather.py index 79fd1166a4b..75a0c51d010 100644 --- a/homeassistant/components/smhi/weather.py +++ b/homeassistant/components/smhi/weather.py @@ -1,9 +1,4 @@ -""" -Support for the Swedish weather institute weather service. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/weather.smhi/ -""" +"""Support for the Swedish weather institute weather service.""" import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/snips/__init__.py b/homeassistant/components/snips/__init__.py index 1aebeae59cb..9a5508c8f32 100644 --- a/homeassistant/components/snips/__init__.py +++ b/homeassistant/components/snips/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Snips on-device ASR and NLU. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/snips/ -""" +"""Support for Snips on-device ASR and NLU.""" import json import logging from datetime import timedelta diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index b4f507a60dd..69d5a9bfc33 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -1,4 +1,4 @@ -"""Component to embed Sonos.""" +"""Support to embed Sonos.""" from homeassistant import config_entries from homeassistant.helpers import config_entry_flow diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 2106e46cb5d..2a7eafaf835 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -1,9 +1,4 @@ -""" -Support to interface with Sonos players. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/media_player.sonos/ -""" +"""Support to interface with Sonos players.""" import datetime import functools as ft import logging diff --git a/homeassistant/components/spaceapi/__init__.py b/homeassistant/components/spaceapi/__init__.py index fa2e5e8e1ea..fb76718f2d5 100644 --- a/homeassistant/components/spaceapi/__init__.py +++ b/homeassistant/components/spaceapi/__init__.py @@ -1,9 +1,4 @@ -""" -Support for the SpaceAPI. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/spaceapi/ -""" +"""Support for the SpaceAPI.""" import logging import voluptuous as vol diff --git a/homeassistant/components/spc/__init__.py b/homeassistant/components/spc/__init__.py index dd1931e27f1..8aafb6f1210 100644 --- a/homeassistant/components/spc/__init__.py +++ b/homeassistant/components/spc/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Vanderbilt (formerly Siemens) SPC alarm systems. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/spc/ -""" +"""Support for Vanderbilt (formerly Siemens) SPC alarm systems.""" import logging import voluptuous as vol diff --git a/homeassistant/components/speedtestdotnet/__init__.py b/homeassistant/components/speedtestdotnet/__init__.py index ce6f376d1b0..3b8d2964f83 100644 --- a/homeassistant/components/speedtestdotnet/__init__.py +++ b/homeassistant/components/speedtestdotnet/__init__.py @@ -1,9 +1,4 @@ -""" -Support for testing internet speed via Speedtest.net. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/speedtestdotnet/ -""" +"""Support for testing internet speed via Speedtest.net.""" import logging from datetime import timedelta diff --git a/homeassistant/components/speedtestdotnet/sensor.py b/homeassistant/components/speedtestdotnet/sensor.py index 33557ddacab..4deb6550444 100644 --- a/homeassistant/components/speedtestdotnet/sensor.py +++ b/homeassistant/components/speedtestdotnet/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Speedtest.net internet speed testing sensor. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.speedtestdotnet/ -""" +"""Support for Speedtest.net internet speed testing sensor.""" import logging from homeassistant.components.speedtestdotnet.const import \ @@ -30,8 +25,8 @@ ATTRIBUTION = 'Data retrieved from Speedtest.net by Ookla' ICON = 'mdi:speedometer' -async def async_setup_platform(hass, config, async_add_entities, - discovery_info): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info): """Set up the Speedtest.net sensor.""" data = hass.data[SPEEDTESTDOTNET_DOMAIN] async_add_entities( diff --git a/homeassistant/components/spider/__init__.py b/homeassistant/components/spider/__init__.py index 5b991e0d3e2..b565f183457 100644 --- a/homeassistant/components/spider/__init__.py +++ b/homeassistant/components/spider/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Spider Smart devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/spider/ -""" +"""Support for Spider Smart devices.""" from datetime import timedelta import logging diff --git a/homeassistant/components/spider/climate.py b/homeassistant/components/spider/climate.py index cfef50e8255..08af44ad1ad 100644 --- a/homeassistant/components/spider/climate.py +++ b/homeassistant/components/spider/climate.py @@ -1,9 +1,4 @@ -""" -Support for Spider thermostats. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/climate.spider/ -""" +"""Support for Spider thermostats.""" import logging @@ -23,7 +18,7 @@ FAN_LIST = [ 'High', 'Boost 10', 'Boost 20', - 'Boost 30' + 'Boost 30', ] OPERATION_LIST = [ @@ -34,7 +29,7 @@ OPERATION_LIST = [ HA_STATE_TO_SPIDER = { STATE_COOL: 'Cool', STATE_HEAT: 'Heat', - STATE_IDLE: 'Idle' + STATE_IDLE: 'Idle', } SPIDER_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_SPIDER.items()} diff --git a/homeassistant/components/spider/switch.py b/homeassistant/components/spider/switch.py index f4bf74ad010..227e4748515 100644 --- a/homeassistant/components/spider/switch.py +++ b/homeassistant/components/spider/switch.py @@ -1,10 +1,4 @@ -""" -Support for Spider switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.spider/ -""" - +"""Support for Spider switches.""" import logging from homeassistant.components.spider import DOMAIN as SPIDER_DOMAIN diff --git a/homeassistant/components/splunk/__init__.py b/homeassistant/components/splunk/__init__.py index 37f56ed3b1c..fed05fe3498 100644 --- a/homeassistant/components/splunk/__init__.py +++ b/homeassistant/components/splunk/__init__.py @@ -1,9 +1,4 @@ -""" -Support to send data to an Splunk instance. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/splunk/ -""" +"""Support to send data to an Splunk instance.""" import json import logging diff --git a/homeassistant/components/statsd/__init__.py b/homeassistant/components/statsd/__init__.py index 6b528733601..a8c34d0a843 100644 --- a/homeassistant/components/statsd/__init__.py +++ b/homeassistant/components/statsd/__init__.py @@ -1,9 +1,4 @@ -""" -A component which allows you to send data to StatsD. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/statsd/ -""" +"""Support for sending data to StatsD.""" import logging import voluptuous as vol diff --git a/homeassistant/components/sun/__init__.py b/homeassistant/components/sun/__init__.py index 250c6a2ed2f..92cdcb0a2e4 100644 --- a/homeassistant/components/sun/__init__.py +++ b/homeassistant/components/sun/__init__.py @@ -1,9 +1,4 @@ -""" -Support for functionality to keep track of the sun. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/sun/ -""" +"""Support for functionality to keep track of the sun.""" import logging from datetime import timedelta diff --git a/homeassistant/components/system_health/__init__.py b/homeassistant/components/system_health/__init__.py index 05e5b76fb5b..9a171296ce9 100644 --- a/homeassistant/components/system_health/__init__.py +++ b/homeassistant/components/system_health/__init__.py @@ -1,9 +1,4 @@ -""" -System health component. - -For more details about this component, please refer to the documentation at -https://www.home-assistant.io/components/system_health/ -""" +"""Support for System health .""" import asyncio from collections import OrderedDict import logging diff --git a/homeassistant/components/system_log/__init__.py b/homeassistant/components/system_log/__init__.py index c59aee56a51..9e968111c9c 100644 --- a/homeassistant/components/system_log/__init__.py +++ b/homeassistant/components/system_log/__init__.py @@ -1,9 +1,4 @@ -""" -Support for system log. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/system_log/ -""" +"""Support for system log.""" from collections import OrderedDict import logging import re diff --git a/homeassistant/components/tado/__init__.py b/homeassistant/components/tado/__init__.py index 7c045518132..767e29ba0b9 100644 --- a/homeassistant/components/tado/__init__.py +++ b/homeassistant/components/tado/__init__.py @@ -1,9 +1,4 @@ -""" -Support for the (unofficial) Tado api. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/tado/ -""" +"""Support for the (unofficial) Tado API.""" import logging import urllib from datetime import timedelta @@ -31,7 +26,7 @@ TADO_COMPONENTS = [ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string + vol.Required(CONF_PASSWORD): cv.string, }) }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/tado/climate.py b/homeassistant/components/tado/climate.py index 1e52c163624..1812d36b7cd 100644 --- a/homeassistant/components/tado/climate.py +++ b/homeassistant/components/tado/climate.py @@ -1,9 +1,4 @@ -""" -Tado component to create a climate device for each zone. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/climate.tado/ -""" +"""Support for Tado to create a climate device for each zone.""" import logging from homeassistant.const import (PRECISION_TENTHS, TEMP_CELSIUS) diff --git a/homeassistant/components/tado/device_tracker.py b/homeassistant/components/tado/device_tracker.py index ef816338ce9..7812bbd812b 100644 --- a/homeassistant/components/tado/device_tracker.py +++ b/homeassistant/components/tado/device_tracker.py @@ -1,9 +1,4 @@ -""" -Support for Tado Smart Thermostat. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/device_tracker.tado/ -""" +"""Support for Tado Smart device trackers.""" import logging from datetime import timedelta from collections import namedtuple @@ -29,7 +24,7 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=30) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_HOME_ID): cv.string + vol.Optional(CONF_HOME_ID): cv.string, }) diff --git a/homeassistant/components/tado/sensor.py b/homeassistant/components/tado/sensor.py index 46ad6206fff..a1eb918ac5d 100644 --- a/homeassistant/components/tado/sensor.py +++ b/homeassistant/components/tado/sensor.py @@ -1,9 +1,4 @@ -""" -Tado component to create some sensors for each zone. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.tado/ -""" +"""Support for Tado sensors for each zone.""" import logging from homeassistant.components.tado import DATA_TADO diff --git a/homeassistant/components/tahoma/__init__.py b/homeassistant/components/tahoma/__init__.py index 5e30b845863..e76cadc7ce3 100644 --- a/homeassistant/components/tahoma/__init__.py +++ b/homeassistant/components/tahoma/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Tahoma devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/tahoma/ -""" +"""Support for Tahoma devices.""" from collections import defaultdict import logging import voluptuous as vol diff --git a/homeassistant/components/tahoma/binary_sensor.py b/homeassistant/components/tahoma/binary_sensor.py index 73035a2da0d..69855f7cb57 100644 --- a/homeassistant/components/tahoma/binary_sensor.py +++ b/homeassistant/components/tahoma/binary_sensor.py @@ -1,10 +1,4 @@ -""" -Support for Tahoma binary sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.tahoma/ -""" - +"""Support for Tahoma binary sensors.""" import logging from datetime import timedelta diff --git a/homeassistant/components/tahoma/cover.py b/homeassistant/components/tahoma/cover.py index baf32073c44..6dbf9a39807 100644 --- a/homeassistant/components/tahoma/cover.py +++ b/homeassistant/components/tahoma/cover.py @@ -1,9 +1,4 @@ -""" -Support for Tahoma cover - shutters etc. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/cover.tahoma/ -""" +"""Support for Tahoma cover - shutters etc.""" from datetime import timedelta import logging diff --git a/homeassistant/components/tahoma/scene.py b/homeassistant/components/tahoma/scene.py index 5846d97c7f9..643cc65aa19 100644 --- a/homeassistant/components/tahoma/scene.py +++ b/homeassistant/components/tahoma/scene.py @@ -1,9 +1,4 @@ -""" -Support for Tahoma scenes. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/scene.tahoma/ -""" +"""Support for Tahoma scenes.""" import logging from homeassistant.components.scene import Scene diff --git a/homeassistant/components/tahoma/sensor.py b/homeassistant/components/tahoma/sensor.py index 5918bd7c9f8..8a2ea976ba7 100644 --- a/homeassistant/components/tahoma/sensor.py +++ b/homeassistant/components/tahoma/sensor.py @@ -1,10 +1,4 @@ -""" -Support for Tahoma sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.tahoma/ -""" - +"""Support for Tahoma sensors.""" import logging from datetime import timedelta diff --git a/homeassistant/components/tahoma/switch.py b/homeassistant/components/tahoma/switch.py index bcac038d43b..779bff9ce0d 100644 --- a/homeassistant/components/tahoma/switch.py +++ b/homeassistant/components/tahoma/switch.py @@ -1,12 +1,4 @@ -""" -Support for Tahoma Switch - those are push buttons for garage door etc. - -Those buttons are implemented as switches that are never on. They only -receive the turn_on action, perform the relay click, and stay in OFF state - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.tahoma/ -""" +"""Support for Tahoma switches.""" import logging from homeassistant.components.switch import SwitchDevice diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index 28bc7a1ad0d..18f206541df 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -1,9 +1,4 @@ -""" -Component to send and receive Telegram messages. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/telegram_bot/ -""" +"""Support to send and receive Telegram messages.""" import io from functools import partial import logging diff --git a/homeassistant/components/telegram_bot/broadcast.py b/homeassistant/components/telegram_bot/broadcast.py index 7cfcc272a33..eb52ab496d7 100644 --- a/homeassistant/components/telegram_bot/broadcast.py +++ b/homeassistant/components/telegram_bot/broadcast.py @@ -1,9 +1,4 @@ -""" -Telegram bot implementation to send messages only. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/telegram_bot.broadcast/ -""" +"""Support for Telegram bot to send messages only.""" import logging from homeassistant.components.telegram_bot import ( @@ -17,8 +12,6 @@ PLATFORM_SCHEMA = TELEGRAM_PLATFORM_SCHEMA async def async_setup_platform(hass, config): """Set up the Telegram broadcast platform.""" - # Check the API key works - bot = initialize_bot(config) bot_config = await hass.async_add_job(bot.getMe) diff --git a/homeassistant/components/telegram_bot/polling.py b/homeassistant/components/telegram_bot/polling.py index d1dea051985..5bca4321a5f 100644 --- a/homeassistant/components/telegram_bot/polling.py +++ b/homeassistant/components/telegram_bot/polling.py @@ -1,9 +1,4 @@ -""" -Telegram bot polling implementation. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/telegram_bot.polling/ -""" +"""Support for Telegram bot using polling.""" import logging from homeassistant.components.telegram_bot import ( diff --git a/homeassistant/components/telegram_bot/webhooks.py b/homeassistant/components/telegram_bot/webhooks.py index 5406ba60b13..41a206944e7 100644 --- a/homeassistant/components/telegram_bot/webhooks.py +++ b/homeassistant/components/telegram_bot/webhooks.py @@ -1,9 +1,4 @@ -""" -Allows utilizing telegram webhooks. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/telegram_bot.webhooks/ -""" +"""Support for Telegram bots using webhooks.""" import datetime as dt from ipaddress import ip_network import logging diff --git a/homeassistant/components/tellduslive/__init__.py b/homeassistant/components/tellduslive/__init__.py index 2a57a78ee9e..1a6f35fe8d8 100644 --- a/homeassistant/components/tellduslive/__init__.py +++ b/homeassistant/components/tellduslive/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Telldus Live. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/tellduslive/ -""" +"""Support for Telldus Live.""" import asyncio from functools import partial import logging @@ -32,8 +27,7 @@ CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema({ - vol.Optional(CONF_HOST, default=DOMAIN): - cv.string, + vol.Optional(CONF_HOST, default=DOMAIN): cv.string, vol.Optional(CONF_UPDATE_INTERVAL, default=SCAN_INTERVAL): (vol.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL))) }), diff --git a/homeassistant/components/tellduslive/binary_sensor.py b/homeassistant/components/tellduslive/binary_sensor.py index f6ed85db132..85faeca96d4 100644 --- a/homeassistant/components/tellduslive/binary_sensor.py +++ b/homeassistant/components/tellduslive/binary_sensor.py @@ -1,12 +1,4 @@ -""" -Support for binary sensors using Tellstick Net. - -This platform uses the Telldus Live online service. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.tellduslive/ - -""" +"""Support for binary sensors using Tellstick Net.""" import logging from homeassistant.components import binary_sensor, tellduslive @@ -35,8 +27,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_dispatcher_connect( hass, - tellduslive.TELLDUS_DISCOVERY_NEW.format(binary_sensor.DOMAIN, - tellduslive.DOMAIN), + tellduslive.TELLDUS_DISCOVERY_NEW.format( + binary_sensor.DOMAIN, tellduslive.DOMAIN), async_discover_binary_sensor) diff --git a/homeassistant/components/tellduslive/cover.py b/homeassistant/components/tellduslive/cover.py index 1879c88c83c..5a22311d7f0 100644 --- a/homeassistant/components/tellduslive/cover.py +++ b/homeassistant/components/tellduslive/cover.py @@ -1,11 +1,4 @@ -""" -Support for Tellstick covers using Tellstick Net. - -This platform uses the Telldus Live online service. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/cover.tellduslive/ -""" +"""Support for Tellstick covers using Tellstick Net.""" import logging from homeassistant.components import cover, tellduslive diff --git a/homeassistant/components/tellduslive/entry.py b/homeassistant/components/tellduslive/entry.py index d6e56329699..9255f9da645 100644 --- a/homeassistant/components/tellduslive/entry.py +++ b/homeassistant/components/tellduslive/entry.py @@ -1,4 +1,4 @@ -"""Base Entity for all TelldusLiveEntities.""" +"""Base Entity for all TelldusLive entities.""" from datetime import datetime import logging diff --git a/homeassistant/components/tellduslive/light.py b/homeassistant/components/tellduslive/light.py index 3f14b34ea78..10eaee1ad8b 100644 --- a/homeassistant/components/tellduslive/light.py +++ b/homeassistant/components/tellduslive/light.py @@ -1,11 +1,4 @@ -""" -Support for Tellstick switches using Tellstick Net. - -This platform uses the Telldus Live online service. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.tellduslive/ -""" +"""Support for Tellstick lights using Tellstick Net.""" import logging from homeassistant.components import light, tellduslive diff --git a/homeassistant/components/tellduslive/sensor.py b/homeassistant/components/tellduslive/sensor.py index f024f62109b..48133fd69e6 100644 --- a/homeassistant/components/tellduslive/sensor.py +++ b/homeassistant/components/tellduslive/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Tellstick Net/Telstick Live. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.tellduslive/ -""" +"""Support for Tellstick Net/Telstick Live sensors.""" import logging from homeassistant.components import sensor, tellduslive diff --git a/homeassistant/components/tellduslive/switch.py b/homeassistant/components/tellduslive/switch.py index 5c04e872623..63d1512698c 100644 --- a/homeassistant/components/tellduslive/switch.py +++ b/homeassistant/components/tellduslive/switch.py @@ -1,12 +1,4 @@ -""" -Support for Tellstick switches using Tellstick Net. - -This platform uses the Telldus Live online service. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.tellduslive/ - -""" +"""Support for Tellstick switches using Tellstick Net.""" import logging from homeassistant.components import switch, tellduslive diff --git a/homeassistant/components/tellstick/__init__.py b/homeassistant/components/tellstick/__init__.py index 8f1c45d7312..c35d2f79027 100644 --- a/homeassistant/components/tellstick/__init__.py +++ b/homeassistant/components/tellstick/__init__.py @@ -1,9 +1,4 @@ -""" -Tellstick Component. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/tellstick/ -""" +"""Support for Tellstick.""" import logging import threading diff --git a/homeassistant/components/tellstick/cover.py b/homeassistant/components/tellstick/cover.py index 88608ac42e9..d0c9c031435 100644 --- a/homeassistant/components/tellstick/cover.py +++ b/homeassistant/components/tellstick/cover.py @@ -1,11 +1,4 @@ -""" -Support for Tellstick covers. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/cover.tellstick/ -""" - - +"""Support for Tellstick covers.""" from homeassistant.components.cover import CoverDevice from homeassistant.components.tellstick import ( DEFAULT_SIGNAL_REPETITIONS, ATTR_DISCOVER_DEVICES, ATTR_DISCOVER_CONFIG, diff --git a/homeassistant/components/tellstick/light.py b/homeassistant/components/tellstick/light.py index cf9dd545e99..5deee5e08a6 100644 --- a/homeassistant/components/tellstick/light.py +++ b/homeassistant/components/tellstick/light.py @@ -1,10 +1,4 @@ -""" -Support for Tellstick lights. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.tellstick/ -""" - +"""Support for Tellstick lights.""" from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) from homeassistant.components.tellstick import ( diff --git a/homeassistant/components/tellstick/sensor.py b/homeassistant/components/tellstick/sensor.py index aac97580f2c..c6d281772a5 100644 --- a/homeassistant/components/tellstick/sensor.py +++ b/homeassistant/components/tellstick/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Tellstick sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.tellstick/ -""" +"""Support for Tellstick sensors.""" import logging from collections import namedtuple diff --git a/homeassistant/components/tellstick/switch.py b/homeassistant/components/tellstick/switch.py index 51a04e9f5b3..56d563e494c 100644 --- a/homeassistant/components/tellstick/switch.py +++ b/homeassistant/components/tellstick/switch.py @@ -1,9 +1,4 @@ -""" -Support for Tellstick switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.tellstick/ -""" +"""Support for Tellstick switches.""" from homeassistant.components.tellstick import ( DEFAULT_SIGNAL_REPETITIONS, ATTR_DISCOVER_DEVICES, ATTR_DISCOVER_CONFIG, DATA_TELLSTICK, TellstickDevice) diff --git a/homeassistant/components/tesla/__init__.py b/homeassistant/components/tesla/__init__.py index 76b5c00d9d4..fc433ae18b1 100644 --- a/homeassistant/components/tesla/__init__.py +++ b/homeassistant/components/tesla/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Tesla cars. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/tesla/ -""" +"""Support for Tesla cars.""" from collections import defaultdict import logging diff --git a/homeassistant/components/tesla/binary_sensor.py b/homeassistant/components/tesla/binary_sensor.py index f7613d74dfb..2c037140f0a 100644 --- a/homeassistant/components/tesla/binary_sensor.py +++ b/homeassistant/components/tesla/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for Tesla binary sensor. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.tesla/ -""" +"""Support for Tesla binary sensor.""" import logging from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/tesla/climate.py b/homeassistant/components/tesla/climate.py index ef5f2227c11..302c0006bcf 100644 --- a/homeassistant/components/tesla/climate.py +++ b/homeassistant/components/tesla/climate.py @@ -1,9 +1,4 @@ -""" -Support for Tesla HVAC system. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/climate.tesla/ -""" +"""Support for Tesla HVAC system.""" import logging from homeassistant.components.climate import ( diff --git a/homeassistant/components/tesla/device_tracker.py b/homeassistant/components/tesla/device_tracker.py index c08ddb4047b..0aeab5b1c7d 100644 --- a/homeassistant/components/tesla/device_tracker.py +++ b/homeassistant/components/tesla/device_tracker.py @@ -1,9 +1,4 @@ -""" -Support for the Tesla platform. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/device_tracker.tesla/ -""" +"""Support for tracking Tesla cars.""" import logging from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN diff --git a/homeassistant/components/tesla/lock.py b/homeassistant/components/tesla/lock.py index 2ffb996aec3..34d660ac83c 100644 --- a/homeassistant/components/tesla/lock.py +++ b/homeassistant/components/tesla/lock.py @@ -1,9 +1,4 @@ -""" -Support for Tesla door locks. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/lock.tesla/ -""" +"""Support for Tesla door locks.""" import logging from homeassistant.components.lock import ENTITY_ID_FORMAT, LockDevice diff --git a/homeassistant/components/tesla/sensor.py b/homeassistant/components/tesla/sensor.py index 51b7ea2325d..1d4505ed9a4 100644 --- a/homeassistant/components/tesla/sensor.py +++ b/homeassistant/components/tesla/sensor.py @@ -1,9 +1,4 @@ -""" -Sensors for the Tesla sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.tesla/ -""" +"""Support for the Tesla sensors.""" from datetime import timedelta import logging diff --git a/homeassistant/components/tesla/switch.py b/homeassistant/components/tesla/switch.py index 30972b1014b..a1787e9993e 100644 --- a/homeassistant/components/tesla/switch.py +++ b/homeassistant/components/tesla/switch.py @@ -1,9 +1,4 @@ -""" -Support for Tesla charger switch. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.tesla/ -""" +"""Support for Tesla charger switches.""" import logging from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice diff --git a/homeassistant/components/thethingsnetwork/__init__.py b/homeassistant/components/thethingsnetwork/__init__.py index 61f9843be45..952755d289e 100644 --- a/homeassistant/components/thethingsnetwork/__init__.py +++ b/homeassistant/components/thethingsnetwork/__init__.py @@ -1,9 +1,4 @@ -""" -Support for The Things network. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/thethingsnetwork/ -""" +"""Support for The Things network.""" import logging import voluptuous as vol diff --git a/homeassistant/components/thethingsnetwork/sensor.py b/homeassistant/components/thethingsnetwork/sensor.py index 13b51d505c3..05da90bf7ac 100644 --- a/homeassistant/components/thethingsnetwork/sensor.py +++ b/homeassistant/components/thethingsnetwork/sensor.py @@ -1,9 +1,4 @@ -""" -Support for The Things Network's Data storage integration. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/sensor.thethingsnetwork_data/ -""" +"""Support for The Things Network's Data storage integration.""" import asyncio import logging @@ -38,8 +33,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up The Things Network Data storage sensors.""" ttn = hass.data.get(DATA_TTN) device_id = config.get(CONF_DEVICE_ID) diff --git a/homeassistant/components/thingspeak/__init__.py b/homeassistant/components/thingspeak/__init__.py index 9a876a87683..0fa15e7efb4 100644 --- a/homeassistant/components/thingspeak/__init__.py +++ b/homeassistant/components/thingspeak/__init__.py @@ -1,9 +1,4 @@ -""" -A component to submit data to thingspeak. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/thingspeak/ -""" +"""Support for submitting data to Thingspeak.""" import logging from requests.exceptions import RequestException @@ -26,8 +21,8 @@ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_API_KEY): cv.string, vol.Required(CONF_ID): int, - vol.Required(CONF_WHITELIST): cv.string - }), + vol.Required(CONF_WHITELIST): cv.string, + }), }, extra=vol.ALLOW_EXTRA) @@ -47,7 +42,7 @@ def setup(hass, config): except RequestException: _LOGGER.error("Error while accessing the ThingSpeak channel. " "Please check that the channel exists and your " - "API key is correct.") + "API key is correct") return False def thingspeak_listener(entity_id, old_state, new_state): diff --git a/homeassistant/components/thinkingcleaner/__init__.py b/homeassistant/components/thinkingcleaner/__init__.py index 5358060ea8a..a72cda45fd5 100644 --- a/homeassistant/components/thinkingcleaner/__init__.py +++ b/homeassistant/components/thinkingcleaner/__init__.py @@ -1 +1 @@ -"""Thinkingcleaner integration.""" +"""Support for Thinkingcleaner devices.""" diff --git a/homeassistant/components/thinkingcleaner/sensor.py b/homeassistant/components/thinkingcleaner/sensor.py index 17e2f717f5a..f8462435a45 100644 --- a/homeassistant/components/thinkingcleaner/sensor.py +++ b/homeassistant/components/thinkingcleaner/sensor.py @@ -1,9 +1,4 @@ -""" -Support for ThinkingCleaner. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.thinkingcleaner/ -""" +"""Support for ThinkingCleaner sensors.""" import logging from datetime import timedelta diff --git a/homeassistant/components/thinkingcleaner/switch.py b/homeassistant/components/thinkingcleaner/switch.py index 89586465b43..38a96eb0298 100644 --- a/homeassistant/components/thinkingcleaner/switch.py +++ b/homeassistant/components/thinkingcleaner/switch.py @@ -1,9 +1,4 @@ -""" -Support for ThinkingCleaner. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.thinkingcleaner/ -""" +"""Support for ThinkingCleaner switches.""" import time import logging from datetime import timedelta @@ -45,8 +40,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): dev = [] for device in devices: for type_name in SWITCH_TYPES: - dev.append(ThinkingCleanerSwitch(device, type_name, - update_devices)) + dev.append(ThinkingCleanerSwitch( + device, type_name, update_devices)) add_entities(dev) diff --git a/homeassistant/components/tibber/__init__.py b/homeassistant/components/tibber/__init__.py index c2d1daa584c..ba9ae43f13b 100644 --- a/homeassistant/components/tibber/__init__.py +++ b/homeassistant/components/tibber/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Tibber. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/tibber/ -""" +"""Support for Tibber.""" import asyncio import logging diff --git a/homeassistant/components/tibber/notify.py b/homeassistant/components/tibber/notify.py index ddbcb3f6c65..6ae22c34209 100644 --- a/homeassistant/components/tibber/notify.py +++ b/homeassistant/components/tibber/notify.py @@ -1,9 +1,4 @@ -""" -Tibber platform for notify component. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/notify.tibber/ -""" +"""Support for Tibber notifications.""" import asyncio import logging @@ -11,7 +6,6 @@ from homeassistant.components.notify import ( ATTR_TITLE, ATTR_TITLE_DEFAULT, BaseNotificationService) from homeassistant.components.tibber import DOMAIN as TIBBER_DOMAIN - _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index 1c3ef601633..f3e0c39a1e6 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Tibber. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.tibber/ -""" +"""Support for Tibber sensors.""" import asyncio import logging @@ -25,8 +20,8 @@ SCAN_INTERVAL = timedelta(minutes=1) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the Tibber sensor.""" if discovery_info is None: return diff --git a/homeassistant/components/timer/__init__.py b/homeassistant/components/timer/__init__.py index a4809369d9b..04d9acc06af 100644 --- a/homeassistant/components/timer/__init__.py +++ b/homeassistant/components/timer/__init__.py @@ -1,21 +1,15 @@ -""" -Timer component. - -For more details about this component, please refer to the documentation -at https://home-assistant.io/components/timer/ -""" -import logging +"""Support for Timers.""" from datetime import timedelta +import logging import voluptuous as vol -import homeassistant.util.dt as dt_util +from homeassistant.const import ATTR_ENTITY_ID, CONF_ICON, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.const import (ATTR_ENTITY_ID, CONF_ICON, CONF_NAME) from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.restore_state import RestoreEntity - +import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/toon/__init__.py b/homeassistant/components/toon/__init__.py index 01f170f0b31..96d8b4e6d15 100644 --- a/homeassistant/components/toon/__init__.py +++ b/homeassistant/components/toon/__init__.py @@ -1,9 +1,4 @@ -""" -Toon van Eneco Support. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/toon/ -""" +"""Support for Toon van Eneco devices.""" from datetime import datetime, timedelta import logging diff --git a/homeassistant/components/toon/climate.py b/homeassistant/components/toon/climate.py index 022a509ce06..3397e3dacc2 100644 --- a/homeassistant/components/toon/climate.py +++ b/homeassistant/components/toon/climate.py @@ -1,12 +1,4 @@ -""" -Toon van Eneco Thermostat Support. - -This provides a component for the rebranded Quby thermostat as provided by -Eneco. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/climate.toon/ -""" +"""Support for Toon van Eneco Thermostats.""" from homeassistant.components.climate import ( ATTR_TEMPERATURE, STATE_COOL, STATE_ECO, STATE_HEAT, STATE_AUTO, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice) diff --git a/homeassistant/components/toon/sensor.py b/homeassistant/components/toon/sensor.py index fb057603a1a..ebd25e02cde 100644 --- a/homeassistant/components/toon/sensor.py +++ b/homeassistant/components/toon/sensor.py @@ -1,9 +1,4 @@ -""" -Component for the rebranded Quby thermostat as provided by Eneco. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.toon/ -""" +"""Support for rebranded Quby thermostat as provided by Eneco.""" import logging import datetime diff --git a/homeassistant/components/toon/switch.py b/homeassistant/components/toon/switch.py index 087ca673e85..08ccec588b4 100644 --- a/homeassistant/components/toon/switch.py +++ b/homeassistant/components/toon/switch.py @@ -1,9 +1,4 @@ -""" -Support for Eneco Slimmer stekkers (Smart Plugs). - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.toon/ -""" +"""Support for Eneco Slimmer stekkers (Smart Plugs).""" import logging from homeassistant.components.switch import SwitchDevice diff --git a/homeassistant/components/tplink_lte/__init__.py b/homeassistant/components/tplink_lte/__init__.py index 3f33cbe9fb3..d0f6e600a0d 100644 --- a/homeassistant/components/tplink_lte/__init__.py +++ b/homeassistant/components/tplink_lte/__init__.py @@ -1,9 +1,4 @@ -""" -Support for TP-Link LTE modems. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/tplink_lte/ -""" +"""Support for TP-Link LTE modems.""" import asyncio import logging @@ -26,7 +21,7 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = 'tplink_lte' DATA_KEY = 'tplink_lte' -CONF_NOTIFY = "notify" +CONF_NOTIFY = 'notify' # Deprecated in 0.88.0, invalidated in 0.91.0, remove in 0.92.0 ATTR_TARGET_INVALIDATION_VERSION = '0.91.0' @@ -49,8 +44,7 @@ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.All(cv.ensure_list, [vol.Schema({ vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_NOTIFY): - vol.All(cv.ensure_list, [_NOTIFY_SCHEMA]), + vol.Optional(CONF_NOTIFY): vol.All(cv.ensure_list, [_NOTIFY_SCHEMA]), })]) }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/tplink_lte/notify.py b/homeassistant/components/tplink_lte/notify.py index f80345a4362..519641ed34b 100644 --- a/homeassistant/components/tplink_lte/notify.py +++ b/homeassistant/components/tplink_lte/notify.py @@ -1,9 +1,4 @@ -"""TP-Link LTE platform for notify component. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/notify.tplink_lte/ -""" - +"""Support for TP-Link LTE notifications.""" import logging import attr diff --git a/homeassistant/components/tradfri/__init__.py b/homeassistant/components/tradfri/__init__.py index ba13b8d511a..b14bc811754 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -1,9 +1,4 @@ -""" -Support for IKEA Tradfri. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/ikea_tradfri/ -""" +"""Support for IKEA Tradfri.""" import logging import voluptuous as vol @@ -20,6 +15,9 @@ from . import config_flow # noqa pylint_disable=unused-import REQUIREMENTS = ['pytradfri[async]==6.0.1'] +_LOGGER = logging.getLogger(__name__) + + DOMAIN = 'tradfri' CONFIG_FILE = '.tradfri_psk.conf' KEY_GATEWAY = 'tradfri_gateway' @@ -35,8 +33,6 @@ CONFIG_SCHEMA = vol.Schema({ }) }, extra=vol.ALLOW_EXTRA) -_LOGGER = logging.getLogger(__name__) - async def async_setup(hass, config): """Set up the Tradfri component.""" diff --git a/homeassistant/components/tradfri/light.py b/homeassistant/components/tradfri/light.py index 50e92f15e3c..e5e27ecbed1 100644 --- a/homeassistant/components/tradfri/light.py +++ b/homeassistant/components/tradfri/light.py @@ -1,9 +1,4 @@ -""" -Support for the IKEA Tradfri platform. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.tradfri/ -""" +"""Support for IKEA Tradfri lights.""" import logging from homeassistant.core import callback diff --git a/homeassistant/components/tradfri/sensor.py b/homeassistant/components/tradfri/sensor.py index 45167874de2..97c7dc9627d 100644 --- a/homeassistant/components/tradfri/sensor.py +++ b/homeassistant/components/tradfri/sensor.py @@ -1,9 +1,4 @@ -""" -Support for the IKEA Tradfri platform. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.tradfri/ -""" +"""Support for IKEA Tradfri sensors.""" import logging from datetime import timedelta diff --git a/homeassistant/components/tradfri/switch.py b/homeassistant/components/tradfri/switch.py index b247858b062..23e6cb20c8f 100644 --- a/homeassistant/components/tradfri/switch.py +++ b/homeassistant/components/tradfri/switch.py @@ -1,9 +1,4 @@ -""" -Support for the IKEA Tradfri platform. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.tradfri/ -""" +"""Support for IKEA Tradfri switches.""" import logging from homeassistant.core import callback diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index dd10c4ecfdf..25e21dc3d8a 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -1,29 +1,18 @@ -""" -Component for monitoring the Transmission BitTorrent client API. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/transmission/ -""" +"""Support for the Transmission BitTorrent client API.""" from datetime import timedelta - import logging + import voluptuous as vol from homeassistant.const import ( - CONF_HOST, - CONF_MONITORED_CONDITIONS, - CONF_NAME, - CONF_PASSWORD, - CONF_PORT, - CONF_USERNAME, - CONF_SCAN_INTERVAL -) -from homeassistant.helpers import discovery, config_validation as cv + CONF_HOST, CONF_MONITORED_CONDITIONS, CONF_NAME, CONF_PASSWORD, CONF_PORT, + CONF_SCAN_INTERVAL, CONF_USERNAME) +from homeassistant.helpers import config_validation as cv, discovery from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.event import track_time_interval - REQUIREMENTS = ['transmissionrpc==0.11'] + _LOGGER = logging.getLogger(__name__) DOMAIN = 'transmission' diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index cb592a74758..061ed2c0c64 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -1,9 +1,4 @@ -""" -Support for monitoring the Transmission BitTorrent client API. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.transmission/ -""" +"""Support for monitoring the Transmission BitTorrent client API.""" from datetime import timedelta import logging @@ -25,10 +20,7 @@ SCAN_INTERVAL = timedelta(seconds=120) async def async_setup_platform( - hass, - config, - async_add_entities, - discovery_info=None): + hass, config, async_add_entities, discovery_info=None): """Set up the Transmission sensors.""" if discovery_info is None: return @@ -40,11 +32,8 @@ async def async_setup_platform( dev = [] for sensor_type in monitored_variables: dev.append(TransmissionSensor( - sensor_type, - transmission_api, - name, - SENSOR_TYPES[sensor_type][0], - SENSOR_TYPES[sensor_type][1])) + sensor_type, transmission_api, name, + SENSOR_TYPES[sensor_type][0], SENSOR_TYPES[sensor_type][1])) async_add_entities(dev, True) @@ -53,11 +42,7 @@ class TransmissionSensor(Entity): """Representation of a Transmission sensor.""" def __init__( - self, - sensor_type, - transmission_api, - client_name, - sensor_name, + self, sensor_type, transmission_api, client_name, sensor_name, unit_of_measurement): """Initialize the sensor.""" self._name = sensor_name @@ -96,8 +81,7 @@ class TransmissionSensor(Entity): async def async_added_to_hass(self): """Handle entity which will be added.""" async_dispatcher_connect( - self.hass, DATA_UPDATED, self._schedule_immediate_update - ) + self.hass, DATA_UPDATED, self._schedule_immediate_update) @callback def _schedule_immediate_update(self): diff --git a/homeassistant/components/transmission/switch.py b/homeassistant/components/transmission/switch.py index aac946dee8b..373397eddd6 100644 --- a/homeassistant/components/transmission/switch.py +++ b/homeassistant/components/transmission/switch.py @@ -1,9 +1,4 @@ -""" -Support for setting the Transmission BitTorrent client Turtle Mode. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.transmission/ -""" +"""Support for setting the Transmission BitTorrent client Turtle Mode.""" import logging from homeassistant.components.transmission import ( @@ -22,10 +17,7 @@ DEFAULT_NAME = 'Transmission Turtle Mode' async def async_setup_platform( - hass, - config, - async_add_entities, - discovery_info=None): + hass, config, async_add_entities, discovery_info=None): """Set up the Transmission switch.""" if discovery_info is None: return diff --git a/homeassistant/components/tuya/__init__.py b/homeassistant/components/tuya/__init__.py index 22a82dec8e2..117424fd55e 100644 --- a/homeassistant/components/tuya/__init__.py +++ b/homeassistant/components/tuya/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Tuya Smart devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/tuya/ -""" +"""Support for Tuya Smart devices.""" from datetime import timedelta import logging import voluptuous as vol diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index 4548867a45e..97ff18ba911 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -1,10 +1,4 @@ -""" -Support for the Tuya climate devices. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/climate.tuya/ -""" - +"""Support for the Tuya climate devices.""" from homeassistant.components.climate import ( ATTR_TEMPERATURE, ENTITY_ID_FORMAT, STATE_AUTO, STATE_COOL, STATE_ECO, STATE_FAN_ONLY, STATE_HEAT, SUPPORT_FAN_MODE, SUPPORT_ON_OFF, diff --git a/homeassistant/components/tuya/cover.py b/homeassistant/components/tuya/cover.py index a3a3db972e9..ac2309cbf9e 100644 --- a/homeassistant/components/tuya/cover.py +++ b/homeassistant/components/tuya/cover.py @@ -1,9 +1,4 @@ -""" -Support for Tuya cover. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/cover.tuya/ -""" +"""Support for Tuya covers.""" from homeassistant.components.cover import ( CoverDevice, ENTITY_ID_FORMAT, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP) from homeassistant.components.tuya import DATA_TUYA, TuyaDevice diff --git a/homeassistant/components/tuya/fan.py b/homeassistant/components/tuya/fan.py index 9cb7cdc3f2c..b6e2cb6950c 100644 --- a/homeassistant/components/tuya/fan.py +++ b/homeassistant/components/tuya/fan.py @@ -1,10 +1,4 @@ -""" -Support for Tuya fans. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/fan.tuya/ -""" - +"""Support for Tuya fans.""" from homeassistant.components.fan import ( ENTITY_ID_FORMAT, FanEntity, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED) from homeassistant.components.tuya import DATA_TUYA, TuyaDevice diff --git a/homeassistant/components/tuya/light.py b/homeassistant/components/tuya/light.py index 0a1468a6a51..1cf2f811872 100644 --- a/homeassistant/components/tuya/light.py +++ b/homeassistant/components/tuya/light.py @@ -1,9 +1,4 @@ -""" -Support for the Tuya light. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.tuya/ -""" +"""Support for the Tuya lights.""" from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ENTITY_ID_FORMAT, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_COLOR, Light) diff --git a/homeassistant/components/tuya/scene.py b/homeassistant/components/tuya/scene.py index 2e03e5dba9a..33d207d8545 100644 --- a/homeassistant/components/tuya/scene.py +++ b/homeassistant/components/tuya/scene.py @@ -1,9 +1,4 @@ -""" -Support for the Tuya scene. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/scene.tuya/ -""" +"""Support for the Tuya scenes.""" from homeassistant.components.scene import Scene, DOMAIN from homeassistant.components.tuya import DATA_TUYA, TuyaDevice diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index 9fc1f92016e..1e8fab2cc1b 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -1,9 +1,4 @@ -""" -Support for Tuya switch. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.tuya/ -""" +"""Support for Tuya switches.""" from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice from homeassistant.components.tuya import DATA_TUYA, TuyaDevice diff --git a/homeassistant/components/twilio/__init__.py b/homeassistant/components/twilio/__init__.py index 9fcba4da817..ce8c272165f 100644 --- a/homeassistant/components/twilio/__init__.py +++ b/homeassistant/components/twilio/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Twilio. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/twilio/ -""" +"""Support for Twilio.""" import voluptuous as vol import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/unifi/__init__.py b/homeassistant/components/unifi/__init__.py index 8476aade7d7..7e236789a5c 100644 --- a/homeassistant/components/unifi/__init__.py +++ b/homeassistant/components/unifi/__init__.py @@ -1,10 +1,4 @@ -""" -Support for devices connected to UniFi POE. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/unifi/ -""" - +"""Support for devices connected to UniFi POE.""" import voluptuous as vol from homeassistant import config_entries diff --git a/homeassistant/components/unifi/const.py b/homeassistant/components/unifi/const.py index 7250feec799..8fe90823c49 100644 --- a/homeassistant/components/unifi/const.py +++ b/homeassistant/components/unifi/const.py @@ -1,5 +1,4 @@ """Constants for the UniFi component.""" - import logging LOGGER = logging.getLogger('homeassistant.components.unifi') diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 11529cbe171..abd102f6187 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -1,5 +1,4 @@ """UniFi Controller abstraction.""" - import asyncio import async_timeout diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index c1f8a96946f..425b9878f6d 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -1,10 +1,4 @@ -""" -Support for devices connected to UniFi POE. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.unifi/ -""" - +"""Support for devices connected to UniFi POE.""" import asyncio import logging @@ -39,7 +33,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """ controller_id = CONTROLLER_ID.format( host=config_entry.data[CONF_CONTROLLER][CONF_HOST], - site=config_entry.data[CONF_CONTROLLER][CONF_SITE_ID] + site=config_entry.data[CONF_CONTROLLER][CONF_SITE_ID], ) controller = hass.data[unifi.DOMAIN][controller_id] switches = {} diff --git a/homeassistant/components/upcloud/__init__.py b/homeassistant/components/upcloud/__init__.py index ca0f554bd39..7981cf948bb 100644 --- a/homeassistant/components/upcloud/__init__.py +++ b/homeassistant/components/upcloud/__init__.py @@ -1,9 +1,4 @@ -""" -Support for UpCloud. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/upcloud/ -""" +"""Support for UpCloud.""" import logging from datetime import timedelta @@ -43,12 +38,12 @@ UPCLOUD_PLATFORMS = ['binary_sensor', 'switch'] SCAN_INTERVAL = timedelta(seconds=60) -SIGNAL_UPDATE_UPCLOUD = "upcloud_update" +SIGNAL_UPDATE_UPCLOUD = 'upcloud_update' STATE_MAP = { - "started": STATE_ON, - "stopped": STATE_OFF, - "error": STATE_PROBLEM, + 'error': STATE_PROBLEM, + 'started': STATE_ON, + 'stopped': STATE_OFF, } CONFIG_SCHEMA = vol.Schema({ diff --git a/homeassistant/components/upcloud/binary_sensor.py b/homeassistant/components/upcloud/binary_sensor.py index c7b8a284dc9..3fd54b349a2 100644 --- a/homeassistant/components/upcloud/binary_sensor.py +++ b/homeassistant/components/upcloud/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for monitoring the state of UpCloud servers. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.upcloud/ -""" +"""Support for monitoring the state of UpCloud servers.""" import logging import voluptuous as vol diff --git a/homeassistant/components/upcloud/switch.py b/homeassistant/components/upcloud/switch.py index f2818e59a9b..0b44d787f6f 100644 --- a/homeassistant/components/upcloud/switch.py +++ b/homeassistant/components/upcloud/switch.py @@ -1,9 +1,4 @@ -""" -Support for interacting with UpCloud servers. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/switch.upcloud/ -""" +"""Support for interacting with UpCloud servers.""" import logging import voluptuous as vol diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 28c88bf5c29..cb2646ea942 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -1,9 +1,4 @@ -""" -Support to check for available updates. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/updater/ -""" +"""Support to check for available updates.""" import asyncio from datetime import timedelta # pylint: disable=import-error,no-name-in-module diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index 2a1b8c52d79..efa3ee73af8 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -1,9 +1,4 @@ -""" -Will open a port in your router for Home Assistant and provide statistics. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/upnp/ -""" +"""Open ports in your router for Home Assistant and provide statistics.""" from ipaddress import ip_address import voluptuous as vol @@ -28,7 +23,6 @@ from .const import DOMAIN from .const import LOGGER as _LOGGER from .device import Device - REQUIREMENTS = ['async-upnp-client==0.14.4'] NOTIFICATION_ID = 'upnp_notification' @@ -41,8 +35,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_LOCAL_IP): vol.All(ip_address, cv.string), vol.Optional(CONF_PORTS): vol.Schema({ - vol.Any(CONF_HASS, cv.port): - vol.Any(CONF_HASS, cv.port) + vol.Any(CONF_HASS, cv.port): vol.Any(CONF_HASS, cv.port) }) }), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/usps/__init__.py b/homeassistant/components/usps/__init__.py index 41aa240492b..8a7d7d52255 100644 --- a/homeassistant/components/usps/__init__.py +++ b/homeassistant/components/usps/__init__.py @@ -1,9 +1,4 @@ -""" -Support for USPS packages and mail. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/usps/ -""" +"""Support for USPS packages and mail.""" from datetime import timedelta import logging @@ -33,7 +28,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Optional(CONF_NAME, default=DOMAIN): cv.string, - vol.Optional(CONF_DRIVER): cv.string + vol.Optional(CONF_DRIVER): cv.string, }), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/usps/camera.py b/homeassistant/components/usps/camera.py index d23359d8c57..d4769102d14 100644 --- a/homeassistant/components/usps/camera.py +++ b/homeassistant/components/usps/camera.py @@ -1,9 +1,4 @@ -""" -Support for a camera made up of usps mail images. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/camera.usps/ -""" +"""Support for a camera made up of USPS mail images.""" from datetime import timedelta import logging diff --git a/homeassistant/components/usps/sensor.py b/homeassistant/components/usps/sensor.py index 17fa11fe8d3..1603715861d 100644 --- a/homeassistant/components/usps/sensor.py +++ b/homeassistant/components/usps/sensor.py @@ -1,9 +1,4 @@ -""" -Sensor for USPS packages. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.usps/ -""" +"""Sensor for USPS packages.""" from collections import defaultdict import logging diff --git a/homeassistant/components/utility_meter/__init__.py b/homeassistant/components/utility_meter/__init__.py index 8a8e669ba88..7d8e4ddf71b 100644 --- a/homeassistant/components/utility_meter/__init__.py +++ b/homeassistant/components/utility_meter/__init__.py @@ -1,10 +1,4 @@ -""" -Component to track utility consumption over given periods of time. - -For more details about this component, please refer to the documentation -at https://www.home-assistant.io/components/utility_meter/ -""" - +"""Support for tracking consumption over given periods of time.""" import logging import voluptuous as vol @@ -25,7 +19,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -TARIFF_ICON = "mdi:clock-outline" +TARIFF_ICON = 'mdi:clock-outline' ATTR_TARIFFS = 'tariffs' diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index cd86f9c0bd0..d3edf7d501b 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -1,9 +1,4 @@ -""" -Utility meter from sensors providing raw data. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.utility_meter/ -""" +"""Utility meter from sensors providing raw data.""" import logging from decimal import Decimal, DecimalException @@ -40,8 +35,8 @@ PAUSED = 'paused' COLLECTING = 'collecting' -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the utility meter sensor.""" if discovery_info is None: _LOGGER.error("This platform is only available through discovery") @@ -56,12 +51,10 @@ async def async_setup_platform(hass, config, async_add_entities, conf_meter_tariff_entity = hass.data[DATA_UTILITY][meter].get( CONF_TARIFF_ENTITY) - meters.append(UtilityMeterSensor(conf_meter_source, - conf.get(CONF_NAME), - conf_meter_type, - conf_meter_offset, - conf.get(CONF_TARIFF), - conf_meter_tariff_entity)) + meters.append(UtilityMeterSensor( + conf_meter_source, conf.get(CONF_NAME), conf_meter_type, + conf_meter_offset, conf.get(CONF_TARIFF), + conf_meter_tariff_entity)) async_add_entities(meters) @@ -181,16 +174,16 @@ class UtilityMeterSensor(RestoreEntity): self._last_reset = state.attributes.get(ATTR_LAST_RESET) await self.async_update_ha_state() if state.attributes.get(ATTR_STATUS) == PAUSED: - # Fake cancelation function to init the meter paused + # Fake cancellation function to init the meter paused self._collecting = lambda: None @callback def async_source_tracking(event): """Wait for source to be ready, then start meter.""" if self._tariff_entity is not None: - _LOGGER.debug("track %s", self._tariff_entity) - async_track_state_change(self.hass, self._tariff_entity, - self.async_tariff_change) + _LOGGER.debug("Track %s", self._tariff_entity) + async_track_state_change( + self.hass, self._tariff_entity, self.async_tariff_change) tariff_entity_state = self.hass.states.get(self._tariff_entity) if self._tariff != tariff_entity_state.state: diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index 15ca8584a4e..38d8b6c3f1c 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Velbus platform. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/velbus/ -""" +"""Support for Velbus devices.""" import logging import voluptuous as vol @@ -18,7 +13,6 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = 'velbus' - VELBUS_MESSAGE = 'velbus.message' CONFIG_SCHEMA = vol.Schema({ diff --git a/homeassistant/components/velbus/binary_sensor.py b/homeassistant/components/velbus/binary_sensor.py index b123b958560..43ffa232b40 100644 --- a/homeassistant/components/velbus/binary_sensor.py +++ b/homeassistant/components/velbus/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for Velbus Binary Sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.velbus/ -""" +"""Support for Velbus Binary Sensors.""" import logging from homeassistant.components.binary_sensor import BinarySensorDevice @@ -15,8 +10,8 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['velbus'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up Velbus binary sensors.""" if discovery_info is None: return diff --git a/homeassistant/components/velbus/climate.py b/homeassistant/components/velbus/climate.py index 0b0205acefb..ae7a2828492 100644 --- a/homeassistant/components/velbus/climate.py +++ b/homeassistant/components/velbus/climate.py @@ -1,9 +1,4 @@ -""" -Support for Velbus thermostat. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/climate.velbus/ -""" +"""Support for Velbus thermostat.""" import logging from homeassistant.components.climate import ( diff --git a/homeassistant/components/velbus/cover.py b/homeassistant/components/velbus/cover.py index 7e5099cecf8..72a5a7af79b 100644 --- a/homeassistant/components/velbus/cover.py +++ b/homeassistant/components/velbus/cover.py @@ -1,9 +1,4 @@ -""" -Support for Velbus covers. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/cover.velbus/ -""" +"""Support for Velbus covers.""" import logging import time diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index 8e9aafd3605..10ad89ab847 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -1,9 +1,4 @@ -""" -Velbus sensors. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/sensor.velbus/ -""" +"""Support for Velbus sensors.""" import logging from homeassistant.components.velbus import ( @@ -14,8 +9,8 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['velbus'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the Velbus temp sensor platform.""" if discovery_info is None: return diff --git a/homeassistant/components/velbus/switch.py b/homeassistant/components/velbus/switch.py index 300ff43d676..7104bb0750d 100644 --- a/homeassistant/components/velbus/switch.py +++ b/homeassistant/components/velbus/switch.py @@ -1,9 +1,4 @@ -""" -Support for Velbus switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.velbus/ -""" +"""Support for Velbus switches.""" import logging from homeassistant.components.switch import SwitchDevice @@ -15,8 +10,8 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['velbus'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the Velbus Switch platform.""" if discovery_info is None: return diff --git a/homeassistant/components/velux/__init__.py b/homeassistant/components/velux/__init__.py index 010612acc7d..1018f72fdbc 100644 --- a/homeassistant/components/velux/__init__.py +++ b/homeassistant/components/velux/__init__.py @@ -1,9 +1,4 @@ -""" -Connects to VELUX KLF 200 interface. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/velux/ -""" +"""Support for VELUX KLF 200 devices.""" import logging import voluptuous as vol diff --git a/homeassistant/components/velux/cover.py b/homeassistant/components/velux/cover.py index b78d981c695..1c3192961af 100644 --- a/homeassistant/components/velux/cover.py +++ b/homeassistant/components/velux/cover.py @@ -1,10 +1,4 @@ -""" -Support for Velux covers. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/cover.velux/ -""" - +"""Support for Velux covers.""" from homeassistant.components.cover import ( ATTR_POSITION, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, SUPPORT_STOP, CoverDevice) @@ -14,8 +8,8 @@ from homeassistant.core import callback DEPENDENCIES = ['velux'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up cover(s) for Velux platform.""" entities = [] for node in hass.data[DATA_VELUX].pyvlx.nodes: diff --git a/homeassistant/components/velux/scene.py b/homeassistant/components/velux/scene.py index 77ba30158e4..db1e9450daf 100644 --- a/homeassistant/components/velux/scene.py +++ b/homeassistant/components/velux/scene.py @@ -1,20 +1,13 @@ -""" -Support for VELUX scenes. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/scene.velux/ -""" - +"""Support for VELUX scenes.""" from homeassistant.components.scene import Scene from homeassistant.components.velux import _LOGGER, DATA_VELUX - DEPENDENCIES = ['velux'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): - """Set up the scenes for velux platform.""" +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): + """Set up the scenes for Velux platform.""" entities = [] for scene in hass.data[DATA_VELUX].pyvlx.scenes: entities.append(VeluxScene(scene)) @@ -22,11 +15,11 @@ async def async_setup_platform(hass, config, async_add_entities, class VeluxScene(Scene): - """Representation of a velux scene.""" + """Representation of a Velux scene.""" def __init__(self, scene): """Init velux scene.""" - _LOGGER.info("Adding VELUX scene: %s", scene) + _LOGGER.info("Adding Velux scene: %s", scene) self.scene = scene @property diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index 127cd008a3a..3f4c66d238a 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Vera devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/vera/ -""" +"""Support for Vera devices.""" import logging from collections import defaultdict @@ -43,7 +38,7 @@ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_CONTROLLER): cv.url, vol.Optional(CONF_EXCLUDE, default=[]): VERA_ID_LIST_SCHEMA, - vol.Optional(CONF_LIGHTS, default=[]): VERA_ID_LIST_SCHEMA + vol.Optional(CONF_LIGHTS, default=[]): VERA_ID_LIST_SCHEMA, }), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/vera/binary_sensor.py b/homeassistant/components/vera/binary_sensor.py index bb1e7331de8..837422dbc7c 100644 --- a/homeassistant/components/vera/binary_sensor.py +++ b/homeassistant/components/vera/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for Vera binary sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.vera/ -""" +"""Support for Vera binary sensors.""" import logging from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/vera/climate.py b/homeassistant/components/vera/climate.py index 5e016b8666b..7cd3129bc14 100644 --- a/homeassistant/components/vera/climate.py +++ b/homeassistant/components/vera/climate.py @@ -1,9 +1,4 @@ -""" -Support for Vera thermostats. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.vera/ -""" +"""Support for Vera thermostats.""" import logging from homeassistant.util import convert diff --git a/homeassistant/components/vera/cover.py b/homeassistant/components/vera/cover.py index 279e4a4307d..1168cca8425 100644 --- a/homeassistant/components/vera/cover.py +++ b/homeassistant/components/vera/cover.py @@ -1,9 +1,4 @@ -""" -Support for Vera cover - curtains, rollershutters etc. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/cover.vera/ -""" +"""Support for Vera cover - curtains, rollershutters etc.""" import logging from homeassistant.components.cover import CoverDevice, ENTITY_ID_FORMAT, \ diff --git a/homeassistant/components/vera/light.py b/homeassistant/components/vera/light.py index 702236ac748..93e54b915c7 100644 --- a/homeassistant/components/vera/light.py +++ b/homeassistant/components/vera/light.py @@ -1,9 +1,4 @@ -""" -Support for Vera lights. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.vera/ -""" +"""Support for Vera lights.""" import logging from homeassistant.components.light import ( diff --git a/homeassistant/components/vera/lock.py b/homeassistant/components/vera/lock.py index 21287b6328e..61d5f0baf28 100644 --- a/homeassistant/components/vera/lock.py +++ b/homeassistant/components/vera/lock.py @@ -1,9 +1,4 @@ -""" -Support for Vera locks. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/lock.vera/ -""" +"""Support for Vera locks.""" import logging from homeassistant.components.lock import ENTITY_ID_FORMAT, LockDevice diff --git a/homeassistant/components/vera/scene.py b/homeassistant/components/vera/scene.py index 6cae1195f87..0960512f6d1 100644 --- a/homeassistant/components/vera/scene.py +++ b/homeassistant/components/vera/scene.py @@ -1,9 +1,4 @@ -""" -Support for Vera scenes. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/scene.vera/ -""" +"""Support for Vera scenes.""" import logging from homeassistant.util import slugify @@ -11,10 +6,10 @@ from homeassistant.components.scene import Scene from homeassistant.components.vera import ( VERA_CONTROLLER, VERA_SCENES, VERA_ID_FORMAT) -DEPENDENCIES = ['vera'] - _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['vera'] + def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Vera scenes.""" diff --git a/homeassistant/components/vera/sensor.py b/homeassistant/components/vera/sensor.py index c9b5a36afa3..8b68cc9190f 100644 --- a/homeassistant/components/vera/sensor.py +++ b/homeassistant/components/vera/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Vera sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.vera/ -""" +"""Support for Vera sensors.""" import logging from datetime import timedelta @@ -15,10 +10,10 @@ from homeassistant.util import convert from homeassistant.components.vera import ( VERA_CONTROLLER, VERA_DEVICES, VeraDevice) -DEPENDENCIES = ['vera'] - _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['vera'] + SCAN_INTERVAL = timedelta(seconds=5) diff --git a/homeassistant/components/vera/switch.py b/homeassistant/components/vera/switch.py index 4742b755944..2f4d18e34e1 100644 --- a/homeassistant/components/vera/switch.py +++ b/homeassistant/components/vera/switch.py @@ -1,9 +1,4 @@ -""" -Support for Vera switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.vera/ -""" +"""Support for Vera switches.""" import logging from homeassistant.util import convert @@ -11,10 +6,10 @@ from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice from homeassistant.components.vera import ( VERA_CONTROLLER, VERA_DEVICES, VeraDevice) -DEPENDENCIES = ['vera'] - _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['vera'] + def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Vera switches.""" diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index 481aa331e41..393a4066002 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Verisure components. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/verisure/ -""" +"""Support for Verisure devices.""" import logging import threading from datetime import timedelta diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index 160f152ef8a..adcdcd668cb 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -1,9 +1,4 @@ -""" -Interfaces with Verisure alarm control panel. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/alarm_control_panel.verisure/ -""" +"""Support for Verisure alarm control panels.""" import logging from time import sleep diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index e040da959ea..4c9e79724fe 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Interfaces with Verisure sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.verisure/ -""" +"""Support for Verisure binary sensors.""" import logging from homeassistant.components.binary_sensor import BinarySensorDevice diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index 01e4e82f3bc..7112e535a95 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -1,9 +1,4 @@ -""" -Camera that loads a picture from a local file. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/camera.verisure/ -""" +"""Support for Verisure cameras.""" import errno import logging import os diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index be9a0a24fee..cdd230ea7f7 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -1,9 +1,4 @@ -""" -Interfaces with Verisure locks. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/verisure/ -""" +"""Support for Verisure locks.""" import logging from time import sleep from time import time @@ -18,7 +13,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Verisure platform.""" + """Set up the Verisure lock platform.""" locks = [] if int(hub.config.get(CONF_LOCKS, 1)): hub.update_overview() diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index b6ea75ae8cc..13706d8408f 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -1,9 +1,4 @@ -""" -Interfaces with Verisure sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.verisure/ -""" +"""Support for Verisure sensors.""" import logging from homeassistant.components.verisure import HUB as hub diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index 11ccd82696e..a418eec6bc5 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -1,9 +1,4 @@ -""" -Support for Verisure Smartplugs. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.verisure/ -""" +"""Support for Verisure Smartplugs.""" import logging from time import time diff --git a/homeassistant/components/volvooncall/__init__.py b/homeassistant/components/volvooncall/__init__.py index 0d89537b8e8..9dbaadf9bee 100644 --- a/homeassistant/components/volvooncall/__init__.py +++ b/homeassistant/components/volvooncall/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Volvo On Call. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/volvooncall/ -""" +"""Support for Volvo On Call.""" from datetime import timedelta import logging @@ -45,7 +40,7 @@ COMPONENTS = { 'binary_sensor': 'binary_sensor', 'lock': 'lock', 'device_tracker': 'device_tracker', - 'switch': 'switch' + 'switch': 'switch', } RESOURCES = [ diff --git a/homeassistant/components/volvooncall/binary_sensor.py b/homeassistant/components/volvooncall/binary_sensor.py index e7092ff16d5..7158e4df69b 100644 --- a/homeassistant/components/volvooncall/binary_sensor.py +++ b/homeassistant/components/volvooncall/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for VOC. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.volvooncall/ -""" +"""Support for VOC.""" import logging from homeassistant.components.volvooncall import VolvoEntity, DATA_KEY @@ -13,8 +8,8 @@ from homeassistant.components.binary_sensor import ( _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the Volvo sensors.""" if discovery_info is None: return diff --git a/homeassistant/components/volvooncall/device_tracker.py b/homeassistant/components/volvooncall/device_tracker.py index 395b539a065..d4838c01505 100644 --- a/homeassistant/components/volvooncall/device_tracker.py +++ b/homeassistant/components/volvooncall/device_tracker.py @@ -1,9 +1,4 @@ -""" -Support for tracking a Volvo. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/device_tracker.volvooncall/ -""" +"""Support for tracking a Volvo.""" import logging from homeassistant.util import slugify diff --git a/homeassistant/components/volvooncall/lock.py b/homeassistant/components/volvooncall/lock.py index 83301aa3d4e..f281ea64461 100644 --- a/homeassistant/components/volvooncall/lock.py +++ b/homeassistant/components/volvooncall/lock.py @@ -1,9 +1,4 @@ -""" -Support for Volvo On Call locks. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/lock.volvooncall/ -""" +"""Support for Volvo On Call locks.""" import logging from homeassistant.components.lock import LockDevice @@ -12,8 +7,8 @@ from homeassistant.components.volvooncall import VolvoEntity, DATA_KEY _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the Volvo On Call lock.""" if discovery_info is None: return diff --git a/homeassistant/components/volvooncall/sensor.py b/homeassistant/components/volvooncall/sensor.py index 65b996a5bd5..07f16e580bd 100644 --- a/homeassistant/components/volvooncall/sensor.py +++ b/homeassistant/components/volvooncall/sensor.py @@ -1,10 +1,4 @@ -""" -Support for VOC. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.volvooncall/ - -""" +"""Support for Volvo On Call sensors.""" import logging from homeassistant.components.volvooncall import VolvoEntity, DATA_KEY @@ -12,8 +6,8 @@ from homeassistant.components.volvooncall import VolvoEntity, DATA_KEY _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the Volvo sensors.""" if discovery_info is None: return diff --git a/homeassistant/components/volvooncall/switch.py b/homeassistant/components/volvooncall/switch.py index 81abf7d0e6c..d3985557cff 100644 --- a/homeassistant/components/volvooncall/switch.py +++ b/homeassistant/components/volvooncall/switch.py @@ -1,11 +1,4 @@ -""" -Support for Volvo heater. - -This platform uses the Volvo online service. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.volvooncall/ -""" +"""Support for Volvo heater.""" import logging from homeassistant.components.volvooncall import VolvoEntity, DATA_KEY @@ -14,8 +7,8 @@ from homeassistant.helpers.entity import ToggleEntity _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up a Volvo switch.""" if discovery_info is None: return diff --git a/homeassistant/components/vultr/__init__.py b/homeassistant/components/vultr/__init__.py index b28189444ee..9f2efabd412 100644 --- a/homeassistant/components/vultr/__init__.py +++ b/homeassistant/components/vultr/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Vultr. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/vultr/ -""" +"""Support for Vultr.""" import logging from datetime import timedelta diff --git a/homeassistant/components/w800rf32/__init__.py b/homeassistant/components/w800rf32/__init__.py index 4b237272546..d2c0cf6b968 100644 --- a/homeassistant/components/w800rf32/__init__.py +++ b/homeassistant/components/w800rf32/__init__.py @@ -1,10 +1,4 @@ -""" -Support for w800rf32 components. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/w800rf32/ - -""" +"""Support for w800rf32 devices.""" import logging import voluptuous as vol @@ -18,15 +12,16 @@ from homeassistant.helpers.dispatcher import (dispatcher_send) REQUIREMENTS = ['pyW800rf32==0.1'] -DOMAIN = 'w800rf32' DATA_W800RF32 = 'data_w800rf32' +DOMAIN = 'w800rf32' + W800RF32_DEVICE = 'w800rf32_{}' _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ - vol.Required(CONF_DEVICE): cv.string + vol.Required(CONF_DEVICE): cv.string, }), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/w800rf32/binary_sensor.py b/homeassistant/components/w800rf32/binary_sensor.py index 48ac6f41a12..855a5f3ed0a 100644 --- a/homeassistant/components/w800rf32/binary_sensor.py +++ b/homeassistant/components/w800rf32/binary_sensor.py @@ -1,10 +1,4 @@ -""" -Support for w800rf32 binary sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.w800rf32/ - -""" +"""Support for w800rf32 binary sensors.""" import logging import voluptuous as vol @@ -30,14 +24,14 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_OFF_DELAY): - vol.All(cv.time_period, cv.positive_timedelta) + vol.All(cv.time_period, cv.positive_timedelta), }) }, }, extra=vol.ALLOW_EXTRA) -async def async_setup_platform(hass, config, - add_entities, discovery_info=None): +async def async_setup_platform( + hass, config, add_entities, discovery_info=None): """Set up the Binary Sensor platform to w800rf32.""" binary_sensors = [] # device_id --> "c1 or a3" X10 device. entity (type dictionary) diff --git a/homeassistant/components/wake_on_lan/__init__.py b/homeassistant/components/wake_on_lan/__init__.py index dba99bf7e3d..e6e12ef0afe 100644 --- a/homeassistant/components/wake_on_lan/__init__.py +++ b/homeassistant/components/wake_on_lan/__init__.py @@ -1,9 +1,4 @@ -""" -Component to wake up devices sending Wake-On-LAN magic packets. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/wake_on_lan/ -""" +"""Support for sending Wake-On-LAN magic packets.""" from functools import partial import logging diff --git a/homeassistant/components/water_heater/__init__.py b/homeassistant/components/water_heater/__init__.py index 07acb15b765..6c3cc7405ba 100644 --- a/homeassistant/components/water_heater/__init__.py +++ b/homeassistant/components/water_heater/__init__.py @@ -1,9 +1,4 @@ -""" -Provides functionality to interact with water heater devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/water_heater/ -""" +"""Support for water heater devices.""" from datetime import timedelta import logging import functools as ft diff --git a/homeassistant/components/water_heater/demo.py b/homeassistant/components/water_heater/demo.py index 89b86c12af4..a0220927f16 100644 --- a/homeassistant/components/water_heater/demo.py +++ b/homeassistant/components/water_heater/demo.py @@ -1,9 +1,4 @@ -""" -Demo platform that offers a fake water_heater device. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/demo/ -""" +"""Demo platform that offers a fake water_heater device.""" from homeassistant.components.water_heater import ( WaterHeaterDevice, SUPPORT_TARGET_TEMPERATURE, @@ -18,11 +13,10 @@ SUPPORT_FLAGS_HEATER = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Demo water_heater devices.""" add_entities([ - DemoWaterHeater('Demo Water Heater', 119, - TEMP_FAHRENHEIT, False, 'eco'), - DemoWaterHeater('Demo Water Heater Celsius', 45, - TEMP_CELSIUS, True, 'eco') - + DemoWaterHeater( + 'Demo Water Heater', 119, TEMP_FAHRENHEIT, False, 'eco'), + DemoWaterHeater( + 'Demo Water Heater Celsius', 45, TEMP_CELSIUS, True, 'eco'), ]) diff --git a/homeassistant/components/water_heater/econet.py b/homeassistant/components/water_heater/econet.py index 6af8ea43fa6..93ae98ed94b 100644 --- a/homeassistant/components/water_heater/econet.py +++ b/homeassistant/components/water_heater/econet.py @@ -1,9 +1,4 @@ -""" -Support for Rheem EcoNet water heaters. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/water_heater.econet/ -""" +"""Support for Rheem EcoNet water heaters.""" import datetime import logging diff --git a/homeassistant/components/waterfurnace/__init__.py b/homeassistant/components/waterfurnace/__init__.py index bbae6170048..38fd44cd1c7 100644 --- a/homeassistant/components/waterfurnace/__init__.py +++ b/homeassistant/components/waterfurnace/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Waterfurnace component. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/waterfurnace/ -""" +"""Support for Waterfurnaces.""" from datetime import timedelta import logging import time @@ -18,12 +13,11 @@ from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers import discovery - -REQUIREMENTS = ["waterfurnace==1.1.0"] +REQUIREMENTS = ['waterfurnace==1.1.0'] _LOGGER = logging.getLogger(__name__) -DOMAIN = "waterfurnace" +DOMAIN = 'waterfurnace' UPDATE_TOPIC = DOMAIN + "_update" SCAN_INTERVAL = timedelta(seconds=10) ERROR_INTERVAL = timedelta(seconds=300) @@ -35,7 +29,7 @@ NOTIFICATION_TITLE = 'WaterFurnace website status' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string + vol.Required(CONF_USERNAME): cv.string, }), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/watson_iot/__init__.py b/homeassistant/components/watson_iot/__init__.py index 889984eb223..e9a907ee6d2 100644 --- a/homeassistant/components/watson_iot/__init__.py +++ b/homeassistant/components/watson_iot/__init__.py @@ -1,9 +1,4 @@ -""" -A component which allows you to send data to the IBM Watson IoT Platform. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/watson_iot/ -""" +"""Support for the IBM Watson IoT Platform.""" import logging import queue import threading @@ -44,7 +39,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_INCLUDE, default={}): vol.Schema({ vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, vol.Optional(CONF_DOMAINS, default=[]): - vol.All(cv.ensure_list, [cv.string]) + vol.All(cv.ensure_list, [cv.string]), }), })), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/webhook/__init__.py b/homeassistant/components/webhook/__init__.py index 9ec6d0298ea..59be3ab1890 100644 --- a/homeassistant/components/webhook/__init__.py +++ b/homeassistant/components/webhook/__init__.py @@ -1,8 +1,4 @@ -"""Webhooks for Home Assistant. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/webhook/ -""" +"""Webhooks for Home Assistant.""" import logging from aiohttp.web import Response @@ -14,13 +10,16 @@ from homeassistant.auth.util import generate_secret from homeassistant.components import websocket_api from homeassistant.components.http.view import HomeAssistantView -DOMAIN = 'webhook' -DEPENDENCIES = ['http'] _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['http'] + +DOMAIN = 'webhook' URL_WEBHOOK_PATH = "/api/webhook/{webhook_id}" + WS_TYPE_LIST = 'webhook/list' + SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('type'): WS_TYPE_LIST, }) diff --git a/homeassistant/components/weblink/__init__.py b/homeassistant/components/weblink/__init__.py index cd87bd838fa..608328c659b 100644 --- a/homeassistant/components/weblink/__init__.py +++ b/homeassistant/components/weblink/__init__.py @@ -1,9 +1,4 @@ -""" -Support for links to external web pages. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/weblink/ -""" +"""Support for links to external web pages.""" import logging import voluptuous as vol diff --git a/homeassistant/components/webostv/__init__.py b/homeassistant/components/webostv/__init__.py index de9de74054c..f0b3c2c5f7e 100644 --- a/homeassistant/components/webostv/__init__.py +++ b/homeassistant/components/webostv/__init__.py @@ -1 +1 @@ -"""WebOS TV integration.""" +"""Support for WebOS TV.""" diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index d34dbe80778..a6cbfbae99d 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -1,9 +1,4 @@ -""" -Support for interface with an LG webOS Smart TV. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/media_player.webostv/ -""" +"""Support for interface with an LG webOS Smart TV.""" import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/webostv/notify.py b/homeassistant/components/webostv/notify.py index 92762b03aea..5887586df65 100644 --- a/homeassistant/components/webostv/notify.py +++ b/homeassistant/components/webostv/notify.py @@ -1,9 +1,4 @@ -""" -LG WebOS TV notification service. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/notify.webostv/ -""" +"""Support for LG WebOS TV notification service.""" import logging import voluptuous as vol @@ -22,7 +17,7 @@ WEBOSTV_CONFIG_FILE = 'webostv.conf' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_FILENAME, default=WEBOSTV_CONFIG_FILE): cv.string, - vol.Optional(CONF_ICON): cv.string + vol.Optional(CONF_ICON): cv.string, }) @@ -32,8 +27,8 @@ def get_service(hass, config, discovery_info=None): from pylgtv import PyLGTVPairException path = hass.config.path(config.get(CONF_FILENAME)) - client = WebOsClient(config.get(CONF_HOST), key_file_path=path, - timeout_connect=8) + client = WebOsClient( + config.get(CONF_HOST), key_file_path=path, timeout_connect=8) if not client.is_registered(): try: diff --git a/homeassistant/components/websocket_api/__init__.py b/homeassistant/components/websocket_api/__init__.py index 48c8f27996a..3734f46abb7 100644 --- a/homeassistant/components/websocket_api/__init__.py +++ b/homeassistant/components/websocket_api/__init__.py @@ -1,9 +1,4 @@ -""" -Websocket based API for Home Assistant. - -For more details about this component, please refer to the documentation at -https://developers.home-assistant.io/docs/external_api_websocket.html -""" +"""WebSocket based API for Home Assistant.""" from homeassistant.core import callback from homeassistant.loader import bind_hass diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index ff928b43873..34bb04cb394 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -9,7 +9,6 @@ from homeassistant.helpers.service import async_get_all_descriptions from . import const, decorators, messages - TYPE_CALL_SERVICE = 'call_service' TYPE_EVENT = 'event' TYPE_GET_CONFIG = 'get_config' diff --git a/homeassistant/components/wemo/__init__.py b/homeassistant/components/wemo/__init__.py index 3ec9b8920c3..709b3ec8672 100644 --- a/homeassistant/components/wemo/__init__.py +++ b/homeassistant/components/wemo/__init__.py @@ -1,9 +1,4 @@ -""" -Support for WeMo device discovery. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/wemo/ -""" +"""Support for WeMo device discovery.""" import logging import requests @@ -31,7 +26,7 @@ WEMO_MODEL_DISPATCH = { 'Maker': 'switch', 'Motion': 'binary_sensor', 'Sensor': 'binary_sensor', - 'Socket': 'switch' + 'Socket': 'switch', } SUBSCRIPTION_REGISTRY = None @@ -68,8 +63,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_STATIC, default=[]): vol.Schema([ vol.All(cv.string, coerce_host_port) ]), - vol.Optional(CONF_DISCOVERY, - default=DEFAULT_DISCOVERY): cv.boolean + vol.Optional(CONF_DISCOVERY, default=DEFAULT_DISCOVERY): cv.boolean, }), }, extra=vol.ALLOW_EXTRA) @@ -115,17 +109,17 @@ def setup(hass, config): # Only register a device once if serial in KNOWN_DEVICES: - _LOGGER.debug('Ignoring known device %s %s', - service, discovery_info) + _LOGGER.debug( + "Ignoring known device %s %s", service, discovery_info) return - _LOGGER.debug('Discovered unique WeMo device: %s', serial) + _LOGGER.debug("Discovered unique WeMo device: %s", serial) KNOWN_DEVICES.append(serial) component = WEMO_MODEL_DISPATCH.get(model_name, 'switch') - discovery.load_platform(hass, component, DOMAIN, - discovery_info, config) + discovery.load_platform( + hass, component, DOMAIN, discovery_info, config) discovery.listen(hass, SERVICE_WEMO, discovery_dispatch) @@ -146,7 +140,7 @@ def setup(hass, config): device = pywemo.discovery.device_from_description(url, None) except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as err: - _LOGGER.error('Unable to access WeMo at %s (%s)', url, err) + _LOGGER.error("Unable to access WeMo at %s (%s)", url, err) continue if not [d[1] for d in devices @@ -162,8 +156,8 @@ def setup(hass, config): device)) for url, device in devices: - _LOGGER.debug('Adding WeMo device at %s:%i', - device.host, device.port) + _LOGGER.debug( + "Adding WeMo device at %s:%i", device.host, device.port) discovery_info = { 'model_name': device.model_name, @@ -176,7 +170,6 @@ def setup(hass, config): _LOGGER.debug("WeMo device discovery has finished") - hass.bus.listen_once(EVENT_HOMEASSISTANT_START, - discover_wemo_devices) + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, discover_wemo_devices) return True diff --git a/homeassistant/components/wemo/binary_sensor.py b/homeassistant/components/wemo/binary_sensor.py index e44cbb31e66..d6c1ad721b9 100644 --- a/homeassistant/components/wemo/binary_sensor.py +++ b/homeassistant/components/wemo/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for WeMo sensors. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.wemo/ -""" +"""Support for WeMo binary sensors.""" import asyncio import logging diff --git a/homeassistant/components/wemo/fan.py b/homeassistant/components/wemo/fan.py index fbf72185ac2..29a493bf5bc 100644 --- a/homeassistant/components/wemo/fan.py +++ b/homeassistant/components/wemo/fan.py @@ -1,9 +1,4 @@ -""" -Support for WeMo humidifier. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/fan.wemo/ -""" +"""Support for WeMo humidifier.""" import asyncio import logging from datetime import timedelta diff --git a/homeassistant/components/wemo/light.py b/homeassistant/components/wemo/light.py index 38044b7a736..e0f729fb165 100644 --- a/homeassistant/components/wemo/light.py +++ b/homeassistant/components/wemo/light.py @@ -1,9 +1,4 @@ -""" -Support for Belkin WeMo lights. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/light.wemo/ -""" +"""Support for Belkin WeMo lights.""" import asyncio import logging from datetime import timedelta diff --git a/homeassistant/components/wemo/switch.py b/homeassistant/components/wemo/switch.py index 0815f307a9a..0a583e49e96 100644 --- a/homeassistant/components/wemo/switch.py +++ b/homeassistant/components/wemo/switch.py @@ -1,9 +1,4 @@ -""" -Support for WeMo switches. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/switch.wemo/ -""" +"""Support for WeMo switches.""" import asyncio import logging from datetime import datetime, timedelta @@ -47,7 +42,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): device = discovery.device_from_description(location, mac) except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as err: - _LOGGER.error('Unable to access %s (%s)', location, err) + _LOGGER.error("Unable to access %s (%s)", location, err) raise PlatformNotReady if device: diff --git a/homeassistant/components/wink/__init__.py b/homeassistant/components/wink/__init__.py index 3cedb0b126b..2b03d7711ac 100644 --- a/homeassistant/components/wink/__init__.py +++ b/homeassistant/components/wink/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Wink hubs. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/wink/ -""" +"""Support for Wink hubs.""" from datetime import timedelta import json import logging @@ -56,7 +51,7 @@ USER_AGENT = "Manufacturer/Home-Assistant{} python/3 Wink/3".format( DEFAULT_CONFIG = { 'client_id': 'CLIENT_ID_HERE', - 'client_secret': 'CLIENT_SECRET_HERE' + 'client_secret': 'CLIENT_SECRET_HERE', } SERVICE_ADD_NEW_DEVICES = 'pull_newly_added_devices_from_wink' @@ -115,42 +110,42 @@ CONFIG_SCHEMA = vol.Schema({ RENAME_DEVICE_SCHEMA = vol.Schema({ vol.Required(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_NAME): cv.string + vol.Required(ATTR_NAME): cv.string, }, extra=vol.ALLOW_EXTRA) DELETE_DEVICE_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_ids + vol.Required(ATTR_ENTITY_ID): cv.entity_ids, }, extra=vol.ALLOW_EXTRA) SET_PAIRING_MODE_SCHEMA = vol.Schema({ vol.Required(ATTR_HUB_NAME): cv.string, vol.Required(ATTR_PAIRING_MODE): cv.string, - vol.Optional(ATTR_KIDDE_RADIO_CODE): cv.string + vol.Optional(ATTR_KIDDE_RADIO_CODE): cv.string, }, extra=vol.ALLOW_EXTRA) SET_VOLUME_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_VOLUME): vol.In(VOLUMES) + vol.Required(ATTR_VOLUME): vol.In(VOLUMES), }) SET_SIREN_TONE_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_TONE): vol.In(TONES) + vol.Required(ATTR_TONE): vol.In(TONES), }) SET_CHIME_MODE_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_TONE): vol.In(CHIME_TONES) + vol.Required(ATTR_TONE): vol.In(CHIME_TONES), }) SET_AUTO_SHUTOFF_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_AUTO_SHUTOFF): vol.In(AUTO_SHUTOFF_TIMES) + vol.Required(ATTR_AUTO_SHUTOFF): vol.In(AUTO_SHUTOFF_TIMES), }) SET_STROBE_ENABLED_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_ENABLED): cv.boolean + vol.Required(ATTR_ENABLED): cv.boolean, }) ENABLED_SIREN_SCHEMA = vol.Schema({ @@ -166,13 +161,13 @@ DIAL_CONFIG_SCHEMA = vol.Schema({ vol.Optional(ATTR_MAX_POSITION): cv.positive_int, vol.Optional(ATTR_ROTATION): vol.In(ROTATIONS), vol.Optional(ATTR_SCALE): vol.In(SCALES), - vol.Optional(ATTR_TICKS): cv.positive_int + vol.Optional(ATTR_TICKS): cv.positive_int, }) DIAL_STATE_SCHEMA = vol.Schema({ vol.Required(ATTR_ENTITY_ID): cv.entity_ids, vol.Required(ATTR_VALUE): vol.Coerce(int), - vol.Optional(ATTR_LABELS): cv.ensure_list(cv.string) + vol.Optional(ATTR_LABELS): cv.ensure_list(cv.string), }) WINK_COMPONENTS = [ diff --git a/homeassistant/components/wink/alarm_control_panel.py b/homeassistant/components/wink/alarm_control_panel.py index b2ae3578133..594198ddd12 100644 --- a/homeassistant/components/wink/alarm_control_panel.py +++ b/homeassistant/components/wink/alarm_control_panel.py @@ -1,9 +1,4 @@ -""" -Interfaces with Wink Cameras. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/alarm_control_panel.wink/ -""" +"""Support Wink alarm control panels.""" import logging import homeassistant.components.alarm_control_panel as alarm diff --git a/homeassistant/components/wink/binary_sensor.py b/homeassistant/components/wink/binary_sensor.py index a950289789e..12e1b557a73 100644 --- a/homeassistant/components/wink/binary_sensor.py +++ b/homeassistant/components/wink/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for Wink binary sensors. - -For more details about this platform, please refer to the documentation at -at https://home-assistant.io/components/binary_sensor.wink/ -""" +"""Support for Wink binary sensors.""" import logging from homeassistant.components.binary_sensor import BinarySensorDevice diff --git a/homeassistant/components/wink/climate.py b/homeassistant/components/wink/climate.py index 7e5230ba3c7..8d946bf03df 100644 --- a/homeassistant/components/wink/climate.py +++ b/homeassistant/components/wink/climate.py @@ -1,9 +1,4 @@ -""" -Support for Wink thermostats and Air Conditioners. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/climate.wink/ -""" +"""Support for Wink thermostats and Air Conditioners.""" import logging from homeassistant.components.climate import ( diff --git a/homeassistant/components/wink/cover.py b/homeassistant/components/wink/cover.py index 3cf9c753e3a..19ff792592f 100644 --- a/homeassistant/components/wink/cover.py +++ b/homeassistant/components/wink/cover.py @@ -1,9 +1,4 @@ -""" -Support for Wink Covers. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/cover.wink/ -""" +"""Support for Wink covers.""" from homeassistant.components.cover import CoverDevice, ATTR_POSITION from homeassistant.components.wink import WinkDevice, DOMAIN diff --git a/homeassistant/components/wink/fan.py b/homeassistant/components/wink/fan.py index eca985a8d1e..5bc6e03c49b 100644 --- a/homeassistant/components/wink/fan.py +++ b/homeassistant/components/wink/fan.py @@ -1,9 +1,4 @@ -""" -Support for Wink fans. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/fan.wink/ -""" +"""Support for Wink fans.""" import logging from homeassistant.components.fan import ( diff --git a/homeassistant/components/wink/light.py b/homeassistant/components/wink/light.py index 96c8f20679e..14a983154f8 100644 --- a/homeassistant/components/wink/light.py +++ b/homeassistant/components/wink/light.py @@ -1,10 +1,4 @@ -""" -Support for Wink lights. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.wink/ -""" - +"""Support for Wink lights.""" from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_COLOR, Light) diff --git a/homeassistant/components/wink/lock.py b/homeassistant/components/wink/lock.py index 68cc7a79ae6..0ef4f30dc5a 100644 --- a/homeassistant/components/wink/lock.py +++ b/homeassistant/components/wink/lock.py @@ -1,9 +1,4 @@ -""" -Support for Wink locks. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/lock.wink/ -""" +"""Support for Wink locks.""" import logging import voluptuous as vol diff --git a/homeassistant/components/wink/scene.py b/homeassistant/components/wink/scene.py index 35db96c3b8b..d05fe58a427 100644 --- a/homeassistant/components/wink/scene.py +++ b/homeassistant/components/wink/scene.py @@ -1,9 +1,4 @@ -""" -Support for Wink scenes. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/scene.wink/ -""" +"""Support for Wink scenes.""" import logging from homeassistant.components.scene import Scene diff --git a/homeassistant/components/wink/sensor.py b/homeassistant/components/wink/sensor.py index 3e228c4b40b..ab61769c94d 100644 --- a/homeassistant/components/wink/sensor.py +++ b/homeassistant/components/wink/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Wink sensors. - -For more details about this platform, please refer to the documentation at -at https://home-assistant.io/components/sensor.wink/ -""" +"""Support for Wink sensors.""" import logging from homeassistant.components.wink import DOMAIN, WinkDevice diff --git a/homeassistant/components/wink/switch.py b/homeassistant/components/wink/switch.py index 9dea93488af..cd55026879a 100644 --- a/homeassistant/components/wink/switch.py +++ b/homeassistant/components/wink/switch.py @@ -1,9 +1,4 @@ -""" -Support for Wink switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.wink/ -""" +"""Support for Wink switches.""" import logging from homeassistant.components.wink import DOMAIN, WinkDevice diff --git a/homeassistant/components/wink/water_heater.py b/homeassistant/components/wink/water_heater.py index a840baf980a..34cd86a50f4 100644 --- a/homeassistant/components/wink/water_heater.py +++ b/homeassistant/components/wink/water_heater.py @@ -1,9 +1,4 @@ -""" -Support for Wink water heaters. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/water_heater.wink/ -""" +"""Support for Wink water heaters.""" import logging from homeassistant.components.water_heater import ( diff --git a/homeassistant/components/wirelesstag/__init__.py b/homeassistant/components/wirelesstag/__init__.py index 77b4c48b41b..28c8cb4d515 100644 --- a/homeassistant/components/wirelesstag/__init__.py +++ b/homeassistant/components/wirelesstag/__init__.py @@ -1,10 +1,4 @@ -""" -Wireless Sensor Tags platform support. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/wirelesstag/ -""" - +"""Support for Wireless Sensor Tags.""" import logging from requests.exceptions import HTTPError, ConnectTimeout @@ -22,11 +16,11 @@ REQUIREMENTS = ['wirelesstagpy==0.4.0'] _LOGGER = logging.getLogger(__name__) -# strength of signal in dBm +# Strength of signal in dBm ATTR_TAG_SIGNAL_STRENGTH = 'signal_strength' -# indicates if tag is out of range or not +# Indicates if tag is out of range or not ATTR_TAG_OUT_OF_RANGE = 'out_of_range' -# number in percents from max power of tag receiver +# Number in percents from max power of tag receiver ATTR_TAG_POWER_CONSUMPTION = 'power_consumption' @@ -36,11 +30,11 @@ NOTIFICATION_TITLE = "Wireless Sensor Tag Setup" DOMAIN = 'wirelesstag' DEFAULT_ENTITY_NAMESPACE = 'wirelesstag' -# template for signal - first parameter is tag_id, +# Template for signal - first parameter is tag_id, # second, tag manager mac address SIGNAL_TAG_UPDATE = 'wirelesstag.tag_info_updated_{}_{}' -# template for signal - tag_id, sensor type and +# Template for signal - tag_id, sensor type and # tag manager mac address SIGNAL_BINARY_EVENT_UPDATE = 'wirelesstag.binary_event_updated_{}_{}_{}' diff --git a/homeassistant/components/wirelesstag/binary_sensor.py b/homeassistant/components/wirelesstag/binary_sensor.py index 190b408abf3..6f2c24a7788 100644 --- a/homeassistant/components/wirelesstag/binary_sensor.py +++ b/homeassistant/components/wirelesstag/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Binary sensor support for Wireless Sensor Tags. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.wirelesstag/ -""" +"""Binary sensor support for Wireless Sensor Tags.""" import logging import voluptuous as vol diff --git a/homeassistant/components/wirelesstag/sensor.py b/homeassistant/components/wirelesstag/sensor.py index eb9ce297065..3703e214d83 100644 --- a/homeassistant/components/wirelesstag/sensor.py +++ b/homeassistant/components/wirelesstag/sensor.py @@ -1,10 +1,4 @@ -""" -Sensor support for Wireless Sensor Tags platform. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.wirelesstag/ -""" - +"""Sensor support for Wireless Sensor Tags platform.""" import logging import voluptuous as vol @@ -32,7 +26,7 @@ SENSOR_TYPES = [ SENSOR_TEMPERATURE, SENSOR_HUMIDITY, SENSOR_MOISTURE, - SENSOR_LIGHT + SENSOR_LIGHT, ] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -69,9 +63,9 @@ class WirelessTagSensor(WirelessTagBaseSensor): # sensor.wirelesstag_bedroom_temperature # and not as sensor.bedroom for temperature and # sensor.bedroom_2 for humidity - self._entity_id = '{}.{}_{}_{}'.format('sensor', WIRELESSTAG_DOMAIN, - self.underscored_name, - self._sensor_type) + self._entity_id = '{}.{}_{}_{}'.format( + 'sensor', WIRELESSTAG_DOMAIN, self.underscored_name, + self._sensor_type) async def async_added_to_hass(self): """Register callbacks.""" @@ -118,8 +112,8 @@ class WirelessTagSensor(WirelessTagBaseSensor): @callback def _update_tag_info_callback(self, event): """Handle push notification sent by tag manager.""" - _LOGGER.info("Entity to update state: %s event data: %s", - self, event.data) + _LOGGER.debug( + "Entity to update state: %s event data: %s", self, event.data) new_value = self._sensor.value_from_update_event(event.data) self._state = self.decorate_value(new_value) self.async_schedule_update_ha_state() diff --git a/homeassistant/components/wirelesstag/switch.py b/homeassistant/components/wirelesstag/switch.py index cbe62d107da..913438e9d8c 100644 --- a/homeassistant/components/wirelesstag/switch.py +++ b/homeassistant/components/wirelesstag/switch.py @@ -1,9 +1,4 @@ -""" -Switch implementation for Wireless Sensor Tags (wirelesstag.net) platform. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.wirelesstag/ -""" +"""Switch implementation for Wireless Sensor Tags (wirelesstag.net).""" import logging import voluptuous as vol @@ -64,8 +59,8 @@ class WirelessTagSwitch(WirelessTagBaseSensor, SwitchDevice): super().__init__(api, tag) self._switch_type = switch_type self.sensor_type = SWITCH_TYPES[self._switch_type][1] - self._name = '{} {}'.format(self._tag.name, - SWITCH_TYPES[self._switch_type][0]) + self._name = '{} {}'.format( + self._tag.name, SWITCH_TYPES[self._switch_type][0]) def turn_on(self, **kwargs): """Turn on the switch.""" diff --git a/homeassistant/components/wunderlist/__init__.py b/homeassistant/components/wunderlist/__init__.py index f64d97dfc0d..d67cf089b5e 100644 --- a/homeassistant/components/wunderlist/__init__.py +++ b/homeassistant/components/wunderlist/__init__.py @@ -1,9 +1,4 @@ -""" -Component to interact with Wunderlist. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/wunderlist/ -""" +"""Support to interact with Wunderlist.""" import logging import voluptuous as vol @@ -25,7 +20,7 @@ CONF_STARRED = 'starred' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_CLIENT_ID): cv.string, - vol.Required(CONF_ACCESS_TOKEN): cv.string + vol.Required(CONF_ACCESS_TOKEN): cv.string, }) }, extra=vol.ALLOW_EXTRA) @@ -35,7 +30,7 @@ SERVICE_CREATE_TASK = 'create_task' SERVICE_SCHEMA_CREATE_TASK = vol.Schema({ vol.Required(CONF_LIST_NAME): cv.string, vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_STARRED): cv.boolean + vol.Optional(CONF_STARRED): cv.boolean, }) diff --git a/homeassistant/components/xiaomi_aqara/__init__.py b/homeassistant/components/xiaomi_aqara/__init__.py index ca2d8c1a169..ce943fb2c93 100644 --- a/homeassistant/components/xiaomi_aqara/__init__.py +++ b/homeassistant/components/xiaomi_aqara/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Xiaomi Gateways. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/xiaomi_aqara/ -""" +"""Support for Xiaomi Gateways.""" import logging from datetime import timedelta diff --git a/homeassistant/components/xiaomi_aqara/lock.py b/homeassistant/components/xiaomi_aqara/lock.py index 15415a73284..f19492664b1 100644 --- a/homeassistant/components/xiaomi_aqara/lock.py +++ b/homeassistant/components/xiaomi_aqara/lock.py @@ -1,9 +1,4 @@ -""" -Support for Xiaomi Aqara Lock. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/lock.xiaomi_aqara/ -""" +"""Support for Xiaomi Aqara locks.""" import logging from homeassistant.components.xiaomi_aqara import (PY_XIAOMI_GATEWAY, XiaomiDevice) @@ -24,8 +19,8 @@ ATTR_VERIFIED_WRONG_TIMES = 'verified_wrong_times' UNLOCK_MAINTAIN_TIME = 5 -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Perform the setup for Xiaomi devices.""" devices = [] diff --git a/homeassistant/components/xiaomi_aqara/sensor.py b/homeassistant/components/xiaomi_aqara/sensor.py index 1859b7be16a..133814e216e 100644 --- a/homeassistant/components/xiaomi_aqara/sensor.py +++ b/homeassistant/components/xiaomi_aqara/sensor.py @@ -1,4 +1,4 @@ -"""Support for Xiaomi aqara sensors.""" +"""Support for Xiaomi Aqara sensors.""" import logging from homeassistant.components.xiaomi_aqara import (PY_XIAOMI_GATEWAY, diff --git a/homeassistant/components/xiaomi_aqara/switch.py b/homeassistant/components/xiaomi_aqara/switch.py index 166f51fb091..c3cde8ede6d 100644 --- a/homeassistant/components/xiaomi_aqara/switch.py +++ b/homeassistant/components/xiaomi_aqara/switch.py @@ -1,4 +1,4 @@ -"""Support for Xiaomi aqara binary sensors.""" +"""Support for Xiaomi Aqara binary sensors.""" import logging from homeassistant.components.switch import SwitchDevice @@ -31,39 +31,33 @@ def setup_platform(hass, config, add_entities, discovery_info=None): data_key = 'status' else: data_key = 'channel_0' - devices.append(XiaomiGenericSwitch(device, "Plug", data_key, - True, gateway)) + devices.append(XiaomiGenericSwitch( + device, "Plug", data_key, True, gateway)) elif model in ['ctrl_neutral1', 'ctrl_neutral1.aq1']: - devices.append(XiaomiGenericSwitch(device, 'Wall Switch', - 'channel_0', - False, gateway)) + devices.append(XiaomiGenericSwitch( + device, 'Wall Switch', 'channel_0', False, gateway)) elif model in ['ctrl_ln1', 'ctrl_ln1.aq1']: - devices.append(XiaomiGenericSwitch(device, 'Wall Switch LN', - 'channel_0', - False, gateway)) + devices.append(XiaomiGenericSwitch( + device, 'Wall Switch LN', 'channel_0', False, gateway)) elif model in ['ctrl_neutral2', 'ctrl_neutral2.aq1']: - devices.append(XiaomiGenericSwitch(device, 'Wall Switch Left', - 'channel_0', - False, gateway)) - devices.append(XiaomiGenericSwitch(device, 'Wall Switch Right', - 'channel_1', - False, gateway)) + devices.append(XiaomiGenericSwitch( + device, 'Wall Switch Left', 'channel_0', False, gateway)) + devices.append(XiaomiGenericSwitch( + device, 'Wall Switch Right', 'channel_1', False, gateway)) elif model in ['ctrl_ln2', 'ctrl_ln2.aq1']: - devices.append(XiaomiGenericSwitch(device, - 'Wall Switch LN Left', - 'channel_0', - False, gateway)) - devices.append(XiaomiGenericSwitch(device, - 'Wall Switch LN Right', - 'channel_1', - False, gateway)) + devices.append(XiaomiGenericSwitch( + device, 'Wall Switch LN Left', 'channel_0', False, + gateway)) + devices.append(XiaomiGenericSwitch( + device, 'Wall Switch LN Right', 'channel_1', + False, gateway)) elif model in ['86plug', 'ctrl_86plug', 'ctrl_86plug.aq1']: if 'proto' not in device or int(device['proto'][0:1]) == 1: data_key = 'status' else: data_key = 'channel_0' - devices.append(XiaomiGenericSwitch(device, 'Wall Plug', - data_key, True, gateway)) + devices.append(XiaomiGenericSwitch( + device, 'Wall Plug', data_key, True, gateway)) add_entities(devices) @@ -96,9 +90,11 @@ class XiaomiGenericSwitch(XiaomiDevice, SwitchDevice): def device_state_attributes(self): """Return the state attributes.""" if self._supports_power_consumption: - attrs = {ATTR_IN_USE: self._in_use, - ATTR_LOAD_POWER: self._load_power, - ATTR_POWER_CONSUMED: self._power_consumed} + attrs = { + ATTR_IN_USE: self._in_use, + ATTR_LOAD_POWER: self._load_power, + ATTR_POWER_CONSUMED: self._power_consumed, + } else: attrs = {} attrs.update(super().device_state_attributes) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 2f9bee9d702..9abc871b9b4 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -1 +1 @@ -"""Xiaomi Miio integration.""" +"""Support for Xiaomi Miio.""" diff --git a/homeassistant/components/xiaomi_miio/device_tracker.py b/homeassistant/components/xiaomi_miio/device_tracker.py index c5c6ebcbc35..1aec3647d61 100644 --- a/homeassistant/components/xiaomi_miio/device_tracker.py +++ b/homeassistant/components/xiaomi_miio/device_tracker.py @@ -1,9 +1,4 @@ -""" -Support for Xiaomi Mi WiFi Repeater 2. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/device_tracker.xiaomi_miio/ -""" +"""Support for Xiaomi Mi WiFi Repeater 2.""" import logging import voluptuous as vol diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index 2e0b1657d23..c4cfa6bbe6b 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -1,9 +1,4 @@ -""" -Support for Xiaomi Mi Air Purifier and Xiaomi Mi Air Humidifier. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/fan.xiaomi_miio/ -""" +"""Support for Xiaomi Mi Air Purifier and Xiaomi Mi Air Humidifier.""" import asyncio from enum import Enum from functools import partial diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index 62433ca9f97..3ae6bfe6cd7 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -1,11 +1,4 @@ -""" -Support for Xiaomi Philips Lights. - -LED Ball, Candle, Downlight, Ceiling, Eyecare 2, Bedside & Desklamp Lamp. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/light.xiaomi_miio/ -""" +"""Support for Xiaomi Philips Lights.""" import asyncio import datetime from datetime import timedelta diff --git a/homeassistant/components/xiaomi_miio/remote.py b/homeassistant/components/xiaomi_miio/remote.py index c8ffd043321..1c4ae85a0d8 100644 --- a/homeassistant/components/xiaomi_miio/remote.py +++ b/homeassistant/components/xiaomi_miio/remote.py @@ -1,9 +1,4 @@ -""" -Support for the Xiaomi IR Remote (Chuangmi IR). - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/remote.xiaomi_miio/ -""" +"""Support for the Xiaomi IR Remote (Chuangmi IR).""" import asyncio import logging import time @@ -37,8 +32,7 @@ DEFAULT_SLOT = 1 LEARN_COMMAND_SCHEMA = vol.Schema({ vol.Required(ATTR_ENTITY_ID): vol.All(str), - vol.Optional(CONF_TIMEOUT, default=10): - vol.All(int, vol.Range(min=0)), + vol.Optional(CONF_TIMEOUT, default=10): vol.All(int, vol.Range(min=0)), vol.Optional(CONF_SLOT, default=1): vol.All(int, vol.Range(min=1, max=1000000)), }) diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index ef5ed1d5c38..bd5f8642e54 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Xiaomi Mi Air Quality Monitor (PM2.5). - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/sensor.xiaomi_miio/ -""" +"""Support for Xiaomi Mi Air Quality Monitor (PM2.5).""" import logging import voluptuous as vol diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index 4ead90ca4ec..eb7f09f95e6 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -1,9 +1,4 @@ -""" -Support for Xiaomi Smart WiFi Socket and Smart Power Strip. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/switch.xiaomi_miio/ -""" +"""Support for Xiaomi Smart WiFi Socket and Smart Power Strip.""" import asyncio from functools import partial import logging diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index a6613f7c3c3..82a92dd317c 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -1,9 +1,4 @@ -""" -Support for the Xiaomi vacuum cleaner robot. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/vacuum.xiaomi_miio/ -""" +"""Support for the Xiaomi vacuum cleaner robot.""" import asyncio from functools import partial import logging diff --git a/homeassistant/components/xs1/__init__.py b/homeassistant/components/xs1/__init__.py index 14656737f5c..f67eb8fd15a 100644 --- a/homeassistant/components/xs1/__init__.py +++ b/homeassistant/components/xs1/__init__.py @@ -1,10 +1,4 @@ -""" -Support for the EZcontrol XS1 gateway. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/xs1/ -""" - +"""Support for the EZcontrol XS1 gateway.""" import asyncio from functools import partial import logging @@ -29,17 +23,17 @@ SENSORS = 'sensors' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, vol.Optional(CONF_PORT, default=80): cv.string, vol.Optional(CONF_SSL, default=False): cv.boolean, vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string }), }, extra=vol.ALLOW_EXTRA) XS1_COMPONENTS = [ - 'switch', + 'climate', 'sensor', - 'climate' + 'switch', ] # Lock used to limit the amount of concurrent update requests @@ -54,13 +48,9 @@ def _create_controller_api(host, port, ssl, user, password): try: return xs1_api_client.XS1( - host=host, - port=port, - ssl=ssl, - user=user, - password=password) + host=host, port=port, ssl=ssl, user=user, password=password) except ConnectionError as error: - _LOGGER.error("Failed to create XS1 api client " + _LOGGER.error("Failed to create XS1 API client " "because of a connection error: %s", error) return None @@ -77,8 +67,7 @@ async def async_setup(hass, config): # initialize XS1 API xs1 = await hass.async_add_executor_job( - partial(_create_controller_api, - host, port, ssl, user, password)) + partial(_create_controller_api, host, port, ssl, user, password)) if xs1 is None: return False @@ -96,7 +85,7 @@ async def async_setup(hass, config): hass.data[DOMAIN][SENSORS] = sensors _LOGGER.debug("Loading components for XS1 platform...") - # load components for supported devices + # Load components for supported devices for component in XS1_COMPONENTS: hass.async_create_task( discovery.async_load_platform( diff --git a/homeassistant/components/xs1/climate.py b/homeassistant/components/xs1/climate.py index 0417d3bcde0..92a2c75895c 100644 --- a/homeassistant/components/xs1/climate.py +++ b/homeassistant/components/xs1/climate.py @@ -1,9 +1,4 @@ -""" -Support for XS1 climate devices. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/climate.xs1/ -""" +"""Support for XS1 climate devices.""" from functools import partial import logging @@ -19,8 +14,8 @@ MIN_TEMP = 8 MAX_TEMP = 25 -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the XS1 thermostat platform.""" from xs1_api_client.api_constants import ActuatorType @@ -37,7 +32,6 @@ async def async_setup_platform(hass, config, async_add_entities, for sensor in sensors: if actuator_name in sensor.name(): matching_sensor = sensor - break thermostat_entities.append( diff --git a/homeassistant/components/xs1/sensor.py b/homeassistant/components/xs1/sensor.py index b4d9bfe5ff9..9de91a6b012 100644 --- a/homeassistant/components/xs1/sensor.py +++ b/homeassistant/components/xs1/sensor.py @@ -1,10 +1,4 @@ -""" -Support for XS1 sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.xs1/ -""" - +"""Support for XS1 sensors.""" import logging from homeassistant.components.xs1 import ( @@ -15,8 +9,8 @@ DEPENDENCIES = ['xs1'] _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the XS1 sensor platform.""" from xs1_api_client.api_constants import ActuatorType diff --git a/homeassistant/components/xs1/switch.py b/homeassistant/components/xs1/switch.py index e6855865845..35568587b19 100644 --- a/homeassistant/components/xs1/switch.py +++ b/homeassistant/components/xs1/switch.py @@ -1,21 +1,17 @@ -""" -Support for XS1 switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.xs1/ -""" +"""Support for XS1 switches.""" import logging from homeassistant.components.xs1 import ( ACTUATORS, DOMAIN as COMPONENT_DOMAIN, XS1DeviceEntity) from homeassistant.helpers.entity import ToggleEntity -DEPENDENCIES = ['xs1'] _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['xs1'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): + +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the XS1 switch platform.""" from xs1_api_client.api_constants import ActuatorType diff --git a/homeassistant/components/zabbix/__init__.py b/homeassistant/components/zabbix/__init__.py index ea5a6d85d6b..f33c60b1c39 100644 --- a/homeassistant/components/zabbix/__init__.py +++ b/homeassistant/components/zabbix/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Zabbix. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/zabbix/ -""" +"""Support for Zabbix.""" import logging from urllib.parse import urljoin @@ -19,17 +14,15 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_SSL = False DEFAULT_PATH = 'zabbix' - DOMAIN = 'zabbix' - CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, + vol.Optional(CONF_PASSWORD): cv.string, vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string, + vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string }) }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/zabbix/sensor.py b/homeassistant/components/zabbix/sensor.py index 7a468a9b906..ae2e70ede2c 100644 --- a/homeassistant/components/zabbix/sensor.py +++ b/homeassistant/components/zabbix/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Zabbix Sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.zabbix/ -""" +"""Support for Zabbix sensors.""" import logging import voluptuous as vol diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 5d6161da904..bf743eaf370 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -1,9 +1,4 @@ -""" -This module exposes Home Assistant via Zeroconf. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/zeroconf/ -""" +"""Support for exposing Home Assistant via Zeroconf.""" import logging import socket diff --git a/homeassistant/components/zigbee/__init__.py b/homeassistant/components/zigbee/__init__.py index 8829a3bb928..0e2d3587829 100644 --- a/homeassistant/components/zigbee/__init__.py +++ b/homeassistant/components/zigbee/__init__.py @@ -1,16 +1,11 @@ -""" -Support for Zigbee devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/zigbee/ -""" +"""Support for Zigbee devices.""" import logging from binascii import hexlify, unhexlify import voluptuous as vol from homeassistant.const import ( - EVENT_HOMEASSISTANT_STOP, CONF_DEVICE, CONF_NAME, CONF_PIN) + EVENT_HOMEASSISTANT_STOP, CONF_DEVICE, CONF_NAME, CONF_PIN, CONF_ADDRESS) from homeassistant.helpers.entity import Entity from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import ( @@ -24,7 +19,6 @@ DOMAIN = 'zigbee' SIGNAL_ZIGBEE_FRAME_RECEIVED = 'zigbee_frame_received' -CONF_ADDRESS = 'address' CONF_BAUD = 'baud' DEFAULT_DEVICE = '/dev/ttyUSB0' diff --git a/homeassistant/components/zigbee/binary_sensor.py b/homeassistant/components/zigbee/binary_sensor.py index 67c05f47094..eec1832f07d 100644 --- a/homeassistant/components/zigbee/binary_sensor.py +++ b/homeassistant/components/zigbee/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Contains functionality to use a Zigbee device as a binary sensor. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.zigbee/ -""" +"""Support for Zigbee binary sensors.""" import voluptuous as vol from homeassistant.components.binary_sensor import BinarySensorDevice diff --git a/homeassistant/components/zigbee/light.py b/homeassistant/components/zigbee/light.py index 42dc95d1163..e5016900be7 100644 --- a/homeassistant/components/zigbee/light.py +++ b/homeassistant/components/zigbee/light.py @@ -1,9 +1,4 @@ -""" -Functionality to use a ZigBee device as a light. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.zigbee/ -""" +"""Support for Zigbee lights.""" import voluptuous as vol from homeassistant.components.light import Light diff --git a/homeassistant/components/zigbee/sensor.py b/homeassistant/components/zigbee/sensor.py index a0a1b8bb7fd..48503e396a4 100644 --- a/homeassistant/components/zigbee/sensor.py +++ b/homeassistant/components/zigbee/sensor.py @@ -1,9 +1,4 @@ -""" -Support for functionality to use a ZigBee device as a sensor. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.zigbee/ -""" +"""Support for Zigbee sensors.""" import logging from binascii import hexlify diff --git a/homeassistant/components/zigbee/switch.py b/homeassistant/components/zigbee/switch.py index 81fb8348c4e..ef36e17d74b 100644 --- a/homeassistant/components/zigbee/switch.py +++ b/homeassistant/components/zigbee/switch.py @@ -1,9 +1,4 @@ -""" -Contains functionality to use a Zigbee device as a switch. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.zigbee/ -""" +"""Support for Zigbee switches.""" import voluptuous as vol from homeassistant.components.switch import SwitchDevice diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index 370b52d1360..242f0362088 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -1,10 +1,4 @@ -""" -Support for the definition of zones. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/zone/ -""" - +"""Support for the definition of zones.""" import logging import voluptuous as vol diff --git a/homeassistant/components/zone/zone.py b/homeassistant/components/zone/zone.py index ee8b53d6ee4..21084e18f06 100644 --- a/homeassistant/components/zone/zone.py +++ b/homeassistant/components/zone/zone.py @@ -1,5 +1,4 @@ -"""Component entity and functionality.""" - +"""Zone entity and functionality.""" from homeassistant.const import ATTR_HIDDEN, ATTR_LATITUDE, ATTR_LONGITUDE from homeassistant.helpers.entity import Entity from homeassistant.loader import bind_hass diff --git a/homeassistant/components/zoneminder/__init__.py b/homeassistant/components/zoneminder/__init__.py index 2cefa2e1049..a4d90d523aa 100644 --- a/homeassistant/components/zoneminder/__init__.py +++ b/homeassistant/components/zoneminder/__init__.py @@ -1,9 +1,4 @@ -""" -Support for ZoneMinder. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/zoneminder/ -""" +"""Support for ZoneMinder.""" import logging import voluptuous as vol diff --git a/homeassistant/components/zoneminder/binary_sensor.py b/homeassistant/components/zoneminder/binary_sensor.py index e206ffa80f1..f20f09e70a6 100644 --- a/homeassistant/components/zoneminder/binary_sensor.py +++ b/homeassistant/components/zoneminder/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for ZoneMinder Binary Sensor. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.zoneminder/ -""" +"""Support for ZoneMinder binary sensors.""" from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.zoneminder import DOMAIN as ZONEMINDER_DOMAIN @@ -11,8 +6,8 @@ from homeassistant.components.zoneminder import DOMAIN as ZONEMINDER_DOMAIN DEPENDENCIES = ['zoneminder'] -async def async_setup_platform(hass, config, add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, add_entities, discovery_info=None): """Set up the ZoneMinder binary sensor platform.""" sensors = [] for host_name, zm_client in hass.data[ZONEMINDER_DOMAIN].items(): diff --git a/homeassistant/components/zoneminder/camera.py b/homeassistant/components/zoneminder/camera.py index 8556fbf1e35..7f74712335c 100644 --- a/homeassistant/components/zoneminder/camera.py +++ b/homeassistant/components/zoneminder/camera.py @@ -1,9 +1,4 @@ -""" -Support for ZoneMinder camera streaming. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/camera.zoneminder/ -""" +"""Support for ZoneMinder camera streaming.""" import logging from homeassistant.const import CONF_NAME, CONF_VERIFY_SSL diff --git a/homeassistant/components/zoneminder/sensor.py b/homeassistant/components/zoneminder/sensor.py index abecc99e278..9eb6beb491c 100644 --- a/homeassistant/components/zoneminder/sensor.py +++ b/homeassistant/components/zoneminder/sensor.py @@ -1,9 +1,4 @@ -""" -Support for ZoneMinder Sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.zoneminder/ -""" +"""Support for ZoneMinder sensors.""" import logging import voluptuous as vol diff --git a/homeassistant/components/zoneminder/switch.py b/homeassistant/components/zoneminder/switch.py index b039d2e3ce9..b411a148d43 100644 --- a/homeassistant/components/zoneminder/switch.py +++ b/homeassistant/components/zoneminder/switch.py @@ -1,9 +1,4 @@ -""" -Support for ZoneMinder switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.zoneminder/ -""" +"""Support for ZoneMinder switches.""" import logging import voluptuous as vol @@ -33,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for zm_client in hass.data[ZONEMINDER_DOMAIN].values(): monitors = zm_client.get_monitors() if not monitors: - _LOGGER.warning('Could not fetch monitors from ZoneMinder') + _LOGGER.warning("Could not fetch monitors from ZoneMinder") return for monitor in monitors: diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 093e6071bb4..be76eca4efd 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Z-Wave. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/zwave/ -""" +"""Support for Z-Wave.""" import asyncio import copy import logging diff --git a/homeassistant/components/zwave/binary_sensor.py b/homeassistant/components/zwave/binary_sensor.py index ca07986976d..478bfcbda7b 100644 --- a/homeassistant/components/zwave/binary_sensor.py +++ b/homeassistant/components/zwave/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Interfaces with Z-Wave sensors. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/binary_sensor.zwave/ -""" +"""Support for Z-Wave binary sensors.""" import logging import datetime import homeassistant.util.dt as dt_util @@ -17,11 +12,10 @@ from homeassistant.components.binary_sensor import ( BinarySensorDevice) _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = [] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Old method of setting up Z-Wave binary sensors.""" pass @@ -33,8 +27,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Add Z-Wave binary sensor.""" async_add_entities([binary_sensor]) - async_dispatcher_connect(hass, 'zwave_new_binary_sensor', - async_add_binary_sensor) + async_dispatcher_connect( + hass, 'zwave_new_binary_sensor', async_add_binary_sensor) def get_device(values, **kwargs): diff --git a/homeassistant/components/zwave/climate.py b/homeassistant/components/zwave/climate.py index 561af9c9f57..bf7b64549ac 100644 --- a/homeassistant/components/zwave/climate.py +++ b/homeassistant/components/zwave/climate.py @@ -1,9 +1,4 @@ -""" -Support for Z-Wave climate devices. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/climate.zwave/ -""" +"""Support for Z-Wave climate devices.""" # Because we do not compile openzwave on CI import logging from homeassistant.core import callback @@ -43,8 +38,8 @@ STATE_MAPPINGS = { } -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Old method of setting up Z-Wave climate devices.""" pass diff --git a/homeassistant/components/zwave/cover.py b/homeassistant/components/zwave/cover.py index 835305449e8..e40a885ede1 100644 --- a/homeassistant/components/zwave/cover.py +++ b/homeassistant/components/zwave/cover.py @@ -1,9 +1,4 @@ -""" -Support for Z-Wave cover components. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/cover.zwave/ -""" +"""Support for Z-Wave covers.""" import logging from homeassistant.core import callback from homeassistant.components.cover import ( @@ -19,8 +14,8 @@ _LOGGER = logging.getLogger(__name__) SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Old method of setting up Z-Wave covers.""" pass diff --git a/homeassistant/components/zwave/discovery_schemas.py b/homeassistant/components/zwave/discovery_schemas.py index 56d63d658a9..0141f4392dd 100644 --- a/homeassistant/components/zwave/discovery_schemas.py +++ b/homeassistant/components/zwave/discovery_schemas.py @@ -1,4 +1,4 @@ -"""Zwave discovery schemas.""" +"""Z-Wave discovery schemas.""" from . import const DEFAULT_VALUES_SCHEMA = { diff --git a/homeassistant/components/zwave/fan.py b/homeassistant/components/zwave/fan.py index 4b4204aa454..b2731f7d9a7 100644 --- a/homeassistant/components/zwave/fan.py +++ b/homeassistant/components/zwave/fan.py @@ -1,9 +1,4 @@ -""" -Z-Wave platform that handles fans. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/fan.zwave/ -""" +"""Support for Z-Wave fans.""" import logging import math @@ -36,8 +31,8 @@ SPEED_TO_VALUE = { } -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Old method of setting up Z-Wave fans.""" pass diff --git a/homeassistant/components/zwave/light.py b/homeassistant/components/zwave/light.py index da712a2f183..0af85b84177 100644 --- a/homeassistant/components/zwave/light.py +++ b/homeassistant/components/zwave/light.py @@ -1,9 +1,4 @@ -""" -Support for Z-Wave lights. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.zwave/ -""" +"""Support for Z-Wave lights.""" import logging from threading import Timer @@ -55,8 +50,8 @@ TEMP_WARM_HASS = (TEMP_COLOR_MAX - TEMP_COLOR_MIN) / 3 * 2 + TEMP_COLOR_MIN TEMP_COLD_HASS = (TEMP_COLOR_MAX - TEMP_COLOR_MIN) / 3 + TEMP_COLOR_MIN -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Old method of setting up Z-Wave lights.""" pass diff --git a/homeassistant/components/zwave/lock.py b/homeassistant/components/zwave/lock.py index b19b06761c4..7c0958e596a 100644 --- a/homeassistant/components/zwave/lock.py +++ b/homeassistant/components/zwave/lock.py @@ -1,9 +1,4 @@ -""" -Z-Wave platform that handles simple door locks. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/lock.zwave/ -""" +"""Support for Z-Wave door locks.""" import logging import voluptuous as vol diff --git a/homeassistant/components/zwave/sensor.py b/homeassistant/components/zwave/sensor.py index ce25b61146b..44fc132cf77 100644 --- a/homeassistant/components/zwave/sensor.py +++ b/homeassistant/components/zwave/sensor.py @@ -1,9 +1,4 @@ -""" -Interfaces with Z-Wave sensors. - -For more details about this platform, please refer to the documentation -at https://home-assistant.io/components/sensor.zwave/ -""" +"""Support for Z-Wave sensors.""" import logging from homeassistant.core import callback from homeassistant.components.sensor import DOMAIN @@ -14,8 +9,8 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Old method of setting up Z-Wave sensors.""" pass diff --git a/homeassistant/components/zwave/switch.py b/homeassistant/components/zwave/switch.py index 54a2a729d04..ef544222546 100644 --- a/homeassistant/components/zwave/switch.py +++ b/homeassistant/components/zwave/switch.py @@ -1,9 +1,4 @@ -""" -Z-Wave platform that handles simple binary switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.zwave/ -""" +"""Support for Z-Wave switches.""" import logging import time from homeassistant.core import callback diff --git a/homeassistant/components/zwave/workaround.py b/homeassistant/components/zwave/workaround.py index 0a882a093c6..ff4db3c0706 100644 --- a/homeassistant/components/zwave/workaround.py +++ b/homeassistant/components/zwave/workaround.py @@ -1,4 +1,4 @@ -"""Zwave workarounds.""" +"""Z-Wave workarounds.""" from . import const # Manufacturers From 62b2b23d0b9cc9698760e4d6d0e351797f15f187 Mon Sep 17 00:00:00 2001 From: Ryan Wagoner <8441200+rwagoner@users.noreply.github.com> Date: Wed, 13 Feb 2019 16:52:32 -0500 Subject: [PATCH 184/242] Add night arm mode to MQTT alarm control panel (#20961) * Add night arm mode to MQTT alarm control panel * Add unit test for MQTT alarm night mode --- .../components/mqtt/alarm_control_panel.py | 24 +++++++++-- .../mqtt/test_alarm_control_panel.py | 43 ++++++++++++++++--- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index b3e4d452b5c..8e1b62414b7 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -19,8 +19,8 @@ from homeassistant.components.mqtt.discovery import ( MQTT_DISCOVERY_NEW, clear_discovery_hash) from homeassistant.const import ( CONF_CODE, CONF_DEVICE, CONF_NAME, STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_PENDING, - STATE_ALARM_TRIGGERED) + STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED, + STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -31,7 +31,9 @@ _LOGGER = logging.getLogger(__name__) CONF_PAYLOAD_DISARM = 'payload_disarm' CONF_PAYLOAD_ARM_HOME = 'payload_arm_home' CONF_PAYLOAD_ARM_AWAY = 'payload_arm_away' +CONF_PAYLOAD_ARM_NIGHT = 'payload_arm_night' +DEFAULT_ARM_NIGHT = 'ARM_NIGHT' DEFAULT_ARM_AWAY = 'ARM_AWAY' DEFAULT_ARM_HOME = 'ARM_HOME' DEFAULT_DISARM = 'DISARM' @@ -44,6 +46,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({ vol.Required(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_CODE): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PAYLOAD_ARM_NIGHT, default=DEFAULT_ARM_NIGHT): cv.string, vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string, vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string, vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string, @@ -124,7 +127,9 @@ class MqttAlarm(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, def message_received(topic, payload, qos): """Run when new MQTT message has been received.""" if payload not in (STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, - STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING, + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED): _LOGGER.warning("Received unexpected payload: %s", payload) return @@ -213,6 +218,19 @@ class MqttAlarm(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, self._config.get(CONF_QOS), self._config.get(CONF_RETAIN)) + async def async_alarm_arm_night(self, code=None): + """Send arm night command. + + This method is a coroutine. + """ + if not self._validate_code(code, 'arming night'): + return + mqtt.async_publish( + self.hass, self._config.get(CONF_COMMAND_TOPIC), + self._config.get(CONF_PAYLOAD_ARM_NIGHT), + self._config.get(CONF_QOS), + self._config.get(CONF_RETAIN)) + def _validate_code(self, code, state): """Validate given code.""" conf_code = self._config.get(CONF_CODE) diff --git a/tests/components/mqtt/test_alarm_control_panel.py b/tests/components/mqtt/test_alarm_control_panel.py index 572cbdb0e10..81c993ed311 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -6,9 +6,9 @@ from unittest.mock import ANY from homeassistant.components import alarm_control_panel, mqtt from homeassistant.components.mqtt.discovery import async_start from homeassistant.const import ( - STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, - STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNAVAILABLE, - STATE_UNKNOWN) + STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, + STATE_UNAVAILABLE, STATE_UNKNOWN) from homeassistant.setup import setup_component from tests.common import ( @@ -72,8 +72,8 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): self.hass.states.get(entity_id).state for state in (STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, - STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING, - STATE_ALARM_TRIGGERED): + STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED): fire_mqtt_message(self.hass, 'alarm/state', state) self.hass.block_till_done() assert state == self.hass.states.get(entity_id).state @@ -164,6 +164,39 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): self.hass.block_till_done() assert call_count == self.mock_publish.call_count + def test_arm_night_publishes_mqtt(self): + """Test publishing of MQTT messages while armed.""" + assert setup_component(self.hass, alarm_control_panel.DOMAIN, { + alarm_control_panel.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'alarm/state', + 'command_topic': 'alarm/command', + } + }) + + common.alarm_arm_night(self.hass) + self.hass.block_till_done() + self.mock_publish.async_publish.assert_called_once_with( + 'alarm/command', 'ARM_NIGHT', 0, False) + + def test_arm_night_not_publishes_mqtt_with_invalid_code(self): + """Test not publishing of MQTT messages with invalid code.""" + assert setup_component(self.hass, alarm_control_panel.DOMAIN, { + alarm_control_panel.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'alarm/state', + 'command_topic': 'alarm/command', + 'code': '1234' + } + }) + + call_count = self.mock_publish.call_count + common.alarm_arm_night(self.hass, 'abcd') + self.hass.block_till_done() + assert call_count == self.mock_publish.call_count + def test_disarm_publishes_mqtt(self): """Test publishing of MQTT messages while disarmed.""" assert setup_component(self.hass, alarm_control_panel.DOMAIN, { From faeb6295b61bed31b4ce80434e5fa9bbb76b34ea Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 13 Feb 2019 22:52:48 +0100 Subject: [PATCH 185/242] Fix updated file header (#21049) --- homeassistant/components/esphome/__init__.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 4710967a625..004162341b1 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -1,9 +1,4 @@ -""" -Support for esphome devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/esphome/ -""" +"""Support for esphome devices.""" import asyncio import logging from typing import Any, Dict, List, Optional, TYPE_CHECKING, Callable From c2579d1d8abbcfe10d27a5d1d6c579997d96e84f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 13 Feb 2019 15:43:55 -0800 Subject: [PATCH 186/242] Updated frontend to 20190213.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 bf4df366ae3..be2551457d0 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -21,7 +21,7 @@ from homeassistant.loader import bind_hass from .storage import async_setup_frontend_storage -REQUIREMENTS = ['home-assistant-frontend==20190212.0'] +REQUIREMENTS = ['home-assistant-frontend==20190213.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index c9f6b7858b3..71e4df18d17 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -535,7 +535,7 @@ hole==0.3.0 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190212.0 +home-assistant-frontend==20190213.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 726d471f7bc..a4ebc2301e1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -116,7 +116,7 @@ hdate==0.8.7 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190212.0 +home-assistant-frontend==20190213.0 # homeassistant.components.homekit_controller homekit==0.12.2 From 02f207ea8ec4ee0ef65c060cd5ff025f7814e4b8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 13 Feb 2019 15:44:18 -0800 Subject: [PATCH 187/242] Update translations --- .../ambient_station/.translations/da.json | 19 ++++++++++ .../ambient_station/.translations/fr.json | 18 ++++++++++ .../ambient_station/.translations/ko.json | 2 +- .../ambient_station/.translations/nl.json | 19 ++++++++++ .../ambient_station/.translations/no.json | 19 ++++++++++ .../ambient_station/.translations/pl.json | 19 ++++++++++ .../ambient_station/.translations/pt.json | 11 ++++++ .../components/auth/.translations/da.json | 35 +++++++++++++++++++ .../components/auth/.translations/ko.json | 4 +-- .../components/cast/.translations/uk.json | 9 +++++ .../components/daikin/.translations/da.json | 19 ++++++++++ .../components/deconz/.translations/da.json | 13 +++++-- .../components/deconz/.translations/ko.json | 4 +-- .../components/deconz/.translations/ru.json | 2 +- .../dialogflow/.translations/da.json | 18 ++++++++++ .../dialogflow/.translations/ko.json | 2 +- .../components/ebusd/.translations/ca.json | 6 ++++ .../components/ebusd/.translations/da.json | 6 ++++ .../components/ebusd/.translations/de.json | 6 ++++ .../components/ebusd/.translations/en.json | 6 ++++ .../components/ebusd/.translations/it.json | 6 ++++ .../components/ebusd/.translations/ko.json | 6 ++++ .../components/ebusd/.translations/lb.json | 6 ++++ .../components/ebusd/.translations/pl.json | 6 ++++ .../components/ebusd/.translations/ru.json | 6 ++++ .../ebusd/.translations/zh-Hant.json | 6 ++++ .../emulated_roku/.translations/da.json | 21 +++++++++++ .../emulated_roku/.translations/fr.json | 10 ++++++ .../emulated_roku/.translations/nl.json | 21 +++++++++++ .../emulated_roku/.translations/pt.json | 11 ++++++ .../emulated_roku/.translations/ru.json | 5 ++- .../components/esphome/.translations/da.json | 30 ++++++++++++++++ .../components/esphome/.translations/fr.json | 8 +++-- .../components/esphome/.translations/pt.json | 4 +-- .../components/esphome/.translations/uk.json | 28 +++++++++++++++ .../components/geofency/.translations/da.json | 18 ++++++++++ .../components/geofency/.translations/fr.json | 17 +++++++++ .../components/geofency/.translations/ko.json | 2 +- .../components/geofency/.translations/nl.json | 18 ++++++++++ .../components/geofency/.translations/ru.json | 6 ++-- .../gpslogger/.translations/da.json | 18 ++++++++++ .../gpslogger/.translations/fr.json | 18 ++++++++++ .../gpslogger/.translations/ko.json | 2 +- .../gpslogger/.translations/nl.json | 5 +++ .../gpslogger/.translations/ru.json | 3 +- .../components/hangouts/.translations/da.json | 29 +++++++++++++++ .../components/hangouts/.translations/ko.json | 2 +- .../homematicip_cloud/.translations/da.json | 22 ++++++++++-- .../components/hue/.translations/da.json | 2 +- .../components/ifttt/.translations/da.json | 18 ++++++++++ .../components/ifttt/.translations/ko.json | 2 +- .../components/ios/.translations/da.json | 14 ++++++++ .../components/ios/.translations/ru.json | 2 +- .../components/ipma/.translations/ca.json | 19 ++++++++++ .../components/ipma/.translations/da.json | 19 ++++++++++ .../components/ipma/.translations/de.json | 19 ++++++++++ .../components/ipma/.translations/en.json | 1 + .../components/ipma/.translations/ko.json | 19 ++++++++++ .../components/ipma/.translations/lb.json | 19 ++++++++++ .../components/ipma/.translations/nl.json | 19 ++++++++++ .../components/ipma/.translations/pl.json | 19 ++++++++++ .../components/ipma/.translations/pt.json | 19 ++++++++++ .../components/ipma/.translations/ru.json | 19 ++++++++++ .../ipma/.translations/zh-Hant.json | 19 ++++++++++ .../components/lifx/.translations/da.json | 15 ++++++++ .../components/locative/.translations/da.json | 18 ++++++++++ .../components/locative/.translations/fr.json | 8 +++++ .../components/locative/.translations/ko.json | 2 +- .../components/locative/.translations/nl.json | 17 +++++++++ .../components/locative/.translations/ru.json | 3 +- .../locative/.translations/zh-Hans.json | 2 +- .../luftdaten/.translations/da.json | 19 ++++++++++ .../luftdaten/.translations/pt.json | 2 +- .../components/mailgun/.translations/da.json | 18 ++++++++++ .../components/mailgun/.translations/fr.json | 16 +++++++++ .../components/mailgun/.translations/ko.json | 2 +- .../components/mqtt/.translations/da.json | 31 ++++++++++++++++ .../components/mqtt/.translations/ru.json | 2 +- .../components/nest/.translations/da.json | 12 +++++-- .../components/openuv/.translations/da.json | 5 +++ .../components/openuv/.translations/ko.json | 2 +- .../components/openuv/.translations/pl.json | 2 +- .../owntracks/.translations/da.json | 17 +++++++++ .../owntracks/.translations/ko.json | 2 +- .../components/point/.translations/da.json | 32 +++++++++++++++++ .../components/point/.translations/pt.json | 2 +- .../rainmachine/.translations/da.json | 19 ++++++++++ .../rainmachine/.translations/ko.json | 2 +- .../rainmachine/.translations/pt.json | 2 +- .../sensor/.translations/moon.da.json | 9 ++++- .../simplisafe/.translations/da.json | 19 ++++++++++ .../simplisafe/.translations/fr.json | 19 ++++++++++ .../simplisafe/.translations/ko.json | 2 +- .../smartthings/.translations/da.json | 27 ++++++++++++++ .../smartthings/.translations/de.json | 17 +++++++++ .../smartthings/.translations/fr.json | 24 +++++++++++++ .../smartthings/.translations/ko.json | 27 ++++++++++++++ .../smartthings/.translations/nl.json | 27 ++++++++++++++ .../smartthings/.translations/no.json | 27 ++++++++++++++ .../smartthings/.translations/pl.json | 17 +++++++++ .../smartthings/.translations/pt.json | 19 ++++++++++ .../smartthings/.translations/ru.json | 27 ++++++++++++++ .../components/smhi/.translations/da.json | 19 ++++++++++ .../components/smhi/.translations/fr.json | 9 +++-- .../components/smhi/.translations/ru.json | 2 +- .../tellduslive/.translations/da.json | 28 +++++++++++++++ .../tellduslive/.translations/fr.json | 6 ++++ .../tellduslive/.translations/ko.json | 2 +- .../tellduslive/.translations/pt.json | 2 +- .../components/tradfri/.translations/da.json | 23 ++++++++++++ .../components/twilio/.translations/da.json | 18 ++++++++++ .../components/twilio/.translations/fr.json | 18 ++++++++++ .../components/twilio/.translations/ko.json | 2 +- .../components/unifi/.translations/da.json | 26 ++++++++++++++ .../components/unifi/.translations/fr.json | 9 +++-- .../components/upnp/.translations/da.json | 34 ++++++++++++++++++ .../components/upnp/.translations/fr.json | 8 ++++- .../components/upnp/.translations/pt.json | 2 +- .../components/zha/.translations/da.json | 21 +++++++++++ .../components/zha/.translations/pt.json | 4 +-- .../components/zone/.translations/da.json | 3 +- .../components/zwave/.translations/da.json | 22 ++++++++++++ .../components/zwave/.translations/fr.json | 1 + 123 files changed, 1513 insertions(+), 59 deletions(-) create mode 100644 homeassistant/components/ambient_station/.translations/da.json create mode 100644 homeassistant/components/ambient_station/.translations/fr.json create mode 100644 homeassistant/components/ambient_station/.translations/nl.json create mode 100644 homeassistant/components/ambient_station/.translations/no.json create mode 100644 homeassistant/components/ambient_station/.translations/pl.json create mode 100644 homeassistant/components/ambient_station/.translations/pt.json create mode 100644 homeassistant/components/auth/.translations/da.json create mode 100644 homeassistant/components/cast/.translations/uk.json create mode 100644 homeassistant/components/daikin/.translations/da.json create mode 100644 homeassistant/components/dialogflow/.translations/da.json create mode 100644 homeassistant/components/ebusd/.translations/ca.json create mode 100644 homeassistant/components/ebusd/.translations/da.json create mode 100644 homeassistant/components/ebusd/.translations/de.json create mode 100644 homeassistant/components/ebusd/.translations/en.json create mode 100644 homeassistant/components/ebusd/.translations/it.json create mode 100644 homeassistant/components/ebusd/.translations/ko.json create mode 100644 homeassistant/components/ebusd/.translations/lb.json create mode 100644 homeassistant/components/ebusd/.translations/pl.json create mode 100644 homeassistant/components/ebusd/.translations/ru.json create mode 100644 homeassistant/components/ebusd/.translations/zh-Hant.json create mode 100644 homeassistant/components/emulated_roku/.translations/da.json create mode 100644 homeassistant/components/emulated_roku/.translations/fr.json create mode 100644 homeassistant/components/emulated_roku/.translations/nl.json create mode 100644 homeassistant/components/emulated_roku/.translations/pt.json create mode 100644 homeassistant/components/esphome/.translations/da.json create mode 100644 homeassistant/components/esphome/.translations/uk.json create mode 100644 homeassistant/components/geofency/.translations/da.json create mode 100644 homeassistant/components/geofency/.translations/fr.json create mode 100644 homeassistant/components/geofency/.translations/nl.json create mode 100644 homeassistant/components/gpslogger/.translations/da.json create mode 100644 homeassistant/components/gpslogger/.translations/fr.json create mode 100644 homeassistant/components/gpslogger/.translations/nl.json create mode 100644 homeassistant/components/hangouts/.translations/da.json create mode 100644 homeassistant/components/ifttt/.translations/da.json create mode 100644 homeassistant/components/ios/.translations/da.json create mode 100644 homeassistant/components/ipma/.translations/ca.json create mode 100644 homeassistant/components/ipma/.translations/da.json create mode 100644 homeassistant/components/ipma/.translations/de.json create mode 100644 homeassistant/components/ipma/.translations/ko.json create mode 100644 homeassistant/components/ipma/.translations/lb.json create mode 100644 homeassistant/components/ipma/.translations/nl.json create mode 100644 homeassistant/components/ipma/.translations/pl.json create mode 100644 homeassistant/components/ipma/.translations/pt.json create mode 100644 homeassistant/components/ipma/.translations/ru.json create mode 100644 homeassistant/components/ipma/.translations/zh-Hant.json create mode 100644 homeassistant/components/lifx/.translations/da.json create mode 100644 homeassistant/components/locative/.translations/da.json create mode 100644 homeassistant/components/locative/.translations/fr.json create mode 100644 homeassistant/components/locative/.translations/nl.json create mode 100644 homeassistant/components/luftdaten/.translations/da.json create mode 100644 homeassistant/components/mailgun/.translations/da.json create mode 100644 homeassistant/components/mailgun/.translations/fr.json create mode 100644 homeassistant/components/mqtt/.translations/da.json create mode 100644 homeassistant/components/owntracks/.translations/da.json create mode 100644 homeassistant/components/point/.translations/da.json create mode 100644 homeassistant/components/rainmachine/.translations/da.json create mode 100644 homeassistant/components/simplisafe/.translations/da.json create mode 100644 homeassistant/components/simplisafe/.translations/fr.json create mode 100644 homeassistant/components/smartthings/.translations/da.json create mode 100644 homeassistant/components/smartthings/.translations/de.json create mode 100644 homeassistant/components/smartthings/.translations/fr.json create mode 100644 homeassistant/components/smartthings/.translations/ko.json create mode 100644 homeassistant/components/smartthings/.translations/nl.json create mode 100644 homeassistant/components/smartthings/.translations/no.json create mode 100644 homeassistant/components/smartthings/.translations/pt.json create mode 100644 homeassistant/components/smartthings/.translations/ru.json create mode 100644 homeassistant/components/smhi/.translations/da.json create mode 100644 homeassistant/components/tellduslive/.translations/da.json create mode 100644 homeassistant/components/tradfri/.translations/da.json create mode 100644 homeassistant/components/twilio/.translations/da.json create mode 100644 homeassistant/components/twilio/.translations/fr.json create mode 100644 homeassistant/components/unifi/.translations/da.json create mode 100644 homeassistant/components/upnp/.translations/da.json create mode 100644 homeassistant/components/zha/.translations/da.json create mode 100644 homeassistant/components/zwave/.translations/da.json diff --git a/homeassistant/components/ambient_station/.translations/da.json b/homeassistant/components/ambient_station/.translations/da.json new file mode 100644 index 00000000000..ac3d86a995b --- /dev/null +++ b/homeassistant/components/ambient_station/.translations/da.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "identifier_exists": "Applikationsn\u00f8gle og/eller API n\u00f8gle er allerede registreret", + "invalid_key": "Ugyldig API n\u00f8gle og/eller applikationsn\u00f8gle", + "no_devices": "Ingen enheder fundet i konto" + }, + "step": { + "user": { + "data": { + "api_key": "API n\u00f8gle", + "app_key": "Applikationsn\u00f8gle" + }, + "title": "Udfyld dine oplysninger" + } + }, + "title": "Ambient PWS" + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/.translations/fr.json b/homeassistant/components/ambient_station/.translations/fr.json new file mode 100644 index 00000000000..ede25d0bd4b --- /dev/null +++ b/homeassistant/components/ambient_station/.translations/fr.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "identifier_exists": "Cl\u00e9 d'application et / ou cl\u00e9 API d\u00e9j\u00e0 enregistr\u00e9e", + "invalid_key": "Cl\u00e9 d'API et / ou cl\u00e9 d'application non valide", + "no_devices": "Aucun appareil trouv\u00e9 dans le compte" + }, + "step": { + "user": { + "data": { + "api_key": "Cl\u00e9 d'API", + "app_key": "Cl\u00e9 d'application" + }, + "title": "Veuillez saisir vos informations" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/.translations/ko.json b/homeassistant/components/ambient_station/.translations/ko.json index 51a09514159..c316741d36b 100644 --- a/homeassistant/components/ambient_station/.translations/ko.json +++ b/homeassistant/components/ambient_station/.translations/ko.json @@ -11,7 +11,7 @@ "api_key": "API \ud0a4", "app_key": "Application \ud0a4" }, - "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694" + "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" } }, "title": "Ambient PWS" diff --git a/homeassistant/components/ambient_station/.translations/nl.json b/homeassistant/components/ambient_station/.translations/nl.json new file mode 100644 index 00000000000..a070128eefe --- /dev/null +++ b/homeassistant/components/ambient_station/.translations/nl.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "identifier_exists": "Applicatiesleutel en/of API-sleutel al geregistreerd", + "invalid_key": "Ongeldige API-sleutel en/of applicatiesleutel", + "no_devices": "Geen apparaten gevonden in account" + }, + "step": { + "user": { + "data": { + "api_key": "API-sleutel", + "app_key": "Applicatiesleutel" + }, + "title": "Vul uw gegevens in" + } + }, + "title": "Ambient PWS" + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/.translations/no.json b/homeassistant/components/ambient_station/.translations/no.json new file mode 100644 index 00000000000..0b9d377718b --- /dev/null +++ b/homeassistant/components/ambient_station/.translations/no.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "identifier_exists": "Programn\u00f8kkel og/eller API-n\u00f8kkel er allerede registrert", + "invalid_key": "Ugyldig API-n\u00f8kkel og/eller programn\u00f8kkel", + "no_devices": "Ingen enheter funnet i kontoen" + }, + "step": { + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "app_key": "Applikasjonsn\u00f8kkel" + }, + "title": "Fyll ut informasjonen din" + } + }, + "title": "Ambient PWS" + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/.translations/pl.json b/homeassistant/components/ambient_station/.translations/pl.json new file mode 100644 index 00000000000..2140b4e29fe --- /dev/null +++ b/homeassistant/components/ambient_station/.translations/pl.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "identifier_exists": "Klucz aplikacji i/lub klucz API ju\u017c jest zarejestrowany", + "invalid_key": "Nieprawid\u0142owy klucz API i/lub klucz aplikacji", + "no_devices": "Nie znaleziono urz\u0105dze\u0144 na koncie" + }, + "step": { + "user": { + "data": { + "api_key": "Klucz API", + "app_key": "Klucz aplikacji" + }, + "title": "Wprowad\u017a swoje dane" + } + }, + "title": "Ambient PWS" + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/.translations/pt.json b/homeassistant/components/ambient_station/.translations/pt.json new file mode 100644 index 00000000000..01078bbddfe --- /dev/null +++ b/homeassistant/components/ambient_station/.translations/pt.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "api_key": "Chave de API" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/auth/.translations/da.json b/homeassistant/components/auth/.translations/da.json new file mode 100644 index 00000000000..f461f376d16 --- /dev/null +++ b/homeassistant/components/auth/.translations/da.json @@ -0,0 +1,35 @@ +{ + "mfa_setup": { + "notify": { + "abort": { + "no_available_service": "Ingen underretningstjenester til r\u00e5dighed." + }, + "error": { + "invalid_code": "Ugyldig kode, pr\u00f8v venligst igen." + }, + "step": { + "init": { + "description": "V\u00e6lg venligst en af meddelelsestjenesterne:", + "title": "Ops\u00e6t engangsadgangskode, der er leveret af besked komponenten" + }, + "setup": { + "description": "En engangsadgangskode er blevet sendt via **notify.{notify_service}**. Indtast den venligst nedenunder:", + "title": "Bekr\u00e6ft ops\u00e6tningen" + } + }, + "title": "Advis\u00e9r engangskodeord" + }, + "totp": { + "error": { + "invalid_code": "Ugyldig kode, pr\u00f8v venligst igen. Hvis du konsekvent f\u00e5r denne fejl skal du s\u00f8rge for at uret p\u00e5 dit Home Assistant system er g\u00e5r n\u00f8jagtigt." + }, + "step": { + "init": { + "description": "Hvis du vil aktivere tofaktorautentificering ved hj\u00e6lp af tidsbaserede engangskoder skal du scanne QR-koden med din autentificeringsapp. Hvis du ikke har en anbefaler vi enten [Google Authenticator] (https://support.google.com/accounts/answer/1066447) eller [Authy] (https://authy.com/). \n\n {qr_code} \n \nN\u00e5r du har scannet koden skal du indtaste den sekscifrede kode fra din app for at bekr\u00e6fte ops\u00e6tningen. Hvis du har problemer med at scanne QR-koden skal du lave en manuel ops\u00e6tning med kode **`{code}`**.", + "title": "Konfigurer to-faktors godkendelse ved hj\u00e6lp af TOTP" + } + }, + "title": "TOTP" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/auth/.translations/ko.json b/homeassistant/components/auth/.translations/ko.json index 7efc50d534c..c5278c63f79 100644 --- a/homeassistant/components/auth/.translations/ko.json +++ b/homeassistant/components/auth/.translations/ko.json @@ -13,7 +13,7 @@ "title": "\uc54c\ub9bc \uad6c\uc131\uc694\uc18c\uac00 \uc81c\uacf5\ud558\ub294 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638 \uc124\uc815" }, "setup": { - "description": "**notify.{notify_service}** \uc5d0\uc11c \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \ubcf4\ub0c8\uc2b5\ub2c8\ub2e4. \uc544\ub798\uc758 \uacf5\ub780\uc5d0 \uc785\ub825\ud574 \uc8fc\uc138\uc694:", + "description": "**notify.{notify_service}** \uc5d0\uc11c \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \ubcf4\ub0c8\uc2b5\ub2c8\ub2e4. \uc544\ub798\uc758 \uacf5\ub780\uc5d0 \uc785\ub825\ud574\uc8fc\uc138\uc694:", "title": "\uc124\uc815 \ud655\uc778" } }, @@ -25,7 +25,7 @@ }, "step": { "init": { - "description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574 \uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [Google OTP](https://support.google.com/accounts/answer/1066447) \ub610\ub294 [Authy](https://authy.com/) \ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc11c \uc124\uc815\uc744 \ud655\uc778\ud558\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\ubcf4\uc138\uc694.", + "description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574\uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [Google OTP](https://support.google.com/accounts/answer/1066447) \ub610\ub294 [Authy](https://authy.com/) \ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc11c \uc124\uc815\uc744 \ud655\uc778\ud558\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\ubcf4\uc138\uc694.", "title": "TOTP \ub97c \uc0ac\uc6a9\ud558\uc5ec 2\ub2e8\uacc4 \uc778\uc99d \uad6c\uc131" } }, diff --git a/homeassistant/components/cast/.translations/uk.json b/homeassistant/components/cast/.translations/uk.json new file mode 100644 index 00000000000..783defdca25 --- /dev/null +++ b/homeassistant/components/cast/.translations/uk.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "confirm": { + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Google Cast?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/daikin/.translations/da.json b/homeassistant/components/daikin/.translations/da.json new file mode 100644 index 00000000000..856bb1445c7 --- /dev/null +++ b/homeassistant/components/daikin/.translations/da.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Enheden er allerede konfigureret", + "device_fail": "Uventet fejl ved oprettelse af enhed.", + "device_timeout": "Timeout ved tilslutning til enheden." + }, + "step": { + "user": { + "data": { + "host": "V\u00e6rt" + }, + "description": "Indtast IP-adresse p\u00e5 dit Daikin AC.", + "title": "Konfigurer Daikin AC" + } + }, + "title": "Daikin AC" + } +} \ No newline at end of file diff --git a/homeassistant/components/deconz/.translations/da.json b/homeassistant/components/deconz/.translations/da.json index 7f9aad83160..e4e5f098a4d 100644 --- a/homeassistant/components/deconz/.translations/da.json +++ b/homeassistant/components/deconz/.translations/da.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "Bridge er allerede konfigureret", "no_bridges": "Ingen deConz bridge fundet", "one_instance_only": "Komponenten underst\u00f8tter kun \u00e9n deCONZ forekomst" }, @@ -11,8 +12,13 @@ "init": { "data": { "host": "V\u00e6rt", - "port": "Port (standardv\u00e6rdi: '80')" - } + "port": "Port" + }, + "title": "Definer deCONZ gateway" + }, + "link": { + "description": "L\u00e5s din deCONZ-gateway op for at registrere dig med Home Assistant. \n\n 1. G\u00e5 til deCONZ settings -> Gateway -> Advanced\n 2. Tryk p\u00e5 knappen \"Authenticate app\"", + "title": "Link med deCONZ" }, "options": { "data": { @@ -21,6 +27,7 @@ }, "title": "Ekstra konfiguration valgmuligheder for deCONZ" } - } + }, + "title": "deCONZ Zigbee gateway" } } \ No newline at end of file diff --git a/homeassistant/components/deconz/.translations/ko.json b/homeassistant/components/deconz/.translations/ko.json index a501951540b..6a527ab0a0b 100644 --- a/homeassistant/components/deconz/.translations/ko.json +++ b/homeassistant/components/deconz/.translations/ko.json @@ -12,12 +12,12 @@ "init": { "data": { "host": "\ud638\uc2a4\ud2b8", - "port": "\ud3ec\ud2b8 (\uae30\ubcf8\uac12: '80')" + "port": "\ud3ec\ud2b8" }, "title": "deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774 \uc815\uc758" }, "link": { - "description": "deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \uc5b8\ub77d\ud558\uc5ec Home Assistant \uc5d0 \uc5f0\uacb0\ud558\uae30\n\n1. deCONZ \uc2dc\uc2a4\ud15c \uc124\uc815\uc73c\ub85c \uc774\ub3d9\ud558\uc138\uc694\n2. \"Unlock Gateway\" \ubc84\ud2bc\uc744 \ub204\ub974\uc138\uc694 ", + "description": "deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \uc5b8\ub77d\ud558\uc5ec Home Assistant \uc5d0 \uc5f0\uacb0\ud558\uae30.\n\n1. deCONZ \uc2dc\uc2a4\ud15c \uc124\uc815\uc73c\ub85c \uc774\ub3d9\ud558\uc138\uc694\n2. \"Authenticate app\" \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694", "title": "deCONZ\uc640 \uc5f0\uacb0" }, "options": { diff --git a/homeassistant/components/deconz/.translations/ru.json b/homeassistant/components/deconz/.translations/ru.json index 3ff60254a6a..c92f1562157 100644 --- a/homeassistant/components/deconz/.translations/ru.json +++ b/homeassistant/components/deconz/.translations/ru.json @@ -17,7 +17,7 @@ "title": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0448\u043b\u044e\u0437 deCONZ" }, "link": { - "description": "\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0439\u0442\u0435 \u0448\u043b\u044e\u0437 deCONZ \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0432 Home Assistant:\n\n1. \u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u044b deCONZ\n2. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u00ab\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0448\u043b\u044e\u0437\u00bb", + "description": "\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0439\u0442\u0435 \u0448\u043b\u044e\u0437 deCONZ \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0432 Home Assistant:\n\n1. \u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u044b deCONZ -> Gateway -> Advanced\n2. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u00abAuthenticate app\u00bb", "title": "\u0421\u0432\u044f\u0437\u044c \u0441 deCONZ" }, "options": { diff --git a/homeassistant/components/dialogflow/.translations/da.json b/homeassistant/components/dialogflow/.translations/da.json new file mode 100644 index 00000000000..2fb203450a5 --- /dev/null +++ b/homeassistant/components/dialogflow/.translations/da.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Dit Home Assistant system skal v\u00e6re tilg\u00e6ngeligt fra internettet for at modtage Dialogflow meddelelser.", + "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning" + }, + "create_entry": { + "default": "For at sende begivenheder til Home Assistant skal du konfigurere [Webhook integration med Dialogflow]({dialogflow_url}).\n\n Udfyld f\u00f8lgende oplysninger: \n\n - URL: `{webhook_url}`\n - Metode: POST\n - Indholdstype: application/json\n\n Se [dokumentationen]({docs_url}) for yderligere oplysninger." + }, + "step": { + "user": { + "description": "Er du sikker p\u00e5 at du vil konfigurere Dialogflow?", + "title": "Konfigurer Dialogflow Webhook" + } + }, + "title": "Dialogflow" + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/.translations/ko.json b/homeassistant/components/dialogflow/.translations/ko.json index cf53f81bdb8..33c465bf0e7 100644 --- a/homeassistant/components/dialogflow/.translations/ko.json +++ b/homeassistant/components/dialogflow/.translations/ko.json @@ -5,7 +5,7 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Dialogflow Webhook]({dialogflow_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694." + "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Dialogflow Webhook]({dialogflow_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/ebusd/.translations/ca.json b/homeassistant/components/ebusd/.translations/ca.json new file mode 100644 index 00000000000..88b76539deb --- /dev/null +++ b/homeassistant/components/ebusd/.translations/ca.json @@ -0,0 +1,6 @@ +{ + "state": { + "day": "Dia", + "night": "Nit" + } +} \ No newline at end of file diff --git a/homeassistant/components/ebusd/.translations/da.json b/homeassistant/components/ebusd/.translations/da.json new file mode 100644 index 00000000000..00b499e2a39 --- /dev/null +++ b/homeassistant/components/ebusd/.translations/da.json @@ -0,0 +1,6 @@ +{ + "state": { + "day": "Dag", + "night": "Nat" + } +} \ No newline at end of file diff --git a/homeassistant/components/ebusd/.translations/de.json b/homeassistant/components/ebusd/.translations/de.json new file mode 100644 index 00000000000..347c6e6eeb5 --- /dev/null +++ b/homeassistant/components/ebusd/.translations/de.json @@ -0,0 +1,6 @@ +{ + "state": { + "day": "Tag", + "night": "Nacht" + } +} \ No newline at end of file diff --git a/homeassistant/components/ebusd/.translations/en.json b/homeassistant/components/ebusd/.translations/en.json new file mode 100644 index 00000000000..abe5fff8fec --- /dev/null +++ b/homeassistant/components/ebusd/.translations/en.json @@ -0,0 +1,6 @@ +{ + "state": { + "day": "Day", + "night": "Night" + } +} \ No newline at end of file diff --git a/homeassistant/components/ebusd/.translations/it.json b/homeassistant/components/ebusd/.translations/it.json new file mode 100644 index 00000000000..dd70cfd2c6e --- /dev/null +++ b/homeassistant/components/ebusd/.translations/it.json @@ -0,0 +1,6 @@ +{ + "state": { + "day": "Giorno", + "night": "Notte" + } +} \ No newline at end of file diff --git a/homeassistant/components/ebusd/.translations/ko.json b/homeassistant/components/ebusd/.translations/ko.json new file mode 100644 index 00000000000..5a302af79e1 --- /dev/null +++ b/homeassistant/components/ebusd/.translations/ko.json @@ -0,0 +1,6 @@ +{ + "state": { + "day": "\uc8fc\uac04", + "night": "\uc57c\uac04" + } +} \ No newline at end of file diff --git a/homeassistant/components/ebusd/.translations/lb.json b/homeassistant/components/ebusd/.translations/lb.json new file mode 100644 index 00000000000..624744de470 --- /dev/null +++ b/homeassistant/components/ebusd/.translations/lb.json @@ -0,0 +1,6 @@ +{ + "state": { + "day": "Dag", + "night": "Nuecht" + } +} \ No newline at end of file diff --git a/homeassistant/components/ebusd/.translations/pl.json b/homeassistant/components/ebusd/.translations/pl.json new file mode 100644 index 00000000000..0c926a0335c --- /dev/null +++ b/homeassistant/components/ebusd/.translations/pl.json @@ -0,0 +1,6 @@ +{ + "state": { + "day": "Dzie\u0144", + "night": "Noc" + } +} \ No newline at end of file diff --git a/homeassistant/components/ebusd/.translations/ru.json b/homeassistant/components/ebusd/.translations/ru.json new file mode 100644 index 00000000000..7b013a4d7bc --- /dev/null +++ b/homeassistant/components/ebusd/.translations/ru.json @@ -0,0 +1,6 @@ +{ + "state": { + "day": "\u0414\u0435\u043d\u044c", + "night": "\u041d\u043e\u0447\u044c" + } +} \ No newline at end of file diff --git a/homeassistant/components/ebusd/.translations/zh-Hant.json b/homeassistant/components/ebusd/.translations/zh-Hant.json new file mode 100644 index 00000000000..1d2851acb6b --- /dev/null +++ b/homeassistant/components/ebusd/.translations/zh-Hant.json @@ -0,0 +1,6 @@ +{ + "state": { + "day": "\u767d\u5929", + "night": "\u591c\u665a" + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/.translations/da.json b/homeassistant/components/emulated_roku/.translations/da.json new file mode 100644 index 00000000000..0479dee437d --- /dev/null +++ b/homeassistant/components/emulated_roku/.translations/da.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "name_exists": "Navnet findes allerede" + }, + "step": { + "user": { + "data": { + "advertise_ip": "Adviserings IP", + "advertise_port": "Adviserings port", + "host_ip": "V\u00e6rt IP", + "listen_port": "Lytte port", + "name": "Navn", + "upnp_bind_multicast": "Bind multicast (sand/falsk)" + }, + "title": "Angiv server konfiguration" + } + }, + "title": "EmulatedRoku" + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/.translations/fr.json b/homeassistant/components/emulated_roku/.translations/fr.json new file mode 100644 index 00000000000..5da2d437a35 --- /dev/null +++ b/homeassistant/components/emulated_roku/.translations/fr.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "user": { + "title": "D\u00e9finir la configuration du serveur" + } + }, + "title": "EmulatedRoku" + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/.translations/nl.json b/homeassistant/components/emulated_roku/.translations/nl.json new file mode 100644 index 00000000000..fe26cda31e2 --- /dev/null +++ b/homeassistant/components/emulated_roku/.translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "name_exists": "Naam bestaat al" + }, + "step": { + "user": { + "data": { + "advertise_ip": "Adverteer IP", + "advertise_port": "Adverterenpoort", + "host_ip": "Host IP", + "listen_port": "Luisterpoort", + "name": "Naam", + "upnp_bind_multicast": "Bind multicast (waar/niet waar)" + }, + "title": "Serverconfiguratie defini\u00ebren" + } + }, + "title": "EmulatedRoku" + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/.translations/pt.json b/homeassistant/components/emulated_roku/.translations/pt.json new file mode 100644 index 00000000000..286cd58dd89 --- /dev/null +++ b/homeassistant/components/emulated_roku/.translations/pt.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/.translations/ru.json b/homeassistant/components/emulated_roku/.translations/ru.json index 611b5647233..c7b85c19592 100644 --- a/homeassistant/components/emulated_roku/.translations/ru.json +++ b/homeassistant/components/emulated_roku/.translations/ru.json @@ -6,9 +6,12 @@ "step": { "user": { "data": { + "advertise_ip": "\u041e\u0431\u044a\u044f\u0432\u043b\u044f\u0442\u044c IP", + "advertise_port": "\u041e\u0431\u044a\u044f\u0432\u043b\u044f\u0442\u044c \u043f\u043e\u0440\u0442", "host_ip": "\u0425\u043e\u0441\u0442", "listen_port": "\u041f\u043e\u0440\u0442", - "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "upnp_bind_multicast": "\u041f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c multicast (True/False)" }, "title": "EmulatedRoku" } diff --git a/homeassistant/components/esphome/.translations/da.json b/homeassistant/components/esphome/.translations/da.json new file mode 100644 index 00000000000..20224ec0d15 --- /dev/null +++ b/homeassistant/components/esphome/.translations/da.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "ESP er allerede konfigureret" + }, + "error": { + "connection_error": "Kan ikke oprette forbindelse til ESP. S\u00f8rg for, at din YAML-fil indeholder en 'api:' linje.", + "invalid_password": "Ugyldig adgangskode!", + "resolve_error": "Kan ikke finde adressen p\u00e5 ESP. Hvis denne fejl forts\u00e6tter skal du angive en statisk IP-adresse: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" + }, + "step": { + "authenticate": { + "data": { + "password": "Adgangskode" + }, + "description": "Indtast venligst den adgangskode, du har angivet i din konfiguration.", + "title": "Indtast adgangskode" + }, + "user": { + "data": { + "host": "V\u00e6rt", + "port": "Port" + }, + "description": "Angiv forbindelsesindstillinger for din [ESPHome](https://esphomelib.com/) node.", + "title": "ESPHome" + } + }, + "title": "ESPHome" + } +} \ No newline at end of file diff --git a/homeassistant/components/esphome/.translations/fr.json b/homeassistant/components/esphome/.translations/fr.json index a021f1fe9f4..cebe6848f1b 100644 --- a/homeassistant/components/esphome/.translations/fr.json +++ b/homeassistant/components/esphome/.translations/fr.json @@ -8,14 +8,18 @@ "data": { "password": "Mot de passe" }, + "description": "Veuillez saisir le mot de passe que vous avez d\u00e9fini dans votre configuration.", "title": "Entrer votre mot de passe" }, "user": { "data": { "host": "H\u00f4te", "port": "Port" - } + }, + "description": "Veuillez saisir les param\u00e8tres de connexion de votre n\u0153ud [ESPHome] (https://esphomelib.com/).", + "title": "ESPHome" } - } + }, + "title": "ESPHome" } } \ No newline at end of file diff --git a/homeassistant/components/esphome/.translations/pt.json b/homeassistant/components/esphome/.translations/pt.json index 70e21e14666..ea1e25c3024 100644 --- a/homeassistant/components/esphome/.translations/pt.json +++ b/homeassistant/components/esphome/.translations/pt.json @@ -22,9 +22,9 @@ "port": "Porta" }, "description": "Por favor, insira as configura\u00e7\u00f5es de liga\u00e7\u00e3o ao seu n\u00f3 [ESPHome] (https://esphomelib.com/).", - "title": "" + "title": "ESPHome" } }, - "title": "" + "title": "ESPHome" } } \ No newline at end of file diff --git a/homeassistant/components/esphome/.translations/uk.json b/homeassistant/components/esphome/.translations/uk.json new file mode 100644 index 00000000000..94dafeb3c2e --- /dev/null +++ b/homeassistant/components/esphome/.translations/uk.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "ESP \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e" + }, + "error": { + "connection_error": "\u041d\u0435 \u0432\u0434\u0430\u0454\u0442\u044c\u0441\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0434\u043e ESP. \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0444\u0430\u0439\u043b YAML \u043c\u0456\u0441\u0442\u0438\u0442\u044c \u0440\u044f\u0434\u043e\u043a \"api:\".", + "invalid_password": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043f\u0430\u0440\u043e\u043b\u044c!", + "resolve_error": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0430\u0434\u0440\u0435\u0441\u0443 ESP. \u042f\u043a\u0449\u043e \u0446\u044f \u043f\u043e\u043c\u0438\u043b\u043a\u0430 \u043d\u0435 \u0437\u043d\u0438\u043a\u0430\u0454, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0456\u0442\u044c \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u0443 IP-\u0430\u0434\u0440\u0435\u0441\u0443: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" + }, + "step": { + "authenticate": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c, \u044f\u043a\u0438\u0439 \u0432\u0438 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0438 \u0443 \u0441\u0432\u043e\u0457\u0439 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457.", + "title": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0432\u0430\u0448\u043e\u0433\u043e \u0432\u0443\u0437\u043b\u0430 [ESPHome] (https://esphomelib.com/)." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/.translations/da.json b/homeassistant/components/geofency/.translations/da.json new file mode 100644 index 00000000000..1390dfb504a --- /dev/null +++ b/homeassistant/components/geofency/.translations/da.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Dit Home Assistant system skal v\u00e6re tilg\u00e6ngeligt fra internettet for at modtage Geofency meddelelser.", + "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning" + }, + "create_entry": { + "default": "For at sende begivenheder til Home Assistant skal du konfigurere webhook funktionen i Geofency.\n\n Udfyld f\u00f8lgende oplysninger: \n\n - URL: `{webhook_url}`\n - Metode: POST\n \n Se [dokumentationen]({docs_url}) for yderligere oplysninger." + }, + "step": { + "user": { + "description": "Er du sikker p\u00e5 at du vil konfigurere Geofency Webhook?", + "title": "Ops\u00e6tning af Geofency Webhook" + } + }, + "title": "Geofency Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/.translations/fr.json b/homeassistant/components/geofency/.translations/fr.json new file mode 100644 index 00000000000..142f40754b9 --- /dev/null +++ b/homeassistant/components/geofency/.translations/fr.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Votre instance Home Assistant doit \u00eatre accessible \u00e0 partir d'Internet pour recevoir les messages Geofency.", + "one_instance_allowed": "Une seule instance est n\u00e9cessaire." + }, + "create_entry": { + "default": "Pour envoyer des \u00e9v\u00e9nements \u00e0 Home Assistant, vous devez configurer la fonctionnalit\u00e9 Webhook dans Geofency. \n\n Remplissez les informations suivantes: \n\n - URL: ` {webhook_url} ` \n - M\u00e9thode: POST \n\n Voir [la documentation] ( {docs_url} ) pour plus de d\u00e9tails." + }, + "step": { + "user": { + "description": "\u00cates-vous s\u00fbr de vouloir configurer le Webhook Geofency ?", + "title": "Configurer le Webhook Geofency" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/.translations/ko.json b/homeassistant/components/geofency/.translations/ko.json index 8a857acbdc6..db60ec18fe1 100644 --- a/homeassistant/components/geofency/.translations/ko.json +++ b/homeassistant/components/geofency/.translations/ko.json @@ -5,7 +5,7 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Geofency \uc5d0\uc11c Webhook \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694." + "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Geofency \uc5d0\uc11c Webhook \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/geofency/.translations/nl.json b/homeassistant/components/geofency/.translations/nl.json new file mode 100644 index 00000000000..04aec33b5d6 --- /dev/null +++ b/homeassistant/components/geofency/.translations/nl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Uw Home Assistant instantie moet toegankelijk zijn vanaf het internet om Geofency-berichten te ontvangen.", + "one_instance_allowed": "Slechts \u00e9\u00e9n instantie is nodig." + }, + "create_entry": { + "default": "Om locaties naar Home Assistant te sturen, moet u de Webhook-functie instellen in Geofency.\n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Methode: POST \n\n Zie [de documentatie]({docs_url}) voor meer informatie." + }, + "step": { + "user": { + "description": "Weet u zeker dat u de Geofency Webhook wilt instellen?", + "title": "Geofency Webhook instellen" + } + }, + "title": "Geofency Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/.translations/ru.json b/homeassistant/components/geofency/.translations/ru.json index 34290b35f42..2460e28393a 100644 --- a/homeassistant/components/geofency/.translations/ru.json +++ b/homeassistant/components/geofency/.translations/ru.json @@ -9,10 +9,10 @@ }, "step": { "user": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Geofency Webhook?", - "title": "Geofency Webhook" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Geofency?", + "title": "Geofency" } }, - "title": "Geofency Webhook" + "title": "Geofency" } } \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/da.json b/homeassistant/components/gpslogger/.translations/da.json new file mode 100644 index 00000000000..6d5c2185718 --- /dev/null +++ b/homeassistant/components/gpslogger/.translations/da.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Dit Home Assistant system skal v\u00e6re tilg\u00e6ngeligt fra internettet for at modtage GPSLogger meddelelser.", + "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning" + }, + "create_entry": { + "default": "For at sende begivenheder til Home Assistant skal du konfigurere webhook funktionen i GPSLogger.\n\n Udfyld f\u00f8lgende oplysninger: \n\n - URL: `{webhook_url}`\n - Metode: POST\n \n Se [dokumentationen]({docs_url}) for yderligere oplysninger." + }, + "step": { + "user": { + "description": "Er du sikker p\u00e5 at du vil konfigurere GPSLogger Webhook?", + "title": "Konfigurer GPSLogger Webhook" + } + }, + "title": "GPSLogger Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/fr.json b/homeassistant/components/gpslogger/.translations/fr.json new file mode 100644 index 00000000000..ae2b2177712 --- /dev/null +++ b/homeassistant/components/gpslogger/.translations/fr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Votre instance Home Assistant doit \u00eatre accessible \u00e0 partir d'Internet pour recevoir les messages GPSLogger.", + "one_instance_allowed": "Une seule instance est n\u00e9cessaire." + }, + "create_entry": { + "default": "Pour envoyer des \u00e9v\u00e9nements \u00e0 Home Assistant, vous devez configurer la fonction Webhook dans GPSLogger. \n\n Remplissez les informations suivantes: \n\n - URL: ` {webhook_url} ` \n - M\u00e9thode: POST \n\n Voir [la documentation] ( {docs_url} ) pour plus de d\u00e9tails." + }, + "step": { + "user": { + "description": "\u00cates-vous s\u00fbr de vouloir configurer le Webhook GPSLogger ?", + "title": "Configurer le Webhook GPSLogger" + } + }, + "title": "Webhook GPSLogger" + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/ko.json b/homeassistant/components/gpslogger/.translations/ko.json index a65e51d7cae..2c8881034ff 100644 --- a/homeassistant/components/gpslogger/.translations/ko.json +++ b/homeassistant/components/gpslogger/.translations/ko.json @@ -5,7 +5,7 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 GPSLogger \uc5d0\uc11c Webhook \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694." + "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 GPSLogger \uc5d0\uc11c Webhook \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/gpslogger/.translations/nl.json b/homeassistant/components/gpslogger/.translations/nl.json new file mode 100644 index 00000000000..d0dece65a0f --- /dev/null +++ b/homeassistant/components/gpslogger/.translations/nl.json @@ -0,0 +1,5 @@ +{ + "config": { + "title": "GPSLogger Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/.translations/ru.json b/homeassistant/components/gpslogger/.translations/ru.json index 34b7e907288..ac9c1c2d43e 100644 --- a/homeassistant/components/gpslogger/.translations/ru.json +++ b/homeassistant/components/gpslogger/.translations/ru.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 GPSLogger." + "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 GPSLogger.", + "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." }, "create_entry": { "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c webhooks \u0434\u043b\u044f GPSLogger.\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438." diff --git a/homeassistant/components/hangouts/.translations/da.json b/homeassistant/components/hangouts/.translations/da.json new file mode 100644 index 00000000000..079b57722e2 --- /dev/null +++ b/homeassistant/components/hangouts/.translations/da.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Google Hangouts er allerede konfigureret", + "unknown": "Ukendt fejl opstod" + }, + "error": { + "invalid_2fa": "Ugyldig 2-faktor godkendelse, pr\u00f8v venligst igen.", + "invalid_2fa_method": "Ugyldig 2FA-metode (Bekr\u00e6ft p\u00e5 telefon).", + "invalid_login": "Ugyldig login, pr\u00f8v venligst igen." + }, + "step": { + "2fa": { + "data": { + "2fa": "2FA pin" + }, + "title": "To-faktor autentificering" + }, + "user": { + "data": { + "email": "Email adresse", + "password": "Adgangskode" + }, + "title": "Google Hangouts login" + } + }, + "title": "Google Hangouts" + } +} \ No newline at end of file diff --git a/homeassistant/components/hangouts/.translations/ko.json b/homeassistant/components/hangouts/.translations/ko.json index af0e76829e5..b1bcf5725be 100644 --- a/homeassistant/components/hangouts/.translations/ko.json +++ b/homeassistant/components/hangouts/.translations/ko.json @@ -5,7 +5,7 @@ "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "invalid_2fa": "2\ub2e8\uacc4 \uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574 \uc8fc\uc138\uc694.", + "invalid_2fa": "2\ub2e8\uacc4 \uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "invalid_2fa_method": "2\ub2e8\uacc4 \uc778\uc99d \ubc29\ubc95\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. (\uc804\ud654\uae30\uc5d0\uc11c \ud655\uc778)", "invalid_login": "\uc798\ubabb\ub41c \ub85c\uadf8\uc778\uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." }, diff --git a/homeassistant/components/homematicip_cloud/.translations/da.json b/homeassistant/components/homematicip_cloud/.translations/da.json index 7473b4a7b86..4b8371fc748 100644 --- a/homeassistant/components/homematicip_cloud/.translations/da.json +++ b/homeassistant/components/homematicip_cloud/.translations/da.json @@ -1,14 +1,30 @@ { "config": { + "abort": { + "already_configured": "Access point er allerede konfigureret", + "connection_aborted": "Kunne ikke oprette forbindelse til HMIP-serveren", + "unknown": "Ukendt fejl opstod" + }, "error": { - "invalid_pin": "Ugyldig PIN, pr\u00f8v igen." + "invalid_pin": "Ugyldig PIN, pr\u00f8v igen.", + "press_the_button": "Tryk venligst p\u00e5 den bl\u00e5 knap.", + "register_failed": "Fejl ved registrering, pr\u00f8v venligst igen.", + "timeout_button": "Tryk p\u00e5 bl\u00e5 knap timeout, pr\u00f8v venligst igen." }, "step": { "init": { "data": { + "hapid": "Access point ID (SGTIN)", + "name": "Navn (valgfrit, bruges som pr\u00e6fiks til navnet for alle enheder)", "pin": "Pin kode (valgfri)" - } + }, + "title": "V\u00e6lg HomematicIP Access point" + }, + "link": { + "description": "Tryk p\u00e5 den bl\u00e5 knap p\u00e5 adgangspunktet og send knappen for at registrere HomematicIP med Home Assistant.\n\n ![Placering af knap p\u00e5 bridge](/static/images/config_flows/config_homematicip_cloud.png)", + "title": "Link adgangspunkt" } - } + }, + "title": "HomematicIP Cloud" } } \ No newline at end of file diff --git a/homeassistant/components/hue/.translations/da.json b/homeassistant/components/hue/.translations/da.json index 19e60b073d3..08bad3e91ea 100644 --- a/homeassistant/components/hue/.translations/da.json +++ b/homeassistant/components/hue/.translations/da.json @@ -24,6 +24,6 @@ "title": "Link Hub" } }, - "title": "Philips Hue Bridge" + "title": "Philips Hue" } } \ No newline at end of file diff --git a/homeassistant/components/ifttt/.translations/da.json b/homeassistant/components/ifttt/.translations/da.json new file mode 100644 index 00000000000..25c502ed05e --- /dev/null +++ b/homeassistant/components/ifttt/.translations/da.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Dit Home Assistant system skal v\u00e6re tilg\u00e6ngeligt fra internettet for at modtage IFTTT meddelelser.", + "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning" + }, + "create_entry": { + "default": "For at sende begivenheder til Home Assistant skal du bruge handlingen \"Foretag en web foresp\u00f8rgsel\" fra [IFTTT Webhook applet] ({applet_url}).\n\n Udfyld f\u00f8lgende oplysninger: \n\n - URL: `{webhook_url}`\n - Metode: POST\n - Indholdstype: application/json\n\n Se [dokumentationen] ({docs_url}) om hvordan du konfigurerer automatiseringer til at h\u00e5ndtere indg\u00e5ende data." + }, + "step": { + "user": { + "description": "Er du sikker p\u00e5 at du vil oprette IFTTT?", + "title": "Konfigurer IFTTT Webhook Applet" + } + }, + "title": "IFTTT" + } +} \ No newline at end of file diff --git a/homeassistant/components/ifttt/.translations/ko.json b/homeassistant/components/ifttt/.translations/ko.json index bb54f7ef6cb..75bdd0d99c8 100644 --- a/homeassistant/components/ifttt/.translations/ko.json +++ b/homeassistant/components/ifttt/.translations/ko.json @@ -5,7 +5,7 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\uae30 \uc704\ud574\uc11c\ub294 [IFTTT Webhook \uc560\ud50c\ub9bf]({applet_url}) \uc5d0\uc11c \"Make a web request\" \ub97c \uc0ac\uc6a9\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c\uc758 \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694.\n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nHome Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694." + "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\uae30 \uc704\ud574\uc11c\ub294 [IFTTT Webhook \uc560\ud50c\ub9bf]({applet_url}) \uc5d0\uc11c \"Make a web request\" \ub97c \uc0ac\uc6a9\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c\uc758 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nHome Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/ios/.translations/da.json b/homeassistant/components/ios/.translations/da.json new file mode 100644 index 00000000000..4a900097b14 --- /dev/null +++ b/homeassistant/components/ios/.translations/da.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning af Home Assistant iOS" + }, + "step": { + "confirm": { + "description": "Er du sikker p\u00e5 at du vil konfigurere Home Assistant iOS?", + "title": "Home Assistant iOS" + } + }, + "title": "Home Assistant iOS" + } +} \ No newline at end of file diff --git a/homeassistant/components/ios/.translations/ru.json b/homeassistant/components/ios/.translations/ru.json index fdcc964a0e6..282715ebb3b 100644 --- a/homeassistant/components/ios/.translations/ru.json +++ b/homeassistant/components/ios/.translations/ru.json @@ -5,7 +5,7 @@ }, "step": { "confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 Home Assistant iOS?", + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Home Assistant iOS?", "title": "Home Assistant iOS" } }, diff --git a/homeassistant/components/ipma/.translations/ca.json b/homeassistant/components/ipma/.translations/ca.json new file mode 100644 index 00000000000..29dbaa4f58d --- /dev/null +++ b/homeassistant/components/ipma/.translations/ca.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "name_exists": "El nom ja existeix" + }, + "step": { + "user": { + "data": { + "latitude": "Latitud", + "longitude": "Longitud", + "name": "Nom" + }, + "description": "Instituto Portugu\u00eas do Mar e Atmosfera", + "title": "Ubicaci\u00f3" + } + }, + "title": "Servei meteorol\u00f2gic portugu\u00e8s (IPMA)" + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/.translations/da.json b/homeassistant/components/ipma/.translations/da.json new file mode 100644 index 00000000000..080c41429ba --- /dev/null +++ b/homeassistant/components/ipma/.translations/da.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "name_exists": "Navnet findes allerede" + }, + "step": { + "user": { + "data": { + "latitude": "Breddegrad", + "longitude": "L\u00e6ngdegrad", + "name": "Navn" + }, + "description": "Instituto Portugu\u00eas do Mar e Atmosfera", + "title": "Beliggenhed" + } + }, + "title": "Portugisisk vejrservice (IPMA)" + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/.translations/de.json b/homeassistant/components/ipma/.translations/de.json new file mode 100644 index 00000000000..9e717b77843 --- /dev/null +++ b/homeassistant/components/ipma/.translations/de.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "name_exists": "Name existiert bereits" + }, + "step": { + "user": { + "data": { + "latitude": "Breitengrad", + "longitude": "L\u00e4ngengrad", + "name": "Name" + }, + "description": "Instituto Portugu\u00eas do Mar e Atmosfera", + "title": "Standort" + } + }, + "title": "Portugiesischer Wetterdienst (IPMA)" + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/.translations/en.json b/homeassistant/components/ipma/.translations/en.json index d1386757305..15459b91f2a 100644 --- a/homeassistant/components/ipma/.translations/en.json +++ b/homeassistant/components/ipma/.translations/en.json @@ -10,6 +10,7 @@ "longitude": "Longitude", "name": "Name" }, + "description": "Instituto Portugu\u00eas do Mar e Atmosfera", "title": "Location" } }, diff --git a/homeassistant/components/ipma/.translations/ko.json b/homeassistant/components/ipma/.translations/ko.json new file mode 100644 index 00000000000..828733c9195 --- /dev/null +++ b/homeassistant/components/ipma/.translations/ko.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "name_exists": "\uc774\ub984\uc774 \uc774\ubbf8 \uc874\uc7ac\ud569\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "latitude": "\uc704\ub3c4", + "longitude": "\uacbd\ub3c4", + "name": "\uc774\ub984" + }, + "description": "\ud3ec\ub974\ud22c\uac08 \ud574\uc591 \ubc0f \ub300\uae30 \uc5f0\uad6c\uc18c (Instituto Portugu\u00eas do Mar e Atmosfera)", + "title": "\uc704\uce58" + } + }, + "title": "\ud3ec\ub974\ud22c\uac08 \uae30\uc0c1 \uc11c\ube44\uc2a4 (IPMA)" + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/.translations/lb.json b/homeassistant/components/ipma/.translations/lb.json new file mode 100644 index 00000000000..c9eb3a01941 --- /dev/null +++ b/homeassistant/components/ipma/.translations/lb.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "name_exists": "Numm g\u00ebtt et schonn" + }, + "step": { + "user": { + "data": { + "latitude": "Breedegrad", + "longitude": "L\u00e4ngegrad", + "name": "Numm" + }, + "description": "Instituto Portugu\u00eas do Mar e Atmosfera", + "title": "Uertschaft" + } + }, + "title": "Portugisesche Wieder D\u00e9ngscht (IPMA)" + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/.translations/nl.json b/homeassistant/components/ipma/.translations/nl.json new file mode 100644 index 00000000000..bc10eb3573e --- /dev/null +++ b/homeassistant/components/ipma/.translations/nl.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "name_exists": "Naam bestaat al" + }, + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Naam" + }, + "description": "Instituto Portugu\u00eas do Mar e Atmosfera", + "title": "Locatie" + } + }, + "title": "Portugese weerservice (IPMA)" + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/.translations/pl.json b/homeassistant/components/ipma/.translations/pl.json new file mode 100644 index 00000000000..735f5a4a126 --- /dev/null +++ b/homeassistant/components/ipma/.translations/pl.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "name_exists": "Nazwa ju\u017c istnieje" + }, + "step": { + "user": { + "data": { + "latitude": "Szeroko\u015b\u0107 geograficzna", + "longitude": "D\u0142ugo\u015b\u0107 geograficzna", + "name": "Nazwa" + }, + "description": "Portugalski Instytut Morza i Atmosfery", + "title": "Lokalizacja" + } + }, + "title": "Portugalski serwis pogodowy (IPMA)" + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/.translations/pt.json b/homeassistant/components/ipma/.translations/pt.json new file mode 100644 index 00000000000..2ddeb9a4b33 --- /dev/null +++ b/homeassistant/components/ipma/.translations/pt.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "name_exists": "Nome j\u00e1 existente" + }, + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nome" + }, + "description": "Instituto Portugu\u00eas do Mar e Atmosfera", + "title": "Localiza\u00e7\u00e3o" + } + }, + "title": "Servi\u00e7o Meteorol\u00f3gico Portugu\u00eas (IPMA)" + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/.translations/ru.json b/homeassistant/components/ipma/.translations/ru.json new file mode 100644 index 00000000000..f49852d5c0c --- /dev/null +++ b/homeassistant/components/ipma/.translations/ru.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "name_exists": "\u0418\u043c\u044f \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + }, + "step": { + "user": { + "data": { + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" + }, + "description": "\u041f\u043e\u0440\u0442\u0443\u0433\u0430\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0441\u0442\u0438\u0442\u0443\u0442 \u043c\u043e\u0440\u044f \u0438 \u0430\u0442\u043c\u043e\u0441\u0444\u0435\u0440\u044b", + "title": "\u041c\u0435\u0441\u0442\u043e\u043d\u0430\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435" + } + }, + "title": "\u041c\u0435\u0442\u0435\u043e\u0440\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u041f\u043e\u0440\u0442\u0443\u0433\u0430\u043b\u0438\u0438 (IPMA)" + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/.translations/zh-Hant.json b/homeassistant/components/ipma/.translations/zh-Hant.json new file mode 100644 index 00000000000..25c832e51c6 --- /dev/null +++ b/homeassistant/components/ipma/.translations/zh-Hant.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "name_exists": "\u8a72\u540d\u7a31\u5df2\u5b58\u5728" + }, + "step": { + "user": { + "data": { + "latitude": "\u7def\u5ea6", + "longitude": "\u7d93\u5ea6", + "name": "\u540d\u7a31" + }, + "description": "Instituto Portugu\u00eas do Mar e Atmosfera", + "title": "\u5ea7\u6a19" + } + }, + "title": "\u8461\u8404\u7259\u6c23\u8c61\u670d\u52d9\uff08IPMA\uff09" + } +} \ No newline at end of file diff --git a/homeassistant/components/lifx/.translations/da.json b/homeassistant/components/lifx/.translations/da.json new file mode 100644 index 00000000000..ffd8e20ce42 --- /dev/null +++ b/homeassistant/components/lifx/.translations/da.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "no_devices_found": "Ingen LIFX enheder kunne findes p\u00e5 netv\u00e6rket.", + "single_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning af LIFX." + }, + "step": { + "confirm": { + "description": "Konfigurer LIFX?", + "title": "LIFX" + } + }, + "title": "LIFX" + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/.translations/da.json b/homeassistant/components/locative/.translations/da.json new file mode 100644 index 00000000000..8211d52fa5d --- /dev/null +++ b/homeassistant/components/locative/.translations/da.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Dit Home Assistant system skal v\u00e6re tilg\u00e6ngeligt fra internettet for at modtage Geofency meddelelser.", + "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning" + }, + "create_entry": { + "default": "For at sende lokationer til Home Assistant skal du konfigurere webhook funktionen i Locative applicationen.\n\n Udfyld f\u00f8lgende oplysninger: \n\n - URL: `{webhook_url}`\n - Metode: POST\n \n Se [dokumentationen]({docs_url}) for yderligere oplysninger." + }, + "step": { + "user": { + "description": "Er du sikker p\u00e5 at du vil konfigurere Locative Webhook?", + "title": "Konfigurer Locative Webhook" + } + }, + "title": "Locative Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/.translations/fr.json b/homeassistant/components/locative/.translations/fr.json new file mode 100644 index 00000000000..81950c49b4c --- /dev/null +++ b/homeassistant/components/locative/.translations/fr.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Votre instance Home Assistant doit \u00eatre accessible \u00e0 partir d'Internet pour recevoir les messages Geofency.", + "one_instance_allowed": "Une seule instance est n\u00e9cessaire." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/.translations/ko.json b/homeassistant/components/locative/.translations/ko.json index a57b27cdd75..92e6775ea27 100644 --- a/homeassistant/components/locative/.translations/ko.json +++ b/homeassistant/components/locative/.translations/ko.json @@ -5,7 +5,7 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Locative \uc571\uc5d0\uc11c Webhook \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694." + "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Locative \uc571\uc5d0\uc11c Webhook \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/locative/.translations/nl.json b/homeassistant/components/locative/.translations/nl.json new file mode 100644 index 00000000000..237d21c46ee --- /dev/null +++ b/homeassistant/components/locative/.translations/nl.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "Slechts \u00e9\u00e9n instantie is nodig." + }, + "create_entry": { + "default": "Om locaties naar Home Assistant te sturen, moet u de Webhook-functie instellen in de Locative app. \n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Methode: POST \n\n Zie [de documentatie]({docs_url}) voor meer informatie." + }, + "step": { + "user": { + "description": "Weet u zeker dat u de Locative Webhook wilt instellen?", + "title": "Stel de Locative Webhook in" + } + }, + "title": "Locative Webhook" + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/.translations/ru.json b/homeassistant/components/locative/.translations/ru.json index d8b8d55a608..ff07393da04 100644 --- a/homeassistant/components/locative/.translations/ru.json +++ b/homeassistant/components/locative/.translations/ru.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 Locative." + "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 Locative.", + "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." }, "create_entry": { "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c webhooks \u0434\u043b\u044f Locative.\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438." diff --git a/homeassistant/components/locative/.translations/zh-Hans.json b/homeassistant/components/locative/.translations/zh-Hans.json index d98793d96e5..96626a57c5b 100644 --- a/homeassistant/components/locative/.translations/zh-Hans.json +++ b/homeassistant/components/locative/.translations/zh-Hans.json @@ -2,7 +2,7 @@ "config": { "abort": { "not_internet_accessible": "\u60a8\u7684Home Assistant\u5b9e\u4f8b\u9700\u8981\u53ef\u4ee5\u4eceInternet\u8bbf\u95ee\u4ee5\u63a5\u6536\u6765\u81eaGeofency\u7684\u6d88\u606f\u3002", - "one_instance_allowed": "\u53ea\u9700\u8981\u4e00\u4e2a\u5b9e\u4f8b\u3002" + "one_instance_allowed": "\u53ea\u6709\u4e00\u4e2a\u5b9e\u4f8b\u662f\u5fc5\u9700\u7684\u3002" }, "step": { "user": { diff --git a/homeassistant/components/luftdaten/.translations/da.json b/homeassistant/components/luftdaten/.translations/da.json new file mode 100644 index 00000000000..d43fc1128ae --- /dev/null +++ b/homeassistant/components/luftdaten/.translations/da.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "communication_error": "Kan ikke oprette forbindelse til Luftdaten API", + "invalid_sensor": "Sensor ikke tilg\u00e6ngelig eller ugyldig", + "sensor_exists": "Sensor er allerede registreret" + }, + "step": { + "user": { + "data": { + "show_on_map": "Vis p\u00e5 kort", + "station_id": "Luftdaten Sensor ID" + }, + "title": "Definer Luftdaten" + } + }, + "title": "Luftdaten" + } +} \ No newline at end of file diff --git a/homeassistant/components/luftdaten/.translations/pt.json b/homeassistant/components/luftdaten/.translations/pt.json index 0402f352c5c..9ed3611da27 100644 --- a/homeassistant/components/luftdaten/.translations/pt.json +++ b/homeassistant/components/luftdaten/.translations/pt.json @@ -14,6 +14,6 @@ "title": "Definir Luftdaten" } }, - "title": "" + "title": "Luftdaten" } } \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/da.json b/homeassistant/components/mailgun/.translations/da.json new file mode 100644 index 00000000000..0e25974031d --- /dev/null +++ b/homeassistant/components/mailgun/.translations/da.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Dit Home Assistant system skal v\u00e6re tilg\u00e6ngeligt fra internettet for at modtage Mailgun meddelelser.", + "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning." + }, + "create_entry": { + "default": "For at sende begivenheder til Home Assistant skal du konfigurere [Webhooks med Mailgun]({mailgun_url}).\n\n Udfyld f\u00f8lgende oplysninger: \n\n - URL: `{webhook_url}`\n - Metode: POST\n - Indholdstype: application/json\n\n Se [dokumentationen] ({docs_url}) om hvordan du konfigurerer automatiseringer til at h\u00e5ndtere indg\u00e5ende data." + }, + "step": { + "user": { + "description": "Er du sikker p\u00e5 at du vil konfigurere Mailgun?", + "title": "Konfigurer Mailgun Webhook" + } + }, + "title": "Mailgun" + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/fr.json b/homeassistant/components/mailgun/.translations/fr.json new file mode 100644 index 00000000000..905715de727 --- /dev/null +++ b/homeassistant/components/mailgun/.translations/fr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Votre instance Home Assistant doit \u00eatre accessible \u00e0 partir d'Internet pour recevoir les messages Mailgun.", + "one_instance_allowed": "Une seule instance est n\u00e9cessaire." + }, + "create_entry": { + "default": "Pour envoyer des \u00e9v\u00e9nements \u00e0 Home Assistant, vous devez configurer [Webhooks avec Mailgun] ( {mailgun_url} ). \n\n Remplissez les informations suivantes: \n\n - URL: ` {webhook_url} ` \n - M\u00e9thode: POST \n - Type de contenu: application / json \n\n Voir [la documentation] ( {docs_url} ) pour savoir comment configurer les automatisations pour g\u00e9rer les donn\u00e9es entrantes." + }, + "step": { + "user": { + "description": "\u00cates-vous s\u00fbr de vouloir configurer Mailgun?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/ko.json b/homeassistant/components/mailgun/.translations/ko.json index 95897a25f15..ae973bdc93d 100644 --- a/homeassistant/components/mailgun/.translations/ko.json +++ b/homeassistant/components/mailgun/.translations/ko.json @@ -5,7 +5,7 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Mailgun Webhook]({mailgun_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/x-www-form-urlencoded\n \nHome Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694." + "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Mailgun Webhook]({mailgun_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \nHome Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/mqtt/.translations/da.json b/homeassistant/components/mqtt/.translations/da.json new file mode 100644 index 00000000000..ebe5696f514 --- /dev/null +++ b/homeassistant/components/mqtt/.translations/da.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning af MQTT" + }, + "error": { + "cannot_connect": "Kunne ikke oprette forbindelse til broker" + }, + "step": { + "broker": { + "data": { + "broker": "Broker", + "discovery": "Aktiv\u00e9r opdagelse", + "password": "Adgangskode", + "port": "Port", + "username": "Brugernavn" + }, + "description": "Indtast venligst forbindelsesindstillinger for din MQTT broker.", + "title": "MQTT" + }, + "hassio_confirm": { + "data": { + "discovery": "Aktiv\u00e9r opdagelse" + }, + "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til MQTT brokeren, der leveres af hass.io add-on {addon}?", + "title": "MQTT Broker via Hass.io add-on" + } + }, + "title": "MQTT" + } +} \ No newline at end of file diff --git a/homeassistant/components/mqtt/.translations/ru.json b/homeassistant/components/mqtt/.translations/ru.json index 9757716b1bf..663d79f3c14 100644 --- a/homeassistant/components/mqtt/.translations/ru.json +++ b/homeassistant/components/mqtt/.translations/ru.json @@ -22,7 +22,7 @@ "data": { "discovery": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0430\u0432\u0442\u043e\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432" }, - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Home Assistant \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0431\u0440\u043e\u043a\u0435\u0440\u0443 MQTT \u0447\u0435\u0440\u0435\u0437 \u0430\u0434\u0434\u043e\u043d Hass.io {addon}?", + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0431\u0440\u043e\u043a\u0435\u0440\u0443 MQTT \u0447\u0435\u0440\u0435\u0437 \u0430\u0434\u0434\u043e\u043d Hass.io {addon}?", "title": "\u0411\u0440\u043e\u043a\u0435\u0440 MQTT \u0447\u0435\u0440\u0435\u0437 \u0430\u0434\u0434\u043e\u043d Hass.io" } }, diff --git a/homeassistant/components/nest/.translations/da.json b/homeassistant/components/nest/.translations/da.json index 5edf3a00af4..7dfd1c8b250 100644 --- a/homeassistant/components/nest/.translations/da.json +++ b/homeassistant/components/nest/.translations/da.json @@ -1,22 +1,30 @@ { "config": { "abort": { - "already_setup": "Du kan kun konfigurere en enkelt Nest konto." + "already_setup": "Du kan kun konfigurere en enkelt Nest konto.", + "authorize_url_fail": "Ukendt fejl ved generering af en autoriseret url.", + "authorize_url_timeout": "Timeout ved generering af autoriseret url.", + "no_flows": "Du skal konfigurere Nest f\u00f8r du kan autentificere med det. [L\u00e6s venligst vejledningen](https://www.home-assistant.io/components/nest/)." }, "error": { - "invalid_code": "Ugyldig kode" + "internal_error": "Intern fejl ved validering af kode", + "invalid_code": "Ugyldig kode", + "timeout": "Timeout ved validering af kode", + "unknown": "Ukendt fejl ved validering af kode" }, "step": { "init": { "data": { "flow_impl": "Udbyder" }, + "description": "V\u00e6lg hvilken godkendelsesudbyder du vil godkende med Nest.", "title": "Godkendelses udbyder" }, "link": { "data": { "code": "PIN-kode" }, + "description": "For at forbinde din Nest-konto, [godkend din konto]({url}). \n\nEfter godkendelse skal du kopiere pin koden nedenfor.", "title": "Link Nest-konto" } }, diff --git a/homeassistant/components/openuv/.translations/da.json b/homeassistant/components/openuv/.translations/da.json index 5cda5c6e663..a783c8646e0 100644 --- a/homeassistant/components/openuv/.translations/da.json +++ b/homeassistant/components/openuv/.translations/da.json @@ -1,9 +1,14 @@ { "config": { + "error": { + "identifier_exists": "Koordinater er allerede registreret", + "invalid_api_key": "Ugyldig API n\u00f8gle" + }, "step": { "user": { "data": { "api_key": "OpenUV API N\u00f8gle", + "elevation": "Elevation", "latitude": "Breddegrad", "longitude": "L\u00e6ngdegrad" }, diff --git a/homeassistant/components/openuv/.translations/ko.json b/homeassistant/components/openuv/.translations/ko.json index bb054f0b3a6..5e06be81d31 100644 --- a/homeassistant/components/openuv/.translations/ko.json +++ b/homeassistant/components/openuv/.translations/ko.json @@ -12,7 +12,7 @@ "latitude": "\uc704\ub3c4", "longitude": "\uacbd\ub3c4" }, - "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694" + "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" } }, "title": "OpenUV" diff --git a/homeassistant/components/openuv/.translations/pl.json b/homeassistant/components/openuv/.translations/pl.json index f6c52ffd04e..2c4c47e8da4 100644 --- a/homeassistant/components/openuv/.translations/pl.json +++ b/homeassistant/components/openuv/.translations/pl.json @@ -12,7 +12,7 @@ "latitude": "Szeroko\u015b\u0107 geograficzna", "longitude": "D\u0142ugo\u015b\u0107 geograficzna" }, - "title": "Wpisz swoje informacje" + "title": "Wprowad\u017a swoje dane" } }, "title": "OpenUV" diff --git a/homeassistant/components/owntracks/.translations/da.json b/homeassistant/components/owntracks/.translations/da.json new file mode 100644 index 00000000000..7f4053f8ead --- /dev/null +++ b/homeassistant/components/owntracks/.translations/da.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning" + }, + "create_entry": { + "default": "\n\n P\u00e5 Android skal du \u00e5bne [OwnTracks applikationen]({android_url}), g\u00e5 til indstillinger -> forbindelse. Skift f\u00f8lgende indstillinger: \n - Tilstand: Privat HTTP\n - V\u00e6rt: {webhook_url}\n - Identifikation:\n - Brugernavn: ` ` \n - Enheds-id: ` ` \n\n P\u00e5 iOS skal du \u00e5bne [OwnTracks applikationen]({ios_url}), tryk p\u00e5 (i) ikonet \u00f8verst til venstre -> indstillinger. Skift f\u00f8lgende indstillinger: \n - Tilstand: HTTP\n - URL: {webhook_url}\n - Aktiver godkendelse \n - Bruger ID: ` ` \n\n {secret}\n \n Se [dokumentationen]({docs_url}) for at f\u00e5 flere oplysninger." + }, + "step": { + "user": { + "description": "Er du sikker p\u00e5 at du vil konfigurere OwnTracks?", + "title": "Konfigurer OwnTracks" + } + }, + "title": "OwnTracks" + } +} \ No newline at end of file diff --git a/homeassistant/components/owntracks/.translations/ko.json b/homeassistant/components/owntracks/.translations/ko.json index ba264ad4b47..d70ca8b114e 100644 --- a/homeassistant/components/owntracks/.translations/ko.json +++ b/homeassistant/components/owntracks/.translations/ko.json @@ -4,7 +4,7 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "\n\nAndroid \uc778 \uacbd\uc6b0, [OwnTracks \uc571]({android_url}) \uc744 \uc5f4\uace0 preferences -> connection \uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \ub2e4\uc74c\uacfc \uac19\uc774 \uc124\uc815\ud574\uc8fc\uc138\uc694:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: ``\n - Device ID: ``\n\niOS \uc778 \uacbd\uc6b0, [OwnTracks \uc571]({ios_url}) \uc744 \uc5f4\uace0 \uc67c\ucabd \uc0c1\ub2e8\uc758 (i) \uc544\uc774\ucf58\uc744 \ud0ed\ud558\uc5ec \uc124\uc815\uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \ub2e4\uc74c\uacfc \uac19\uc774 \uc124\uc815\ud574\uc8fc\uc138\uc694:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: ``\n\n{secret} \n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694." + "default": "\n\nAndroid \uc778 \uacbd\uc6b0, [OwnTracks \uc571]({android_url}) \uc744 \uc5f4\uace0 preferences -> connection \uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \ub2e4\uc74c\uacfc \uac19\uc774 \uc124\uc815\ud574\uc8fc\uc138\uc694:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: ``\n - Device ID: ``\n\niOS \uc778 \uacbd\uc6b0, [OwnTracks \uc571]({ios_url}) \uc744 \uc5f4\uace0 \uc67c\ucabd \uc0c1\ub2e8\uc758 (i) \uc544\uc774\ucf58\uc744 \ud0ed\ud558\uc5ec \uc124\uc815\uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \ub2e4\uc74c\uacfc \uac19\uc774 \uc124\uc815\ud574\uc8fc\uc138\uc694:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: ``\n\n{secret} \n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/point/.translations/da.json b/homeassistant/components/point/.translations/da.json new file mode 100644 index 00000000000..109bcbe6c37 --- /dev/null +++ b/homeassistant/components/point/.translations/da.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_setup": "Du kan kun konfigurere en enkelt Point konto.", + "authorize_url_fail": "Ukendt fejl ved generering af en autoriseret url.", + "authorize_url_timeout": "Timeout ved generering af autoriseret url.", + "external_setup": "Point er konfigureret med succes fra et andet flow.", + "no_flows": "Du skal konfigurere Point f\u00f8r du kan godkende med det. [L\u00e6s venligst vejledningen](https://www.home-assistant.io/components/point/)." + }, + "create_entry": { + "default": "Godkendt med Minut mod Point enhed(er)" + }, + "error": { + "follow_link": "F\u00f8lg linket og godkend f\u00f8r du trykker p\u00e5 send", + "no_token": "Ikke godkendt med Minut" + }, + "step": { + "auth": { + "description": "F\u00f8lg linket herunder og Accept\u00e9r adgang til din Minut konto. Vend s\u00e5 tilbage og tryk p\u00e5 Tilf\u00f8j nedenfor. \n\n [Link]({authorization_url})", + "title": "Godkend Point" + }, + "user": { + "data": { + "flow_impl": "Udbyder" + }, + "description": "V\u00e6lg hvilken godkendelsesudbyder du vil godkende med Point.", + "title": "Godkendelses udbyder" + } + }, + "title": "Minut Point" + } +} \ No newline at end of file diff --git a/homeassistant/components/point/.translations/pt.json b/homeassistant/components/point/.translations/pt.json index 6d24d56233c..874f0832b6c 100644 --- a/homeassistant/components/point/.translations/pt.json +++ b/homeassistant/components/point/.translations/pt.json @@ -27,6 +27,6 @@ "title": "Fornecedor de Autentica\u00e7\u00e3o" } }, - "title": "" + "title": "Minut Point" } } \ No newline at end of file diff --git a/homeassistant/components/rainmachine/.translations/da.json b/homeassistant/components/rainmachine/.translations/da.json new file mode 100644 index 00000000000..61d29894fe2 --- /dev/null +++ b/homeassistant/components/rainmachine/.translations/da.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "identifier_exists": "Konto er allerede registreret", + "invalid_credentials": "Ugyldige legitimationsoplysninger" + }, + "step": { + "user": { + "data": { + "ip_address": "V\u00e6rtsnavn eller IP-adresse", + "password": "Password", + "port": "Port" + }, + "title": "Udfyld dine oplysninger" + } + }, + "title": "RainMachine" + } +} \ No newline at end of file diff --git a/homeassistant/components/rainmachine/.translations/ko.json b/homeassistant/components/rainmachine/.translations/ko.json index 0885c7e9e66..5ce254c4026 100644 --- a/homeassistant/components/rainmachine/.translations/ko.json +++ b/homeassistant/components/rainmachine/.translations/ko.json @@ -11,7 +11,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8" }, - "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694" + "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" } }, "title": "RainMachine" diff --git a/homeassistant/components/rainmachine/.translations/pt.json b/homeassistant/components/rainmachine/.translations/pt.json index 97b9f84f26c..12e77ed8e46 100644 --- a/homeassistant/components/rainmachine/.translations/pt.json +++ b/homeassistant/components/rainmachine/.translations/pt.json @@ -14,6 +14,6 @@ "title": "Preencha as suas informa\u00e7\u00f5es" } }, - "title": "" + "title": "RainMachine" } } \ No newline at end of file diff --git a/homeassistant/components/sensor/.translations/moon.da.json b/homeassistant/components/sensor/.translations/moon.da.json index 330df6d0cf7..c2406de68bb 100644 --- a/homeassistant/components/sensor/.translations/moon.da.json +++ b/homeassistant/components/sensor/.translations/moon.da.json @@ -1,5 +1,12 @@ { "state": { - "full_moon": "Fuldm\u00e5ne" + "first_quarter": "F\u00f8rste kvartal", + "full_moon": "Fuldm\u00e5ne", + "last_quarter": "Sidste kvartal", + "new_moon": "Nym\u00e5ne", + "waning_crescent": "Aftagende halvm\u00e5ne", + "waning_gibbous": "Aftagende m\u00e5ne", + "waxing_crescent": "Tiltagende halvm\u00e5ne", + "waxing_gibbous": "Tiltagende m\u00e5ne" } } \ No newline at end of file diff --git a/homeassistant/components/simplisafe/.translations/da.json b/homeassistant/components/simplisafe/.translations/da.json new file mode 100644 index 00000000000..3ec3d7b456c --- /dev/null +++ b/homeassistant/components/simplisafe/.translations/da.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "identifier_exists": "Konto er allerede registreret", + "invalid_credentials": "Ugyldige legitimationsoplysninger" + }, + "step": { + "user": { + "data": { + "code": "Kode (til Home Assistant)", + "password": "Adgangskode", + "username": "Email adresse" + }, + "title": "Udfyld dine oplysninger" + } + }, + "title": "SimpliSafe" + } +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/.translations/fr.json b/homeassistant/components/simplisafe/.translations/fr.json new file mode 100644 index 00000000000..de05edea8c9 --- /dev/null +++ b/homeassistant/components/simplisafe/.translations/fr.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "identifier_exists": "Compte d\u00e9j\u00e0 enregistr\u00e9", + "invalid_credentials": "Informations d'identification invalides" + }, + "step": { + "user": { + "data": { + "code": "Code (pour Home Assistant)", + "password": "Mot de passe", + "username": "Adresse e-mail" + }, + "title": "Veuillez saisir vos informations" + } + }, + "title": "SimpliSafe" + } +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/.translations/ko.json b/homeassistant/components/simplisafe/.translations/ko.json index 5426c564e03..8fb056e3f93 100644 --- a/homeassistant/components/simplisafe/.translations/ko.json +++ b/homeassistant/components/simplisafe/.translations/ko.json @@ -11,7 +11,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc774\uba54\uc77c \uc8fc\uc18c" }, - "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694" + "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" } }, "title": "SimpliSafe" diff --git a/homeassistant/components/smartthings/.translations/da.json b/homeassistant/components/smartthings/.translations/da.json new file mode 100644 index 00000000000..1c571b4e639 --- /dev/null +++ b/homeassistant/components/smartthings/.translations/da.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "app_not_installed": "S\u00f8rg for at du har installeret og autoriseret Home Assistant SmartApp og pr\u00f8v igen.", + "app_setup_error": "SmartApp kunne ikke konfigureres. Pr\u00f8v igen.", + "base_url_not_https": "`base_url` til` http` komponenten skal konfigureres og starte med `https://`.", + "token_already_setup": "Token er allerede konfigureret.", + "token_forbidden": "Adgangstoken er ikke indenfor OAuth", + "token_invalid_format": "Adgangstoken skal v\u00e6re i UID/GUID format", + "token_unauthorized": "Adgangstoken er ugyldigt eller ikke l\u00e6ngere godkendt." + }, + "step": { + "user": { + "data": { + "access_token": "Adgangstoken" + }, + "description": "Indtast venligst en SmartThings [Personal Access Token]({token_url}), som er oprettet if\u00f8lge [instruktionerne]({component_url}).", + "title": "Indtast personlig adgangstoken" + }, + "wait_install": { + "description": "Installer Home Assistant SmartApp mindst et sted og klik p\u00e5 send.", + "title": "Installer SmartApp" + } + }, + "title": "SmartThings" + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/.translations/de.json b/homeassistant/components/smartthings/.translations/de.json new file mode 100644 index 00000000000..f65c338bf03 --- /dev/null +++ b/homeassistant/components/smartthings/.translations/de.json @@ -0,0 +1,17 @@ +{ + "config": { + "step": { + "user": { + "data": { + "access_token": "Zugangstoken" + }, + "description": "Bitte gib einen SmartThings [pers\u00f6nlichen Zugangstoken]({token_url}) ein, welcher gem\u00e4\u00df den [Anweisungen]({component_url}) erstellt wurde.", + "title": "Gib den pers\u00f6nlichen Zugangstoken an" + }, + "wait_install": { + "title": "SmartApp installieren" + } + }, + "title": "SmartThings" + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/.translations/fr.json b/homeassistant/components/smartthings/.translations/fr.json new file mode 100644 index 00000000000..6503634b92c --- /dev/null +++ b/homeassistant/components/smartthings/.translations/fr.json @@ -0,0 +1,24 @@ +{ + "config": { + "error": { + "app_not_installed": "Assurez-vous d'avoir install\u00e9 et autoris\u00e9 l'application Home Assistant SmartApp, puis r\u00e9essayez.", + "app_setup_error": "Impossible de configurer la SmartApp. Veuillez r\u00e9essayer.", + "base_url_not_https": "Le param\u00e8tre `base_url` du composant` http` doit \u00eatre configur\u00e9 et commencer par `https: //`.", + "token_already_setup": "Le jeton a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9.", + "token_invalid_format": "Le jeton doit \u00eatre au format UID / GUID", + "token_unauthorized": "Le jeton est invalide ou n'est plus autoris\u00e9." + }, + "step": { + "user": { + "data": { + "access_token": "Jeton d'acc\u00e8s" + }, + "title": "Entrer un jeton d'acc\u00e8s personnel" + }, + "wait_install": { + "title": "Installer SmartApp" + } + }, + "title": "SmartThings" + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/.translations/ko.json b/homeassistant/components/smartthings/.translations/ko.json new file mode 100644 index 00000000000..e4131543d50 --- /dev/null +++ b/homeassistant/components/smartthings/.translations/ko.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "app_not_installed": "Home Assistant SmartApp \uc744 \uc124\uce58\ud558\uace0 \uc778\uc99d\ud588\ub294\uc9c0 \ud655\uc778\ud558\uace0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "app_setup_error": "SmartApp \uc744 \uc124\uc815\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "base_url_not_https": "`http` \uad6c\uc131\uc694\uc18c\ub97c \uc704\ud55c `base_url` \uc740 `https://`\ub85c \uc2dc\uc791\ud558\ub3c4\ub85d \uad6c\uc131\ub418\uc5b4\uc57c\ud569\ub2c8\ub2e4.", + "token_already_setup": "\ud1a0\ud070\uc774 \uc774\ubbf8 \uc124\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "token_forbidden": "\ud1a0\ud070\uc5d0 \ud544\uc694\ud55c OAuth \ubc94\uc704\ubaa9\ub85d\uc774 \uc5c6\uc2b5\ub2c8\ub2e4.", + "token_invalid_format": "\ud1a0\ud070\uc740 UID/GUID \ud615\uc2dd\uc774\uc5b4\uc57c\ud569\ub2c8\ub2e4", + "token_unauthorized": "\ud1a0\ud070\uc774 \uc720\ud6a8\ud558\uc9c0 \uc54a\uac70\ub098 \uc2b9\uc778\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4." + }, + "step": { + "user": { + "data": { + "access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070" + }, + "description": "[\uc548\ub0b4]({component_url}) \uc5d0 \ub530\ub77c \uc0dd\uc131 \ub41c SmartThings [\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070]({token_url}) \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070 \uc785\ub825" + }, + "wait_install": { + "description": "\ud558\ub098 \uc774\uc0c1\uc758 \uc704\uce58\uc5d0 Home Assistant SmartApp \uc744 \uc124\uce58\ud558\uace0 submit \uc744 \ud074\ub9ad\ud574\uc8fc\uc138\uc694.", + "title": "SmartApp \uc124\uce58" + } + }, + "title": "SmartThings" + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/.translations/nl.json b/homeassistant/components/smartthings/.translations/nl.json new file mode 100644 index 00000000000..93150b2ae7d --- /dev/null +++ b/homeassistant/components/smartthings/.translations/nl.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "app_not_installed": "Zorg ervoor dat u de Home Assistant SmartApp heeft ge\u00efnstalleerd en geautoriseerd en probeer het opnieuw.", + "app_setup_error": "Instellen van SmartApp mislukt. Probeer het opnieuw.", + "base_url_not_https": "De `base_url` voor het `http` component moet worden geconfigureerd en beginnen met `https://`.", + "token_already_setup": "Het token is al ingesteld.", + "token_forbidden": "Het token heeft niet de vereiste OAuth-scopes.", + "token_invalid_format": "Het token moet de UID/GUID-indeling hebben", + "token_unauthorized": "Het token is ongeldig of niet langer geautoriseerd." + }, + "step": { + "user": { + "data": { + "access_token": "Toegangstoken" + }, + "description": "Voer een SmartThings [Personal Access Token]({token_url}) in die is aangemaakt volgens de [instructies]({component_url}).", + "title": "Persoonlijk toegangstoken invoeren" + }, + "wait_install": { + "description": "Installeer de Home Assistant SmartApp in tenminste \u00e9\u00e9n locatie en klik Verzenden.", + "title": "Installeer SmartApp" + } + }, + "title": "SmartThings" + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/.translations/no.json b/homeassistant/components/smartthings/.translations/no.json new file mode 100644 index 00000000000..4d7df8bd65d --- /dev/null +++ b/homeassistant/components/smartthings/.translations/no.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "app_not_installed": "V\u00e6r sikker p\u00e5 at du har installert og autorisert Home Assistant SmartApp og pr\u00f8v igjen.", + "app_setup_error": "Kan ikke konfigurere SmartApp. Vennligst pr\u00f8v p\u00e5 nytt.", + "base_url_not_https": "`base_url` for `http` komponenten m\u00e5 konfigureres og starte med `https://`.", + "token_already_setup": "Token har allerede blitt satt opp.", + "token_forbidden": "Tollet har ikke de n\u00f8dvendige OAuth m\u00e5lene.", + "token_invalid_format": "Token m\u00e5 v\u00e6re i UID/GUID format", + "token_unauthorized": "Tollet er ugyldig eller ikke lenger autorisert." + }, + "step": { + "user": { + "data": { + "access_token": "Tilgangstoken" + }, + "description": "Vennligst skriv inn en SmartThings [Personlig tilgangstoken]({token_url}) som er opprettet etter [instruksjonene]({component_url}).", + "title": "Oppgi Personlig Tilgangstoken" + }, + "wait_install": { + "description": "Vennligst installer Home Assistant SmartApp p\u00e5 minst ett sted og klikk p\u00e5 send.", + "title": "Installer SmartApp" + } + }, + "title": "SmartThings" + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/.translations/pl.json b/homeassistant/components/smartthings/.translations/pl.json index 379cdf699b7..570f1130383 100644 --- a/homeassistant/components/smartthings/.translations/pl.json +++ b/homeassistant/components/smartthings/.translations/pl.json @@ -1,7 +1,24 @@ { "config": { + "error": { + "app_not_installed": "Upewnij si\u0119, \u017ce zainstalowa\u0142e\u015b i autoryzowa\u0142e\u015b Home Assistant SmartApp i spr\u00f3buj ponownie.", + "app_setup_error": "Nie mo\u017cna skonfigurowa\u0107 SmartApp. Prosz\u0119 spr\u00f3buj ponownie.", + "base_url_not_https": "Parametr `base_url` dla komponentu `http` musi by\u0107 skonfigurowany i rozpoczyna\u0107 si\u0119 od `https://`.", + "token_already_setup": "Token zosta\u0142 ju\u017c skonfigurowany.", + "token_forbidden": "Token nie ma wymaganych zakres\u00f3w OAuth.", + "token_invalid_format": "Token musi by\u0107 w formacie UID/GUID", + "token_unauthorized": "Token jest niewa\u017cny lub nie ma ju\u017c autoryzacji." + }, "step": { + "user": { + "data": { + "access_token": "Token dost\u0119pu" + }, + "description": "Wprowad\u017a [token dost\u0119pu osobistego]({token_url}) SmartThings, kt\u00f3ry zosta\u0142 utworzony zgodnie z [instrukcj\u0105]({component_url}).", + "title": "Wprowad\u017a osobisty token dost\u0119pu" + }, "wait_install": { + "description": "Prosz\u0119 zainstalowa\u0107 Home Assistant SmartApp w co najmniej jednej lokalizacji i klikn\u0105\u0107 przycisk Wy\u015blij.", "title": "Zainstaluj SmartApp" } }, diff --git a/homeassistant/components/smartthings/.translations/pt.json b/homeassistant/components/smartthings/.translations/pt.json new file mode 100644 index 00000000000..d805cfc563d --- /dev/null +++ b/homeassistant/components/smartthings/.translations/pt.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "app_setup_error": "N\u00e3o \u00e9 poss\u00edvel configurar o SmartApp. Por favor, tente novamente.", + "token_already_setup": "O token j\u00e1 foi configurado." + }, + "step": { + "user": { + "data": { + "access_token": "Token de Acesso" + } + }, + "wait_install": { + "title": "Instalar SmartApp" + } + }, + "title": "SmartThings" + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/.translations/ru.json b/homeassistant/components/smartthings/.translations/ru.json new file mode 100644 index 00000000000..334e5d8cb23 --- /dev/null +++ b/homeassistant/components/smartthings/.translations/ru.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "app_not_installed": "\u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0412\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0438 \u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043b\u0438 SmartApp Home Assistant \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443.", + "app_setup_error": "\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c SmartApp. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "base_url_not_https": "\u0412 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0435 `http` \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0443\u043a\u0430\u0437\u0430\u043d \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 `base_url`, \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u0439\u0441\u044f \u0441 `https://`.", + "token_already_setup": "\u0422\u043e\u043a\u0435\u043d \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d.", + "token_forbidden": "\u0422\u043e\u043a\u0435\u043d \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d \u0434\u043b\u044f OAuth.", + "token_invalid_format": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 UID / GUID", + "token_unauthorized": "\u0422\u043e\u043a\u0435\u043d \u043d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u0435\u043d \u0438\u043b\u0438 \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d." + }, + "step": { + "user": { + "data": { + "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430" + }, + "description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 [\u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430]({token_url}) SmartThings, \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438]({component_url}).", + "title": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430" + }, + "wait_install": { + "description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 SmartApp Home Assistant \u0438 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 **\u041f\u041e\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u042c**.", + "title": "SmartThings" + } + }, + "title": "SmartThings" + } +} \ No newline at end of file diff --git a/homeassistant/components/smhi/.translations/da.json b/homeassistant/components/smhi/.translations/da.json new file mode 100644 index 00000000000..b43fef7ec45 --- /dev/null +++ b/homeassistant/components/smhi/.translations/da.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "name_exists": "Navnet findes allerede", + "wrong_location": "Placering kun i Sverige" + }, + "step": { + "user": { + "data": { + "latitude": "Breddegrad", + "longitude": "L\u00e6ngdegrad", + "name": "Navn" + }, + "title": "Placering i Sverige" + } + }, + "title": "Svensk vejr service (SMHI)" + } +} \ No newline at end of file diff --git a/homeassistant/components/smhi/.translations/fr.json b/homeassistant/components/smhi/.translations/fr.json index d1378f183d5..aa4589e558d 100644 --- a/homeassistant/components/smhi/.translations/fr.json +++ b/homeassistant/components/smhi/.translations/fr.json @@ -1,7 +1,8 @@ { "config": { "error": { - "name_exists": "Ce nom est d\u00e9j\u00e0 utilis\u00e9" + "name_exists": "Ce nom est d\u00e9j\u00e0 utilis\u00e9", + "wrong_location": "En Su\u00e8de uniquement" }, "step": { "user": { @@ -9,8 +10,10 @@ "latitude": "Latitude", "longitude": "Longitude", "name": "Nom" - } + }, + "title": "Localisation en Su\u00e8de" } - } + }, + "title": "Service m\u00e9t\u00e9orologique su\u00e9dois (SMHI)" } } \ No newline at end of file diff --git a/homeassistant/components/smhi/.translations/ru.json b/homeassistant/components/smhi/.translations/ru.json index 012bb74c568..3496d19f5f4 100644 --- a/homeassistant/components/smhi/.translations/ru.json +++ b/homeassistant/components/smhi/.translations/ru.json @@ -14,6 +14,6 @@ "title": "\u041c\u0435\u0441\u0442\u043e\u043d\u0430\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435 \u0432 \u0428\u0432\u0435\u0446\u0438\u0438" } }, - "title": "\u0428\u0432\u0435\u0434\u0441\u043a\u0430\u044f \u043c\u0435\u0442\u0435\u043e\u0440\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0441\u043b\u0443\u0436\u0431\u0430 (SMHI)" + "title": "\u041c\u0435\u0442\u0435\u043e\u0440\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0428\u0432\u0435\u0446\u0438\u0438 (SMHI)" } } \ No newline at end of file diff --git a/homeassistant/components/tellduslive/.translations/da.json b/homeassistant/components/tellduslive/.translations/da.json new file mode 100644 index 00000000000..717e3ec5ac9 --- /dev/null +++ b/homeassistant/components/tellduslive/.translations/da.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "all_configured": "TelldusLive er allerede konfigureret", + "already_setup": "TelldusLive er allerede konfigureret", + "authorize_url_fail": "Ukendt fejl ved generering af en autoriseret url.", + "authorize_url_timeout": "Timeout ved generering af autoriseret url.", + "unknown": "Ukendt fejl opstod" + }, + "error": { + "auth_error": "Godkendelsesfejl, pr\u00f8v venligst igen" + }, + "step": { + "auth": { + "description": "For at forbinde din TelldusLive-konto:\n 1. Klik p\u00e5 linket herunder\n 2. Log p\u00e5 Telldus Live\n 3. Tillad **{app_name}** (klik **Ja**). \n 4. Vend tilbage hertil og klik **SUBMIT**.\n\n [Forbind TelldusLive konto]({auth_url})", + "title": "Godkendelse mod TelldusLive" + }, + "user": { + "data": { + "host": "V\u00e6rt" + }, + "description": "Tom", + "title": "V\u00e6lg slutpunkt." + } + }, + "title": "Telldus Live" + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/.translations/fr.json b/homeassistant/components/tellduslive/.translations/fr.json index 9a3121c5896..2dd1c03022a 100644 --- a/homeassistant/components/tellduslive/.translations/fr.json +++ b/homeassistant/components/tellduslive/.translations/fr.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "already_setup": "TelldusLive est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "auth_error": "Erreur d'authentification, veuillez r\u00e9essayer." + }, "step": { "user": { "description": "Vide" diff --git a/homeassistant/components/tellduslive/.translations/ko.json b/homeassistant/components/tellduslive/.translations/ko.json index 29f64a87cb3..8a68e303aff 100644 --- a/homeassistant/components/tellduslive/.translations/ko.json +++ b/homeassistant/components/tellduslive/.translations/ko.json @@ -8,7 +8,7 @@ "unknown": "\uc54c \uc218\uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "auth_error": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574 \uc8fc\uc138\uc694." + "auth_error": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." }, "step": { "auth": { diff --git a/homeassistant/components/tellduslive/.translations/pt.json b/homeassistant/components/tellduslive/.translations/pt.json index d0d38a42caf..90da12451df 100644 --- a/homeassistant/components/tellduslive/.translations/pt.json +++ b/homeassistant/components/tellduslive/.translations/pt.json @@ -19,6 +19,6 @@ "title": "Escolher endpoint." } }, - "title": "" + "title": "Telldus Live" } } \ No newline at end of file diff --git a/homeassistant/components/tradfri/.translations/da.json b/homeassistant/components/tradfri/.translations/da.json new file mode 100644 index 00000000000..f0e5acf9d9c --- /dev/null +++ b/homeassistant/components/tradfri/.translations/da.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Bridge er allerede konfigureret" + }, + "error": { + "cannot_connect": "Kan ikke oprette forbindelse til gateway.", + "invalid_key": "Fejl ved registrerering med den leverede n\u00f8gle. Hvis dette sker konsekvent skal du pr\u00f8ve at genstarte gatewayen.", + "timeout": "Timeout ved validering af kode" + }, + "step": { + "auth": { + "data": { + "host": "V\u00e6rt", + "security_code": "Sikkerhedskode" + }, + "description": "Du kan finde sikkerhedskoden p\u00e5 bagsiden af din gateway.", + "title": "Indtast sikkerhedskode" + } + }, + "title": "IKEA TR\u00c5DFRI" + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/.translations/da.json b/homeassistant/components/twilio/.translations/da.json new file mode 100644 index 00000000000..3c1ab7c01b5 --- /dev/null +++ b/homeassistant/components/twilio/.translations/da.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Dit Home Assistant system skal v\u00e6re tilg\u00e6ngeligt fra internettet for at modtage Twilio meddelelser.", + "one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning." + }, + "create_entry": { + "default": "For at sende begivenheder til Home Assistant skal du konfigurere [Webhooks med Twilio]({twilio_url}).\n\n Udfyld f\u00f8lgende oplysninger: \n\n - URL: `{webhook_url}`\n - Metode: POST\n - Indholdstype: application/x-www-form-urlencoded\n\n Se [dokumentationen]({docs_url}) om hvordan du konfigurerer automatiseringer til at h\u00e5ndtere indg\u00e5ende data." + }, + "step": { + "user": { + "description": "Er du sikker p\u00e5 at du vil konfigurere Twilio?", + "title": "Konfigurer Twilio Webhook" + } + }, + "title": "Twilio" + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/.translations/fr.json b/homeassistant/components/twilio/.translations/fr.json new file mode 100644 index 00000000000..09ca0f63cfd --- /dev/null +++ b/homeassistant/components/twilio/.translations/fr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Votre instance de Home Assistant doit \u00eatre accessible depuis Internet pour recevoir les messages Twilio.", + "one_instance_allowed": "Une seule instance est n\u00e9cessaire." + }, + "create_entry": { + "default": "Pour envoyer des \u00e9v\u00e9nements \u00e0 Home Assistant, vous devez configurer [Webhooks avec Twilio] ( {twilio_url} ). \n\n Remplissez les informations suivantes: \n\n - URL: ` {webhook_url} ` \n - M\u00e9thode: POST \n - Type de contenu: application / x-www-form-urlencoded \n\n Voir [la documentation] ( {docs_url} ) pour savoir comment configurer les automatisations pour g\u00e9rer les donn\u00e9es entrantes." + }, + "step": { + "user": { + "description": "\u00cates-vous s\u00fbr de vouloir configurer Twilio?", + "title": "Configurer le Webhook Twilio" + } + }, + "title": "Twilio" + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/.translations/ko.json b/homeassistant/components/twilio/.translations/ko.json index 8790c708008..618c91e6a65 100644 --- a/homeassistant/components/twilio/.translations/ko.json +++ b/homeassistant/components/twilio/.translations/ko.json @@ -5,7 +5,7 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Twilio Webhook]({twilio_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/x-www-form-urlencoded\n \nHome Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694." + "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Twilio Webhook]({twilio_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/x-www-form-urlencoded\n \nHome Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/unifi/.translations/da.json b/homeassistant/components/unifi/.translations/da.json new file mode 100644 index 00000000000..4155658d7de --- /dev/null +++ b/homeassistant/components/unifi/.translations/da.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Controller site er allerede konfigureret", + "user_privilege": "Bruger skal v\u00e6re administrator" + }, + "error": { + "faulty_credentials": "Ugyldige legitimationsoplysninger", + "service_unavailable": "Service utilg\u00e6ngelig" + }, + "step": { + "user": { + "data": { + "host": "V\u00e6rt", + "password": "Adgangskode", + "port": "Port", + "site": "Site ID", + "username": "Brugernavn", + "verify_ssl": "Controller bruger korrekt certifikat" + }, + "title": "Konfigurer UniFi Controller" + } + }, + "title": "UniFi Controller" + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/.translations/fr.json b/homeassistant/components/unifi/.translations/fr.json index 68e90811a3e..767962e37eb 100644 --- a/homeassistant/components/unifi/.translations/fr.json +++ b/homeassistant/components/unifi/.translations/fr.json @@ -14,9 +14,12 @@ "password": "Mot de passe", "port": "Port", "site": "ID du site", - "username": "Nom d'utilisateur" - } + "username": "Nom d'utilisateur", + "verify_ssl": "Contr\u00f4leur utilisant un certificat appropri\u00e9" + }, + "title": "Configurer le contr\u00f4leur UniFi" } - } + }, + "title": "Contr\u00f4leur UniFi" } } \ No newline at end of file diff --git a/homeassistant/components/upnp/.translations/da.json b/homeassistant/components/upnp/.translations/da.json new file mode 100644 index 00000000000..1d0097c2f1f --- /dev/null +++ b/homeassistant/components/upnp/.translations/da.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "UPnP/IGD er allerede konfigureret", + "incomplete_device": "Ignorerer ufuldst\u00e6ndig UPnP-enhed", + "no_devices_discovered": "Ingen UPnP/IGD enheder fundet.", + "no_devices_found": "Ingen UPnP/IGD enheder kunne findes p\u00e5 netv\u00e6rket.", + "no_sensors_or_port_mapping": "Aktiv\u00e9r enten sensorer eller porttilknytning", + "single_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning af UPnP/IGD." + }, + "error": { + "one": "En", + "other": "Anden" + }, + "step": { + "confirm": { + "description": "Er du sikker p\u00e5 at du vil konfigurere UPnP/IGD?", + "title": "UPnP/IGD" + }, + "init": { + "title": "UPnP/IGD" + }, + "user": { + "data": { + "enable_port_mapping": "Aktiv\u00e9r porttilknytning til Home Assistent", + "enable_sensors": "Tilf\u00f8j trafik sensorer", + "igd": "UPnP/IGD" + }, + "title": "Konfigurationsindstillinger for UPnP/IGD" + } + }, + "title": "UPnP/IGD" + } +} \ No newline at end of file diff --git a/homeassistant/components/upnp/.translations/fr.json b/homeassistant/components/upnp/.translations/fr.json index 3eac9577890..d1ff04d4824 100644 --- a/homeassistant/components/upnp/.translations/fr.json +++ b/homeassistant/components/upnp/.translations/fr.json @@ -3,9 +3,15 @@ "abort": { "already_configured": "UPnP / IGD est d\u00e9j\u00e0 configur\u00e9", "no_devices_discovered": "Aucun UPnP / IGD d\u00e9couvert", - "no_sensors_or_port_mapping": "Activer au moins les capteurs ou la cartographie des ports" + "no_devices_found": "Aucun p\u00e9riph\u00e9rique UPnP / IGD trouv\u00e9 sur le r\u00e9seau.", + "no_sensors_or_port_mapping": "Activer au moins les capteurs ou la cartographie des ports", + "single_instance_allowed": "Une seule configuration UPnP / IGD est n\u00e9cessaire." }, "step": { + "confirm": { + "description": "Voulez-vous configurer UPnP / IGD?", + "title": "UPnP / IGD" + }, "init": { "title": "UPnP / IGD" }, diff --git a/homeassistant/components/upnp/.translations/pt.json b/homeassistant/components/upnp/.translations/pt.json index 5c5693e6a0c..d559a05ff23 100644 --- a/homeassistant/components/upnp/.translations/pt.json +++ b/homeassistant/components/upnp/.translations/pt.json @@ -15,7 +15,7 @@ "step": { "confirm": { "description": "Deseja configurar o UPnP / IGD?", - "title": "" + "title": "UPnP/IGD" }, "init": { "title": "UPnP/IGD" diff --git a/homeassistant/components/zha/.translations/da.json b/homeassistant/components/zha/.translations/da.json new file mode 100644 index 00000000000..e336c14dcce --- /dev/null +++ b/homeassistant/components/zha/.translations/da.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning af ZHA." + }, + "error": { + "cannot_connect": "Kunne ikke oprette forbindelse til ZHA-enhed." + }, + "step": { + "user": { + "data": { + "radio_type": "Radio type", + "usb_path": "Sti til USB enhed" + }, + "description": "Tom", + "title": "ZHA" + } + }, + "title": "ZHA" + } +} \ No newline at end of file diff --git a/homeassistant/components/zha/.translations/pt.json b/homeassistant/components/zha/.translations/pt.json index 0259403fabc..c1de13b5381 100644 --- a/homeassistant/components/zha/.translations/pt.json +++ b/homeassistant/components/zha/.translations/pt.json @@ -13,9 +13,9 @@ "usb_path": "Caminho do Dispositivo USB" }, "description": "Vazio", - "title": "" + "title": "ZHA" } }, - "title": "" + "title": "ZHA" } } \ No newline at end of file diff --git a/homeassistant/components/zone/.translations/da.json b/homeassistant/components/zone/.translations/da.json index 908ef9dc43a..c6981f242d2 100644 --- a/homeassistant/components/zone/.translations/da.json +++ b/homeassistant/components/zone/.translations/da.json @@ -10,7 +10,8 @@ "latitude": "Breddegrad", "longitude": "L\u00e6ngdegrad", "name": "Navn", - "passive": "Passiv" + "passive": "Passiv", + "radius": "Radius" }, "title": "Definer zoneparametre" } diff --git a/homeassistant/components/zwave/.translations/da.json b/homeassistant/components/zwave/.translations/da.json new file mode 100644 index 00000000000..e9049026a4f --- /dev/null +++ b/homeassistant/components/zwave/.translations/da.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Z-Wave er allerede konfigureret", + "one_instance_only": "Komponenten underst\u00f8tter kun \u00e9n Z-Wave forekomst" + }, + "error": { + "option_error": "Z-Wave validering mislykkedes. Er stien til USB enhed korrekt?" + }, + "step": { + "user": { + "data": { + "network_key": "Netv\u00e6rksn\u00f8gle (efterlad blank for autogenerering)", + "usb_path": "Sti til USB enhed" + }, + "description": "Se https://www.home-assistant.io/docs/z-wave/installation/ for oplysninger om konfigurationsvariabler", + "title": "Ops\u00e6t Z-Wave" + } + }, + "title": "Z-Wave" + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave/.translations/fr.json b/homeassistant/components/zwave/.translations/fr.json index c667965bebc..797a64b2076 100644 --- a/homeassistant/components/zwave/.translations/fr.json +++ b/homeassistant/components/zwave/.translations/fr.json @@ -13,6 +13,7 @@ "network_key": "Cl\u00e9 r\u00e9seau (laisser vide pour g\u00e9n\u00e9rer automatiquement)", "usb_path": "Chemin USB" }, + "description": "Voir https://www.home-assistant.io/docs/z-wave/installation/ pour plus d'informations sur les variables de configuration.", "title": "Configurer Z-Wave" } }, From c20e0b985a4c88351df9ad9d0450a377181ec440 Mon Sep 17 00:00:00 2001 From: Ben Dews Date: Thu, 14 Feb 2019 11:36:49 +1100 Subject: [PATCH 188/242] Add Lock capability to SmartThings platform (#20977) * Bumped pysmartthings version to 0.6.1 * Added Lock to supported platforms * Added SmartThings Lock component * Updated lock to eagerly set state * Updated requirements_all.txt & requirements_test_all.txt with pysmartthings==0.6.1 * Added SmartThings Lock tests * Removed inapplicable comment * Removed unused import (STATE_UNLOCKED) * Populated device_state_attributes with values provided by SmartThings * Condensed if_lock assertion function * Updated gathered attributes * Fixed typo * Updated tests to use new setup_platform * Updated assignment of device state attributes * Updated tests to utilise the LOCK_DOMAIN constant where suitable * Fixed false positive for Switch test: (test_unload_config_entry) * Implemented constant to contain expected SmartThings state for is_locked check * Improved allocation of State Attributes * Improved allocation of state attributes * Fixed lint error (was running lint checks against the wrong file, whoops) * Added test for unloading lock config * Use isinstance instead of type() * Updated device state to explicitly check for is not None instead of a truthy value --- homeassistant/components/smartthings/const.py | 1 + homeassistant/components/smartthings/lock.py | 73 ++++++++++++ tests/components/smartthings/test_lock.py | 110 ++++++++++++++++++ tests/components/smartthings/test_switch.py | 2 +- 4 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/smartthings/lock.py create mode 100644 tests/components/smartthings/test_lock.py diff --git a/homeassistant/components/smartthings/const.py b/homeassistant/components/smartthings/const.py index df14ab68055..25cd9e8305f 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -23,6 +23,7 @@ SUPPORTED_PLATFORMS = [ 'climate', 'fan', 'light', + 'lock', 'sensor', 'switch' ] diff --git a/homeassistant/components/smartthings/lock.py b/homeassistant/components/smartthings/lock.py new file mode 100644 index 00000000000..6dfff0bd02c --- /dev/null +++ b/homeassistant/components/smartthings/lock.py @@ -0,0 +1,73 @@ +""" +Support for locks through the SmartThings cloud API. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/smartthings.lock/ +""" +from homeassistant.components.lock import LockDevice + +from . import SmartThingsEntity +from .const import DATA_BROKERS, DOMAIN + +DEPENDENCIES = ['smartthings'] + +ST_STATE_LOCKED = 'locked' +ST_LOCK_ATTR_MAP = { + 'method': 'method', + 'codeId': 'code_id', + 'timeout': 'timeout' +} + + +async def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): + """Platform uses config entry setup.""" + pass + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Add locks for a config entry.""" + broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] + async_add_entities( + [SmartThingsLock(device) for device in broker.devices.values() + if is_lock(device)]) + + +def is_lock(device): + """Determine if the device supports the lock capability.""" + from pysmartthings import Capability + return Capability.lock in device.capabilities + + +class SmartThingsLock(SmartThingsEntity, LockDevice): + """Define a SmartThings lock.""" + + async def async_lock(self, **kwargs): + """Lock the device.""" + await self._device.lock(set_status=True) + self.async_schedule_update_ha_state() + + async def async_unlock(self, **kwargs): + """Unlock the device.""" + await self._device.unlock(set_status=True) + self.async_schedule_update_ha_state() + + @property + def is_locked(self): + """Return true if lock is locked.""" + return self._device.status.lock == ST_STATE_LOCKED + + @property + def device_state_attributes(self): + """Return device specific state attributes.""" + from pysmartthings import Attribute + state_attrs = {} + status = self._device.status.attributes[Attribute.lock] + if status.value: + state_attrs['lock_state'] = status.value + if isinstance(status.data, dict): + for st_attr, ha_attr in ST_LOCK_ATTR_MAP.items(): + data_val = status.data.get(st_attr) + if data_val is not None: + state_attrs[ha_attr] = data_val + return state_attrs diff --git a/tests/components/smartthings/test_lock.py b/tests/components/smartthings/test_lock.py new file mode 100644 index 00000000000..c73f4ff549e --- /dev/null +++ b/tests/components/smartthings/test_lock.py @@ -0,0 +1,110 @@ +""" +Test for the SmartThings lock platform. + +The only mocking required is of the underlying SmartThings API object so +real HTTP calls are not initiated during testing. +""" +from pysmartthings import Attribute, Capability + +from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN +from homeassistant.components.smartthings import lock +from homeassistant.components.smartthings.const import ( + DOMAIN, SIGNAL_SMARTTHINGS_UPDATE) +from homeassistant.helpers.dispatcher import async_dispatcher_send + +from .conftest import setup_platform + + +async def test_async_setup_platform(): + """Test setup platform does nothing (it uses config entries).""" + await lock.async_setup_platform(None, None, None) + + +def test_is_lock(device_factory): + """Test locks are correctly identified.""" + lock_device = device_factory('Lock', [Capability.lock]) + assert lock.is_lock(lock_device) + + +async def test_entity_and_device_attributes(hass, device_factory): + """Test the attributes of the entity are correct.""" + # Arrange + device = device_factory('Lock_1', [Capability.lock], + {Attribute.lock: 'unlocked'}) + entity_registry = await hass.helpers.entity_registry.async_get_registry() + device_registry = await hass.helpers.device_registry.async_get_registry() + # Act + await setup_platform(hass, LOCK_DOMAIN, device) + # Assert + entry = entity_registry.async_get('lock.lock_1') + assert entry + assert entry.unique_id == device.device_id + + entry = device_registry.async_get_device( + {(DOMAIN, device.device_id)}, []) + assert entry + assert entry.name == device.label + assert entry.model == device.device_type_name + assert entry.manufacturer == 'Unavailable' + + +async def test_lock(hass, device_factory): + """Test the lock locks successfully.""" + # Arrange + device = device_factory('Lock_1', [Capability.lock], + {Attribute.lock: 'unlocked'}) + await setup_platform(hass, LOCK_DOMAIN, device) + # Act + await hass.services.async_call( + LOCK_DOMAIN, 'lock', {'entity_id': 'lock.lock_1'}, + blocking=True) + # Assert + state = hass.states.get('lock.lock_1') + assert state is not None + assert state.state == 'locked' + + +async def test_unlock(hass, device_factory): + """Test the lock unlocks successfully.""" + # Arrange + device = device_factory('Lock_1', [Capability.lock], + {Attribute.lock: 'locked'}) + await setup_platform(hass, LOCK_DOMAIN, device) + # Act + await hass.services.async_call( + LOCK_DOMAIN, 'unlock', {'entity_id': 'lock.lock_1'}, + blocking=True) + # Assert + state = hass.states.get('lock.lock_1') + assert state is not None + assert state.state == 'unlocked' + + +async def test_update_from_signal(hass, device_factory): + """Test the lock updates when receiving a signal.""" + # Arrange + device = device_factory('Lock_1', [Capability.lock], + {Attribute.lock: 'unlocked'}) + await setup_platform(hass, LOCK_DOMAIN, device) + await device.lock(True) + # Act + async_dispatcher_send(hass, SIGNAL_SMARTTHINGS_UPDATE, + [device.device_id]) + # Assert + await hass.async_block_till_done() + state = hass.states.get('lock.lock_1') + assert state is not None + assert state.state == 'locked' + + +async def test_unload_config_entry(hass, device_factory): + """Test the lock is removed when the config entry is unloaded.""" + # Arrange + device = device_factory('Lock_1', [Capability.lock], + {Attribute.lock: 'locked'}) + config_entry = await setup_platform(hass, LOCK_DOMAIN, device) + # Act + await hass.config_entries.async_forward_entry_unload( + config_entry, 'lock') + # Assert + assert not hass.states.get('lock.lock_1') diff --git a/tests/components/smartthings/test_switch.py b/tests/components/smartthings/test_switch.py index a8013105291..15ff3adce86 100644 --- a/tests/components/smartthings/test_switch.py +++ b/tests/components/smartthings/test_switch.py @@ -126,7 +126,7 @@ async def test_update_from_signal(hass, device_factory): async def test_unload_config_entry(hass, device_factory): """Test the switch is removed when the config entry is unloaded.""" # Arrange - device = device_factory('Switch', [Capability.switch], + device = device_factory('Switch 1', [Capability.switch], {Attribute.switch: 'on'}) config_entry = await _setup_platform(hass, device) # Act From bf0a50cdb206ca10bc074a08bfc935ef8b646137 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 13 Feb 2019 19:39:53 -0500 Subject: [PATCH 189/242] Add template support to Bayesian sensor (#20757) * Add template support to Bayesian sensor * Removed unused import --- .../components/binary_sensor/bayesian.py | 50 +++++++++++++------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/binary_sensor/bayesian.py b/homeassistant/components/binary_sensor/bayesian.py index f7802f0f29d..97889ea7497 100644 --- a/homeassistant/components/binary_sensor/bayesian.py +++ b/homeassistant/components/binary_sensor/bayesian.py @@ -4,29 +4,27 @@ Use Bayesian Inference to trigger a binary sensor. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.bayesian/ """ -import logging from collections import OrderedDict import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) + PLATFORM_SCHEMA, BinarySensorDevice) from homeassistant.const import ( CONF_ABOVE, CONF_BELOW, CONF_DEVICE_CLASS, CONF_ENTITY_ID, CONF_NAME, - CONF_PLATFORM, CONF_STATE, STATE_UNKNOWN) + CONF_PLATFORM, CONF_STATE, CONF_VALUE_TEMPLATE, STATE_UNKNOWN) from homeassistant.core import callback from homeassistant.helpers import condition +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_state_change -_LOGGER = logging.getLogger(__name__) - ATTR_OBSERVATIONS = 'observations' ATTR_PROBABILITY = 'probability' ATTR_PROBABILITY_THRESHOLD = 'probability_threshold' CONF_OBSERVATIONS = 'observations' CONF_PRIOR = 'prior' +CONF_TEMPLATE = "template" CONF_PROBABILITY_THRESHOLD = 'probability_threshold' CONF_P_GIVEN_F = 'prob_given_false' CONF_P_GIVEN_T = 'prob_given_true' @@ -52,12 +50,20 @@ STATE_SCHEMA = vol.Schema({ vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float) }, required=True) +TEMPLATE_SCHEMA = vol.Schema({ + CONF_PLATFORM: CONF_TEMPLATE, + vol.Required(CONF_VALUE_TEMPLATE): cv.template, + vol.Required(CONF_P_GIVEN_T): vol.Coerce(float), + vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float) +}, required=True) + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_DEVICE_CLASS): cv.string, vol.Required(CONF_OBSERVATIONS): vol.Schema(vol.All(cv.ensure_list, - [vol.Any(NUMERIC_STATE_SCHEMA, STATE_SCHEMA)])), + [vol.Any(NUMERIC_STATE_SCHEMA, STATE_SCHEMA, + TEMPLATE_SCHEMA)])), vol.Required(CONF_PRIOR): vol.Coerce(float), vol.Optional(CONF_PROBABILITY_THRESHOLD, default=DEFAULT_PROBABILITY_THRESHOLD): vol.Coerce(float), @@ -68,7 +74,6 @@ def update_probability(prior, prob_true, prob_false): """Update probability using Bayes' rule.""" numerator = prob_true * prior denominator = numerator + prob_false * (1 - prior) - probability = numerator / denominator return probability @@ -104,17 +109,27 @@ class BayesianBinarySensor(BinarySensorDevice): self.current_obs = OrderedDict({}) - to_observe = set(obs['entity_id'] for obs in self._observations) - + to_observe = set() + for obs in self._observations: + if 'entity_id' in obs: + to_observe.update(set([obs.get('entity_id')])) + if 'value_template' in obs: + to_observe.update( + set(obs.get(CONF_VALUE_TEMPLATE).extract_entities())) self.entity_obs = dict.fromkeys(to_observe, []) for ind, obs in enumerate(self._observations): obs['id'] = ind - self.entity_obs[obs['entity_id']].append(obs) + if 'entity_id' in obs: + self.entity_obs[obs['entity_id']].append(obs) + if 'value_template' in obs: + for ent in obs.get(CONF_VALUE_TEMPLATE).extract_entities(): + self.entity_obs[ent].append(obs) self.watchers = { 'numeric_state': self._process_numeric_state, - 'state': self._process_state + 'state': self._process_state, + 'template': self._process_template } async def async_added_to_hass(self): @@ -141,9 +156,8 @@ class BayesianBinarySensor(BinarySensorDevice): self.hass.async_add_job(self.async_update_ha_state, True) - entities = [obs['entity_id'] for obs in self._observations] async_track_state_change( - self.hass, entities, async_threshold_sensor_state_listener) + self.hass, self.entity_obs, async_threshold_sensor_state_listener) def _update_current_obs(self, entity_observation, should_trigger): """Update current observation.""" @@ -182,6 +196,14 @@ class BayesianBinarySensor(BinarySensorDevice): self._update_current_obs(entity_observation, should_trigger) + def _process_template(self, entity_observation): + """Add entity to current_obs if template is true.""" + template = entity_observation.get(CONF_VALUE_TEMPLATE) + template.hass = self.hass + should_trigger = condition.async_template( + self.hass, template, entity_observation) + self._update_current_obs(entity_observation, should_trigger) + @property def name(self): """Return the name of the sensor.""" From 50ba3d0427a3dc2a90244cd796ebc969f2d87add Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 13 Feb 2019 20:00:08 -0800 Subject: [PATCH 190/242] Create a person during onboarding (#21057) --- homeassistant/components/default_config/__init__.py | 1 + homeassistant/components/onboarding/views.py | 4 ++++ homeassistant/components/person/__init__.py | 12 ++++++++++++ tests/components/onboarding/test_views.py | 2 ++ 4 files changed, 19 insertions(+) diff --git a/homeassistant/components/default_config/__init__.py b/homeassistant/components/default_config/__init__.py index 3a99757b54b..d56cf9a4ee8 100644 --- a/homeassistant/components/default_config/__init__.py +++ b/homeassistant/components/default_config/__init__.py @@ -11,6 +11,7 @@ DEPENDENCIES = ( 'history', 'logbook', 'map', + 'person', 'script', 'sun', 'system_health', diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py index 497fa827f08..804589200fa 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -94,6 +94,10 @@ class UserOnboardingView(_BaseOnboardingView): }) await provider.data.async_save() await hass.auth.async_link_user(user, credentials) + if 'person' in hass.config.components: + await hass.components.person.async_create_person( + data['name'], user_id=user.id + ) await self._async_mark_done(hass) diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 8ad03e3f0ff..162d8f97a73 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -24,6 +24,7 @@ from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.components import websocket_api from homeassistant.helpers.typing import HomeAssistantType, ConfigType from homeassistant.util import dt as dt_util +from homeassistant.loader import bind_hass _LOGGER = logging.getLogger(__name__) ATTR_EDITABLE = 'editable' @@ -51,6 +52,17 @@ CONFIG_SCHEMA = vol.Schema({ _UNDEF = object() +@bind_hass +async def async_create_person(hass, name, *, user_id=None, + device_trackers=None): + """Create a new person.""" + await hass.data[DOMAIN].async_create_person( + name=name, + user_id=user_id, + device_trackers=device_trackers, + ) + + class PersonManager: """Manage person data.""" diff --git a/tests/components/onboarding/test_views.py b/tests/components/onboarding/test_views.py index d6a4030190d..5b303943747 100644 --- a/tests/components/onboarding/test_views.py +++ b/tests/components/onboarding/test_views.py @@ -69,6 +69,7 @@ async def test_onboarding_user_already_done(hass, hass_storage, async def test_onboarding_user(hass, hass_storage, aiohttp_client): """Test creating a new user.""" + assert await async_setup_component(hass, 'person', {}) mock_storage(hass_storage, { 'done': ['hello'] }) @@ -90,6 +91,7 @@ async def test_onboarding_user(hass, hass_storage, aiohttp_client): assert user.name == 'Test Name' assert len(user.credentials) == 1 assert user.credentials[0].data['username'] == 'test-user' + assert len(hass.data['person'].storage_data) == 1 async def test_onboarding_user_invalid_name(hass, hass_storage, From 4d3790e2d42ae78f86b393cab5e48d5824135ab0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 13 Feb 2019 20:04:08 -0800 Subject: [PATCH 191/242] Person checks (#21056) * Do not allow creating/updating persons with invalid user IDs * Unset user_id from person when user deleted * Lint * Lint * Lint --- homeassistant/components/person/__init__.py | 80 +++++++++++++++--- tests/components/person/test_init.py | 90 ++++++++++++++++++++- 2 files changed, 156 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 162d8f97a73..df19d86bb5c 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -5,6 +5,7 @@ For more details about this component, please refer to the documentation. https://home-assistant.io/components/person/ """ from collections import OrderedDict +from itertools import chain import logging import uuid @@ -15,7 +16,8 @@ from homeassistant.components.device_tracker import ( from homeassistant.const import ( ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_ID, CONF_NAME, EVENT_HOMEASSISTANT_START) -from homeassistant.core import callback +from homeassistant.core import callback, Event +from homeassistant.auth import EVENT_USER_REMOVED import homeassistant.helpers.config_validation as cv from homeassistant.helpers.storage import Store from homeassistant.helpers.entity_component import EntityComponent @@ -110,34 +112,62 @@ class PersonManager: storage_data[person[CONF_ID]] = person entities = [] + seen_users = set() for person_conf in self.config_data.values(): person_id = person_conf[CONF_ID] user_id = person_conf.get(CONF_USER_ID) - if (user_id is not None - and await self.hass.auth.async_get_user(user_id) is None): - _LOGGER.error( - "Invalid user_id detected for person %s", person_id) - continue + if user_id is not None: + if await self.hass.auth.async_get_user(user_id) is None: + _LOGGER.error( + "Invalid user_id detected for person %s", person_id) + continue + + if user_id in seen_users: + _LOGGER.error( + "Duplicate user_id %s detected for person %s", + user_id, person_id) + continue + + seen_users.add(user_id) entities.append(Person(person_conf, False)) for person_conf in storage_data.values(): - if person_conf[CONF_ID] in self.config_data: + person_id = person_conf[CONF_ID] + user_id = person_conf[CONF_USER_ID] + + if user_id in self.config_data: _LOGGER.error( "Skipping adding person from storage with same ID as" " configuration.yaml entry: %s", person_id) continue + if user_id in seen_users: + _LOGGER.error( + "Duplicate user_id %s detected for person %s", + user_id, person_id) + continue + + seen_users.add(user_id) + entities.append(Person(person_conf, True)) if entities: await self.component.async_add_entities(entities) + self.hass.bus.async_listen(EVENT_USER_REMOVED, self._user_removed) + async def async_create_person(self, *, name, device_trackers=None, user_id=None): """Create a new person.""" + if not name: + raise ValueError("Name is required") + + if user_id is not None: + await self._validate_user_id(user_id) + person = { CONF_ID: uuid.uuid4().hex, CONF_NAME: name, @@ -152,17 +182,22 @@ class PersonManager: async def async_update_person(self, person_id, *, name=_UNDEF, device_trackers=_UNDEF, user_id=_UNDEF): """Update person.""" - if person_id not in self.storage_data: + current = self.storage_data.get(person_id) + + if current is None: raise ValueError("Invalid person specified.") changes = { key: value for key, value in ( - ('name', name), - ('device_trackers', device_trackers), - ('user_id', user_id) - ) if value is not _UNDEF + (CONF_NAME, name), + (CONF_DEVICE_TRACKERS, device_trackers), + (CONF_USER_ID, user_id) + ) if value is not _UNDEF and current[key] != value } + if CONF_USER_ID in changes and user_id is not None: + await self._validate_user_id(user_id) + self.storage_data[person_id].update(changes) self._async_schedule_save() @@ -200,6 +235,27 @@ class PersonManager: 'persons': list(self.storage_data.values()) } + async def _validate_user_id(self, user_id): + """Validate the used user_id.""" + if await self.hass.auth.async_get_user(user_id) is None: + raise ValueError("User does not exist") + + if any(person for person + in chain(self.storage_data.values(), + self.config_data.values()) + if person[CONF_USER_ID] == user_id): + raise ValueError("User already taken") + + async def _user_removed(self, event: Event): + """Handle event that a person is removed.""" + user_id = event.data['user_id'] + for person in self.storage_data.values(): + if person[CONF_USER_ID] == user_id: + await self.async_update_person( + person_id=person[CONF_ID], + user_id=None + ) + async def async_setup(hass: HomeAssistantType, config: ConfigType): """Set up the person component.""" diff --git a/tests/components/person/test_init.py b/tests/components/person/test_init.py index 9b76135f743..4cef84746ed 100644 --- a/tests/components/person/test_init.py +++ b/tests/components/person/test_init.py @@ -1,5 +1,8 @@ """The tests for the person component.""" -from homeassistant.components.person import ATTR_SOURCE, ATTR_USER_ID, DOMAIN +from unittest.mock import Mock + +from homeassistant.components.person import ( + ATTR_SOURCE, ATTR_USER_ID, DOMAIN, PersonManager) from homeassistant.const import ( ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, STATE_UNKNOWN, EVENT_HOMEASSISTANT_START) @@ -8,7 +11,7 @@ from homeassistant.setup import async_setup_component import pytest -from tests.common import mock_component, mock_restore_cache +from tests.common import mock_component, mock_restore_cache, mock_coro_func DEVICE_TRACKER = 'device_tracker.test_tracker' DEVICE_TRACKER_2 = 'device_tracker.test_tracker_2' @@ -409,3 +412,86 @@ async def test_ws_delete_require_admin(hass, hass_ws_client, storage_setup, persons = manager.storage_persons assert len(persons) == 1 + + +async def test_create_invalid_user_id(hass): + """Test we do not allow invalid user ID during creation.""" + manager = PersonManager(hass, Mock(), []) + await manager.async_initialize() + with pytest.raises(ValueError): + await manager.async_create_person( + name='Hello', + user_id='non-existing' + ) + + +async def test_create_duplicate_user_id(hass, hass_admin_user): + """Test we do not allow duplicate user ID during creation.""" + manager = PersonManager( + hass, Mock(async_add_entities=mock_coro_func()), [] + ) + await manager.async_initialize() + await manager.async_create_person( + name='Hello', + user_id=hass_admin_user.id + ) + + with pytest.raises(ValueError): + await manager.async_create_person( + name='Hello', + user_id=hass_admin_user.id + ) + + +async def test_update_double_user_id(hass, hass_admin_user): + """Test we do not allow double user ID during update.""" + manager = PersonManager( + hass, Mock(async_add_entities=mock_coro_func()), [] + ) + await manager.async_initialize() + await manager.async_create_person( + name='Hello', + user_id=hass_admin_user.id + ) + person = await manager.async_create_person( + name='Hello', + ) + + with pytest.raises(ValueError): + await manager.async_update_person( + person_id=person['id'], + user_id=hass_admin_user.id + ) + + +async def test_update_invalid_user_id(hass): + """Test updating to invalid user ID.""" + manager = PersonManager( + hass, Mock(async_add_entities=mock_coro_func()), [] + ) + await manager.async_initialize() + person = await manager.async_create_person( + name='Hello', + ) + + with pytest.raises(ValueError): + await manager.async_update_person( + person_id=person['id'], + user_id='non-existing' + ) + + +async def test_update_person_when_user_removed(hass, hass_read_only_user): + """Update person when user is removed.""" + manager = PersonManager( + hass, Mock(async_add_entities=mock_coro_func()), [] + ) + await manager.async_initialize() + person = await manager.async_create_person( + name='Hello', + user_id=hass_read_only_user.id + ) + + await hass.auth.async_remove_user(hass_read_only_user) + await hass.async_block_till_done() + assert person['user_id'] is None From 81d2ec9618e166c85be5648be82f7f3d76680986 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 13 Feb 2019 20:10:31 -0800 Subject: [PATCH 192/242] Person: Ignore unavailable states (#21058) * Ignore unavailable states * Revert validation --- homeassistant/components/person/__init__.py | 44 +++++++++++---------- tests/components/person/test_init.py | 39 +++++++++++++++++- 2 files changed, 61 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index df19d86bb5c..20014c3414b 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -15,7 +15,7 @@ from homeassistant.components.device_tracker import ( DOMAIN as DEVICE_TRACKER_DOMAIN) from homeassistant.const import ( ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_ID, CONF_NAME, - EVENT_HOMEASSISTANT_START) + EVENT_HOMEASSISTANT_START, STATE_UNKNOWN, STATE_UNAVAILABLE) from homeassistant.core import callback, Event from homeassistant.auth import EVENT_USER_REMOVED import homeassistant.helpers.config_validation as cv @@ -25,7 +25,6 @@ from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.components import websocket_api from homeassistant.helpers.typing import HomeAssistantType, ConfigType -from homeassistant.util import dt as dt_util from homeassistant.loader import bind_hass _LOGGER = logging.getLogger(__name__) @@ -38,6 +37,8 @@ DOMAIN = 'person' STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 SAVE_DELAY = 10 +# Device tracker states to ignore +IGNORE_STATES = (STATE_UNKNOWN, STATE_UNAVAILABLE) PERSON_SCHEMA = vol.Schema({ vol.Required(CONF_ID): cv.string, @@ -350,30 +351,31 @@ class Person(RestoreEntity): trackers = self._config.get(CONF_DEVICE_TRACKERS) if trackers: - def sort_key(state): - if state: - return state.last_updated - return dt_util.utc_from_timestamp(0) - - latest = max( - [self.hass.states.get(entity_id) for entity_id in trackers], - key=sort_key - ) - - @callback - def async_handle_tracker_update(entity, old_state, new_state): - """Handle the device tracker state changes.""" - self._parse_source_state(new_state) - self.async_schedule_update_ha_state() - _LOGGER.debug( "Subscribe to device trackers for %s", self.entity_id) self._unsub_track_device = async_track_state_change( - self.hass, trackers, async_handle_tracker_update) + self.hass, trackers, self._async_handle_tracker_update) - else: - latest = None + self._update_state() + + @callback + def _async_handle_tracker_update(self, entity, old_state, new_state): + """Handle the device tracker state changes.""" + self._update_state() + + @callback + def _update_state(self): + """Update the state.""" + latest = None + for entity_id in self._config.get(CONF_DEVICE_TRACKERS, []): + state = self.hass.states.get(entity_id) + + if not state or state.state in IGNORE_STATES: + continue + + if latest is None or state.last_updated > latest.last_updated: + latest = state if latest: self._parse_source_state(latest) diff --git a/tests/components/person/test_init.py b/tests/components/person/test_init.py index 4cef84746ed..2eacb162f8e 100644 --- a/tests/components/person/test_init.py +++ b/tests/components/person/test_init.py @@ -29,7 +29,7 @@ def storage_setup(hass, hass_storage, hass_admin_user): 'id': '1234', 'name': 'tracked person', 'user_id': hass_admin_user.id, - 'device_trackers': DEVICE_TRACKER + 'device_trackers': [DEVICE_TRACKER] } ] } @@ -189,6 +189,43 @@ async def test_setup_two_trackers(hass, hass_admin_user): assert state.attributes.get(ATTR_USER_ID) == user_id +async def test_ignore_unavailable_states(hass, hass_admin_user): + """Test set up person with two device trackers, one unavailable.""" + user_id = hass_admin_user.id + config = {DOMAIN: { + 'id': '1234', 'name': 'tracked person', 'user_id': user_id, + 'device_trackers': [DEVICE_TRACKER, DEVICE_TRACKER_2]}} + assert await async_setup_component(hass, DOMAIN, config) + + state = hass.states.get('person.tracked_person') + assert state.state == STATE_UNKNOWN + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + hass.states.async_set(DEVICE_TRACKER, 'home') + await hass.async_block_till_done() + hass.states.async_set(DEVICE_TRACKER, 'unavailable') + await hass.async_block_till_done() + + # Unknown, as only 1 device tracker has a state, but we ignore that one + state = hass.states.get('person.tracked_person') + assert state.state == STATE_UNKNOWN + + hass.states.async_set(DEVICE_TRACKER_2, 'not_home') + await hass.async_block_till_done() + + # Take state of tracker 2 + state = hass.states.get('person.tracked_person') + assert state.state == 'not_home' + + # state 1 is newer but ignored, keep tracker 2 state + hass.states.async_set(DEVICE_TRACKER, 'unknown') + await hass.async_block_till_done() + + state = hass.states.get('person.tracked_person') + assert state.state == 'not_home' + + async def test_restore_home_state(hass, hass_admin_user): """Test that the state is restored for a person on startup.""" user_id = hass_admin_user.id From f1f3074612345adb02bdc874f7f3a10a8ac5dce2 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Thu, 14 Feb 2019 04:26:27 +0000 Subject: [PATCH 193/242] Add integration method to sensor.integration (#21050) * add integration method and respective new methods * ack @ottowinter tip * align const name with value --- .../components/sensor/integration.py | 29 ++++- tests/components/sensor/test_integration.py | 104 ++++++++++++++++++ 2 files changed, 127 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/sensor/integration.py b/homeassistant/components/sensor/integration.py index 9426730be35..9250c8dde05 100644 --- a/homeassistant/components/sensor/integration.py +++ b/homeassistant/components/sensor/integration.py @@ -26,6 +26,12 @@ CONF_ROUND_DIGITS = 'round' CONF_UNIT_PREFIX = 'unit_prefix' CONF_UNIT_TIME = 'unit_time' CONF_UNIT_OF_MEASUREMENT = 'unit' +CONF_METHOD = 'method' + +TRAPEZOIDAL_METHOD = 'trapezoidal' +LEFT_METHOD = 'left' +RIGHT_METHOD = 'right' +INTEGRATION_METHOD = [TRAPEZOIDAL_METHOD, LEFT_METHOD, RIGHT_METHOD] # SI Metric prefixes UNIT_PREFIXES = {None: 1, @@ -49,7 +55,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_ROUND_DIGITS, default=DEFAULT_ROUND): vol.Coerce(int), vol.Optional(CONF_UNIT_PREFIX, default=None): vol.In(UNIT_PREFIXES), vol.Optional(CONF_UNIT_TIME, default='h'): vol.In(UNIT_TIME), - vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string + vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, + vol.Optional(CONF_METHOD, default=TRAPEZOIDAL_METHOD): + vol.In(INTEGRATION_METHOD), }) @@ -61,7 +69,8 @@ async def async_setup_platform(hass, config, async_add_entities, config[CONF_ROUND_DIGITS], config[CONF_UNIT_PREFIX], config[CONF_UNIT_TIME], - config.get(CONF_UNIT_OF_MEASUREMENT)) + config.get(CONF_UNIT_OF_MEASUREMENT), + config[CONF_METHOD]) async_add_entities([integral]) @@ -70,11 +79,12 @@ class IntegrationSensor(RestoreEntity): """Representation of an integration sensor.""" def __init__(self, source_entity, name, round_digits, unit_prefix, - unit_time, unit_of_measurement): + unit_time, unit_of_measurement, integration_method): """Initialize the integration sensor.""" self._sensor_source_id = source_entity self._round_digits = round_digits self._state = 0 + self._method = integration_method self._name = name if name is not None\ else '{} integral'.format(source_entity) @@ -117,12 +127,19 @@ class IntegrationSensor(RestoreEntity): try: # integration as the Riemann integral of previous measures. + area = 0 elapsed_time = (new_state.last_updated - old_state.last_updated).total_seconds() - area = (Decimal(new_state.state) - + Decimal(old_state.state))*Decimal(elapsed_time)/2 - integral = area / (self._unit_prefix * self._unit_time) + if self._method == TRAPEZOIDAL_METHOD: + area = (Decimal(new_state.state) + + Decimal(old_state.state))*Decimal(elapsed_time)/2 + elif self._method == LEFT_METHOD: + area = Decimal(old_state.state)*Decimal(elapsed_time) + elif self._method == RIGHT_METHOD: + area = Decimal(new_state.state)*Decimal(elapsed_time) + + integral = area / (self._unit_prefix * self._unit_time) assert isinstance(integral, Decimal) except ValueError as err: _LOGGER.warning("While calculating integration: %s", err) diff --git a/tests/components/sensor/test_integration.py b/tests/components/sensor/test_integration.py index bb4a02c042b..7f02d59f591 100644 --- a/tests/components/sensor/test_integration.py +++ b/tests/components/sensor/test_integration.py @@ -39,6 +39,110 @@ async def test_state(hass): assert state.attributes.get('unit_of_measurement') == 'kWh' +async def test_trapezoidal(hass): + """Test integration sensor state.""" + config = { + 'sensor': { + 'platform': 'integration', + 'name': 'integration', + 'source': 'sensor.power', + 'unit': 'kWh', + 'round': 2, + } + } + + assert await async_setup_component(hass, 'sensor', config) + + entity_id = config['sensor']['source'] + hass.states.async_set(entity_id, 0, {}) + await hass.async_block_till_done() + + # Testing a power sensor with non-monotonic intervals and values + for time, value in [(20, 10), (30, 30), (40, 5), (50, 0)]: + now = dt_util.utcnow() + timedelta(minutes=time) + with patch('homeassistant.util.dt.utcnow', + return_value=now): + hass.states.async_set(entity_id, value, {}, force_update=True) + await hass.async_block_till_done() + + state = hass.states.get('sensor.integration') + assert state is not None + + assert round(float(state.state), config['sensor']['round']) == 8.33 + + assert state.attributes.get('unit_of_measurement') == 'kWh' + + +async def test_left(hass): + """Test integration sensor state with left reimann method.""" + config = { + 'sensor': { + 'platform': 'integration', + 'name': 'integration', + 'method': 'left', + 'source': 'sensor.power', + 'unit': 'kWh', + 'round': 2, + } + } + + assert await async_setup_component(hass, 'sensor', config) + + entity_id = config['sensor']['source'] + hass.states.async_set(entity_id, 0, {}) + await hass.async_block_till_done() + + # Testing a power sensor with non-monotonic intervals and values + for time, value in [(20, 10), (30, 30), (40, 5), (50, 0)]: + now = dt_util.utcnow() + timedelta(minutes=time) + with patch('homeassistant.util.dt.utcnow', + return_value=now): + hass.states.async_set(entity_id, value, {}, force_update=True) + await hass.async_block_till_done() + + state = hass.states.get('sensor.integration') + assert state is not None + + assert round(float(state.state), config['sensor']['round']) == 7.5 + + assert state.attributes.get('unit_of_measurement') == 'kWh' + + +async def test_right(hass): + """Test integration sensor state with left reimann method.""" + config = { + 'sensor': { + 'platform': 'integration', + 'name': 'integration', + 'method': 'right', + 'source': 'sensor.power', + 'unit': 'kWh', + 'round': 2, + } + } + + assert await async_setup_component(hass, 'sensor', config) + + entity_id = config['sensor']['source'] + hass.states.async_set(entity_id, 0, {}) + await hass.async_block_till_done() + + # Testing a power sensor with non-monotonic intervals and values + for time, value in [(20, 10), (30, 30), (40, 5), (50, 0)]: + now = dt_util.utcnow() + timedelta(minutes=time) + with patch('homeassistant.util.dt.utcnow', + return_value=now): + hass.states.async_set(entity_id, value, {}, force_update=True) + await hass.async_block_till_done() + + state = hass.states.get('sensor.integration') + assert state is not None + + assert round(float(state.state), config['sensor']['round']) == 9.17 + + assert state.attributes.get('unit_of_measurement') == 'kWh' + + async def test_prefix(hass): """Test integration sensor state using a power source.""" config = { From 1faf2f49d07805aaafac215ab1e259fe5d683451 Mon Sep 17 00:00:00 2001 From: Fredrik Erlandsson Date: Thu, 14 Feb 2019 05:27:17 +0100 Subject: [PATCH 194/242] fix webhook update (#21048) --- homeassistant/components/point/__init__.py | 2 +- homeassistant/components/point/binary_sensor.py | 3 ++- requirements_all.txt | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/point/__init__.py b/homeassistant/components/point/__init__.py index e0f1e6651c6..fa0217c023b 100644 --- a/homeassistant/components/point/__init__.py +++ b/homeassistant/components/point/__init__.py @@ -25,7 +25,7 @@ from .const import ( CONF_WEBHOOK_URL, DOMAIN, EVENT_RECEIVED, POINT_DISCOVERY_NEW, SCAN_INTERVAL, SIGNAL_UPDATE_ENTITY, SIGNAL_WEBHOOK) -REQUIREMENTS = ['pypoint==1.0.7'] +REQUIREMENTS = ['pypoint==1.0.8'] DEPENDENCIES = ['webhook'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/point/binary_sensor.py b/homeassistant/components/point/binary_sensor.py index 5f4834894bc..2c79bf21c61 100644 --- a/homeassistant/components/point/binary_sensor.py +++ b/homeassistant/components/point/binary_sensor.py @@ -91,7 +91,8 @@ class MinutPointBinarySensor(MinutPointEntity, BinarySensorDevice): if self.device.webhook != webhook: return _type = data.get('event', {}).get('type') - if _type not in self._events: + _device_id = data.get('event', {}).get('device_id') + if _type not in self._events or _device_id != self.device.device_id: return _LOGGER.debug("Recieved webhook: %s", _type) if _type == self._events[0]: diff --git a/requirements_all.txt b/requirements_all.txt index 71e4df18d17..5925ff13596 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1196,7 +1196,7 @@ pypck==0.5.9 pypjlink2==1.2.0 # homeassistant.components.point -pypoint==1.0.7 +pypoint==1.0.8 # homeassistant.components.sensor.pollen pypollencom==2.2.2 From 3a386e627ef53f0810acf0188393e44dbc1e48e9 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 14 Feb 2019 05:29:11 +0100 Subject: [PATCH 195/242] Upgrade ruamel.yaml to 0.15.88 (#21055) --- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 27bb10d99f5..d0a192a9dc7 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ python-slugify==1.2.6 pytz>=2018.07 pyyaml>=3.13,<4 requests==2.21.0 -ruamel.yaml==0.15.87 +ruamel.yaml==0.15.88 voluptuous==0.11.5 voluptuous-serialize==2.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index 5925ff13596..b91036fa1f6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -13,7 +13,7 @@ python-slugify==1.2.6 pytz>=2018.07 pyyaml>=3.13,<4 requests==2.21.0 -ruamel.yaml==0.15.87 +ruamel.yaml==0.15.88 voluptuous==0.11.5 voluptuous-serialize==2.0.0 diff --git a/setup.py b/setup.py index 285757ce710..52be310574a 100755 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ REQUIRES = [ 'pytz>=2018.07', 'pyyaml>=3.13,<4', 'requests==2.21.0', - 'ruamel.yaml==0.15.87', + 'ruamel.yaml==0.15.88', 'voluptuous==0.11.5', 'voluptuous-serialize==2.0.0', ] From 161c368c9d6e7106721c2e0ab99066b902a38d0f Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 14 Feb 2019 05:35:12 +0100 Subject: [PATCH 196/242] Update file header (#21054) * Update file header * Update __init__.py --- .../components/hydrawise/__init__.py | 7 +-- .../components/hydrawise/binary_sensor.py | 7 +-- homeassistant/components/hydrawise/sensor.py | 7 +-- homeassistant/components/hydrawise/switch.py | 13 ++--- .../components/idteck_prox/__init__.py | 6 +-- homeassistant/components/ifttt/__init__.py | 7 +-- .../components/ifttt/alarm_control_panel.py | 7 +-- homeassistant/components/influxdb/__init__.py | 7 +-- .../components/input_boolean/__init__.py | 7 +-- .../components/input_datetime/__init__.py | 7 +-- .../components/input_number/__init__.py | 7 +-- .../components/input_select/__init__.py | 7 +-- .../components/input_text/__init__.py | 7 +-- .../components/insteon_local/__init__.py | 7 +-- .../components/insteon_plm/__init__.py | 7 +-- .../components/introduction/__init__.py | 7 +-- homeassistant/components/iota/__init__.py | 7 +-- homeassistant/components/iota/sensor.py | 9 +--- homeassistant/components/itach/__init__.py | 6 +-- homeassistant/components/itach/remote.py | 10 +--- .../components/joaoapps_join/__init__.py | 10 ++-- .../components/joaoapps_join/notify.py | 7 +-- homeassistant/components/juicenet/__init__.py | 10 +--- homeassistant/components/juicenet/sensor.py | 11 ++-- homeassistant/components/keyboard/__init__.py | 7 +-- .../components/keyboard_remote/__init__.py | 7 +-- homeassistant/components/kira/__init__.py | 10 +--- homeassistant/components/kira/remote.py | 9 +--- homeassistant/components/kira/sensor.py | 7 +-- homeassistant/components/knx/__init__.py | 12 ++--- homeassistant/components/knx/binary_sensor.py | 10 +--- homeassistant/components/knx/climate.py | 8 +-- homeassistant/components/knx/cover.py | 12 ++--- homeassistant/components/knx/light.py | 7 +-- homeassistant/components/knx/notify.py | 9 +--- homeassistant/components/knx/scene.py | 11 ++-- homeassistant/components/knx/sensor.py | 8 +-- homeassistant/components/knx/switch.py | 8 +-- .../components/konnected/__init__.py | 7 +-- .../components/konnected/binary_sensor.py | 15 ++---- homeassistant/components/konnected/switch.py | 16 ++---- homeassistant/components/lametric/__init__.py | 10 +--- homeassistant/components/lametric/notify.py | 32 +++++------- homeassistant/components/lcn/__init__.py | 8 +-- homeassistant/components/lcn/light.py | 12 ++--- homeassistant/components/lcn/switch.py | 8 +-- homeassistant/components/lifx/__init__.py | 3 +- homeassistant/components/lifx/light.py | 7 +-- .../components/lightwave/__init__.py | 9 ++-- homeassistant/components/lightwave/light.py | 11 ++-- homeassistant/components/lightwave/switch.py | 11 ++-- homeassistant/components/linode/__init__.py | 11 ++-- .../components/linode/binary_sensor.py | 17 +++---- homeassistant/components/linode/switch.py | 17 +++---- homeassistant/components/lirc/__init__.py | 7 +-- homeassistant/components/litejet/__init__.py | 8 +-- homeassistant/components/locative/__init__.py | 7 +-- homeassistant/components/logbook/__init__.py | 11 ++-- .../components/logentries/__init__.py | 27 ++++------ homeassistant/components/logger/__init__.py | 7 +-- .../components/logi_circle/__init__.py | 7 +-- .../components/logi_circle/camera.py | 7 +-- .../components/logi_circle/sensor.py | 34 +++---------- homeassistant/components/lovelace/__init__.py | 7 +-- .../components/luftdaten/__init__.py | 7 +-- homeassistant/components/luftdaten/sensor.py | 7 +-- homeassistant/components/lupusec/__init__.py | 11 ++-- .../components/lupusec/alarm_control_panel.py | 8 +-- .../components/lupusec/binary_sensor.py | 7 +-- homeassistant/components/lupusec/switch.py | 7 +-- homeassistant/components/lutron/__init__.py | 7 +-- homeassistant/components/lutron/cover.py | 7 +-- homeassistant/components/lutron/light.py | 7 +-- homeassistant/components/lutron/scene.py | 21 +++----- homeassistant/components/lutron/switch.py | 7 +-- .../components/lutron_caseta/__init__.py | 20 +++----- .../components/lutron_caseta/cover.py | 11 ++-- .../components/lutron_caseta/light.py | 11 ++-- .../components/lutron_caseta/scene.py | 15 ++---- .../components/lutron_caseta/switch.py | 11 ++-- homeassistant/components/mailbox/__init__.py | 7 +-- .../components/mailbox/asterisk_cdr.py | 13 ++--- homeassistant/components/mailbox/demo.py | 9 +--- homeassistant/components/mailgun/__init__.py | 16 +++--- homeassistant/components/mailgun/notify.py | 12 ++--- homeassistant/components/map/__init__.py | 7 +-- homeassistant/components/matrix/__init__.py | 25 +++------ homeassistant/components/matrix/notify.py | 7 +-- homeassistant/components/maxcube/__init__.py | 9 +--- .../components/maxcube/binary_sensor.py | 7 +-- homeassistant/components/maxcube/climate.py | 7 +-- .../components/media_extractor/__init__.py | 7 +-- homeassistant/components/melissa/__init__.py | 9 +--- .../components/microsoft_face/__init__.py | 9 +--- homeassistant/components/mochad/__init__.py | 7 +-- homeassistant/components/mochad/light.py | 20 +++----- homeassistant/components/mochad/switch.py | 15 ++---- homeassistant/components/modbus/__init__.py | 7 +-- .../components/modbus/binary_sensor.py | 15 ++---- homeassistant/components/modbus/climate.py | 21 +++----- homeassistant/components/modbus/sensor.py | 11 ++-- homeassistant/components/modbus/switch.py | 51 ++++++------------- .../components/mqtt_eventstream/__init__.py | 9 +--- .../components/mqtt_statestream/__init__.py | 14 ++--- homeassistant/components/mychevy/__init__.py | 11 ++-- .../components/mychevy/binary_sensor.py | 16 ++---- homeassistant/components/mychevy/sensor.py | 19 +++---- homeassistant/components/mycroft/__init__.py | 10 +--- .../components/mysensors/__init__.py | 7 +-- .../components/mysensors/binary_sensor.py | 7 +-- homeassistant/components/mysensors/climate.py | 7 +-- homeassistant/components/mysensors/cover.py | 7 +-- .../components/mysensors/device_tracker.py | 7 +-- homeassistant/components/mysensors/light.py | 7 +-- homeassistant/components/mysensors/notify.py | 7 +-- homeassistant/components/mysensors/sensor.py | 7 +-- homeassistant/components/mysensors/switch.py | 17 +++---- .../components/mythicbeastsdns/__init__.py | 7 +-- .../components/namecheapdns/__init__.py | 7 +-- homeassistant/components/neato/__init__.py | 11 ++-- homeassistant/components/neato/camera.py | 7 +-- homeassistant/components/neato/switch.py | 7 +-- homeassistant/components/neato/vacuum.py | 7 +-- .../components/ness_alarm/__init__.py | 13 ++--- homeassistant/components/nest/__init__.py | 23 ++++----- .../components/nest/binary_sensor.py | 29 +++++------ homeassistant/components/nest/camera.py | 7 +-- homeassistant/components/nest/climate.py | 7 +-- homeassistant/components/nest/sensor.py | 7 +-- homeassistant/components/netatmo/__init__.py | 7 +-- .../components/netatmo/binary_sensor.py | 9 +--- homeassistant/components/netatmo/camera.py | 7 +-- homeassistant/components/netatmo/climate.py | 7 +-- homeassistant/components/netatmo/sensor.py | 10 +--- .../components/netgear_lte/__init__.py | 7 +-- .../components/netgear_lte/notify.py | 7 +-- .../components/netgear_lte/sensor.py | 9 +--- homeassistant/components/no_ip/__init__.py | 7 +-- homeassistant/components/nuheat/__init__.py | 9 +--- .../components/nuimo_controller/__init__.py | 7 +-- .../components/octoprint/__init__.py | 12 ++--- .../components/octoprint/binary_sensor.py | 7 +-- homeassistant/components/octoprint/sensor.py | 8 +-- .../components/onboarding/__init__.py | 6 +-- .../components/opentherm_gw/__init__.py | 15 ++---- .../components/opentherm_gw/binary_sensor.py | 15 ++---- .../components/opentherm_gw/climate.py | 18 +++---- .../components/opentherm_gw/sensor.py | 17 +++---- homeassistant/components/openuv/__init__.py | 11 ++-- .../components/openuv/binary_sensor.py | 19 +++---- .../components/openuv/config_flow.py | 2 +- homeassistant/components/openuv/sensor.py | 24 ++++----- .../components/owntracks/__init__.py | 13 ++--- .../components/panel_custom/__init__.py | 17 +++---- .../components/panel_iframe/__init__.py | 9 +--- .../persistent_notification/__init__.py | 13 ++--- homeassistant/components/person/__init__.py | 29 +++++------ homeassistant/components/plant/__init__.py | 45 ++++++++-------- .../components/plum_lightpad/__init__.py | 7 +-- .../components/plum_lightpad/light.py | 24 ++++----- homeassistant/components/point/__init__.py | 10 ++-- .../components/point/binary_sensor.py | 12 ++--- homeassistant/components/point/sensor.py | 11 ++-- .../components/prometheus/__init__.py | 17 +++---- .../components/proximity/__init__.py | 14 ++--- 165 files changed, 482 insertions(+), 1346 deletions(-) diff --git a/homeassistant/components/hydrawise/__init__.py b/homeassistant/components/hydrawise/__init__.py index 5a045a083b3..800d19d7efe 100644 --- a/homeassistant/components/hydrawise/__init__.py +++ b/homeassistant/components/hydrawise/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Hydrawise cloud. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/hydrawise/ -""" +"""Support for Hydrawise cloud.""" from datetime import timedelta import logging diff --git a/homeassistant/components/hydrawise/binary_sensor.py b/homeassistant/components/hydrawise/binary_sensor.py index 38b660c506f..bfe7cbd5531 100644 --- a/homeassistant/components/hydrawise/binary_sensor.py +++ b/homeassistant/components/hydrawise/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for Hydrawise sprinkler. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.hydrawise/ -""" +"""Support for Hydrawise sprinkler binary sensors.""" import logging import voluptuous as vol diff --git a/homeassistant/components/hydrawise/sensor.py b/homeassistant/components/hydrawise/sensor.py index bff99ddc501..575686b92cd 100644 --- a/homeassistant/components/hydrawise/sensor.py +++ b/homeassistant/components/hydrawise/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Hydrawise sprinkler. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.hydrawise/ -""" +"""Support for Hydrawise sprinkler sensors.""" import logging import voluptuous as vol diff --git a/homeassistant/components/hydrawise/switch.py b/homeassistant/components/hydrawise/switch.py index 6b73333431d..a6a8b9c54cf 100644 --- a/homeassistant/components/hydrawise/switch.py +++ b/homeassistant/components/hydrawise/switch.py @@ -1,9 +1,4 @@ -""" -Support for Hydrawise cloud. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.hydrawise/ -""" +"""Support for Hydrawise cloud switches.""" import logging import voluptuous as vol @@ -36,12 +31,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): sensors = [] for sensor_type in config.get(CONF_MONITORED_CONDITIONS): - # create a switch for each zone + # Create a switch for each zone for zone in hydrawise.relays: sensors.append( - HydrawiseSwitch(default_watering_timer, - zone, - sensor_type)) + HydrawiseSwitch(default_watering_timer, zone, sensor_type)) add_entities(sensors, True) diff --git a/homeassistant/components/idteck_prox/__init__.py b/homeassistant/components/idteck_prox/__init__.py index 90c07c487b6..8ec6f49b95d 100644 --- a/homeassistant/components/idteck_prox/__init__.py +++ b/homeassistant/components/idteck_prox/__init__.py @@ -1,8 +1,4 @@ -"""Component for interfacing RFK101 proximity card readers. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/idteck_prox/ -""" +"""Component for interfacing RFK101 proximity card readers.""" import logging import voluptuous as vol diff --git a/homeassistant/components/ifttt/__init__.py b/homeassistant/components/ifttt/__init__.py index 209bbcef607..7dee93b2260 100644 --- a/homeassistant/components/ifttt/__init__.py +++ b/homeassistant/components/ifttt/__init__.py @@ -1,9 +1,4 @@ -""" -Support to trigger Maker IFTTT recipes. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/ifttt/ -""" +"""Support to trigger Maker IFTTT recipes.""" import json import logging diff --git a/homeassistant/components/ifttt/alarm_control_panel.py b/homeassistant/components/ifttt/alarm_control_panel.py index fe9c96a0083..bbb9a02c8a1 100644 --- a/homeassistant/components/ifttt/alarm_control_panel.py +++ b/homeassistant/components/ifttt/alarm_control_panel.py @@ -1,9 +1,4 @@ -""" -Interfaces with alarm control panels that have to be controlled through IFTTT. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/alarm_control_panel.ifttt/ -""" +"""Support for alarm control panels that can be controlled through IFTTT.""" import logging import re diff --git a/homeassistant/components/influxdb/__init__.py b/homeassistant/components/influxdb/__init__.py index 2b9a5d9e193..b421960b51f 100644 --- a/homeassistant/components/influxdb/__init__.py +++ b/homeassistant/components/influxdb/__init__.py @@ -1,9 +1,4 @@ -""" -A component which allows you to send data to an Influx database. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/influxdb/ -""" +"""Support for sending data to an Influx database.""" import logging import re import queue diff --git a/homeassistant/components/input_boolean/__init__.py b/homeassistant/components/input_boolean/__init__.py index 896de61130c..246af2613a7 100644 --- a/homeassistant/components/input_boolean/__init__.py +++ b/homeassistant/components/input_boolean/__init__.py @@ -1,9 +1,4 @@ -""" -Component to keep track of user controlled booleans for within automation. - -For more details about this component, please refer to the documentation -at https://home-assistant.io/components/input_boolean/ -""" +"""Support to keep track of user controlled booleans for within automation.""" import logging import voluptuous as vol diff --git a/homeassistant/components/input_datetime/__init__.py b/homeassistant/components/input_datetime/__init__.py index 63dcc364c9c..34faffd2028 100644 --- a/homeassistant/components/input_datetime/__init__.py +++ b/homeassistant/components/input_datetime/__init__.py @@ -1,9 +1,4 @@ -""" -Component to offer a way to select a date and / or a time. - -For more details about this component, please refer to the documentation -at https://home-assistant.io/components/input_datetime/ -""" +"""Support to select a date and/or a time.""" import logging import datetime diff --git a/homeassistant/components/input_number/__init__.py b/homeassistant/components/input_number/__init__.py index 8cfa7abaf20..d9d3ac8bbc0 100644 --- a/homeassistant/components/input_number/__init__.py +++ b/homeassistant/components/input_number/__init__.py @@ -1,9 +1,4 @@ -""" -Component to offer a way to set a numeric value from a slider or text box. - -For more details about this component, please refer to the documentation -at https://home-assistant.io/components/input_number/ -""" +"""Support to set a numeric value from a slider or text box.""" import logging import voluptuous as vol diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py index fc858e75397..fd3e4335c33 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -1,9 +1,4 @@ -""" -Component to offer a way to select an option from a list. - -For more details about this component, please refer to the documentation -at https://home-assistant.io/components/input_select/ -""" +"""Support to select an option from a list.""" import logging import voluptuous as vol diff --git a/homeassistant/components/input_text/__init__.py b/homeassistant/components/input_text/__init__.py index 580337a3af3..48a467b54a2 100644 --- a/homeassistant/components/input_text/__init__.py +++ b/homeassistant/components/input_text/__init__.py @@ -1,9 +1,4 @@ -""" -Component to offer a way to enter a value into a text box. - -For more details about this component, please refer to the documentation -at https://home-assistant.io/components/input_text/ -""" +"""Support to enter a value into a text box.""" import logging import voluptuous as vol diff --git a/homeassistant/components/insteon_local/__init__.py b/homeassistant/components/insteon_local/__init__.py index 003714d0f94..f73c46746f0 100644 --- a/homeassistant/components/insteon_local/__init__.py +++ b/homeassistant/components/insteon_local/__init__.py @@ -1,9 +1,4 @@ -""" -Local support for Insteon. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/insteon_local/ -""" +"""Local support for Insteon.""" import logging _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/insteon_plm/__init__.py b/homeassistant/components/insteon_plm/__init__.py index b3011e9d7bd..5ff492b6f6c 100644 --- a/homeassistant/components/insteon_plm/__init__.py +++ b/homeassistant/components/insteon_plm/__init__.py @@ -1,9 +1,4 @@ -""" -Support for INSTEON PowerLinc Modem. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/insteon_plm/ -""" +"""Support for INSTEON PowerLinc Modem.""" import logging _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/introduction/__init__.py b/homeassistant/components/introduction/__init__.py index 17de7fcd6ca..8a2d72ebbdd 100644 --- a/homeassistant/components/introduction/__init__.py +++ b/homeassistant/components/introduction/__init__.py @@ -1,9 +1,4 @@ -""" -Component that will help guide the user taking its first steps. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/introduction/ -""" +"""Component that will help guide the user taking its first steps.""" import logging import voluptuous as vol diff --git a/homeassistant/components/iota/__init__.py b/homeassistant/components/iota/__init__.py index 717213da9ac..e28de61aad0 100644 --- a/homeassistant/components/iota/__init__.py +++ b/homeassistant/components/iota/__init__.py @@ -1,9 +1,4 @@ -""" -Support for IOTA wallets. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/iota/ -""" +"""Support for IOTA wallets.""" import logging from datetime import timedelta diff --git a/homeassistant/components/iota/sensor.py b/homeassistant/components/iota/sensor.py index 961cd119d78..5cd5db6169b 100644 --- a/homeassistant/components/iota/sensor.py +++ b/homeassistant/components/iota/sensor.py @@ -1,9 +1,4 @@ -""" -Support for IOTA wallets. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/iota -""" +"""Support for IOTA wallet sensors.""" import logging from datetime import timedelta @@ -26,12 +21,10 @@ SCAN_INTERVAL = timedelta(minutes=3) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the IOTA sensor.""" - # Add sensors for wallet balance iota_config = discovery_info sensors = [IotaBalanceSensor(wallet, iota_config) for wallet in iota_config[CONF_WALLETS]] - # Add sensor for node information sensors.append(IotaNodeSensor(iota_config=iota_config)) add_entities(sensors) diff --git a/homeassistant/components/itach/__init__.py b/homeassistant/components/itach/__init__.py index 267370dbcd7..de43b41fdb7 100644 --- a/homeassistant/components/itach/__init__.py +++ b/homeassistant/components/itach/__init__.py @@ -1,5 +1 @@ -"""The itach component. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/itach/ -""" +"""Support for itach devices.""" diff --git a/homeassistant/components/itach/remote.py b/homeassistant/components/itach/remote.py index e7f23dfcd13..beb773838fb 100644 --- a/homeassistant/components/itach/remote.py +++ b/homeassistant/components/itach/remote.py @@ -1,10 +1,4 @@ -""" -Support for iTach IR Devices. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/remote.itach/ -""" - +"""Support for iTach IR devices.""" import logging import voluptuous as vol @@ -38,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_CONNADDR): vol.Coerce(int), vol.Required(CONF_COMMANDS): vol.All(cv.ensure_list, [{ vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_DATA): cv.string + vol.Required(CONF_DATA): cv.string, }]) }]) }) diff --git a/homeassistant/components/joaoapps_join/__init__.py b/homeassistant/components/joaoapps_join/__init__.py index b5bcb1e1a8a..adc856bdd3a 100644 --- a/homeassistant/components/joaoapps_join/__init__.py +++ b/homeassistant/components/joaoapps_join/__init__.py @@ -1,9 +1,4 @@ -""" -Component for Joaoapps Join services. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/join/ -""" +"""Support for Joaoapps Join services.""" import logging import voluptuous as vol @@ -16,6 +11,7 @@ REQUIREMENTS = ['python-join-api==0.0.2'] _LOGGER = logging.getLogger(__name__) DOMAIN = 'joaoapps_join' + CONF_DEVICE_ID = 'device_id' CONF_DEVICE_IDS = 'device_ids' CONF_DEVICE_NAMES = 'device_names' @@ -26,7 +22,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_DEVICE_ID): cv.string, vol.Optional(CONF_DEVICE_IDS): cv.string, vol.Optional(CONF_DEVICE_NAMES): cv.string, - vol.Optional(CONF_NAME): cv.string + vol.Optional(CONF_NAME): cv.string, }]) }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/joaoapps_join/notify.py b/homeassistant/components/joaoapps_join/notify.py index a75ff9cd165..c586147d632 100644 --- a/homeassistant/components/joaoapps_join/notify.py +++ b/homeassistant/components/joaoapps_join/notify.py @@ -1,9 +1,4 @@ -""" -Join platform for notify component. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/notify.join/ -""" +"""Support for Join notifications.""" import logging import voluptuous as vol from homeassistant.components.notify import ( diff --git a/homeassistant/components/juicenet/__init__.py b/homeassistant/components/juicenet/__init__.py index 55567d45879..f62331d1502 100644 --- a/homeassistant/components/juicenet/__init__.py +++ b/homeassistant/components/juicenet/__init__.py @@ -1,10 +1,4 @@ -""" -Support for Juicenet cloud. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/juicenet -""" - +"""Support for Juicenet cloud.""" import logging import voluptuous as vol @@ -22,7 +16,7 @@ DOMAIN = 'juicenet' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ - vol.Required(CONF_ACCESS_TOKEN): cv.string + vol.Required(CONF_ACCESS_TOKEN): cv.string, }) }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/juicenet/sensor.py b/homeassistant/components/juicenet/sensor.py index 18725394a1f..e3786627075 100644 --- a/homeassistant/components/juicenet/sensor.py +++ b/homeassistant/components/juicenet/sensor.py @@ -1,19 +1,14 @@ -""" -Support for monitoring juicenet/juicepoint/juicebox based EVSE sensors. - -For more details about this platform, please refer to the documentation at -at https://home-assistant.io/components/sensor.juicenet/ -""" - +"""Support for monitoring juicenet/juicepoint/juicebox based EVSE sensors.""" import logging from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity from homeassistant.components.juicenet import JuicenetDevice, DOMAIN -DEPENDENCIES = ['juicenet'] _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['juicenet'] + SENSOR_TYPES = { 'status': ['Charging Status', None], 'temperature': ['Temperature', TEMP_CELSIUS], diff --git a/homeassistant/components/keyboard/__init__.py b/homeassistant/components/keyboard/__init__.py index 16253ba271a..44accca2f56 100644 --- a/homeassistant/components/keyboard/__init__.py +++ b/homeassistant/components/keyboard/__init__.py @@ -1,9 +1,4 @@ -""" -Provides functionality to emulate keyboard presses on host machine. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/keyboard/ -""" +"""Support to emulate keyboard presses on host machine.""" import voluptuous as vol from homeassistant.const import ( diff --git a/homeassistant/components/keyboard_remote/__init__.py b/homeassistant/components/keyboard_remote/__init__.py index e02c2ee5475..e786fe458a8 100644 --- a/homeassistant/components/keyboard_remote/__init__.py +++ b/homeassistant/components/keyboard_remote/__init__.py @@ -1,9 +1,4 @@ -""" -Receive signals from a keyboard and use it as a remote control. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/keyboard_remote/ -""" +"""Receive signals from a keyboard and use it as a remote control.""" # pylint: disable=import-error import threading import logging diff --git a/homeassistant/components/kira/__init__.py b/homeassistant/components/kira/__init__.py index 3a5ee25f05e..d60d8e0cfeb 100644 --- a/homeassistant/components/kira/__init__.py +++ b/homeassistant/components/kira/__init__.py @@ -1,9 +1,4 @@ -""" -KIRA interface to receive UDP packets from an IR-IP bridge. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/kira/ -""" +"""KIRA interface to receive UDP packets from an IR-IP bridge.""" import logging import os @@ -13,7 +8,7 @@ import yaml from homeassistant.const import ( CONF_DEVICE, CONF_HOST, CONF_NAME, CONF_PORT, CONF_SENSORS, CONF_TYPE, - EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN) + EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN, CONF_CODE) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv @@ -26,7 +21,6 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_HOST = "0.0.0.0" DEFAULT_PORT = 65432 -CONF_CODE = "code" CONF_REPEAT = "repeat" CONF_REMOTES = "remotes" CONF_SENSOR = "sensor" diff --git a/homeassistant/components/kira/remote.py b/homeassistant/components/kira/remote.py index 24fc54ee78c..8ddf0858e16 100644 --- a/homeassistant/components/kira/remote.py +++ b/homeassistant/components/kira/remote.py @@ -1,9 +1,4 @@ -""" -Support for Keene Electronics IR-IP devices. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/remote.kira/ -""" +"""Support for Keene Electronics IR-IP devices.""" import functools as ft import logging @@ -15,7 +10,7 @@ DOMAIN = 'kira' _LOGGER = logging.getLogger(__name__) -CONF_REMOTE = "remote" +CONF_REMOTE = 'remote' def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/kira/sensor.py b/homeassistant/components/kira/sensor.py index ced17281cc8..8885ebcbe24 100644 --- a/homeassistant/components/kira/sensor.py +++ b/homeassistant/components/kira/sensor.py @@ -1,9 +1,4 @@ -""" -KIRA interface to receive UDP packets from an IR-IP bridge. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.kira/ -""" +"""KIRA interface to receive UDP packets from an IR-IP bridge.""" import logging from homeassistant.const import CONF_DEVICE, CONF_NAME, STATE_UNKNOWN diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index fae18bf8b77..fdaba5e5709 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -1,10 +1,4 @@ -""" -Connects to KNX platform. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/knx/ -""" - +"""Support KNX devices.""" import logging import voluptuous as vol @@ -19,6 +13,8 @@ from homeassistant.helpers.script import Script REQUIREMENTS = ['xknx==0.9.4'] +_LOGGER = logging.getLogger(__name__) + DOMAIN = "knx" DATA_KNX = "data_knx" CONF_KNX_CONFIG = "config_file" @@ -39,8 +35,6 @@ SERVICE_KNX_ATTR_PAYLOAD = "payload" ATTR_DISCOVER_DEVICES = 'devices' -_LOGGER = logging.getLogger(__name__) - TUNNELING_SCHEMA = vol.Schema({ vol.Required(CONF_HOST): cv.string, vol.Required(CONF_KNX_LOCAL_IP): cv.string, diff --git a/homeassistant/components/knx/binary_sensor.py b/homeassistant/components/knx/binary_sensor.py index a89d5d1c945..ca7037fe81d 100644 --- a/homeassistant/components/knx/binary_sensor.py +++ b/homeassistant/components/knx/binary_sensor.py @@ -1,10 +1,4 @@ -""" -Support for KNX/IP binary sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.knx/ -""" - +"""Support for KNX/IP binary sensors.""" import voluptuous as vol from homeassistant.components.binary_sensor import ( @@ -35,7 +29,7 @@ DEPENDENCIES = ['knx'] AUTOMATION_SCHEMA = vol.Schema({ vol.Optional(CONF_HOOK, default=CONF_DEFAULT_HOOK): cv.string, vol.Optional(CONF_COUNTER, default=CONF_DEFAULT_COUNTER): cv.port, - vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA + vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA, }) AUTOMATIONS_SCHEMA = vol.All( diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index 77d995af2a1..82eaa52ae5a 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -1,10 +1,4 @@ -""" -Support for KNX/IP climate devices. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/climate.knx/ -""" - +"""Support for KNX/IP climate devices.""" import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.climate import ( diff --git a/homeassistant/components/knx/cover.py b/homeassistant/components/knx/cover.py index 4173db5f450..9423983f9f7 100644 --- a/homeassistant/components/knx/cover.py +++ b/homeassistant/components/knx/cover.py @@ -1,10 +1,4 @@ -""" -Support for KNX/IP covers. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/cover.knx/ -""" - +"""Support for KNX/IP covers.""" import voluptuous as vol from homeassistant.components.cover import ( @@ -49,8 +43,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up cover(s) for KNX platform.""" if discovery_info is not None: async_add_entities_discovery(hass, discovery_info, async_add_entities) diff --git a/homeassistant/components/knx/light.py b/homeassistant/components/knx/light.py index bb92f1d0ce0..f2a6f15e08b 100644 --- a/homeassistant/components/knx/light.py +++ b/homeassistant/components/knx/light.py @@ -1,9 +1,4 @@ -""" -Support for KNX/IP lights. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.knx/ -""" +"""Support for KNX/IP lights.""" from enum import Enum import voluptuous as vol diff --git a/homeassistant/components/knx/notify.py b/homeassistant/components/knx/notify.py index 750e3945569..2488114aa41 100644 --- a/homeassistant/components/knx/notify.py +++ b/homeassistant/components/knx/notify.py @@ -1,10 +1,4 @@ -""" -KNX/IP notification service. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/notify.knx/ -""" - +"""Support for KNX/IP notification services.""" import voluptuous as vol from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES @@ -16,6 +10,7 @@ import homeassistant.helpers.config_validation as cv CONF_ADDRESS = 'address' DEFAULT_NAME = 'KNX Notify' + DEPENDENCIES = ['knx'] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ diff --git a/homeassistant/components/knx/scene.py b/homeassistant/components/knx/scene.py index cd333ba79b4..008e81508b9 100644 --- a/homeassistant/components/knx/scene.py +++ b/homeassistant/components/knx/scene.py @@ -1,9 +1,4 @@ -""" -Support for KNX scenes. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/scene.knx/ -""" +"""Support for KNX scenes.""" import voluptuous as vol from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX @@ -26,8 +21,8 @@ PLATFORM_SCHEMA = vol.Schema({ }) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the scenes for KNX platform.""" if discovery_info is not None: async_add_entities_discovery(hass, discovery_info, async_add_entities) diff --git a/homeassistant/components/knx/sensor.py b/homeassistant/components/knx/sensor.py index c096e15192d..6a2d8144b1e 100644 --- a/homeassistant/components/knx/sensor.py +++ b/homeassistant/components/knx/sensor.py @@ -1,10 +1,4 @@ -""" -Support for KNX/IP sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.knx/ -""" - +"""Support for KNX/IP sensors.""" import voluptuous as vol from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX diff --git a/homeassistant/components/knx/switch.py b/homeassistant/components/knx/switch.py index 26b9f77028d..305234e1eec 100644 --- a/homeassistant/components/knx/switch.py +++ b/homeassistant/components/knx/switch.py @@ -1,10 +1,4 @@ -""" -Support for KNX/IP switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.knx/ -""" - +"""Support for KNX/IP switches.""" import voluptuous as vol from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX diff --git a/homeassistant/components/konnected/__init__.py b/homeassistant/components/konnected/__init__.py index 0cba8552346..e3f9a46743d 100644 --- a/homeassistant/components/konnected/__init__.py +++ b/homeassistant/components/konnected/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Konnected devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/konnected/ -""" +"""Support for Konnected devices.""" import asyncio import hmac import json diff --git a/homeassistant/components/konnected/binary_sensor.py b/homeassistant/components/konnected/binary_sensor.py index e91d3f6136a..cb15e44e798 100644 --- a/homeassistant/components/konnected/binary_sensor.py +++ b/homeassistant/components/konnected/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for wired binary sensors attached to a Konnected device. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.konnected/ -""" +"""Support for wired binary sensors attached to a Konnected device.""" import logging from homeassistant.components.binary_sensor import BinarySensorDevice @@ -20,8 +15,8 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['konnected'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up binary sensors attached to a Konnected device.""" if discovery_info is None: return @@ -38,7 +33,7 @@ class KonnectedBinarySensor(BinarySensorDevice): """Representation of a Konnected binary sensor.""" def __init__(self, device_id, pin_num, data): - """Initialize the binary sensor.""" + """Initialize the Konnected binary sensor.""" self._data = data self._device_id = device_id self._pin_num = pin_num @@ -46,7 +41,7 @@ class KonnectedBinarySensor(BinarySensorDevice): self._device_class = self._data.get(CONF_TYPE) self._name = self._data.get(CONF_NAME, 'Konnected {} Zone {}'.format( device_id, PIN_TO_ZONE[pin_num])) - _LOGGER.debug('Created new Konnected sensor: %s', self._name) + _LOGGER.debug("Created new Konnected sensor: %s", self._name) @property def name(self): diff --git a/homeassistant/components/konnected/switch.py b/homeassistant/components/konnected/switch.py index 84016dac28d..897933e6d80 100644 --- a/homeassistant/components/konnected/switch.py +++ b/homeassistant/components/konnected/switch.py @@ -1,10 +1,4 @@ -""" -Support for wired switches attached to a Konnected device. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.konnected/ -""" - +"""Support for wired switches attached to a Konnected device.""" import logging from homeassistant.components.konnected import ( @@ -19,8 +13,8 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['konnected'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set switches attached to a Konnected device.""" if discovery_info is None: return @@ -37,7 +31,7 @@ class KonnectedSwitch(ToggleEntity): """Representation of a Konnected switch.""" def __init__(self, device_id, pin_num, data): - """Initialize the switch.""" + """Initialize the Konnected switch.""" self._data = data self._device_id = device_id self._pin_num = pin_num @@ -49,7 +43,7 @@ class KonnectedSwitch(ToggleEntity): self._name = self._data.get( 'name', 'Konnected {} Actuator {}'.format( device_id, PIN_TO_ZONE[pin_num])) - _LOGGER.debug('Created new switch: %s', self._name) + _LOGGER.debug("Created new switch: %s", self._name) @property def name(self): diff --git a/homeassistant/components/lametric/__init__.py b/homeassistant/components/lametric/__init__.py index 96ea3781566..0c3c8b08dd7 100644 --- a/homeassistant/components/lametric/__init__.py +++ b/homeassistant/components/lametric/__init__.py @@ -1,12 +1,4 @@ -""" -Support for LaMetric time. - -This is the base platform to support LaMetric components: -Notify, Light, Mediaplayer - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/lametric/ -""" +"""Support for LaMetric time.""" import logging import voluptuous as vol diff --git a/homeassistant/components/lametric/notify.py b/homeassistant/components/lametric/notify.py index 61bc5172af0..e5e6a5bd522 100644 --- a/homeassistant/components/lametric/notify.py +++ b/homeassistant/components/lametric/notify.py @@ -1,9 +1,4 @@ -""" -Notifier for LaMetric time. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/notify.lametric/ -""" +"""Support for LaMetric notifications.""" import logging from requests.exceptions import ConnectionError as RequestsConnectionError @@ -17,33 +12,32 @@ import homeassistant.helpers.config_validation as cv from homeassistant.components.lametric import DOMAIN as LAMETRIC_DOMAIN REQUIREMENTS = ['lmnotify==0.0.4'] -DEPENDENCIES = ['lametric'] _LOGGER = logging.getLogger(__name__) -CONF_LIFETIME = "lifetime" -CONF_CYCLES = "cycles" -CONF_PRIORITY = "priority" +AVAILABLE_PRIORITIES = ['info', 'warning', 'critical'] -AVAILABLE_PRIORITIES = ["info", "warning", "critical"] +CONF_CYCLES = 'cycles' +CONF_LIFETIME = 'lifetime' +CONF_PRIORITY = 'priority' + +DEPENDENCIES = ['lametric'] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_ICON, default="i555"): cv.string, + vol.Optional(CONF_ICON, default='i555'): cv.string, vol.Optional(CONF_LIFETIME, default=10): cv.positive_int, vol.Optional(CONF_CYCLES, default=1): cv.positive_int, - vol.Optional(CONF_PRIORITY, default="warning"): - vol.In(AVAILABLE_PRIORITIES) + vol.Optional(CONF_PRIORITY, default='warning'): + vol.In(AVAILABLE_PRIORITIES), }) def get_service(hass, config, discovery_info=None): """Get the LaMetric notification service.""" hlmn = hass.data.get(LAMETRIC_DOMAIN) - return LaMetricNotificationService(hlmn, - config[CONF_ICON], - config[CONF_LIFETIME] * 1000, - config[CONF_CYCLES], - config[CONF_PRIORITY]) + return LaMetricNotificationService( + hlmn, config[CONF_ICON], config[CONF_LIFETIME] * 1000, + config[CONF_CYCLES], config[CONF_PRIORITY]) class LaMetricNotificationService(BaseNotificationService): diff --git a/homeassistant/components/lcn/__init__.py b/homeassistant/components/lcn/__init__.py index 8efdcc99794..941160b6397 100644 --- a/homeassistant/components/lcn/__init__.py +++ b/homeassistant/components/lcn/__init__.py @@ -1,10 +1,4 @@ -""" -Connects to LCN platform. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/lcn/ -""" - +"""Support for LCN devices.""" import logging import re diff --git a/homeassistant/components/lcn/light.py b/homeassistant/components/lcn/light.py index b9457b7b7d9..2b7f4ed4074 100644 --- a/homeassistant/components/lcn/light.py +++ b/homeassistant/components/lcn/light.py @@ -1,10 +1,4 @@ -""" -Support for LCN lights. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.lcn/ -""" - +"""Support for LCN lights.""" from homeassistant.components.lcn import ( CONF_CONNECTIONS, CONF_DIMMABLE, CONF_OUTPUT, CONF_TRANSITION, DATA_LCN, OUTPUT_PORTS, LcnDevice, get_connection) @@ -16,8 +10,8 @@ from homeassistant.const import CONF_ADDRESS DEPENDENCIES = ['lcn'] -async def async_setup_platform(hass, hass_config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, hass_config, async_add_entities, discovery_info=None): """Set up the LCN light platform.""" if discovery_info is None: return diff --git a/homeassistant/components/lcn/switch.py b/homeassistant/components/lcn/switch.py index 468afe178b5..60eda2ea779 100755 --- a/homeassistant/components/lcn/switch.py +++ b/homeassistant/components/lcn/switch.py @@ -1,10 +1,4 @@ -""" -Support for LCN switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.lcn/ -""" - +"""Support for LCN switches.""" from homeassistant.components.lcn import ( CONF_CONNECTIONS, CONF_OUTPUT, DATA_LCN, OUTPUT_PORTS, LcnDevice, get_connection) diff --git a/homeassistant/components/lifx/__init__.py b/homeassistant/components/lifx/__init__.py index a2ae6266a8d..82802bab4af 100644 --- a/homeassistant/components/lifx/__init__.py +++ b/homeassistant/components/lifx/__init__.py @@ -1,4 +1,4 @@ -"""Component to embed LIFX.""" +"""Support for LIFX.""" import voluptuous as vol import homeassistant.helpers.config_validation as cv @@ -7,7 +7,6 @@ from homeassistant.const import CONF_PORT from homeassistant.helpers import config_entry_flow from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN - DOMAIN = 'lifx' REQUIREMENTS = ['aiolifx==0.6.7'] diff --git a/homeassistant/components/lifx/light.py b/homeassistant/components/lifx/light.py index f0cd7b7a7fe..c0b6158f186 100644 --- a/homeassistant/components/lifx/light.py +++ b/homeassistant/components/lifx/light.py @@ -1,9 +1,4 @@ -""" -Support for the LIFX platform that implements lights. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.lifx/ -""" +"""Support for LIFX lights.""" import asyncio from datetime import timedelta from functools import partial diff --git a/homeassistant/components/lightwave/__init__.py b/homeassistant/components/lightwave/__init__.py index e1aa1664eba..a9e5dcf9823 100644 --- a/homeassistant/components/lightwave/__init__.py +++ b/homeassistant/components/lightwave/__init__.py @@ -1,9 +1,4 @@ -""" -Support for device connected via Lightwave WiFi-link hub. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/lightwave/ -""" +"""Support for device connected via Lightwave WiFi-link hub.""" import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import (CONF_HOST, CONF_LIGHTS, CONF_NAME, @@ -11,7 +6,9 @@ from homeassistant.const import (CONF_HOST, CONF_LIGHTS, CONF_NAME, from homeassistant.helpers.discovery import async_load_platform REQUIREMENTS = ['lightwave==0.15'] + LIGHTWAVE_LINK = 'lightwave_link' + DOMAIN = 'lightwave' diff --git a/homeassistant/components/lightwave/light.py b/homeassistant/components/lightwave/light.py index 50c664d9046..1dfbac37c88 100644 --- a/homeassistant/components/lightwave/light.py +++ b/homeassistant/components/lightwave/light.py @@ -1,9 +1,4 @@ -""" -Implements LightwaveRF lights. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.lightwave/ -""" +"""Support for LightwaveRF lights.""" from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) from homeassistant.components.lightwave import LIGHTWAVE_LINK @@ -14,8 +9,8 @@ DEPENDENCIES = ['lightwave'] MAX_BRIGHTNESS = 255 -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Find and return LightWave lights.""" if not discovery_info: return diff --git a/homeassistant/components/lightwave/switch.py b/homeassistant/components/lightwave/switch.py index b612cd8dec7..d6c00b7fddb 100644 --- a/homeassistant/components/lightwave/switch.py +++ b/homeassistant/components/lightwave/switch.py @@ -1,9 +1,4 @@ -""" -Implements LightwaveRF switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.lightwave/ -""" +"""Support for LightwaveRF switches.""" from homeassistant.components.lightwave import LIGHTWAVE_LINK from homeassistant.components.switch import SwitchDevice from homeassistant.const import CONF_NAME @@ -11,8 +6,8 @@ from homeassistant.const import CONF_NAME DEPENDENCIES = ['lightwave'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Find and return LightWave switches.""" if not discovery_info: return diff --git a/homeassistant/components/linode/__init__.py b/homeassistant/components/linode/__init__.py index c98ef16c7ed..8bbd98c0acf 100644 --- a/homeassistant/components/linode/__init__.py +++ b/homeassistant/components/linode/__init__.py @@ -1,17 +1,12 @@ -""" -Support for Linode. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/linode/ -""" -import logging +"""Support for Linode.""" from datetime import timedelta +import logging import voluptuous as vol from homeassistant.const import CONF_ACCESS_TOKEN -from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv +from homeassistant.util import Throttle REQUIREMENTS = ['linode-api==4.1.9b1'] diff --git a/homeassistant/components/linode/binary_sensor.py b/homeassistant/components/linode/binary_sensor.py index 24abc3dd8be..a05681497de 100644 --- a/homeassistant/components/linode/binary_sensor.py +++ b/homeassistant/components/linode/binary_sensor.py @@ -1,20 +1,15 @@ -""" -Support for monitoring the state of Linode Nodes. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.linode/ -""" +"""Support for monitoring the state of Linode Nodes.""" import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) + PLATFORM_SCHEMA, BinarySensorDevice) from homeassistant.components.linode import ( - CONF_NODES, ATTR_CREATED, ATTR_NODE_ID, ATTR_NODE_NAME, - ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, - ATTR_REGION, ATTR_VCPUS, DATA_LINODE) + ATTR_CREATED, ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, + ATTR_NODE_ID, ATTR_NODE_NAME, ATTR_REGION, ATTR_VCPUS, CONF_NODES, + DATA_LINODE) +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/linode/switch.py b/homeassistant/components/linode/switch.py index 47bba280e1c..0cab2f4d0f2 100644 --- a/homeassistant/components/linode/switch.py +++ b/homeassistant/components/linode/switch.py @@ -1,19 +1,14 @@ -""" -Support for interacting with Linode nodes. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/switch.linode/ -""" +"""Support for interacting with Linode nodes.""" import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) from homeassistant.components.linode import ( - CONF_NODES, ATTR_CREATED, ATTR_NODE_ID, ATTR_NODE_NAME, - ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, - ATTR_REGION, ATTR_VCPUS, DATA_LINODE) + ATTR_CREATED, ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, + ATTR_NODE_ID, ATTR_NODE_NAME, ATTR_REGION, ATTR_VCPUS, CONF_NODES, + DATA_LINODE) +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/lirc/__init__.py b/homeassistant/components/lirc/__init__.py index f15020a5d72..0f00eda2007 100644 --- a/homeassistant/components/lirc/__init__.py +++ b/homeassistant/components/lirc/__init__.py @@ -1,9 +1,4 @@ -""" -LIRC interface to receive signals from an infrared remote control. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/lirc/ -""" +"""Support for LIRC devices.""" # pylint: disable=no-member, import-error import threading import time diff --git a/homeassistant/components/litejet/__init__.py b/homeassistant/components/litejet/__init__.py index e7c8452b27b..b4e8e45fa0b 100644 --- a/homeassistant/components/litejet/__init__.py +++ b/homeassistant/components/litejet/__init__.py @@ -1,8 +1,4 @@ -"""Allows the LiteJet lighting system to be controlled by Home Assistant. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/litejet/ -""" +"""Support for the LiteJet lighting system.""" import logging import voluptuous as vol @@ -24,7 +20,7 @@ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_PORT): cv.string, vol.Optional(CONF_EXCLUDE_NAMES): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_INCLUDE_SWITCHES, default=False): cv.boolean + vol.Optional(CONF_INCLUDE_SWITCHES, default=False): cv.boolean, }) }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/locative/__init__.py b/homeassistant/components/locative/__init__.py index 5a27fbaec63..e6a5b56ecda 100644 --- a/homeassistant/components/locative/__init__.py +++ b/homeassistant/components/locative/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Locative. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/locative/ -""" +"""Support for Locative.""" import logging from typing import Dict diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 0c6608e3572..74a90f0f5f0 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -1,9 +1,4 @@ -""" -Event parser and human readable log generator. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/logbook/ -""" +"""Event parser and human readable log generator.""" from datetime import timedelta from itertools import groupby import logging @@ -47,12 +42,12 @@ CONFIG_SCHEMA = vol.Schema({ CONF_EXCLUDE: vol.Schema({ vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, vol.Optional(CONF_DOMAINS, default=[]): - vol.All(cv.ensure_list, [cv.string]) + vol.All(cv.ensure_list, [cv.string]), }), CONF_INCLUDE: vol.Schema({ vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, vol.Optional(CONF_DOMAINS, default=[]): - vol.All(cv.ensure_list, [cv.string]) + vol.All(cv.ensure_list, [cv.string]), }) }), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/logentries/__init__.py b/homeassistant/components/logentries/__init__.py index 6dc76d8d932..383fa000514 100644 --- a/homeassistant/components/logentries/__init__.py +++ b/homeassistant/components/logentries/__init__.py @@ -1,9 +1,4 @@ -""" -Support for sending data to Logentries webhook endpoint. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/logentries/ -""" +"""Support for sending data to Logentries webhook endpoint.""" import json import logging import requests @@ -42,19 +37,17 @@ def setup(hass, config): _state = state_helper.state_as_number(state) except ValueError: _state = state.state - json_body = [ - { - 'domain': state.domain, - 'entity_id': state.object_id, - 'attributes': dict(state.attributes), - 'time': str(event.time_fired), - 'value': _state, - } - ] + json_body = [{ + 'domain': state.domain, + 'entity_id': state.object_id, + 'attributes': dict(state.attributes), + 'time': str(event.time_fired), + 'value': _state, + }] try: payload = { - "host": le_wh, - "event": json_body + 'host': le_wh, + 'event': json_body } requests.post(le_wh, data=json.dumps(payload), timeout=10) except requests.exceptions.RequestException as error: diff --git a/homeassistant/components/logger/__init__.py b/homeassistant/components/logger/__init__.py index 21ae7595ab8..2bfc6656945 100644 --- a/homeassistant/components/logger/__init__.py +++ b/homeassistant/components/logger/__init__.py @@ -1,9 +1,4 @@ -""" -Component that will help set the level of logging for components. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/logger/ -""" +"""Support for settting the level of logging for components.""" import logging from collections import OrderedDict diff --git a/homeassistant/components/logi_circle/__init__.py b/homeassistant/components/logi_circle/__init__.py index c0a7f4c2621..50500f47e42 100644 --- a/homeassistant/components/logi_circle/__init__.py +++ b/homeassistant/components/logi_circle/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Logi Circle cameras. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/logi_circle/ -""" +"""Support for Logi Circle devices.""" import logging import asyncio diff --git a/homeassistant/components/logi_circle/camera.py b/homeassistant/components/logi_circle/camera.py index 1dae58ad0f7..51bd7c124a3 100644 --- a/homeassistant/components/logi_circle/camera.py +++ b/homeassistant/components/logi_circle/camera.py @@ -1,9 +1,4 @@ -""" -This component provides support to the Logi Circle camera. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/camera.logi_circle/ -""" +"""Support to the Logi Circle cameras.""" import logging import asyncio from datetime import timedelta diff --git a/homeassistant/components/logi_circle/sensor.py b/homeassistant/components/logi_circle/sensor.py index 104de68ce03..74c2039c120 100644 --- a/homeassistant/components/logi_circle/sensor.py +++ b/homeassistant/components/logi_circle/sensor.py @@ -1,9 +1,4 @@ -""" -This component provides HA sensor support for Logi Circle cameras. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.logi_circle/ -""" +"""Support for Logi Circle sensors.""" import logging import voluptuous as vol @@ -26,26 +21,13 @@ _LOGGER = logging.getLogger(__name__) # Sensor types: Name, unit of measure, icon per sensor key. SENSOR_TYPES = { - 'battery_level': [ - 'Battery', '%', 'battery-50'], - - 'last_activity_time': [ - 'Last Activity', None, 'history'], - - 'privacy_mode': [ - 'Privacy Mode', None, 'eye'], - - 'signal_strength_category': [ - 'WiFi Signal Category', None, 'wifi'], - - 'signal_strength_percentage': [ - 'WiFi Signal Strength', '%', 'wifi'], - - 'speaker_volume': [ - 'Volume', '%', 'volume-high'], - - 'streaming_mode': [ - 'Streaming Mode', None, 'camera'], + 'battery_level': ['Battery', '%', 'battery-50'], + 'last_activity_time': ['Last Activity', None, 'history'], + 'privacy_mode': ['Privacy Mode', None, 'eye'], + 'signal_strength_category': ['WiFi Signal Category', None, 'wifi'], + 'signal_strength_percentage': ['WiFi Signal Strength', '%', 'wifi'], + 'speaker_volume': ['Volume', '%', 'volume-high'], + 'streaming_mode': ['Streaming Mode', None, 'camera'], } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index b4cb2b18dca..03b1cf06d68 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -1,9 +1,4 @@ -""" -Support for the Lovelace UI. - -For more details about this component, please refer to the documentation -at https://www.home-assistant.io/lovelace/ -""" +"""Support for the Lovelace UI.""" from functools import wraps import logging import os diff --git a/homeassistant/components/luftdaten/__init__.py b/homeassistant/components/luftdaten/__init__.py index 45d75b90f7f..125cefb9026 100644 --- a/homeassistant/components/luftdaten/__init__.py +++ b/homeassistant/components/luftdaten/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Luftdaten stations. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/luftdaten/ -""" +"""Support for Luftdaten stations.""" import logging import voluptuous as vol diff --git a/homeassistant/components/luftdaten/sensor.py b/homeassistant/components/luftdaten/sensor.py index d0792883457..398ec30a3f5 100644 --- a/homeassistant/components/luftdaten/sensor.py +++ b/homeassistant/components/luftdaten/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Luftdaten sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.luftdaten/ -""" +"""Support for Luftdaten sensors.""" import logging from homeassistant.components.luftdaten import ( diff --git a/homeassistant/components/lupusec/__init__.py b/homeassistant/components/lupusec/__init__.py index 94cb3abc4a2..8a5f098f741 100644 --- a/homeassistant/components/lupusec/__init__.py +++ b/homeassistant/components/lupusec/__init__.py @@ -1,10 +1,4 @@ -""" -This component provides basic support for Lupusec Home Security system. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/lupusec -""" - +"""Support for Lupusec Home Security system.""" import logging import voluptuous as vol @@ -14,6 +8,7 @@ from homeassistant.helpers import discovery from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, CONF_NAME, CONF_IP_ADDRESS) from homeassistant.helpers.entity import Entity + _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['lupupy==0.0.17'] @@ -28,7 +23,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_IP_ADDRESS): cv.string, - vol.Optional(CONF_NAME): cv.string + vol.Optional(CONF_NAME): cv.string, }), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/lupusec/alarm_control_panel.py b/homeassistant/components/lupusec/alarm_control_panel.py index 21eefc238a0..de62e5bfac2 100644 --- a/homeassistant/components/lupusec/alarm_control_panel.py +++ b/homeassistant/components/lupusec/alarm_control_panel.py @@ -1,10 +1,4 @@ -""" -This component provides HA alarm_control_panel support for Lupusec System. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/alarm_control_panel.lupusec/ -""" - +"""Support for Lupusec System alarm control panels.""" from datetime import timedelta from homeassistant.components.alarm_control_panel import AlarmControlPanel diff --git a/homeassistant/components/lupusec/binary_sensor.py b/homeassistant/components/lupusec/binary_sensor.py index df8210df026..8a5e103db0d 100644 --- a/homeassistant/components/lupusec/binary_sensor.py +++ b/homeassistant/components/lupusec/binary_sensor.py @@ -1,9 +1,4 @@ -""" -This component provides HA binary_sensor support for Lupusec Security System. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.lupusec/ -""" +"""Support for Lupusec Security System binary sensors.""" import logging from datetime import timedelta diff --git a/homeassistant/components/lupusec/switch.py b/homeassistant/components/lupusec/switch.py index 35744160f24..8a30d65fec3 100644 --- a/homeassistant/components/lupusec/switch.py +++ b/homeassistant/components/lupusec/switch.py @@ -1,9 +1,4 @@ -""" -This component provides HA switch support for Lupusec Security System. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.lupusec/ -""" +"""Support for Lupusec Security System switches.""" import logging from datetime import timedelta diff --git a/homeassistant/components/lutron/__init__.py b/homeassistant/components/lutron/__init__.py index 435039ce4bd..e4ebec4cc5a 100644 --- a/homeassistant/components/lutron/__init__.py +++ b/homeassistant/components/lutron/__init__.py @@ -1,9 +1,4 @@ -""" -Component for interacting with a Lutron RadioRA 2 system. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/lutron/ -""" +"""Component for interacting with a Lutron RadioRA 2 system.""" import logging import voluptuous as vol diff --git a/homeassistant/components/lutron/cover.py b/homeassistant/components/lutron/cover.py index 7ea7abf882d..cc7a57a5522 100644 --- a/homeassistant/components/lutron/cover.py +++ b/homeassistant/components/lutron/cover.py @@ -1,9 +1,4 @@ -""" -Support for Lutron shades. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/cover.lutron/ -""" +"""Support for Lutron shades.""" import logging from homeassistant.components.cover import ( diff --git a/homeassistant/components/lutron/light.py b/homeassistant/components/lutron/light.py index 359ef0114c5..c0b3b991147 100644 --- a/homeassistant/components/lutron/light.py +++ b/homeassistant/components/lutron/light.py @@ -1,9 +1,4 @@ -""" -Support for Lutron lights. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.lutron/ -""" +"""Support for Lutron lights.""" import logging from homeassistant.components.light import ( diff --git a/homeassistant/components/lutron/scene.py b/homeassistant/components/lutron/scene.py index bdb8bc344fe..f9002f2a839 100644 --- a/homeassistant/components/lutron/scene.py +++ b/homeassistant/components/lutron/scene.py @@ -1,9 +1,4 @@ -""" -Support for Lutron scenes. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/scene.lutron/ -""" +"""Support for Lutron scenes.""" import logging from homeassistant.components.lutron import ( @@ -30,12 +25,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class LutronScene(LutronDevice, Scene): """Representation of a Lutron Scene.""" - def __init__(self, - area_name, - keypad_name, - lutron_device, - lutron_led, - controller): + def __init__( + self, area_name, keypad_name, lutron_device, lutron_led, + controller): """Initialize the scene/button.""" super().__init__(area_name, lutron_device, controller) self._keypad_name = keypad_name @@ -48,6 +40,5 @@ class LutronScene(LutronDevice, Scene): @property def name(self): """Return the name of the device.""" - return "{} {}: {}".format(self._area_name, - self._keypad_name, - self._lutron_device.name) + return "{} {}: {}".format( + self._area_name, self._keypad_name, self._lutron_device.name) diff --git a/homeassistant/components/lutron/switch.py b/homeassistant/components/lutron/switch.py index 4146ba5a43b..bfdb06be33c 100644 --- a/homeassistant/components/lutron/switch.py +++ b/homeassistant/components/lutron/switch.py @@ -1,9 +1,4 @@ -""" -Support for Lutron switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.lutron/ -""" +"""Support for Lutron switches.""" import logging from homeassistant.components.switch import SwitchDevice diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index eb4010e43a1..61c005f60b2 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -1,9 +1,4 @@ -""" -Component for interacting with a Lutron Caseta system. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/lutron_caseta/ -""" +"""Component for interacting with a Lutron Caseta system.""" import logging import voluptuous as vol @@ -30,7 +25,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_HOST): cv.string, vol.Required(CONF_KEYFILE): cv.string, vol.Required(CONF_CERTFILE): cv.string, - vol.Required(CONF_CA_CERTS): cv.string + vol.Required(CONF_CA_CERTS): cv.string, }) }, extra=vol.ALLOW_EXTRA) @@ -47,15 +42,14 @@ async def async_setup(hass, base_config): keyfile = hass.config.path(config[CONF_KEYFILE]) certfile = hass.config.path(config[CONF_CERTFILE]) ca_certs = hass.config.path(config[CONF_CA_CERTS]) - bridge = Smartbridge.create_tls(hostname=config[CONF_HOST], - keyfile=keyfile, - certfile=certfile, - ca_certs=ca_certs) + bridge = Smartbridge.create_tls( + hostname=config[CONF_HOST], keyfile=keyfile, certfile=certfile, + ca_certs=ca_certs) hass.data[LUTRON_CASETA_SMARTBRIDGE] = bridge await bridge.connect() if not hass.data[LUTRON_CASETA_SMARTBRIDGE].is_connected(): - _LOGGER.error("Unable to connect to Lutron smartbridge at %s", - config[CONF_HOST]) + _LOGGER.error( + "Unable to connect to Lutron smartbridge at %s", config[CONF_HOST]) return False _LOGGER.info("Connected to Lutron smartbridge at %s", config[CONF_HOST]) diff --git a/homeassistant/components/lutron_caseta/cover.py b/homeassistant/components/lutron_caseta/cover.py index 37b7c1be42c..5e09dcc3c85 100644 --- a/homeassistant/components/lutron_caseta/cover.py +++ b/homeassistant/components/lutron_caseta/cover.py @@ -1,9 +1,4 @@ -""" -Support for Lutron Caseta shades. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/cover.lutron_caseta/ -""" +"""Support for Lutron Caseta shades.""" import logging from homeassistant.components.cover import ( @@ -17,8 +12,8 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['lutron_caseta'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the Lutron Caseta shades as a cover device.""" devs = [] bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE] diff --git a/homeassistant/components/lutron_caseta/light.py b/homeassistant/components/lutron_caseta/light.py index d454fe3c75e..3bab781f3b6 100644 --- a/homeassistant/components/lutron_caseta/light.py +++ b/homeassistant/components/lutron_caseta/light.py @@ -1,9 +1,4 @@ -""" -Support for Lutron Caseta lights. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.lutron_caseta/ -""" +"""Support for Lutron Caseta lights.""" import logging from homeassistant.components.light import ( @@ -18,8 +13,8 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['lutron_caseta'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the Lutron Caseta lights.""" devs = [] bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE] diff --git a/homeassistant/components/lutron_caseta/scene.py b/homeassistant/components/lutron_caseta/scene.py index 0ef974e2778..c6ca7bad3ac 100644 --- a/homeassistant/components/lutron_caseta/scene.py +++ b/homeassistant/components/lutron_caseta/scene.py @@ -1,9 +1,4 @@ -""" -Support for Lutron Caseta scenes. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/scene.lutron_caseta/ -""" +"""Support for Lutron Caseta scenes.""" import logging from homeassistant.components.lutron_caseta import LUTRON_CASETA_SMARTBRIDGE @@ -14,8 +9,8 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['lutron_caseta'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the Lutron Caseta lights.""" devs = [] bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE] @@ -32,8 +27,8 @@ class LutronCasetaScene(Scene): def __init__(self, scene, bridge): """Initialize the Lutron Caseta scene.""" - self._scene_name = scene["name"] - self._scene_id = scene["scene_id"] + self._scene_name = scene['name'] + self._scene_id = scene['scene_id'] self._bridge = bridge @property diff --git a/homeassistant/components/lutron_caseta/switch.py b/homeassistant/components/lutron_caseta/switch.py index f983050cffa..0ef0595187b 100644 --- a/homeassistant/components/lutron_caseta/switch.py +++ b/homeassistant/components/lutron_caseta/switch.py @@ -1,9 +1,4 @@ -""" -Support for Lutron Caseta switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sitch.lutron_caseta/ -""" +"""Support for Lutron Caseta switches.""" import logging from homeassistant.components.lutron_caseta import ( @@ -15,8 +10,8 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['lutron_caseta'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up Lutron switch.""" devs = [] bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE] diff --git a/homeassistant/components/mailbox/__init__.py b/homeassistant/components/mailbox/__init__.py index 2ed12b23164..1907a1e9e97 100644 --- a/homeassistant/components/mailbox/__init__.py +++ b/homeassistant/components/mailbox/__init__.py @@ -1,9 +1,4 @@ -""" -Provides functionality for mailboxes. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/mailbox/ -""" +"""Support for Voice mailboxes.""" import asyncio from contextlib import suppress from datetime import timedelta diff --git a/homeassistant/components/mailbox/asterisk_cdr.py b/homeassistant/components/mailbox/asterisk_cdr.py index ae0939c3da5..db5d4e8d6ee 100644 --- a/homeassistant/components/mailbox/asterisk_cdr.py +++ b/homeassistant/components/mailbox/asterisk_cdr.py @@ -1,9 +1,4 @@ -""" -Asterisk CDR interface. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/mailbox.asterisk_cdr/ -""" +"""Support for the Asterisk CDR interface.""" import logging import hashlib import datetime @@ -14,9 +9,11 @@ from homeassistant.components.asterisk_mbox import DOMAIN as ASTERISK_DOMAIN from homeassistant.components.mailbox import Mailbox from homeassistant.helpers.dispatcher import async_dispatcher_connect -DEPENDENCIES = ['asterisk_mbox'] _LOGGER = logging.getLogger(__name__) -MAILBOX_NAME = "asterisk_cdr" + +DEPENDENCIES = ['asterisk_mbox'] + +MAILBOX_NAME = 'asterisk_cdr' async def async_get_handler(hass, config, discovery_info=None): diff --git a/homeassistant/components/mailbox/demo.py b/homeassistant/components/mailbox/demo.py index 2aabde42b36..885988adb6b 100644 --- a/homeassistant/components/mailbox/demo.py +++ b/homeassistant/components/mailbox/demo.py @@ -1,9 +1,4 @@ -""" -Asterisk Voicemail interface. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/mailbox.asteriskvm/ -""" +"""Support for a demo mailbox.""" from hashlib import sha1 import logging import os @@ -14,7 +9,7 @@ from homeassistant.util import dt _LOGGER = logging.getLogger(__name__) -MAILBOX_NAME = "DemoMailbox" +MAILBOX_NAME = 'DemoMailbox' async def async_get_handler(hass, config, discovery_info=None): diff --git a/homeassistant/components/mailgun/__init__.py b/homeassistant/components/mailgun/__init__.py index 7fa08bb0f22..3903bd14e25 100644 --- a/homeassistant/components/mailgun/__init__.py +++ b/homeassistant/components/mailgun/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Mailgun. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/mailgun/ -""" +"""Support for Mailgun.""" import hashlib import hmac import json @@ -15,12 +10,15 @@ import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_API_KEY, CONF_DOMAIN, CONF_WEBHOOK_ID from homeassistant.helpers import config_entry_flow -DOMAIN = 'mailgun' _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['webhook'] -MESSAGE_RECEIVED = '{}_message_received'.format(DOMAIN) + CONF_SANDBOX = 'sandbox' + DEFAULT_SANDBOX = False +DEPENDENCIES = ['webhook'] +DOMAIN = 'mailgun' + +MESSAGE_RECEIVED = '{}_message_received'.format(DOMAIN) CONFIG_SCHEMA = vol.Schema({ vol.Optional(DOMAIN): vol.Schema({ diff --git a/homeassistant/components/mailgun/notify.py b/homeassistant/components/mailgun/notify.py index d4052678d36..05137254fcc 100644 --- a/homeassistant/components/mailgun/notify.py +++ b/homeassistant/components/mailgun/notify.py @@ -1,9 +1,4 @@ -""" -Support for the Mailgun mail notification service. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/notify.mailgun/ -""" +"""Support for the Mailgun mail notifications.""" import logging import voluptuous as vol @@ -16,10 +11,11 @@ from homeassistant.components.notify import ( from homeassistant.const import ( CONF_API_KEY, CONF_DOMAIN, CONF_RECIPIENT, CONF_SENDER) +REQUIREMENTS = ['pymailgunner==1.4'] + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['mailgun'] -REQUIREMENTS = ['pymailgunner==1.4'] # Images to attach to notification ATTR_IMAGES = 'images' @@ -30,7 +26,7 @@ DEFAULT_SANDBOX = False # pylint: disable=no-value-for-parameter PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_RECIPIENT): vol.Email(), - vol.Optional(CONF_SENDER): vol.Email() + vol.Optional(CONF_SENDER): vol.Email(), }) diff --git a/homeassistant/components/map/__init__.py b/homeassistant/components/map/__init__.py index d30a7568452..df8ac49a6d5 100644 --- a/homeassistant/components/map/__init__.py +++ b/homeassistant/components/map/__init__.py @@ -1,9 +1,4 @@ -""" -Provides a map panel for showing device locations. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/map/ -""" +"""Support for showing device locations.""" DOMAIN = 'map' diff --git a/homeassistant/components/matrix/__init__.py b/homeassistant/components/matrix/__init__.py index 5f6c30aaeba..4b3c1bf4d76 100644 --- a/homeassistant/components/matrix/__init__.py +++ b/homeassistant/components/matrix/__init__.py @@ -1,9 +1,4 @@ -""" -The matrix bot component. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/matrix/ -""" +"""The matrix bot component.""" import logging import os from functools import partial @@ -41,8 +36,8 @@ COMMAND_SCHEMA = vol.All( vol.Exclusive(CONF_WORD, 'trigger'): cv.string, vol.Exclusive(CONF_EXPRESSION, 'trigger'): cv.is_regex, vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_ROOMS, default=[]): vol.All(cv.ensure_list, - [cv.string]), + vol.Optional(CONF_ROOMS, default=[]): + vol.All(cv.ensure_list, [cv.string]), }), # Make sure it's either a word or an expression command cv.has_at_least_one_key(CONF_WORD, CONF_EXPRESSION) @@ -54,8 +49,8 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, vol.Required(CONF_USERNAME): cv.matches_regex("@[^:]*:.*"), vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_ROOMS, default=[]): vol.All(cv.ensure_list, - [cv.string]), + vol.Optional(CONF_ROOMS, default=[]): + vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_COMMANDS, default=[]): [COMMAND_SCHEMA] }) }, extra=vol.ALLOW_EXTRA) @@ -76,13 +71,9 @@ def setup(hass, config): try: bot = MatrixBot( - hass, - os.path.join(hass.config.path(), SESSION_FILE), - config[CONF_HOMESERVER], - config[CONF_VERIFY_SSL], - config[CONF_USERNAME], - config[CONF_PASSWORD], - config[CONF_ROOMS], + hass, os.path.join(hass.config.path(), SESSION_FILE), + config[CONF_HOMESERVER], config[CONF_VERIFY_SSL], + config[CONF_USERNAME], config[CONF_PASSWORD], config[CONF_ROOMS], config[CONF_COMMANDS]) hass.data[DOMAIN] = bot except MatrixRequestError as exception: diff --git a/homeassistant/components/matrix/notify.py b/homeassistant/components/matrix/notify.py index fc29ad91dc9..f1f53268c2b 100644 --- a/homeassistant/components/matrix/notify.py +++ b/homeassistant/components/matrix/notify.py @@ -1,9 +1,4 @@ -""" -Matrix notification service. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/notify.matrix/ -""" +"""Support for Matrix notifications.""" import logging import voluptuous as vol diff --git a/homeassistant/components/maxcube/__init__.py b/homeassistant/components/maxcube/__init__.py index 9980d554232..c398ccbde4f 100644 --- a/homeassistant/components/maxcube/__init__.py +++ b/homeassistant/components/maxcube/__init__.py @@ -1,9 +1,4 @@ -""" -Platform for the MAX! Cube LAN Gateway. - -For more details about this component, please refer to the documentation -https://home-assistant.io/components/maxcube/ -""" +"""Support for the MAX! Cube LAN Gateway.""" import logging import time from socket import timeout @@ -38,7 +33,7 @@ CONFIG_GATEWAY = vol.Schema({ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_GATEWAYS, default={}): - vol.All(cv.ensure_list, [CONFIG_GATEWAY]) + vol.All(cv.ensure_list, [CONFIG_GATEWAY]), }), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/maxcube/binary_sensor.py b/homeassistant/components/maxcube/binary_sensor.py index 850a416acc5..8d5ab84f6d3 100644 --- a/homeassistant/components/maxcube/binary_sensor.py +++ b/homeassistant/components/maxcube/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for MAX! Window Shutter via MAX! Cube. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/maxcube/ -""" +"""Support for MAX! binary sensors via MAX! Cube.""" import logging from homeassistant.components.binary_sensor import BinarySensorDevice diff --git a/homeassistant/components/maxcube/climate.py b/homeassistant/components/maxcube/climate.py index 328cdabde62..f5c4533123f 100644 --- a/homeassistant/components/maxcube/climate.py +++ b/homeassistant/components/maxcube/climate.py @@ -1,9 +1,4 @@ -""" -Support for MAX! Thermostats via MAX! Cube. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/maxcube/ -""" +"""Support for MAX! Thermostats via MAX! Cube.""" import socket import logging diff --git a/homeassistant/components/media_extractor/__init__.py b/homeassistant/components/media_extractor/__init__.py index 8e00949da07..efc3e8bddc8 100644 --- a/homeassistant/components/media_extractor/__init__.py +++ b/homeassistant/components/media_extractor/__init__.py @@ -1,9 +1,4 @@ -""" -Decorator service for the media_player.play_media service. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/media_extractor/ -""" +"""Decorator service for the media_player.play_media service.""" import logging import voluptuous as vol diff --git a/homeassistant/components/melissa/__init__.py b/homeassistant/components/melissa/__init__.py index 638d8c55bd5..2037caa11c3 100644 --- a/homeassistant/components/melissa/__init__.py +++ b/homeassistant/components/melissa/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Melissa climate. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/melissa/ -""" +"""Support for Melissa climate.""" import logging import voluptuous as vol @@ -16,7 +11,7 @@ REQUIREMENTS = ["py-melissa-climate==2.0.0"] _LOGGER = logging.getLogger(__name__) -DOMAIN = "melissa" +DOMAIN = 'melissa' DATA_MELISSA = 'MELISSA' diff --git a/homeassistant/components/microsoft_face/__init__.py b/homeassistant/components/microsoft_face/__init__.py index 9be2f8eadf5..9b3ee960fb2 100644 --- a/homeassistant/components/microsoft_face/__init__.py +++ b/homeassistant/components/microsoft_face/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Microsoft face recognition. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/microsoft_face/ -""" +"""Support for Microsoft face recognition.""" import asyncio import json import logging @@ -45,7 +40,7 @@ SERVICE_TRAIN_GROUP = 'train_group' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_AZURE_REGION, default="westus"): cv.string, + vol.Optional(CONF_AZURE_REGION, default='westus'): cv.string, vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, }), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/mochad/__init__.py b/homeassistant/components/mochad/__init__.py index 7e6738b95f8..e10adf693fe 100644 --- a/homeassistant/components/mochad/__init__.py +++ b/homeassistant/components/mochad/__init__.py @@ -1,9 +1,4 @@ -""" -Support for CM15A/CM19A X10 Controller using mochad daemon. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/mochad/ -""" +"""Support for CM15A/CM19A X10 Controller using mochad daemon.""" import logging import threading diff --git a/homeassistant/components/mochad/light.py b/homeassistant/components/mochad/light.py index 2e68c369ba6..d2e1a567d27 100644 --- a/homeassistant/components/mochad/light.py +++ b/homeassistant/components/mochad/light.py @@ -1,10 +1,4 @@ -""" -Contains functionality to use a X10 dimmer over Mochad. - -For more details about this platform, please refer to the documentation at -https://home.assistant.io/components/light.mochad/ -""" - +"""Support for X10 dimmer over Mochad.""" import logging import voluptuous as vol @@ -16,11 +10,11 @@ from homeassistant.const import ( CONF_NAME, CONF_PLATFORM, CONF_DEVICES, CONF_ADDRESS) from homeassistant.helpers import config_validation as cv -DEPENDENCIES = ['mochad'] _LOGGER = logging.getLogger(__name__) -CONF_BRIGHTNESS_LEVELS = 'brightness_levels' +DEPENDENCIES = ['mochad'] +CONF_BRIGHTNESS_LEVELS = 'brightness_levels' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_PLATFORM): mochad.DOMAIN, @@ -51,11 +45,11 @@ class MochadLight(Light): self._controller = ctrl self._address = dev[CONF_ADDRESS] - self._name = dev.get(CONF_NAME, - 'x10_light_dev_{}'.format(self._address)) + self._name = dev.get( + CONF_NAME, 'x10_light_dev_{}'.format(self._address)) self._comm_type = dev.get(mochad.CONF_COMM_TYPE, 'pl') - self.light = device.Device(ctrl, self._address, - comm_type=self._comm_type) + self.light = device.Device( + ctrl, self._address, comm_type=self._comm_type) self._brightness = 0 self._state = self._get_device_status() self._brightness_levels = dev.get(CONF_BRIGHTNESS_LEVELS) - 1 diff --git a/homeassistant/components/mochad/switch.py b/homeassistant/components/mochad/switch.py index b703d91be34..03fd2db07bf 100644 --- a/homeassistant/components/mochad/switch.py +++ b/homeassistant/components/mochad/switch.py @@ -1,10 +1,4 @@ -""" -Contains functionality to use a X10 switch over Mochad. - -For more details about this platform, please refer to the documentation at -https://home.assistant.io/components/switch.mochad -""" - +"""Support for X10 switch over Mochad.""" import logging import voluptuous as vol @@ -15,9 +9,10 @@ from homeassistant.const import (CONF_NAME, CONF_DEVICES, CONF_PLATFORM, CONF_ADDRESS) from homeassistant.helpers import config_validation as cv -DEPENDENCIES = ['mochad'] _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['mochad'] + PLATFORM_SCHEMA = vol.Schema({ vol.Required(CONF_PLATFORM): mochad.DOMAIN, @@ -48,8 +43,8 @@ class MochadSwitch(SwitchDevice): self._address = dev[CONF_ADDRESS] self._name = dev.get(CONF_NAME, 'x10_switch_dev_%s' % self._address) self._comm_type = dev.get(mochad.CONF_COMM_TYPE, 'pl') - self.switch = device.Device(ctrl, self._address, - comm_type=self._comm_type) + self.switch = device.Device( + ctrl, self._address, comm_type=self._comm_type) # Init with false to avoid locking HA for long on CM19A (goes from rf # to pl via TM751, but not other way around) if self._comm_type == 'pl': diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index 77a62103f80..f42423bf9a8 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Modbus. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/modbus/ -""" +"""Support for Modbus.""" import logging import threading diff --git a/homeassistant/components/modbus/binary_sensor.py b/homeassistant/components/modbus/binary_sensor.py index 7089439a7e1..38511ffed7e 100644 --- a/homeassistant/components/modbus/binary_sensor.py +++ b/homeassistant/components/modbus/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for Modbus Coil sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.modbus/ -""" +"""Support for Modbus Coil sensors.""" import logging import voluptuous as vol @@ -25,7 +20,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string, vol.Required(CONF_COIL): cv.positive_int, vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_SLAVE): cv.positive_int + vol.Optional(CONF_SLAVE): cv.positive_int, }] }) @@ -36,9 +31,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for coil in config.get(CONF_COILS): hub = hass.data[MODBUS_DOMAIN][coil.get(CONF_HUB)] sensors.append(ModbusCoilSensor( - hub, - coil.get(CONF_NAME), - coil.get(CONF_SLAVE), + hub, coil.get(CONF_NAME), coil.get(CONF_SLAVE), coil.get(CONF_COIL))) add_entities(sensors) @@ -70,5 +63,5 @@ class ModbusCoilSensor(BinarySensorDevice): try: self._value = result.bits[0] except AttributeError: - _LOGGER.error('No response from hub %s, slave %s, coil %s', + _LOGGER.error("No response from hub %s, slave %s, coil %s", self._hub.name, self._slave, self._coil) diff --git a/homeassistant/components/modbus/climate.py b/homeassistant/components/modbus/climate.py index 23051898679..ed8cbda863f 100644 --- a/homeassistant/components/modbus/climate.py +++ b/homeassistant/components/modbus/climate.py @@ -1,13 +1,4 @@ -""" -Platform for a Generic Modbus Thermostat. - -This uses a setpoint and process -value within the controller, so both the current temperature register and the -target temperature register need to be configured. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/climate.modbus/ -""" +"""Support for Generic Modbus Thermostats.""" import logging import struct @@ -21,6 +12,8 @@ from homeassistant.components.modbus import ( CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN) import homeassistant.helpers.config_validation as cv +_LOGGER = logging.getLogger(__name__) + DEPENDENCIES = ['modbus'] # Parameters not defined by homeassistant.const @@ -46,8 +39,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_PRECISION, default=1): cv.positive_int }) -_LOGGER = logging.getLogger(__name__) - SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE @@ -63,9 +54,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hub_name = config.get(CONF_HUB) hub = hass.data[MODBUS_DOMAIN][hub_name] - add_entities([ModbusThermostat(hub, name, modbus_slave, - target_temp_register, current_temp_register, - data_type, count, precision)], True) + add_entities([ModbusThermostat( + hub, name, modbus_slave, target_temp_register, current_temp_register, + data_type, count, precision)], True) class ModbusThermostat(ClimateDevice): diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py index b263bad5318..6ba8d92d155 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Modbus Register sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.modbus/ -""" +"""Support for Modbus Register sensors.""" import logging import struct @@ -56,7 +51,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.In([DATA_TYPE_INT, DATA_TYPE_UINT, DATA_TYPE_FLOAT, DATA_TYPE_CUSTOM]), vol.Optional(CONF_STRUCTURE): cv.string, - vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string + vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, }] }) @@ -76,7 +71,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): CONF_DATA_TYPE)][register.get(CONF_COUNT)]) except KeyError: _LOGGER.error("Unable to detect data type for %s sensor, " - "try a custom type.", register.get(CONF_NAME)) + "try a custom type", register.get(CONF_NAME)) continue else: structure = register.get(CONF_STRUCTURE) diff --git a/homeassistant/components/modbus/switch.py b/homeassistant/components/modbus/switch.py index 04c73d7d372..47ad8e98958 100644 --- a/homeassistant/components/modbus/switch.py +++ b/homeassistant/components/modbus/switch.py @@ -1,9 +1,4 @@ -""" -Support for Modbus switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.modbus/ -""" +"""Support for Modbus switches.""" import logging import voluptuous as vol @@ -17,6 +12,7 @@ from homeassistant.helpers import config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA _LOGGER = logging.getLogger(__name__) + DEPENDENCIES = ['modbus'] CONF_COIL = "coil" @@ -59,7 +55,7 @@ PLATFORM_SCHEMA = vol.All( cv.has_at_least_one_key(CONF_COILS, CONF_REGISTERS), PLATFORM_SCHEMA.extend({ vol.Optional(CONF_COILS): [COILS_SCHEMA], - vol.Optional(CONF_REGISTERS): [REGISTERS_SCHEMA] + vol.Optional(CONF_REGISTERS): [REGISTERS_SCHEMA], })) @@ -71,9 +67,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hub_name = coil.get(CONF_HUB) hub = hass.data[MODBUS_DOMAIN][hub_name] switches.append(ModbusCoilSwitch( - hub, - coil.get(CONF_NAME), - coil.get(CONF_SLAVE), + hub, coil.get(CONF_NAME), coil.get(CONF_SLAVE), coil.get(CONF_COIL))) if CONF_REGISTERS in config: for register in config.get(CONF_REGISTERS): @@ -92,6 +86,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): register.get(CONF_REGISTER_TYPE), register.get(CONF_STATE_ON), register.get(CONF_STATE_OFF))) + add_entities(switches) @@ -139,9 +134,7 @@ class ModbusCoilSwitch(ToggleEntity, RestoreEntity): except AttributeError: _LOGGER.error( 'No response from hub %s, slave %s, coil %s', - self._hub.name, - self._slave, - self._coil) + self._hub.name, self._slave, self._coil) class ModbusRegisterSwitch(ModbusCoilSwitch): @@ -177,19 +170,14 @@ class ModbusRegisterSwitch(ModbusCoilSwitch): def turn_on(self, **kwargs): """Set switch on.""" - self._hub.write_register( - self._slave, - self._register, - self._command_on) + self._hub.write_register(self._slave, self._register, self._command_on) if not self._verify_state: self._is_on = True def turn_off(self, **kwargs): """Set switch off.""" self._hub.write_register( - self._slave, - self._register, - self._command_off) + self._slave, self._register, self._command_off) if not self._verify_state: self._is_on = False @@ -201,23 +189,17 @@ class ModbusRegisterSwitch(ModbusCoilSwitch): value = 0 if self._register_type == REGISTER_TYPE_INPUT: result = self._hub.read_input_registers( - self._slave, - self._register, - 1) + self._slave, self._register, 1) else: result = self._hub.read_holding_registers( - self._slave, - self._register, - 1) + self._slave, self._register, 1) try: value = int(result.registers[0]) except AttributeError: _LOGGER.error( - 'No response from hub %s, slave %s, register %s', - self._hub.name, - self._slave, - self._verify_register) + "No response from hub %s, slave %s, register %s", + self._hub.name, self._slave, self._verify_register) if value == self._state_on: self._is_on = True @@ -225,9 +207,6 @@ class ModbusRegisterSwitch(ModbusCoilSwitch): self._is_on = False else: _LOGGER.error( - 'Unexpected response from hub %s, slave %s ' - 'register %s, got 0x%2x', - self._hub.name, - self._slave, - self._verify_register, - value) + "Unexpected response from hub %s, slave %s " + "register %s, got 0x%2x", + self._hub.name, self._slave, self._verify_register, value) diff --git a/homeassistant/components/mqtt_eventstream/__init__.py b/homeassistant/components/mqtt_eventstream/__init__.py index 2cde7825734..6e545d19fe2 100644 --- a/homeassistant/components/mqtt_eventstream/__init__.py +++ b/homeassistant/components/mqtt_eventstream/__init__.py @@ -1,9 +1,4 @@ -""" -Connect two Home Assistant instances via MQTT. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/mqtt_eventstream/ -""" +"""Connect two Home Assistant instances via MQTT.""" import asyncio import json @@ -33,7 +28,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_SUBSCRIBE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_PUBLISH_EVENTSTREAM_RECEIVED, default=False): cv.boolean, - vol.Optional(CONF_IGNORE_EVENT, default=[]): cv.ensure_list + vol.Optional(CONF_IGNORE_EVENT, default=[]): cv.ensure_list, }), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/mqtt_statestream/__init__.py b/homeassistant/components/mqtt_statestream/__init__.py index 3a0e5d39ff0..18a70bf75bb 100644 --- a/homeassistant/components/mqtt_statestream/__init__.py +++ b/homeassistant/components/mqtt_statestream/__init__.py @@ -1,9 +1,4 @@ -""" -Publish simple item state changes via MQTT. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/mqtt_statestream/ -""" +"""Publish simple item state changes via MQTT.""" import json import voluptuous as vol @@ -20,6 +15,7 @@ import homeassistant.helpers.config_validation as cv CONF_BASE_TOPIC = 'base_topic' CONF_PUBLISH_ATTRIBUTES = 'publish_attributes' CONF_PUBLISH_TIMESTAMPS = 'publish_timestamps' + DEPENDENCIES = ['mqtt'] DOMAIN = 'mqtt_statestream' @@ -28,16 +24,16 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_EXCLUDE, default={}): vol.Schema({ vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, vol.Optional(CONF_DOMAINS, default=[]): - vol.All(cv.ensure_list, [cv.string]) + vol.All(cv.ensure_list, [cv.string]), }), vol.Optional(CONF_INCLUDE, default={}): vol.Schema({ vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, vol.Optional(CONF_DOMAINS, default=[]): - vol.All(cv.ensure_list, [cv.string]) + vol.All(cv.ensure_list, [cv.string]), }), vol.Required(CONF_BASE_TOPIC): valid_publish_topic, vol.Optional(CONF_PUBLISH_ATTRIBUTES, default=False): cv.boolean, - vol.Optional(CONF_PUBLISH_TIMESTAMPS, default=False): cv.boolean + vol.Optional(CONF_PUBLISH_TIMESTAMPS, default=False): cv.boolean, }) }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/mychevy/__init__.py b/homeassistant/components/mychevy/__init__.py index 209027ad472..e6fd7f19c2a 100644 --- a/homeassistant/components/mychevy/__init__.py +++ b/homeassistant/components/mychevy/__init__.py @@ -1,9 +1,4 @@ -""" -MyChevy Component. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/mychevy/ -""" +"""Support for MyChevy.""" from datetime import timedelta import logging import threading @@ -16,7 +11,7 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers import discovery from homeassistant.util import Throttle -REQUIREMENTS = ["mychevy==1.2.0"] +REQUIREMENTS = ['mychevy==1.2.0'] DOMAIN = 'mychevy' UPDATE_TOPIC = DOMAIN @@ -41,7 +36,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Optional(CONF_COUNTRY, default=DEFAULT_COUNTRY): - vol.All(cv.string, vol.In(['us', 'ca'])) + vol.All(cv.string, vol.In(['us', 'ca'])), }), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/mychevy/binary_sensor.py b/homeassistant/components/mychevy/binary_sensor.py index c1e3b6f0aac..67f12a14359 100644 --- a/homeassistant/components/mychevy/binary_sensor.py +++ b/homeassistant/components/mychevy/binary_sensor.py @@ -1,8 +1,4 @@ -"""Support for MyChevy sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.mychevy/ -""" +"""Support for MyChevy binary sensors.""" import logging from homeassistant.components.mychevy import ( @@ -20,8 +16,8 @@ SENSORS = [ ] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the MyChevy sensors.""" if discovery_info is None: return @@ -41,7 +37,6 @@ class EVBinarySensor(BinarySensorDevice): The only real difference between sensors is which units and what attribute from the car object they are returning. All logic can be built with just setting subclass attributes. - """ def __init__(self, connection, config, car_vid): @@ -53,9 +48,8 @@ class EVBinarySensor(BinarySensorDevice): self._is_on = None self._car_vid = car_vid self.entity_id = ENTITY_ID_FORMAT.format( - '{}_{}_{}'.format(MYCHEVY_DOMAIN, - slugify(self._car.name), - slugify(self._name))) + '{}_{}_{}'.format( + MYCHEVY_DOMAIN, slugify(self._car.name), slugify(self._name))) @property def name(self): diff --git a/homeassistant/components/mychevy/sensor.py b/homeassistant/components/mychevy/sensor.py index b478e2ef3ca..c7d140e0c4c 100644 --- a/homeassistant/components/mychevy/sensor.py +++ b/homeassistant/components/mychevy/sensor.py @@ -1,9 +1,4 @@ -"""Support for MyChevy sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.mychevy/ -""" - +"""Support for MyChevy sensors.""" import logging from homeassistant.components.mychevy import ( @@ -16,6 +11,8 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.util import slugify +_LOGGER = logging.getLogger(__name__) + BATTERY_SENSOR = "batteryLevel" SENSORS = [ @@ -28,8 +25,6 @@ SENSORS = [ ["charging"]) ] -_LOGGER = logging.getLogger(__name__) - def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the MyChevy sensors.""" @@ -49,7 +44,7 @@ class MyChevyStatus(Entity): """A string representing the charge mode.""" _name = "MyChevy Status" - _icon = "mdi:car-connected" + _icon = 'mdi:car-connected' def __init__(self): """Initialize sensor with car connection.""" @@ -107,7 +102,6 @@ class EVSensor(Entity): The only real difference between sensors is which units and what attribute from the car object they are returning. All logic can be built with just setting subclass attributes. - """ def __init__(self, connection, config, car_vid): @@ -123,9 +117,8 @@ class EVSensor(Entity): self._car_vid = car_vid self.entity_id = ENTITY_ID_FORMAT.format( - '{}_{}_{}'.format(MYCHEVY_DOMAIN, - slugify(self._car.name), - slugify(self._name))) + '{}_{}_{}'.format( + MYCHEVY_DOMAIN, slugify(self._car.name), slugify(self._name))) async def async_added_to_hass(self): """Register callbacks.""" diff --git a/homeassistant/components/mycroft/__init__.py b/homeassistant/components/mycroft/__init__.py index 834572bc551..29f6383f686 100644 --- a/homeassistant/components/mycroft/__init__.py +++ b/homeassistant/components/mycroft/__init__.py @@ -1,10 +1,4 @@ -""" -Support for Mycroft AI. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/mycroft -""" - +"""Support for Mycroft AI.""" import logging import voluptuous as vol @@ -17,10 +11,8 @@ REQUIREMENTS = ['mycroftapi==2.0'] _LOGGER = logging.getLogger(__name__) - DOMAIN = 'mycroft' - CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_HOST): cv.string diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index 49f8560c6b3..7ca21ac582a 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -1,9 +1,4 @@ -""" -Connect to a MySensors gateway via pymysensors API. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/mysensors/ -""" +"""Connect to a MySensors gateway via pymysensors API.""" import logging import voluptuous as vol diff --git a/homeassistant/components/mysensors/binary_sensor.py b/homeassistant/components/mysensors/binary_sensor.py index f0b7832cf25..57e8f1c1ef8 100644 --- a/homeassistant/components/mysensors/binary_sensor.py +++ b/homeassistant/components/mysensors/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for MySensors binary sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.mysensors/ -""" +"""Support for MySensors binary sensors.""" from homeassistant.components import mysensors from homeassistant.components.binary_sensor import ( DEVICE_CLASSES, DOMAIN, BinarySensorDevice) diff --git a/homeassistant/components/mysensors/climate.py b/homeassistant/components/mysensors/climate.py index 66c634d8cd9..20d608e1ca5 100644 --- a/homeassistant/components/mysensors/climate.py +++ b/homeassistant/components/mysensors/climate.py @@ -1,9 +1,4 @@ -""" -MySensors platform that offers a Climate (MySensors-HVAC) component. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/climate.mysensors/ -""" +"""MySensors platform that offers a Climate (MySensors-HVAC) component.""" from homeassistant.components import mysensors from homeassistant.components.climate import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, STATE_AUTO, diff --git a/homeassistant/components/mysensors/cover.py b/homeassistant/components/mysensors/cover.py index 60ff7aeef1d..01605bb9afe 100644 --- a/homeassistant/components/mysensors/cover.py +++ b/homeassistant/components/mysensors/cover.py @@ -1,9 +1,4 @@ -""" -Support for MySensors covers. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/cover.mysensors/ -""" +"""Support for MySensors covers.""" from homeassistant.components import mysensors from homeassistant.components.cover import ATTR_POSITION, DOMAIN, CoverDevice from homeassistant.const import STATE_OFF, STATE_ON diff --git a/homeassistant/components/mysensors/device_tracker.py b/homeassistant/components/mysensors/device_tracker.py index 705dc9968c9..b50286585a4 100644 --- a/homeassistant/components/mysensors/device_tracker.py +++ b/homeassistant/components/mysensors/device_tracker.py @@ -1,9 +1,4 @@ -""" -Support for tracking MySensors devices. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/device_tracker.mysensors/ -""" +"""Support for tracking MySensors devices.""" from homeassistant.components import mysensors from homeassistant.components.device_tracker import DOMAIN from homeassistant.helpers.dispatcher import async_dispatcher_connect diff --git a/homeassistant/components/mysensors/light.py b/homeassistant/components/mysensors/light.py index 23d602c5d40..56511b73dfe 100644 --- a/homeassistant/components/mysensors/light.py +++ b/homeassistant/components/mysensors/light.py @@ -1,9 +1,4 @@ -""" -Support for MySensors lights. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.mysensors/ -""" +"""Support for MySensors lights.""" from homeassistant.components import mysensors from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_WHITE_VALUE, DOMAIN, diff --git a/homeassistant/components/mysensors/notify.py b/homeassistant/components/mysensors/notify.py index 71ce7fb0b74..ab198bc21bc 100644 --- a/homeassistant/components/mysensors/notify.py +++ b/homeassistant/components/mysensors/notify.py @@ -1,9 +1,4 @@ -""" -MySensors notification service. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/notify.mysensors/ -""" +"""MySensors notification service.""" from homeassistant.components import mysensors from homeassistant.components.notify import ( ATTR_TARGET, DOMAIN, BaseNotificationService) diff --git a/homeassistant/components/mysensors/sensor.py b/homeassistant/components/mysensors/sensor.py index 160d4b4784b..ce6d5da2b4c 100644 --- a/homeassistant/components/mysensors/sensor.py +++ b/homeassistant/components/mysensors/sensor.py @@ -1,9 +1,4 @@ -""" -Support for MySensors sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.mysensors/ -""" +"""Support for MySensors sensors.""" from homeassistant.components import mysensors from homeassistant.components.sensor import DOMAIN from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT diff --git a/homeassistant/components/mysensors/switch.py b/homeassistant/components/mysensors/switch.py index 20e50518df8..0ad9be1d508 100644 --- a/homeassistant/components/mysensors/switch.py +++ b/homeassistant/components/mysensors/switch.py @@ -1,9 +1,4 @@ -""" -Support for MySensors switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.mysensors/ -""" +"""Support for MySensors switches.""" import voluptuous as vol import homeassistant.helpers.config_validation as cv @@ -89,7 +84,7 @@ class MySensorsSwitch(mysensors.device.MySensorsEntity, SwitchDevice): self.gateway.set_child_value( self.node_id, self.child_id, self.value_type, 1) if self.gateway.optimistic: - # optimistically assume that switch has changed state + # Optimistically assume that switch has changed state self._values[self.value_type] = STATE_ON self.async_schedule_update_ha_state() @@ -98,7 +93,7 @@ class MySensorsSwitch(mysensors.device.MySensorsEntity, SwitchDevice): self.gateway.set_child_value( self.node_id, self.child_id, self.value_type, 0) if self.gateway.optimistic: - # optimistically assume that switch has changed state + # Optimistically assume that switch has changed state self._values[self.value_type] = STATE_OFF self.async_schedule_update_ha_state() @@ -127,11 +122,11 @@ class MySensorsIRSwitch(MySensorsSwitch): self.gateway.set_child_value( self.node_id, self.child_id, set_req.V_LIGHT, 1) if self.gateway.optimistic: - # optimistically assume that switch has changed state + # Optimistically assume that switch has changed state self._values[self.value_type] = self._ir_code self._values[set_req.V_LIGHT] = STATE_ON self.async_schedule_update_ha_state() - # turn off switch after switch was turned on + # Turn off switch after switch was turned on await self.async_turn_off() async def async_turn_off(self, **kwargs): @@ -140,7 +135,7 @@ class MySensorsIRSwitch(MySensorsSwitch): self.gateway.set_child_value( self.node_id, self.child_id, set_req.V_LIGHT, 0) if self.gateway.optimistic: - # optimistically assume that switch has changed state + # Optimistically assume that switch has changed state self._values[set_req.V_LIGHT] = STATE_OFF self.async_schedule_update_ha_state() diff --git a/homeassistant/components/mythicbeastsdns/__init__.py b/homeassistant/components/mythicbeastsdns/__init__.py index 6b5ce2e8597..f34b2736710 100644 --- a/homeassistant/components/mythicbeastsdns/__init__.py +++ b/homeassistant/components/mythicbeastsdns/__init__.py @@ -1,9 +1,4 @@ -""" -Integrate with Mythic Beasts Dynamic DNS service. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/mythicbeastsdns/ -""" +"""Support for Mythic Beasts Dynamic DNS service.""" from datetime import timedelta import logging diff --git a/homeassistant/components/namecheapdns/__init__.py b/homeassistant/components/namecheapdns/__init__.py index f817544ca77..f86e7d18556 100644 --- a/homeassistant/components/namecheapdns/__init__.py +++ b/homeassistant/components/namecheapdns/__init__.py @@ -1,9 +1,4 @@ -""" -Integrate with namecheap DNS services. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/namecheapdns/ -""" +"""Support for namecheap DNS services.""" import logging from datetime import timedelta diff --git a/homeassistant/components/neato/__init__.py b/homeassistant/components/neato/__init__.py index 31410d1c9b2..2b4af3e1e91 100644 --- a/homeassistant/components/neato/__init__.py +++ b/homeassistant/components/neato/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Neato botvac connected vacuum cleaners. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/neato/ -""" +"""Support for Neato botvac connected vacuum cleaners.""" import logging from datetime import timedelta from urllib.error import HTTPError @@ -15,10 +10,10 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import discovery from homeassistant.util import Throttle -_LOGGER = logging.getLogger(__name__) - REQUIREMENTS = ['pybotvac==0.0.13'] +_LOGGER = logging.getLogger(__name__) + DOMAIN = 'neato' NEATO_ROBOTS = 'neato_robots' NEATO_LOGIN = 'neato_login' diff --git a/homeassistant/components/neato/camera.py b/homeassistant/components/neato/camera.py index 4df423344bb..530aa8fc6f1 100644 --- a/homeassistant/components/neato/camera.py +++ b/homeassistant/components/neato/camera.py @@ -1,9 +1,4 @@ -""" -Camera that loads a picture from Neato. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/camera.neato/ -""" +"""Support for loading picture from Neato.""" import logging from datetime import timedelta diff --git a/homeassistant/components/neato/switch.py b/homeassistant/components/neato/switch.py index 0b49cb71ba2..fcc72762b8d 100644 --- a/homeassistant/components/neato/switch.py +++ b/homeassistant/components/neato/switch.py @@ -1,9 +1,4 @@ -""" -Support for Neato Connected Vacuums switches. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.neato/ -""" +"""Support for Neato Connected Vacuums switches.""" import logging from datetime import timedelta import requests diff --git a/homeassistant/components/neato/vacuum.py b/homeassistant/components/neato/vacuum.py index 9ec9fe688b7..45cfd273aca 100644 --- a/homeassistant/components/neato/vacuum.py +++ b/homeassistant/components/neato/vacuum.py @@ -1,9 +1,4 @@ -""" -Support for Neato Connected Vacuums. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/vacuum.neato/ -""" +"""Support for Neato Connected Vacuums.""" import logging from datetime import timedelta import requests diff --git a/homeassistant/components/ness_alarm/__init__.py b/homeassistant/components/ness_alarm/__init__.py index e97ee903abc..dae244ece3f 100644 --- a/homeassistant/components/ness_alarm/__init__.py +++ b/homeassistant/components/ness_alarm/__init__.py @@ -1,16 +1,11 @@ -""" -Support for Ness D8X/D16X devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/ness_alarm/ -""" -import logging +"""Support for Ness D8X/D16X devices.""" from collections import namedtuple +import logging import voluptuous as vol from homeassistant.components.binary_sensor import DEVICE_CLASSES -from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.const import ATTR_CODE, ATTR_STATE, EVENT_HOMEASSISTANT_STOP from homeassistant.helpers import config_validation as cv from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -28,9 +23,7 @@ CONF_ZONES = 'zones' CONF_ZONE_NAME = 'name' CONF_ZONE_TYPE = 'type' CONF_ZONE_ID = 'id' -ATTR_CODE = 'code' ATTR_OUTPUT_ID = 'output_id' -ATTR_STATE = 'state' DEFAULT_ZONES = [] SIGNAL_ZONE_CHANGED = 'ness_alarm.zone_changed' diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index 7f0fe27df73..fe6a34cf404 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Nest devices. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/nest/ -""" +"""Support for Nest devices.""" import logging import socket from datetime import datetime, timedelta @@ -53,7 +48,7 @@ AWAY_MODE_AWAY = 'away' AWAY_MODE_HOME = 'home' SENSOR_SCHEMA = vol.Schema({ - vol.Optional(CONF_MONITORED_CONDITIONS): vol.All(cv.ensure_list) + vol.Optional(CONF_MONITORED_CONDITIONS): vol.All(cv.ensure_list), }) CONFIG_SCHEMA = vol.Schema({ @@ -62,25 +57,25 @@ CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_CLIENT_SECRET): cv.string, vol.Optional(CONF_STRUCTURE): vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_SENSORS): SENSOR_SCHEMA, - vol.Optional(CONF_BINARY_SENSORS): SENSOR_SCHEMA + vol.Optional(CONF_BINARY_SENSORS): SENSOR_SCHEMA, }) }, extra=vol.ALLOW_EXTRA) SET_AWAY_MODE_SCHEMA = vol.Schema({ vol.Required(ATTR_AWAY_MODE): vol.In([AWAY_MODE_AWAY, AWAY_MODE_HOME]), - vol.Optional(ATTR_STRUCTURE): vol.All(cv.ensure_list, [cv.string]) + vol.Optional(ATTR_STRUCTURE): vol.All(cv.ensure_list, [cv.string]), }) SET_ETA_SCHEMA = vol.Schema({ vol.Required(ATTR_ETA): cv.time_period, vol.Optional(ATTR_TRIP_ID): cv.string, vol.Optional(ATTR_ETA_WINDOW): cv.time_period, - vol.Optional(ATTR_STRUCTURE): vol.All(cv.ensure_list, [cv.string]) + vol.Optional(ATTR_STRUCTURE): vol.All(cv.ensure_list, [cv.string]), }) CANCEL_ETA_SCHEMA = vol.Schema({ vol.Required(ATTR_TRIP_ID): cv.string, - vol.Optional(ATTR_STRUCTURE): vol.All(cv.ensure_list, [cv.string]) + vol.Optional(ATTR_STRUCTURE): vol.All(cv.ensure_list, [cv.string]), }) @@ -90,7 +85,7 @@ def nest_update_event_broker(hass, nest): Runs in its own thread. """ - _LOGGER.debug("listening nest.update_event") + _LOGGER.debug("Listening for nest.update_event") while hass.is_running: nest.update_event.wait() @@ -99,10 +94,10 @@ def nest_update_event_broker(hass, nest): break nest.update_event.clear() - _LOGGER.debug("dispatching nest data update") + _LOGGER.debug("Dispatching nest data update") dispatcher_send(hass, SIGNAL_NEST_UPDATE) - _LOGGER.debug("stop listening nest.update_event") + _LOGGER.debug("Stop listening for nest.update_event") async def async_setup(hass, config): diff --git a/homeassistant/components/nest/binary_sensor.py b/homeassistant/components/nest/binary_sensor.py index 7f7278d9789..1077fdb073e 100644 --- a/homeassistant/components/nest/binary_sensor.py +++ b/homeassistant/components/nest/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for Nest Thermostat Binary Sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.nest/ -""" +"""Support for Nest Thermostat binary sensors.""" from itertools import chain import logging @@ -12,6 +7,8 @@ from homeassistant.components.nest import ( DATA_NEST, DATA_NEST_CONFIG, CONF_BINARY_SENSORS, NestSensorDevice) from homeassistant.const import CONF_MONITORED_CONDITIONS +_LOGGER = logging.getLogger(__name__) + DEPENDENCIES = ['nest'] BINARY_TYPES = {'online': 'connectivity'} @@ -48,10 +45,12 @@ _BINARY_TYPES_DEPRECATED = [ 'hvac_emer_heat_state', ] -_VALID_BINARY_SENSOR_TYPES = {**BINARY_TYPES, **CLIMATE_BINARY_TYPES, - **CAMERA_BINARY_TYPES, **STRUCTURE_BINARY_TYPES} - -_LOGGER = logging.getLogger(__name__) +_VALID_BINARY_SENSOR_TYPES = { + **BINARY_TYPES, + **CLIMATE_BINARY_TYPES, + **CAMERA_BINARY_TYPES, + **STRUCTURE_BINARY_TYPES, +} def setup_platform(hass, config, add_entities, discovery_info=None): @@ -89,9 +88,8 @@ async def async_setup_entry(hass, entry, async_add_entities): sensors += [NestBinarySensor(structure, None, variable) for variable in conditions if variable in STRUCTURE_BINARY_TYPES] - device_chain = chain(nest.thermostats(), - nest.smoke_co_alarms(), - nest.cameras()) + device_chain = chain( + nest.thermostats(), nest.smoke_co_alarms(), nest.cameras()) for structure, device in device_chain: sensors += [NestBinarySensor(structure, device, variable) for variable in conditions @@ -106,9 +104,8 @@ async def async_setup_entry(hass, entry, async_add_entities): for variable in conditions if variable in CAMERA_BINARY_TYPES] for activity_zone in device.activity_zones: - sensors += [NestActivityZoneSensor(structure, - device, - activity_zone)] + sensors += [NestActivityZoneSensor( + structure, device, activity_zone)] return sensors diff --git a/homeassistant/components/nest/camera.py b/homeassistant/components/nest/camera.py index 158123989c0..8b450e02b46 100644 --- a/homeassistant/components/nest/camera.py +++ b/homeassistant/components/nest/camera.py @@ -1,9 +1,4 @@ -""" -Support for Nest Cameras. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/camera.nest/ -""" +"""Support for Nest Cameras.""" import logging from datetime import timedelta diff --git a/homeassistant/components/nest/climate.py b/homeassistant/components/nest/climate.py index bd6bb2991cc..8746a1959ae 100644 --- a/homeassistant/components/nest/climate.py +++ b/homeassistant/components/nest/climate.py @@ -1,9 +1,4 @@ -""" -Support for Nest thermostats. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/climate.nest/ -""" +"""Support for Nest thermostats.""" import logging import voluptuous as vol diff --git a/homeassistant/components/nest/sensor.py b/homeassistant/components/nest/sensor.py index 5514203c6ea..10fa83d23e0 100644 --- a/homeassistant/components/nest/sensor.py +++ b/homeassistant/components/nest/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Nest Thermostat Sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.nest/ -""" +"""Support for Nest Thermostat sensors.""" import logging from homeassistant.components.climate import ( diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index ce39ad9d55e..495e22aae24 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -1,9 +1,4 @@ -""" -Support for the Netatmo devices. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/netatmo/ -""" +"""Support for the Netatmo devices.""" import logging from datetime import timedelta from urllib.error import HTTPError diff --git a/homeassistant/components/netatmo/binary_sensor.py b/homeassistant/components/netatmo/binary_sensor.py index 2cafacf401c..727ed0a68c7 100644 --- a/homeassistant/components/netatmo/binary_sensor.py +++ b/homeassistant/components/netatmo/binary_sensor.py @@ -1,11 +1,4 @@ -""" -Support for the Netatmo binary sensors. - -The binary sensors based on events seen by the Netatmo cameras. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.netatmo/. -""" +"""Support for the Netatmo binary sensors.""" import logging import voluptuous as vol diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index 93ad2cd055b..a3a5461631d 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -1,9 +1,4 @@ -""" -Support for the Netatmo cameras. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/camera.netatmo/. -""" +"""Support for the Netatmo cameras.""" import logging import requests diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index 8849ada5ccc..2b9bcbebaf2 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -1,9 +1,4 @@ -""" -Support for Netatmo Smart Thermostat. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/climate.netatmo/ -""" +"""Support for Netatmo Smart thermostats.""" import logging from datetime import timedelta import voluptuous as vol diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index f4dad6a3b6b..78a118528b9 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -1,9 +1,4 @@ -""" -Support for the NetAtmo Weather Service. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.netatmo/ -""" +"""Support for the NetAtmo Weather Service.""" import logging from time import time import threading @@ -55,8 +50,7 @@ SENSOR_TYPES = { } MODULE_SCHEMA = vol.Schema({ - vol.Required(cv.string): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), + vol.Required(cv.string): vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ diff --git a/homeassistant/components/netgear_lte/__init__.py b/homeassistant/components/netgear_lte/__init__.py index 77b9783b239..5f8c680b7f0 100644 --- a/homeassistant/components/netgear_lte/__init__.py +++ b/homeassistant/components/netgear_lte/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Netgear LTE modems. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/netgear_lte/ -""" +"""Support for Netgear LTE modems.""" import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/netgear_lte/notify.py b/homeassistant/components/netgear_lte/notify.py index 3f39ccfb43f..20a20b21291 100644 --- a/homeassistant/components/netgear_lte/notify.py +++ b/homeassistant/components/netgear_lte/notify.py @@ -1,9 +1,4 @@ -""" -The Netgear LTE platform for notify component. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/notify.netgear_lte/ -""" +"""Suport for Netgear LTE notifications.""" import logging import attr diff --git a/homeassistant/components/netgear_lte/sensor.py b/homeassistant/components/netgear_lte/sensor.py index 99907a21b57..339fa678d61 100644 --- a/homeassistant/components/netgear_lte/sensor.py +++ b/homeassistant/components/netgear_lte/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Netgear LTE sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.netgear_lte/ -""" +"""Support for Netgear LTE sensors.""" import attr import voluptuous as vol @@ -23,7 +18,7 @@ SENSOR_USAGE = 'usage' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_HOST): cv.string, vol.Required(CONF_SENSORS): vol.All( - cv.ensure_list, [vol.In([SENSOR_SMS, SENSOR_USAGE])]) + cv.ensure_list, [vol.In([SENSOR_SMS, SENSOR_USAGE])]), }) diff --git a/homeassistant/components/no_ip/__init__.py b/homeassistant/components/no_ip/__init__.py index beb11ed738f..6a714747484 100644 --- a/homeassistant/components/no_ip/__init__.py +++ b/homeassistant/components/no_ip/__init__.py @@ -1,9 +1,4 @@ -""" -Integrate with NO-IP Dynamic DNS service. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/no_ip/ -""" +"""Integrate with NO-IP Dynamic DNS service.""" import asyncio import base64 from datetime import timedelta diff --git a/homeassistant/components/nuheat/__init__.py b/homeassistant/components/nuheat/__init__.py index fb14f119dbd..4ea37339ef3 100644 --- a/homeassistant/components/nuheat/__init__.py +++ b/homeassistant/components/nuheat/__init__.py @@ -1,9 +1,4 @@ -""" -Support for NuHeat thermostats. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/nuheat/ -""" +"""Support for NuHeat thermostats.""" import logging import voluptuous as vol @@ -16,7 +11,7 @@ REQUIREMENTS = ["nuheat==0.3.0"] _LOGGER = logging.getLogger(__name__) -DOMAIN = "nuheat" +DOMAIN = 'nuheat' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ diff --git a/homeassistant/components/nuimo_controller/__init__.py b/homeassistant/components/nuimo_controller/__init__.py index 0f8fbb39073..70509469d2b 100644 --- a/homeassistant/components/nuimo_controller/__init__.py +++ b/homeassistant/components/nuimo_controller/__init__.py @@ -1,9 +1,4 @@ -""" -Component that connects to a Nuimo device over Bluetooth LE. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/nuimo_controller/ -""" +"""Support for Nuimo device over Bluetooth LE.""" import logging import threading import time diff --git a/homeassistant/components/octoprint/__init__.py b/homeassistant/components/octoprint/__init__.py index 869f3bd7d6e..35740a7be0d 100644 --- a/homeassistant/components/octoprint/__init__.py +++ b/homeassistant/components/octoprint/__init__.py @@ -1,9 +1,4 @@ -""" -Support for monitoring OctoPrint 3D printers. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/octoprint/ -""" +"""Support for monitoring OctoPrint 3D printers.""" import logging import time @@ -23,10 +18,11 @@ from homeassistant.util import slugify as util_slugify _LOGGER = logging.getLogger(__name__) -DOMAIN = 'octoprint' -CONF_NUMBER_OF_TOOLS = 'number_of_tools' CONF_BED = 'bed' +CONF_NUMBER_OF_TOOLS = 'number_of_tools' + DEFAULT_NAME = 'OctoPrint' +DOMAIN = 'octoprint' def has_all_unique_names(value): diff --git a/homeassistant/components/octoprint/binary_sensor.py b/homeassistant/components/octoprint/binary_sensor.py index 285495c03a0..cb860177796 100644 --- a/homeassistant/components/octoprint/binary_sensor.py +++ b/homeassistant/components/octoprint/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for monitoring OctoPrint binary sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.octoprint/ -""" +"""Support for monitoring OctoPrint binary sensors.""" import logging import requests diff --git a/homeassistant/components/octoprint/sensor.py b/homeassistant/components/octoprint/sensor.py index 8170b97c4c8..2df307f02ef 100644 --- a/homeassistant/components/octoprint/sensor.py +++ b/homeassistant/components/octoprint/sensor.py @@ -1,9 +1,4 @@ -""" -Support for monitoring OctoPrint sensors. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.octoprint/ -""" +"""Support for monitoring OctoPrint sensors.""" import logging import requests @@ -16,6 +11,7 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['octoprint'] + NOTIFICATION_ID = 'octoprint_notification' NOTIFICATION_TITLE = 'OctoPrint sensor setup error' diff --git a/homeassistant/components/onboarding/__init__.py b/homeassistant/components/onboarding/__init__.py index 25aca9f8afa..6bbe546dcb1 100644 --- a/homeassistant/components/onboarding/__init__.py +++ b/homeassistant/components/onboarding/__init__.py @@ -1,4 +1,4 @@ -"""Component to help onboard new users.""" +"""Support to help onboard new users.""" from homeassistant.core import callback from homeassistant.loader import bind_hass @@ -19,8 +19,8 @@ def async_is_onboarded(hass): async def async_setup(hass, config): """Set up the onboarding component.""" - store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY, - private=True) + store = hass.helpers.storage.Store( + STORAGE_VERSION, STORAGE_KEY, private=True) data = await store.async_load() if data is None: diff --git a/homeassistant/components/opentherm_gw/__init__.py b/homeassistant/components/opentherm_gw/__init__.py index cf74aae7577..7676806cfdf 100644 --- a/homeassistant/components/opentherm_gw/__init__.py +++ b/homeassistant/components/opentherm_gw/__init__.py @@ -1,9 +1,4 @@ -""" -Support for OpenTherm Gateway devices. - -For more details about this component, please refer to the documentation at -http://home-assistant.io/components/opentherm_gw/ -""" +"""Support for OpenTherm Gateway devices.""" import logging from datetime import datetime, date @@ -20,6 +15,10 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send import homeassistant.helpers.config_validation as cv +REQUIREMENTS = ['pyotgw==0.4b1'] + +_LOGGER = logging.getLogger(__name__) + DOMAIN = 'opentherm_gw' ATTR_MODE = 'mode' @@ -104,10 +103,6 @@ CONFIG_SCHEMA = vol.Schema({ }), }, extra=vol.ALLOW_EXTRA) -REQUIREMENTS = ['pyotgw==0.4b1'] - -_LOGGER = logging.getLogger(__name__) - async def async_setup(hass, config): """Set up the OpenTherm Gateway component.""" diff --git a/homeassistant/components/opentherm_gw/binary_sensor.py b/homeassistant/components/opentherm_gw/binary_sensor.py index 8c5ff8c44d1..b35998c807b 100644 --- a/homeassistant/components/opentherm_gw/binary_sensor.py +++ b/homeassistant/components/opentherm_gw/binary_sensor.py @@ -1,9 +1,4 @@ -""" -Support for OpenTherm Gateway binary sensors. - -For more details about this platform, please refer to the documentation at -http://home-assistant.io/components/binary_sensor.opentherm_gw/ -""" +"""Support for OpenTherm Gateway binary sensors.""" import logging from homeassistant.components.binary_sensor import ( @@ -13,17 +8,17 @@ from homeassistant.components.opentherm_gw import ( from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import async_generate_entity_id +_LOGGER = logging.getLogger(__name__) + DEVICE_CLASS_COLD = 'cold' DEVICE_CLASS_HEAT = 'heat' DEVICE_CLASS_PROBLEM = 'problem' DEPENDENCIES = ['opentherm_gw'] -_LOGGER = logging.getLogger(__name__) - -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the OpenTherm Gateway binary sensors.""" if discovery_info is None: return diff --git a/homeassistant/components/opentherm_gw/climate.py b/homeassistant/components/opentherm_gw/climate.py index 6dc52e6acc7..ff6acc1a884 100644 --- a/homeassistant/components/opentherm_gw/climate.py +++ b/homeassistant/components/opentherm_gw/climate.py @@ -1,9 +1,4 @@ -""" -Support for OpenTherm Gateway climate devices. - -For more details about this platform, please refer to the documentation at -http://home-assistant.io/components/climate.opentherm_gw/ -""" +"""Support for OpenTherm Gateway climate devices.""" import logging from homeassistant.components.climate import (ClimateDevice, STATE_IDLE, @@ -17,14 +12,15 @@ from homeassistant.const import (ATTR_TEMPERATURE, CONF_NAME, PRECISION_HALVES, TEMP_CELSIUS) from homeassistant.helpers.dispatcher import async_dispatcher_connect -DEPENDENCIES = ['opentherm_gw'] - -SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE) _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['opentherm_gw'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE + + +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the opentherm_gw device.""" gateway = OpenThermGateway(hass, discovery_info) async_add_entities([gateway]) diff --git a/homeassistant/components/opentherm_gw/sensor.py b/homeassistant/components/opentherm_gw/sensor.py index 9ae557654ce..070f847e5e5 100644 --- a/homeassistant/components/opentherm_gw/sensor.py +++ b/homeassistant/components/opentherm_gw/sensor.py @@ -1,9 +1,4 @@ -""" -Support for OpenTherm Gateway sensors. - -For more details about this platform, please refer to the documentation at -http://home-assistant.io/components/sensor.opentherm_gw/ -""" +"""Support for OpenTherm Gateway sensors.""" import logging from homeassistant.components.opentherm_gw import ( @@ -13,6 +8,8 @@ from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity, async_generate_entity_id +_LOGGER = logging.getLogger(__name__) + UNIT_BAR = 'bar' UNIT_HOUR = 'h' UNIT_KW = 'kW' @@ -21,11 +18,9 @@ UNIT_PERCENT = '%' DEPENDENCIES = ['opentherm_gw'] -_LOGGER = logging.getLogger(__name__) - -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the OpenTherm Gateway sensors.""" if discovery_info is None: return @@ -163,7 +158,7 @@ class OpenThermSensor(Entity): """Representation of an OpenTherm Gateway sensor.""" def __init__(self, entity_id, var, device_class, unit, friendly_name): - """Initialize the sensor.""" + """Initialize the OpenTherm Gateway sensor.""" self.entity_id = entity_id self._var = var self._value = None diff --git a/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py index 32c3da0d3e5..52383366c4d 100644 --- a/homeassistant/components/openuv/__init__.py +++ b/homeassistant/components/openuv/__init__.py @@ -1,9 +1,4 @@ -""" -Support for UV data from openuv.io. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/openuv/ -""" +"""Support for UV data from openuv.io.""" import logging import voluptuous as vol @@ -11,8 +6,7 @@ import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, CONF_BINARY_SENSORS, CONF_ELEVATION, - CONF_LATITUDE, CONF_LONGITUDE, CONF_MONITORED_CONDITIONS, - CONF_SENSORS) + CONF_LATITUDE, CONF_LONGITUDE, CONF_MONITORED_CONDITIONS, CONF_SENSORS) from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -22,6 +16,7 @@ from .config_flow import configured_instances from .const import DOMAIN REQUIREMENTS = ['pyopenuv==1.0.4'] + _LOGGER = logging.getLogger(__name__) DATA_OPENUV_CLIENT = 'data_client' diff --git a/homeassistant/components/openuv/binary_sensor.py b/homeassistant/components/openuv/binary_sensor.py index 3e9bb0b0bc3..b790427b228 100644 --- a/homeassistant/components/openuv/binary_sensor.py +++ b/homeassistant/components/openuv/binary_sensor.py @@ -1,26 +1,21 @@ -""" -This platform provides binary sensors for OpenUV data. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.openuv/ -""" +"""Support for OpenUV binary sensors.""" import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.components.openuv import ( BINARY_SENSORS, DATA_OPENUV_CLIENT, DATA_PROTECTION_WINDOW, DOMAIN, TOPIC_UPDATE, TYPE_PROTECTION_WINDOW, OpenUvEntity) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.dt import as_local, parse_datetime, utcnow -DEPENDENCIES = ['openuv'] _LOGGER = logging.getLogger(__name__) - -ATTR_PROTECTION_WINDOW_STARTING_TIME = 'start_time' -ATTR_PROTECTION_WINDOW_STARTING_UV = 'start_uv' ATTR_PROTECTION_WINDOW_ENDING_TIME = 'end_time' ATTR_PROTECTION_WINDOW_ENDING_UV = 'end_uv' +ATTR_PROTECTION_WINDOW_STARTING_TIME = 'start_time' +ATTR_PROTECTION_WINDOW_STARTING_UV = 'start_uv' + +DEPENDENCIES = ['openuv'] async def async_setup_platform( diff --git a/homeassistant/components/openuv/config_flow.py b/homeassistant/components/openuv/config_flow.py index 0f566e5a9ef..7150a8499d8 100644 --- a/homeassistant/components/openuv/config_flow.py +++ b/homeassistant/components/openuv/config_flow.py @@ -3,9 +3,9 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.core import callback from homeassistant.const import ( CONF_API_KEY, CONF_ELEVATION, CONF_LATITUDE, CONF_LONGITUDE) +from homeassistant.core import callback from homeassistant.helpers import aiohttp_client, config_validation as cv from .const import DOMAIN diff --git a/homeassistant/components/openuv/sensor.py b/homeassistant/components/openuv/sensor.py index 63527db42a6..489a100a5e5 100644 --- a/homeassistant/components/openuv/sensor.py +++ b/homeassistant/components/openuv/sensor.py @@ -1,24 +1,20 @@ -""" -This platform provides sensors for OpenUV data. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.openuv/ -""" +"""Support for OpenUV sensors.""" import logging -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.components.openuv import ( DATA_OPENUV_CLIENT, DATA_UV, DOMAIN, SENSORS, TOPIC_UPDATE, TYPE_CURRENT_OZONE_LEVEL, TYPE_CURRENT_UV_INDEX, TYPE_CURRENT_UV_LEVEL, TYPE_MAX_UV_INDEX, TYPE_SAFE_EXPOSURE_TIME_1, TYPE_SAFE_EXPOSURE_TIME_2, TYPE_SAFE_EXPOSURE_TIME_3, TYPE_SAFE_EXPOSURE_TIME_4, TYPE_SAFE_EXPOSURE_TIME_5, TYPE_SAFE_EXPOSURE_TIME_6, OpenUvEntity) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.dt import as_local, parse_datetime -DEPENDENCIES = ['openuv'] _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['openuv'] + ATTR_MAX_UV_TIME = 'time' EXPOSURE_TYPE_MAP = { @@ -30,11 +26,11 @@ EXPOSURE_TYPE_MAP = { TYPE_SAFE_EXPOSURE_TIME_6: 'st6' } -UV_LEVEL_EXTREME = "Extreme" -UV_LEVEL_VHIGH = "Very High" -UV_LEVEL_HIGH = "High" -UV_LEVEL_MODERATE = "Moderate" -UV_LEVEL_LOW = "Low" +UV_LEVEL_EXTREME = 'Extreme' +UV_LEVEL_VHIGH = 'Very High' +UV_LEVEL_HIGH = 'High' +UV_LEVEL_MODERATE = 'Moderate' +UV_LEVEL_LOW = 'Low' async def async_setup_platform( diff --git a/homeassistant/components/owntracks/__init__.py b/homeassistant/components/owntracks/__init__.py index d0ba27aeddd..cc918dcf674 100644 --- a/homeassistant/components/owntracks/__init__.py +++ b/homeassistant/components/owntracks/__init__.py @@ -1,4 +1,4 @@ -"""Component for OwnTracks.""" +"""Support for OwnTracks.""" from collections import defaultdict import json import logging @@ -8,16 +8,19 @@ from aiohttp.web import json_response import voluptuous as vol from homeassistant import config_entries +from homeassistant.components import mqtt from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.core import callback -from homeassistant.components import mqtt -from homeassistant.setup import async_when_setup import homeassistant.helpers.config_validation as cv +from homeassistant.setup import async_when_setup from .config_flow import CONF_SECRET -DOMAIN = "owntracks" REQUIREMENTS = ['libnacl==1.6.1'] + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = 'owntracks' DEPENDENCIES = ['webhook'] CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy' @@ -47,8 +50,6 @@ CONFIG_SCHEMA = vol.Schema({ } }, extra=vol.ALLOW_EXTRA) -_LOGGER = logging.getLogger(__name__) - async def async_setup(hass, config): """Initialize OwnTracks component.""" diff --git a/homeassistant/components/panel_custom/__init__.py b/homeassistant/components/panel_custom/__init__.py index 740a28a9dec..f6602169eb2 100644 --- a/homeassistant/components/panel_custom/__init__.py +++ b/homeassistant/components/panel_custom/__init__.py @@ -1,16 +1,13 @@ -""" -Register a custom front end panel. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/panel_custom/ -""" +"""Register a custom front end panel.""" import logging import os import voluptuous as vol -from homeassistant.loader import bind_hass import homeassistant.helpers.config_validation as cv +from homeassistant.loader import bind_hass + +_LOGGER = logging.getLogger(__name__) DOMAIN = 'panel_custom' DEPENDENCIES = ['frontend'] @@ -58,8 +55,6 @@ CONFIG_SCHEMA = vol.Schema({ })]) }, extra=vol.ALLOW_EXTRA) -_LOGGER = logging.getLogger(__name__) - @bind_hass async def async_register_panel( @@ -154,8 +149,8 @@ async def async_setup(hass, config): kwargs['module_url'] = panel[CONF_MODULE_URL] elif not await hass.async_add_job(os.path.isfile, panel_path): - _LOGGER.error('Unable to find webcomponent for %s: %s', - name, panel_path) + _LOGGER.error( + "Unable to find webcomponent for %s: %s", name, panel_path) continue else: diff --git a/homeassistant/components/panel_iframe/__init__.py b/homeassistant/components/panel_iframe/__init__.py index 030fbbf9324..b82f9fa9789 100644 --- a/homeassistant/components/panel_iframe/__init__.py +++ b/homeassistant/components/panel_iframe/__init__.py @@ -1,12 +1,7 @@ -""" -Register an iFrame front end panel. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/panel_iframe/ -""" +"""Register an iFrame front end panel.""" import voluptuous as vol -from homeassistant.const import (CONF_ICON, CONF_URL) +from homeassistant.const import CONF_ICON, CONF_URL import homeassistant.helpers.config_validation as cv DEPENDENCIES = ['frontend'] diff --git a/homeassistant/components/persistent_notification/__init__.py b/homeassistant/components/persistent_notification/__init__.py index d38501b9b07..0a648f6eff7 100644 --- a/homeassistant/components/persistent_notification/__init__.py +++ b/homeassistant/components/persistent_notification/__init__.py @@ -1,21 +1,16 @@ -""" -A component which is collecting configuration errors. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/persistent_notification/ -""" -import logging +"""Support for displaying persistent notifications.""" from collections import OrderedDict +import logging from typing import Awaitable import voluptuous as vol from homeassistant.components import websocket_api -from homeassistant.core import callback, HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import TemplateError -from homeassistant.loader import bind_hass from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id +from homeassistant.loader import bind_hass from homeassistant.util import slugify import homeassistant.util.dt as dt_util diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 20014c3414b..d11d4208dc8 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -1,9 +1,4 @@ -""" -Support for tracking people. - -For more details about this component, please refer to the documentation. -https://home-assistant.io/components/person/ -""" +"""Support for tracking people.""" from collections import OrderedDict from itertools import chain import logging @@ -11,6 +6,7 @@ import uuid import voluptuous as vol +from homeassistant.components import websocket_api from homeassistant.components.device_tracker import ( DOMAIN as DEVICE_TRACKER_DOMAIN) from homeassistant.const import ( @@ -19,21 +15,24 @@ from homeassistant.const import ( from homeassistant.core import callback, Event from homeassistant.auth import EVENT_USER_REMOVED import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.storage import Store from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.restore_state import RestoreEntity -from homeassistant.components import websocket_api -from homeassistant.helpers.typing import HomeAssistantType, ConfigType +from homeassistant.helpers.storage import Store +from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.loader import bind_hass _LOGGER = logging.getLogger(__name__) + ATTR_EDITABLE = 'editable' ATTR_SOURCE = 'source' ATTR_USER_ID = 'user_id' + CONF_DEVICE_TRACKERS = 'device_trackers' CONF_USER_ID = 'user_id' + DOMAIN = 'person' + STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 SAVE_DELAY = 10 @@ -82,8 +81,8 @@ class PersonManager: person_id = conf[CONF_ID] if person_id in config_data: - _LOGGER.error("Found config user with duplicate ID: %s", - person_id) + _LOGGER.error( + "Found config user with duplicate ID: %s", person_id) continue config_data[person_id] = conf @@ -160,8 +159,8 @@ class PersonManager: self.hass.bus.async_listen(EVENT_USER_REMOVED, self._user_removed) - async def async_create_person(self, *, name, device_trackers=None, - user_id=None): + async def async_create_person( + self, *, name, device_trackers=None, user_id=None): """Create a new person.""" if not name: raise ValueError("Name is required") @@ -338,8 +337,8 @@ class Person(RestoreEntity): def person_start_hass(now): self.person_updated() - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, - person_start_hass) + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_START, person_start_hass) @callback def person_updated(self): diff --git a/homeassistant/components/plant/__init__.py b/homeassistant/components/plant/__init__.py index ed43562d221..27324ad57a3 100644 --- a/homeassistant/components/plant/__init__.py +++ b/homeassistant/components/plant/__init__.py @@ -1,24 +1,21 @@ -"""Component to monitor plants. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/plant/ -""" -import logging -from datetime import datetime, timedelta +"""Support for monitoring plants.""" from collections import deque +from datetime import datetime, timedelta +import logging + import voluptuous as vol -from homeassistant.exceptions import HomeAssistantError -from homeassistant.const import ( - STATE_OK, STATE_PROBLEM, STATE_UNKNOWN, TEMP_CELSIUS, ATTR_TEMPERATURE, - CONF_SENSORS, ATTR_UNIT_OF_MEASUREMENT) from homeassistant.components import group +from homeassistant.components.recorder.util import execute, session_scope +from homeassistant.const import ( + ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT, CONF_SENSORS, STATE_OK, + STATE_PROBLEM, STATE_UNKNOWN, TEMP_CELSIUS) +from homeassistant.core import callback +from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.core import callback from homeassistant.helpers.event import async_track_state_change -from homeassistant.components.recorder.util import session_scope, execute _LOGGER = logging.getLogger(__name__) @@ -111,8 +108,8 @@ ENABLE_LOAD_HISTORY = False async def async_setup(hass, config): """Set up the Plant component.""" - component = EntityComponent(_LOGGER, DOMAIN, hass, - group_name=GROUP_NAME_ALL_PLANTS) + component = EntityComponent( + _LOGGER, DOMAIN, hass, group_name=GROUP_NAME_ALL_PLANTS) entities = [] for plant_name, plant_config in config[DOMAIN].items(): @@ -204,8 +201,8 @@ class Plant(Entity): self._conductivity = int(float(value)) elif reading == READING_BRIGHTNESS: self._brightness = int(float(value)) - self._brightness_history.add_measurement(self._brightness, - new_state.last_updated) + self._brightness_history.add_measurement( + self._brightness, new_state.last_updated) else: raise HomeAssistantError( "Unknown reading from sensor {}: {}".format(entity_id, value)) @@ -260,8 +257,8 @@ class Plant(Entity): # only use the database if it's configured self.hass.async_add_job(self._load_history_from_db) - async_track_state_change(self.hass, list(self._sensormap), - self.state_changed) + async_track_state_change( + self.hass, list(self._sensormap), self.state_changed) for entity_id in self._sensormap: state = self.hass.states.get(entity_id) @@ -277,11 +274,11 @@ class Plant(Entity): start_date = datetime.now() - timedelta(days=self._conf_check_days) entity_id = self._readingmap.get(READING_BRIGHTNESS) if entity_id is None: - _LOGGER.debug("not reading the history from the database as " - "there is no brightness sensor configured.") + _LOGGER.debug("Not reading the history from the database as " + "there is no brightness sensor configured") return - _LOGGER.debug("initializing values for %s from the database", + _LOGGER.debug("Initializing values for %s from the database", self._name) with session_scope(hass=self.hass) as session: query = session.query(States).filter( @@ -298,7 +295,7 @@ class Plant(Entity): int(state.state), state.last_updated) except ValueError: pass - _LOGGER.debug("initializing from database completed") + _LOGGER.debug("Initializing from database completed") self.async_schedule_update_ha_state() @property @@ -366,7 +363,7 @@ class DailyHistory: elif day > current_day: self._add_day(day, value) else: - _LOGGER.warning('received old measurement, not storing it!') + _LOGGER.warning("Received old measurement, not storing it") self.max = max(self._max_dict.values()) diff --git a/homeassistant/components/plum_lightpad/__init__.py b/homeassistant/components/plum_lightpad/__init__.py index 979f257f25f..5b99223d25a 100644 --- a/homeassistant/components/plum_lightpad/__init__.py +++ b/homeassistant/components/plum_lightpad/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Plum Lightpad switches. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/plum_lightpad -""" +"""Support for Plum Lightpad devices.""" import asyncio import logging diff --git a/homeassistant/components/plum_lightpad/light.py b/homeassistant/components/plum_lightpad/light.py index fa15c842deb..43cceaa671f 100644 --- a/homeassistant/components/plum_lightpad/light.py +++ b/homeassistant/components/plum_lightpad/light.py @@ -1,9 +1,4 @@ -""" -Support for Plum Lightpad switches. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/light.plum_lightpad/ -""" +"""Support for Plum Lightpad lights.""" from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, Light) from homeassistant.components.plum_lightpad import PLUM_DATA @@ -12,8 +7,8 @@ import homeassistant.util.color as color_util DEPENDENCIES = ['plum_lightpad'] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Initialize the Plum Lightpad Light and GlowRing.""" if discovery_info is None: return @@ -35,7 +30,7 @@ async def async_setup_platform(hass, config, async_add_entities, class PlumLight(Light): - """Represenation of a Plum Lightpad dimmer.""" + """Representation of a Plum Lightpad dimmer.""" def __init__(self, load): """Initialize the light.""" @@ -91,7 +86,7 @@ class PlumLight(Light): class GlowRing(Light): - """Represenation of a Plum Lightpad dimmer glow ring.""" + """Representation of a Plum Lightpad dimmer glow ring.""" def __init__(self, lightpad): """Initialize the light.""" @@ -107,8 +102,8 @@ class GlowRing(Light): async def async_added_to_hass(self): """Subscribe to configchange events.""" - self._lightpad.add_event_listener('configchange', - self.configchange_event) + self._lightpad.add_event_listener( + 'configchange', self.configchange_event) def configchange_event(self, event): """Handle Configuration change event.""" @@ -155,7 +150,7 @@ class GlowRing(Light): @property def icon(self): - """Return the crop-portait icon representing the glow ring.""" + """Return the crop-portrait icon representing the glow ring.""" return 'mdi:crop-portrait' @property @@ -181,5 +176,4 @@ class GlowRing(Light): await self._lightpad.set_config( {"glowIntensity": kwargs[ATTR_BRIGHTNESS]}) else: - await self._lightpad.set_config( - {"glowEnabled": False}) + await self._lightpad.set_config({"glowEnabled": False}) diff --git a/homeassistant/components/point/__init__.py b/homeassistant/components/point/__init__.py index fa0217c023b..f223ded998f 100644 --- a/homeassistant/components/point/__init__.py +++ b/homeassistant/components/point/__init__.py @@ -1,9 +1,4 @@ -""" -Support for Minut Point. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/point/ -""" +"""Support for Minut Point.""" import asyncio import logging @@ -26,10 +21,11 @@ from .const import ( SCAN_INTERVAL, SIGNAL_UPDATE_ENTITY, SIGNAL_WEBHOOK) REQUIREMENTS = ['pypoint==1.0.8'] -DEPENDENCIES = ['webhook'] _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['webhook'] + CONF_CLIENT_ID = 'client_id' CONF_CLIENT_SECRET = 'client_secret' diff --git a/homeassistant/components/point/binary_sensor.py b/homeassistant/components/point/binary_sensor.py index 2c79bf21c61..c29ce421682 100644 --- a/homeassistant/components/point/binary_sensor.py +++ b/homeassistant/components/point/binary_sensor.py @@ -1,10 +1,4 @@ -""" -Support for Minut Point. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.point/ -""" - +"""Support for Minut Point binary sensors.""" import logging from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice @@ -56,7 +50,7 @@ class MinutPointBinarySensor(MinutPointEntity, BinarySensorDevice): """The platform class required by Home Assistant.""" def __init__(self, point_client, device_id, device_class): - """Initialize the entity.""" + """Initialize the binary sensor.""" super().__init__(point_client, device_id, device_class) self._async_unsub_hook_dispatcher_connect = None @@ -64,7 +58,7 @@ class MinutPointBinarySensor(MinutPointEntity, BinarySensorDevice): self._is_on = None async def async_added_to_hass(self): - """Call when entity is added to hass.""" + """Call when entity is added to HOme Assistant.""" await super().async_added_to_hass() self._async_unsub_hook_dispatcher_connect = async_dispatcher_connect( self.hass, SIGNAL_WEBHOOK, self._webhook_event) diff --git a/homeassistant/components/point/sensor.py b/homeassistant/components/point/sensor.py index 902c1a6c981..90b83ba42e3 100644 --- a/homeassistant/components/point/sensor.py +++ b/homeassistant/components/point/sensor.py @@ -1,9 +1,4 @@ -""" -Support for Minut Point. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.point/ -""" +"""Support for Minut Point sensors.""" import logging from homeassistant.components.point import MinutPointEntity @@ -45,14 +40,14 @@ class MinutPointSensor(MinutPointEntity): """The platform class required by Home Assistant.""" def __init__(self, point_client, device_id, device_class): - """Initialize the entity.""" + """Initialize the sensor.""" super().__init__(point_client, device_id, device_class) self._device_prop = SENSOR_TYPES[device_class] async def _update_callback(self): """Update the value of the sensor.""" if self.is_updated: - _LOGGER.debug('Update sensor value for %s', self) + _LOGGER.debug("Update sensor value for %s", self) self._value = await self.hass.async_add_executor_job( self.device.sensor, self.device_class) self._updated = parse_datetime(self.device.last_update) diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index dc868530f88..4508611e51b 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -1,22 +1,17 @@ -""" -Support for Prometheus metrics export. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/prometheus/ -""" +"""Support for Prometheus metrics export.""" import logging -import voluptuous as vol from aiohttp import web +import voluptuous as vol +from homeassistant import core as hacore from homeassistant.components.climate import ATTR_CURRENT_TEMPERATURE from homeassistant.components.http import HomeAssistantView from homeassistant.const import ( - EVENT_STATE_CHANGED, TEMP_FAHRENHEIT, CONTENT_TYPE_TEXT_PLAIN, - ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT) -from homeassistant import core as hacore -import homeassistant.helpers.config_validation as cv + ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT, CONTENT_TYPE_TEXT_PLAIN, + EVENT_STATE_CHANGED, TEMP_FAHRENHEIT) from homeassistant.helpers import entityfilter, state as state_helper +import homeassistant.helpers.config_validation as cv from homeassistant.util.temperature import fahrenheit_to_celsius REQUIREMENTS = ['prometheus_client==0.2.0'] diff --git a/homeassistant/components/proximity/__init__.py b/homeassistant/components/proximity/__init__.py index e8d86d480e5..0a617bcec90 100644 --- a/homeassistant/components/proximity/__init__.py +++ b/homeassistant/components/proximity/__init__.py @@ -1,19 +1,11 @@ -""" -Support for tracking the proximity of a device. - -Component to monitor the proximity of devices to a particular zone and the -direction of travel. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/proximity/ -""" +"""Support for tracking the proximity of a device.""" import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_ZONE, CONF_DEVICES, CONF_UNIT_OF_MEASUREMENT) + CONF_DEVICES, CONF_UNIT_OF_MEASUREMENT, CONF_ZONE) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_state_change from homeassistant.util.distance import convert From 882f5ed0794c2dbfc1ff8ffa291f44a3809af381 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 13 Feb 2019 20:36:06 -0800 Subject: [PATCH 197/242] Don't directly update config entries (#20877) * Don't directly update config entries * Use ConfigEntryNotReady * Fix tests * Remove old test * Lint --- homeassistant/components/deconz/gateway.py | 29 +++------------ .../components/homematicip_cloud/hap.py | 20 ++--------- homeassistant/components/hue/bridge.py | 24 ++----------- homeassistant/components/unifi/controller.py | 24 ++----------- homeassistant/config_entries.py | 6 ++-- tests/components/deconz/test_gateway.py | 25 ++++--------- tests/components/deconz/test_init.py | 9 +++-- .../components/homematicip_cloud/test_hap.py | 10 ++++-- tests/components/hue/test_bridge.py | 33 ++++------------- tests/components/unifi/test_controller.py | 36 ++++--------------- tests/test_config_entries.py | 7 +++- 11 files changed, 54 insertions(+), 169 deletions(-) diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py index 8d33e011b94..fe9fc4b7752 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -1,5 +1,5 @@ """Representation of a deCONZ gateway.""" -from homeassistant import config_entries +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.const import CONF_EVENT, CONF_ID from homeassistant.core import EventOrigin, callback from homeassistant.helpers import aiohttp_client @@ -8,7 +8,7 @@ from homeassistant.helpers.dispatcher import ( from homeassistant.util import slugify from .const import ( - _LOGGER, DECONZ_REACHABLE, CONF_ALLOW_CLIP_SENSOR, SUPPORTED_PLATFORMS) + DECONZ_REACHABLE, CONF_ALLOW_CLIP_SENSOR, SUPPORTED_PLATFORMS) class DeconzGateway: @@ -20,7 +20,6 @@ class DeconzGateway: self.config_entry = config_entry self.available = True self.api = None - self._cancel_retry_setup = None self.deconz_ids = {} self.events = [] @@ -35,22 +34,8 @@ class DeconzGateway: self.async_connection_status_callback ) - if self.api is False: - retry_delay = 2 ** (tries + 1) - _LOGGER.error( - "Error connecting to deCONZ gateway. Retrying in %d seconds", - retry_delay) - - async def retry_setup(_now): - """Retry setup.""" - if await self.async_setup(tries + 1): - # This feels hacky, we should find a better way to do this - self.config_entry.state = config_entries.ENTRY_STATE_LOADED - - self._cancel_retry_setup = hass.helpers.event.async_call_later( - retry_delay, retry_setup) - - return False + if not self.api: + raise ConfigEntryNotReady for component in SUPPORTED_PLATFORMS: hass.async_create_task( @@ -107,12 +92,6 @@ class DeconzGateway: Will cancel any scheduled setup retry and will unload the config entry. """ - # If we have a retry scheduled, we were never setup. - if self._cancel_retry_setup is not None: - self._cancel_retry_setup() - self._cancel_retry_setup = None - return True - self.api.close() for component in SUPPORTED_PLATFORMS: diff --git a/homeassistant/components/homematicip_cloud/hap.py b/homeassistant/components/homematicip_cloud/hap.py index 37507a1fca8..9af6669652d 100644 --- a/homeassistant/components/homematicip_cloud/hap.py +++ b/homeassistant/components/homematicip_cloud/hap.py @@ -2,7 +2,7 @@ import asyncio import logging -from homeassistant import config_entries +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -84,7 +84,6 @@ class HomematicipHAP: self._retry_task = None self._tries = 0 self._accesspoint_connected = True - self._retry_setup = None async def async_setup(self, tries=0): """Initialize connection.""" @@ -96,20 +95,7 @@ class HomematicipHAP: self.config_entry.data.get(HMIPC_NAME) ) except HmipcConnectionError: - retry_delay = 2 ** min(tries, 8) - _LOGGER.error("Error connecting to HomematicIP with HAP %s. " - "Retrying in %d seconds", - self.config_entry.data.get(HMIPC_HAPID), retry_delay) - - async def retry_setup(_now): - """Retry setup.""" - if await self.async_setup(tries + 1): - self.config_entry.state = config_entries.ENTRY_STATE_LOADED - - self._retry_setup = self.hass.helpers.event.async_call_later( - retry_delay, retry_setup) - - return False + raise ConfigEntryNotReady _LOGGER.info("Connected to HomematicIP with HAP %s", self.config_entry.data.get(HMIPC_HAPID)) @@ -209,8 +195,6 @@ class HomematicipHAP: async def async_reset(self): """Close the websocket connection.""" self._ws_close_requested = True - if self._retry_setup is not None: - self._retry_setup.cancel() if self._retry_task is not None: self._retry_task.cancel() await self.home.disable_events() diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py index 93241622f0b..6e3d818db68 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -5,6 +5,7 @@ import async_timeout import voluptuous as vol from homeassistant import config_entries +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, config_validation as cv from .const import DOMAIN, LOGGER @@ -30,7 +31,6 @@ class HueBridge: self.allow_groups = allow_groups self.available = True self.api = None - self._cancel_retry_setup = None @property def host(self): @@ -59,20 +59,7 @@ class HueBridge: return False except CannotConnect: - retry_delay = 2 ** (tries + 1) - LOGGER.error("Error connecting to the Hue bridge at %s. Retrying " - "in %d seconds", host, retry_delay) - - async def retry_setup(_now): - """Retry setup.""" - if await self.async_setup(tries + 1): - # This feels hacky, we should find a better way to do this - self.config_entry.state = config_entries.ENTRY_STATE_LOADED - - self._cancel_retry_setup = hass.helpers.event.async_call_later( - retry_delay, retry_setup) - - return False + raise ConfigEntryNotReady except Exception: # pylint: disable=broad-except LOGGER.exception('Unknown error connecting with Hue bridge at %s', @@ -97,13 +84,6 @@ class HueBridge: # The bridge can be in 3 states: # - Setup was successful, self.api is not None # - Authentication was wrong, self.api is None, not retrying setup. - # - Host was down. self.api is None, we're retrying setup - - # If we have a retry scheduled, we were never setup. - if self._cancel_retry_setup is not None: - self._cancel_retry_setup() - self._cancel_retry_setup = None - return True # If the authentication was wrong. if self.api is None: diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index abd102f6187..2b9aa89fef2 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -4,7 +4,7 @@ import async_timeout from aiohttp import CookieJar -from homeassistant import config_entries +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.const import CONF_HOST from homeassistant.helpers import aiohttp_client @@ -22,7 +22,6 @@ class UniFiController: self.available = True self.api = None self.progress = None - self._cancel_retry_setup = None @property def host(self): @@ -47,20 +46,7 @@ class UniFiController: await self.api.initialize() except CannotConnect: - retry_delay = 2 ** (tries + 1) - LOGGER.error("Error connecting to the UniFi controller. Retrying " - "in %d seconds", retry_delay) - - async def retry_setup(_now): - """Retry setup.""" - if await self.async_setup(tries + 1): - # This feels hacky, we should find a better way to do this - self.config_entry.state = config_entries.ENTRY_STATE_LOADED - - self._cancel_retry_setup = hass.helpers.event.async_call_later( - retry_delay, retry_setup) - - return False + raise ConfigEntryNotReady except Exception: # pylint: disable=broad-except LOGGER.error( @@ -80,12 +66,6 @@ class UniFiController: Will cancel any scheduled setup retry and will unload the config entry. """ - # If we have a retry scheduled, we were never setup. - if self._cancel_retry_setup is not None: - self._cancel_retry_setup() - self._cancel_retry_setup = None - return True - # If the authentication was wrong. if self.api is None: return True diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 7c6da2644f6..c7dfc0c889b 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -127,6 +127,7 @@ from homeassistant.util.decorator import Registry _LOGGER = logging.getLogger(__name__) +_UNDEF = object() SOURCE_USER = 'user' SOURCE_DISCOVERY = 'discovery' @@ -441,9 +442,10 @@ class ConfigEntries: for entry in config['entries']] @callback - def async_update_entry(self, entry, *, data): + def async_update_entry(self, entry, *, data=_UNDEF): """Update a config entry.""" - entry.data = data + if data is not _UNDEF: + entry.data = data self._async_schedule_save() async def async_forward_entry_setup(self, entry, component): diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index 3411f96b981..dbc45c955b5 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -1,6 +1,9 @@ """Test deCONZ gateway.""" from unittest.mock import Mock, patch +import pytest + +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.components.deconz import gateway from tests.common import mock_coro @@ -56,8 +59,10 @@ async def test_gateway_retry(): deconz_gateway = gateway.DeconzGateway(hass, entry) - with patch.object(gateway, 'get_gateway', return_value=mock_coro(False)): - assert await deconz_gateway.async_setup() is False + with patch.object( + gateway, 'get_gateway', return_value=mock_coro(False) + ), pytest.raises(ConfigEntryNotReady): + await deconz_gateway.async_setup() async def test_connection_status(hass): @@ -118,22 +123,6 @@ async def test_shutdown(): assert len(deconz_gateway.api.close.mock_calls) == 1 -async def test_reset_cancel_retry(): - """Verify async reset can handle a scheduled retry.""" - hass = Mock() - entry = Mock() - entry.data = ENTRY_CONFIG - - deconz_gateway = gateway.DeconzGateway(hass, entry) - - with patch.object(gateway, 'get_gateway', return_value=mock_coro(False)): - assert await deconz_gateway.async_setup() is False - - assert deconz_gateway._cancel_retry_setup is not None - - assert await deconz_gateway.async_reset() is True - - async def test_reset_after_successful_setup(): """Verify that reset works on a setup component.""" hass = Mock() diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index 5fa8ddcfe38..cbba47eb431 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -4,6 +4,7 @@ from unittest.mock import Mock, patch import pytest import voluptuous as vol +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.setup import async_setup_component from homeassistant.components import deconz @@ -79,9 +80,11 @@ async def test_setup_entry_no_available_bridge(hass): """Test setup entry fails if deCONZ is not available.""" entry = Mock() entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'} - with patch('pydeconz.DeconzSession.async_load_parameters', - return_value=mock_coro(False)): - assert await deconz.async_setup_entry(hass, entry) is False + with patch( + 'pydeconz.DeconzSession.async_load_parameters', + return_value=mock_coro(False) + ), pytest.raises(ConfigEntryNotReady): + await deconz.async_setup_entry(hass, entry) async def test_setup_entry_successful(hass): diff --git a/tests/components/homematicip_cloud/test_hap.py b/tests/components/homematicip_cloud/test_hap.py index 521920b9281..c39e7d4e26b 100644 --- a/tests/components/homematicip_cloud/test_hap.py +++ b/tests/components/homematicip_cloud/test_hap.py @@ -1,6 +1,9 @@ """Test HomematicIP Cloud accesspoint.""" from unittest.mock import Mock, patch +import pytest + +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.components.homematicip_cloud import hap as hmipc from homeassistant.components.homematicip_cloud import const, errors from tests.common import mock_coro, mock_coro_func @@ -82,9 +85,10 @@ async def test_hap_setup_connection_error(): hmipc.HMIPC_NAME: 'hmip', } hap = hmipc.HomematicipHAP(hass, entry) - with patch.object(hap, 'get_hap', - side_effect=errors.HmipcConnectionError): - assert await hap.async_setup() is False + with patch.object( + hap, 'get_hap', side_effect=errors.HmipcConnectionError + ), pytest.raises(ConfigEntryNotReady): + await hap.async_setup() assert len(hass.async_add_job.mock_calls) == 0 assert len(hass.config_entries.flow.async_init.mock_calls) == 0 diff --git a/tests/components/hue/test_bridge.py b/tests/components/hue/test_bridge.py index ceb30091970..855a12e2620 100644 --- a/tests/components/hue/test_bridge.py +++ b/tests/components/hue/test_bridge.py @@ -1,6 +1,9 @@ """Test Hue bridge.""" from unittest.mock import Mock, patch +import pytest + +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.components.hue import bridge, errors from tests.common import mock_coro @@ -48,32 +51,10 @@ async def test_bridge_setup_timeout(hass): entry.data = {'host': '1.2.3.4', 'username': 'mock-username'} hue_bridge = bridge.HueBridge(hass, entry, False, False) - with patch.object(bridge, 'get_bridge', side_effect=errors.CannotConnect): - assert await hue_bridge.async_setup() is False - - assert len(hass.helpers.event.async_call_later.mock_calls) == 1 - # Assert we are going to wait 2 seconds - assert hass.helpers.event.async_call_later.mock_calls[0][1][0] == 2 - - -async def test_reset_cancels_retry_setup(): - """Test resetting a bridge while we're waiting to retry setup.""" - hass = Mock() - entry = Mock() - entry.data = {'host': '1.2.3.4', 'username': 'mock-username'} - hue_bridge = bridge.HueBridge(hass, entry, False, False) - - with patch.object(bridge, 'get_bridge', side_effect=errors.CannotConnect): - assert await hue_bridge.async_setup() is False - - mock_call_later = hass.helpers.event.async_call_later - - assert len(mock_call_later.mock_calls) == 1 - - assert await hue_bridge.async_reset() - - assert len(mock_call_later.mock_calls) == 2 - assert len(mock_call_later.return_value.mock_calls) == 1 + with patch.object( + bridge, 'get_bridge', side_effect=errors.CannotConnect + ), pytest.raises(ConfigEntryNotReady): + await hue_bridge.async_setup() async def test_reset_if_entry_had_wrong_auth(): diff --git a/tests/components/unifi/test_controller.py b/tests/components/unifi/test_controller.py index b3b222d902a..e5e1d84bfcd 100644 --- a/tests/components/unifi/test_controller.py +++ b/tests/components/unifi/test_controller.py @@ -1,6 +1,9 @@ """Test UniFi Controller.""" from unittest.mock import Mock, patch +import pytest + +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.components import unifi from homeassistant.components.unifi import controller, errors @@ -103,13 +106,10 @@ async def test_controller_not_accessible(): unifi_controller = controller.UniFiController(hass, entry) - with patch.object(controller, 'get_controller', - side_effect=errors.CannotConnect): - assert await unifi_controller.async_setup() is False - - assert len(hass.helpers.event.async_call_later.mock_calls) == 1 - # Assert we are going to wait 2 seconds - assert hass.helpers.event.async_call_later.mock_calls[0][1][0] == 2 + with patch.object( + controller, 'get_controller', side_effect=errors.CannotConnect + ), pytest.raises(ConfigEntryNotReady): + await unifi_controller.async_setup() async def test_controller_unknown_error(): @@ -128,28 +128,6 @@ async def test_controller_unknown_error(): assert not hass.helpers.event.async_call_later.mock_calls -async def test_reset_cancels_retry_setup(): - """Resetting a controller while we're waiting to retry setup.""" - hass = Mock() - entry = Mock() - entry.data = ENTRY_CONFIG - - unifi_controller = controller.UniFiController(hass, entry) - - with patch.object(controller, 'get_controller', - side_effect=errors.CannotConnect): - assert await unifi_controller.async_setup() is False - - mock_call_later = hass.helpers.event.async_call_later - - assert len(mock_call_later.mock_calls) == 1 - - assert await unifi_controller.async_reset() - - assert len(mock_call_later.mock_calls) == 2 - assert len(mock_call_later.return_value.mock_calls) == 1 - - async def test_reset_if_entry_had_wrong_auth(): """Calling reset when the entry contains wrong auth.""" hass = Mock() diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 59777e2e6bb..496ad785275 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -411,13 +411,18 @@ async def test_updating_entry_data(manager): entry = MockConfigEntry( domain='test', data={'first': True}, + state=config_entries.ENTRY_STATE_SETUP_ERROR, ) entry.add_to_manager(manager) + manager.async_update_entry(entry) + assert entry.data == { + 'first': True + } + manager.async_update_entry(entry, data={ 'second': True }) - assert entry.data == { 'second': True } From e404afc0d066d3b0a5853991112c466951337726 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 13 Feb 2019 20:37:46 -0800 Subject: [PATCH 198/242] Bumped version to 0.88.0b0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 1a3d4e2e455..84d1350a80f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 88 -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 6c574a4eb4d1145d99ff4fec9d7a3d92e81aafd2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 15 Feb 2019 10:08:59 -0800 Subject: [PATCH 199/242] Updated frontend to 20190215.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 be2551457d0..d35514160c9 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -21,7 +21,7 @@ from homeassistant.loader import bind_hass from .storage import async_setup_frontend_storage -REQUIREMENTS = ['home-assistant-frontend==20190213.0'] +REQUIREMENTS = ['home-assistant-frontend==20190215.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index b91036fa1f6..689fe739eba 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -535,7 +535,7 @@ hole==0.3.0 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190213.0 +home-assistant-frontend==20190215.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a4ebc2301e1..a5b45a585b9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -116,7 +116,7 @@ hdate==0.8.7 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190213.0 +home-assistant-frontend==20190215.0 # homeassistant.components.homekit_controller homekit==0.12.2 From 2b6b922e3f18e8211cfefd35bd0b1ab0f95819a4 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Fri, 15 Feb 2019 13:14:58 -0500 Subject: [PATCH 200/242] Set ZHA device availability on new join (#21066) * set availability on device join * fix new join test --- homeassistant/components/zha/core/gateway.py | 3 +++ tests/components/zha/common.py | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 02ed1d73699..ff3c374a850 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -164,6 +164,9 @@ class ZHAGateway: device_entity = _create_device_entity(zha_device) await self._component.async_add_entities([device_entity]) + if is_new_join: + zha_device.update_available(True) + async def _async_process_endpoint( self, endpoint_id, endpoint, discovery_infos, device, zha_device, is_new_join): diff --git a/tests/components/zha/common.py b/tests/components/zha/common.py index 1a923849ce5..f0e1aa701e7 100644 --- a/tests/components/zha/common.py +++ b/tests/components/zha/common.py @@ -1,7 +1,6 @@ """Common test objects.""" import time from unittest.mock import patch, Mock -from homeassistant.const import STATE_UNAVAILABLE from homeassistant.components.zha.core.helpers import convert_ieee from homeassistant.components.zha.core.const import ( DATA_ZHA, DATA_ZHA_CONFIG, DATA_ZHA_DISPATCHERS, DATA_ZHA_BRIDGE_ID @@ -191,4 +190,4 @@ async def async_test_device_join( cluster = zigpy_device.endpoints.get(1).in_clusters[cluster_id] entity_id = make_entity_id( domain, zigpy_device, cluster, use_suffix=device_type is None) - assert hass.states.get(entity_id).state == STATE_UNAVAILABLE + assert hass.states.get(entity_id) is not None From 8973852a2e5257bfee5d63a0e2e9450bbf59e5f0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 14 Feb 2019 12:06:25 -0800 Subject: [PATCH 201/242] Fix pushover schema --- homeassistant/components/notify/pushover.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/notify/pushover.py b/homeassistant/components/notify/pushover.py index 3ec0b27e7c4..b249ca804b3 100644 --- a/homeassistant/components/notify/pushover.py +++ b/homeassistant/components/notify/pushover.py @@ -10,7 +10,7 @@ import voluptuous as vol from homeassistant.components.notify import ( ATTR_TITLE, ATTR_TITLE_DEFAULT, ATTR_TARGET, ATTR_DATA, - BaseNotificationService) + BaseNotificationService, PLATFORM_SCHEMA) from homeassistant.const import CONF_API_KEY import homeassistant.helpers.config_validation as cv @@ -20,7 +20,7 @@ _LOGGER = logging.getLogger(__name__) CONF_USER_KEY = 'user_key' -PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_USER_KEY): cv.string, vol.Required(CONF_API_KEY): cv.string, }) From dc606b40ac6661c5e0370d72e6251edfd75e97d5 Mon Sep 17 00:00:00 2001 From: Phil Hawthorne Date: Sat, 16 Feb 2019 05:25:03 +1100 Subject: [PATCH 202/242] Set uvloop version consistent with hass.io (#21080) This sets the uvloop version in Docker containers to 0.11.3, which is the same version that hass.io uses. uvloop might be causing issues with some Docker containers on some host systems, as reported in #20829 --- Dockerfile | 2 +- virtualization/Docker/Dockerfile.dev | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index c863ff9433c..aa9415fd1e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ COPY requirements_all.txt requirements_all.txt # Uninstall enum34 because some dependencies install it but breaks Python 3.4+. # See PR #8103 for more info. RUN pip3 install --no-cache-dir -r requirements_all.txt && \ - pip3 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet cython tensorflow + pip3 install --no-cache-dir mysqlclient psycopg2 uvloop==0.11.3 cchardet cython tensorflow # Copy source COPY . . diff --git a/virtualization/Docker/Dockerfile.dev b/virtualization/Docker/Dockerfile.dev index de460319bc2..03d6ab47c24 100644 --- a/virtualization/Docker/Dockerfile.dev +++ b/virtualization/Docker/Dockerfile.dev @@ -29,7 +29,7 @@ COPY requirements_all.txt requirements_all.txt # Uninstall enum34 because some dependencies install it but breaks Python 3.4+. # See PR #8103 for more info. RUN pip3 install --no-cache-dir -r requirements_all.txt && \ - pip3 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet cython + pip3 install --no-cache-dir mysqlclient psycopg2 uvloop==0.11.3 cchardet cython # BEGIN: Development additions From 2de65af3faf68ab335ea7c381f8a8ca826567cbf Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 14 Feb 2019 15:54:38 -0800 Subject: [PATCH 203/242] Check against unlinked user (#21081) --- homeassistant/components/person/__init__.py | 8 ++++-- tests/components/person/test_init.py | 29 +++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index d11d4208dc8..63e588f911b 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -134,22 +134,26 @@ class PersonManager: entities.append(Person(person_conf, False)) + # To make sure IDs don't overlap between config/storage + seen_persons = set(self.config_data) + for person_conf in storage_data.values(): person_id = person_conf[CONF_ID] user_id = person_conf[CONF_USER_ID] - if user_id in self.config_data: + if person_id in seen_persons: _LOGGER.error( "Skipping adding person from storage with same ID as" " configuration.yaml entry: %s", person_id) continue - if user_id in seen_users: + if user_id is not None and user_id in seen_users: _LOGGER.error( "Duplicate user_id %s detected for person %s", user_id, person_id) continue + # To make sure all users have just 1 person linked. seen_users.add(user_id) entities.append(Person(person_conf, True)) diff --git a/tests/components/person/test_init.py b/tests/components/person/test_init.py index 2eacb162f8e..f2d796fb204 100644 --- a/tests/components/person/test_init.py +++ b/tests/components/person/test_init.py @@ -287,6 +287,35 @@ async def test_load_person_storage(hass, hass_admin_user, storage_setup): assert state.attributes.get(ATTR_USER_ID) == hass_admin_user.id +async def test_load_person_storage_two_nonlinked(hass, hass_storage): + """Test loading two users with both not having a user linked.""" + hass_storage[DOMAIN] = { + 'key': DOMAIN, + 'version': 1, + 'data': { + 'persons': [ + { + 'id': '1234', + 'name': 'tracked person 1', + 'user_id': None, + 'device_trackers': [] + }, + { + 'id': '5678', + 'name': 'tracked person 2', + 'user_id': None, + 'device_trackers': [] + }, + ] + } + } + await async_setup_component(hass, DOMAIN, {}) + + assert len(hass.states.async_entity_ids('person')) == 2 + assert hass.states.get('person.tracked_person_1') is not None + assert hass.states.get('person.tracked_person_2') is not None + + async def test_ws_list(hass, hass_ws_client, storage_setup): """Test listing via WS.""" manager = hass.data[DOMAIN] From da672c6593a1374ebf0d0475fcce69ebc719c585 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 15 Feb 2019 08:43:30 -0800 Subject: [PATCH 204/242] Fix hue retry crash (#21083) * Fix Hue retry crash * Fix hue retry crash * Fix tests --- homeassistant/components/hue/__init__.py | 9 ++++++--- tests/components/hue/test_init.py | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/hue/__init__.py b/homeassistant/components/hue/__init__.py index 7618e702d04..0871d961a93 100644 --- a/homeassistant/components/hue/__init__.py +++ b/homeassistant/components/hue/__init__.py @@ -28,6 +28,8 @@ CONF_BRIDGES = "bridges" CONF_ALLOW_UNREACHABLE = 'allow_unreachable' DEFAULT_ALLOW_UNREACHABLE = False +DATA_CONFIGS = 'hue_configs' + PHUE_CONFIG_FILE = 'phue.conf' CONF_ALLOW_HUE_GROUPS = "allow_hue_groups" @@ -59,6 +61,7 @@ async def async_setup(hass, config): conf = {} hass.data[DOMAIN] = {} + hass.data[DATA_CONFIGS] = {} configured = configured_hosts(hass) # User has configured bridges @@ -71,7 +74,7 @@ async def async_setup(hass, config): host = bridge_conf[CONF_HOST] # Store config in hass.data so the config entry can find it - hass.data[DOMAIN][host] = bridge_conf + hass.data[DATA_CONFIGS][host] = bridge_conf # If configured, the bridge will be set up during config entry phase if host in configured: @@ -96,7 +99,7 @@ async def async_setup(hass, config): async def async_setup_entry(hass, entry): """Set up a bridge from a config entry.""" host = entry.data['host'] - config = hass.data[DOMAIN].get(host) + config = hass.data[DATA_CONFIGS].get(host) if config is None: allow_unreachable = DEFAULT_ALLOW_UNREACHABLE @@ -106,11 +109,11 @@ async def async_setup_entry(hass, entry): allow_groups = config[CONF_ALLOW_HUE_GROUPS] bridge = HueBridge(hass, entry, allow_unreachable, allow_groups) - hass.data[DOMAIN][host] = bridge if not await bridge.async_setup(): return False + hass.data[DOMAIN][host] = bridge config = bridge.api.config device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( diff --git a/tests/components/hue/test_init.py b/tests/components/hue/test_init.py index 1fcc092dd30..6c89995a1a1 100644 --- a/tests/components/hue/test_init.py +++ b/tests/components/hue/test_init.py @@ -39,7 +39,7 @@ async def test_setup_defined_hosts_known_auth(hass): assert len(mock_config_entries.flow.mock_calls) == 0 # Config stored for domain. - assert hass.data[hue.DOMAIN] == { + assert hass.data[hue.DATA_CONFIGS] == { '0.0.0.0': { hue.CONF_HOST: '0.0.0.0', hue.CONF_FILENAME: 'bla.conf', @@ -73,7 +73,7 @@ async def test_setup_defined_hosts_no_known_auth(hass): } # Config stored for domain. - assert hass.data[hue.DOMAIN] == { + assert hass.data[hue.DATA_CONFIGS] == { '0.0.0.0': { hue.CONF_HOST: '0.0.0.0', hue.CONF_FILENAME: 'bla.conf', From 90da9120e83831c22a30eb5bbdebdd2aa4bb1df2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 15 Feb 2019 09:46:03 -0800 Subject: [PATCH 205/242] Update pychromecast (#21097) --- homeassistant/components/cast/__init__.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py index 94b926795e7..5e6bd720d4b 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -2,7 +2,7 @@ from homeassistant import config_entries from homeassistant.helpers import config_entry_flow -REQUIREMENTS = ['pychromecast==2.5.0'] +REQUIREMENTS = ['pychromecast==2.5.1'] DOMAIN = 'cast' diff --git a/requirements_all.txt b/requirements_all.txt index 689fe739eba..cb23edaa232 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -956,7 +956,7 @@ pycfdns==0.0.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==2.5.0 +pychromecast==2.5.1 # homeassistant.components.media_player.cmus pycmus==0.1.1 From 91a2c73a2cf169524de06fc1f6c9249c032c1a3d Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Fri, 15 Feb 2019 11:28:23 -0700 Subject: [PATCH 206/242] Bump aioambient to 0.1.2 (#21098) --- homeassistant/components/ambient_station/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index 5972660c6e6..4a7864d3f7f 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -20,7 +20,7 @@ from .const import ( ATTR_LAST_DATA, CONF_APP_KEY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE, TYPE_BINARY_SENSOR, TYPE_SENSOR) -REQUIREMENTS = ['aioambient==0.1.1'] +REQUIREMENTS = ['aioambient==0.1.2'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index cb23edaa232..cc4e5d3d896 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -90,7 +90,7 @@ abodepy==0.15.0 afsapi==0.0.4 # homeassistant.components.ambient_station -aioambient==0.1.1 +aioambient==0.1.2 # homeassistant.components.asuswrt aioasuswrt==1.1.20 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a5b45a585b9..fc66a4b72f6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -31,7 +31,7 @@ PyTransportNSW==0.1.1 YesssSMS==0.2.3 # homeassistant.components.ambient_station -aioambient==0.1.1 +aioambient==0.1.2 # homeassistant.components.device_tracker.automatic aioautomatic==0.6.5 From 5cf936c777a4edb671cf77a1ac4bf50e53f9bf7a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 15 Feb 2019 10:32:28 -0800 Subject: [PATCH 207/242] Bumped version to 0.88.0b1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 84d1350a80f..12f394b9e03 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 88 -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 d34fe106e42e793da1b06faa9772fbafd3373da5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 16 Feb 2019 12:00:04 -0800 Subject: [PATCH 208/242] Updated frontend to 20190216.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 d35514160c9..3db63e65a6d 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -21,7 +21,7 @@ from homeassistant.loader import bind_hass from .storage import async_setup_frontend_storage -REQUIREMENTS = ['home-assistant-frontend==20190215.0'] +REQUIREMENTS = ['home-assistant-frontend==20190216.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index cc4e5d3d896..af11e32e516 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -535,7 +535,7 @@ hole==0.3.0 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190215.0 +home-assistant-frontend==20190216.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fc66a4b72f6..5830085e544 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -116,7 +116,7 @@ hdate==0.8.7 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190215.0 +home-assistant-frontend==20190216.0 # homeassistant.components.homekit_controller homekit==0.12.2 From c91512d55df89e5b8d6830c20c62a7929ed466c1 Mon Sep 17 00:00:00 2001 From: Nick Horvath Date: Sat, 16 Feb 2019 05:18:13 -0500 Subject: [PATCH 209/242] Bump thermoworks_smoke version to get new pyrebase version (#21100) --- homeassistant/components/sensor/thermoworks_smoke.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/thermoworks_smoke.py b/homeassistant/components/sensor/thermoworks_smoke.py index e81a3974176..0c6cddd9fcd 100644 --- a/homeassistant/components/sensor/thermoworks_smoke.py +++ b/homeassistant/components/sensor/thermoworks_smoke.py @@ -17,7 +17,7 @@ from homeassistant.const import TEMP_FAHRENHEIT, CONF_EMAIL, CONF_PASSWORD,\ CONF_MONITORED_CONDITIONS, CONF_EXCLUDE, ATTR_BATTERY_LEVEL from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['thermoworks_smoke==0.1.7', 'stringcase==1.2.0'] +REQUIREMENTS = ['thermoworks_smoke==0.1.8', 'stringcase==1.2.0'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index af11e32e516..508f7bb2d04 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1656,7 +1656,7 @@ temperusb==1.5.3 teslajsonpy==0.0.23 # homeassistant.components.sensor.thermoworks_smoke -thermoworks_smoke==0.1.7 +thermoworks_smoke==0.1.8 # homeassistant.components.thingspeak thingspeak==0.4.1 From bc17adda8d4e0d2ae3adc633b053c2932a047f4b Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 17 Feb 2019 04:04:56 +0000 Subject: [PATCH 210/242] Don't expose services in Utility_Meter unless tariffs are available (#20878) * only expose services when tariffs configured * don't register services multiple times --- .../components/utility_meter/__init__.py | 28 +++++++++++-------- .../components/utility_meter/sensor.py | 1 + 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/utility_meter/__init__.py b/homeassistant/components/utility_meter/__init__.py index 7d8e4ddf71b..3cf1b2fea61 100644 --- a/homeassistant/components/utility_meter/__init__.py +++ b/homeassistant/components/utility_meter/__init__.py @@ -51,6 +51,7 @@ async def async_setup(hass, config): """Set up an Utility Meter.""" component = EntityComponent(_LOGGER, DOMAIN, hass) hass.data[DATA_UTILITY] = {} + register_services = False for meter, conf in config.get(DOMAIN).items(): _LOGGER.debug("Setup %s.%s", DOMAIN, meter) @@ -80,21 +81,23 @@ async def async_setup(hass, config): }) hass.async_create_task(discovery.async_load_platform( hass, SENSOR_DOMAIN, DOMAIN, tariff_confs, config)) + register_services = True - component.async_register_entity_service( - SERVICE_RESET, SERVICE_METER_SCHEMA, - 'async_reset_meters' - ) + if register_services: + component.async_register_entity_service( + SERVICE_RESET, SERVICE_METER_SCHEMA, + 'async_reset_meters' + ) - component.async_register_entity_service( - SERVICE_SELECT_TARIFF, SERVICE_SELECT_TARIFF_SCHEMA, - 'async_select_tariff' - ) + component.async_register_entity_service( + SERVICE_SELECT_TARIFF, SERVICE_SELECT_TARIFF_SCHEMA, + 'async_select_tariff' + ) - component.async_register_entity_service( - SERVICE_SELECT_NEXT_TARIFF, SERVICE_METER_SCHEMA, - 'async_next_tariff' - ) + component.async_register_entity_service( + SERVICE_SELECT_NEXT_TARIFF, SERVICE_METER_SCHEMA, + 'async_next_tariff' + ) return True @@ -150,6 +153,7 @@ class TariffSelect(RestoreEntity): async def async_reset_meters(self): """Reset all sensors of this meter.""" + _LOGGER.debug("reset meter %s", self.entity_id) async_dispatcher_send(self.hass, SIGNAL_RESET_METER, self.entity_id) diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index d3edf7d501b..a59d51d97e2 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -189,6 +189,7 @@ class UtilityMeterSensor(RestoreEntity): if self._tariff != tariff_entity_state.state: return + _LOGGER.debug("tracking source: %s", self._sensor_source_id) self._collecting = async_track_state_change( self.hass, self._sensor_source_id, self.async_reading) From 84053103f0ff7cfecb77303ea588d89420f626d3 Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Sat, 16 Feb 2019 21:23:09 -0800 Subject: [PATCH 211/242] Deprecate conf_update_interval (#20924) * Deprecate update_interval and replace with scan_interval * Update tests * Fix Darksky tests * Fix Darksky tests correctly This reverts commit a73384a223ba8a93c682042d9351cd5a7a399183. * Provide the default for the non deprecated option * Don't override default schema for sensors --- .../components/fastdotcom/__init__.py | 27 ++++++--- homeassistant/components/freedns/__init__.py | 7 ++- .../components/mythicbeastsdns/__init__.py | 30 +++++++--- homeassistant/components/sensor/broadlink.py | 34 +++++++---- homeassistant/components/sensor/darksky.py | 57 ++++++++++++------- homeassistant/components/sensor/fedex.py | 30 +++++++--- homeassistant/components/sensor/ups.py | 35 ++++++++---- .../components/speedtestdotnet/__init__.py | 35 ++++++++---- .../components/tellduslive/__init__.py | 27 +++++---- .../components/volvooncall/__init__.py | 43 ++++++++------ homeassistant/const.py | 4 ++ homeassistant/helpers/config_validation.py | 3 +- tests/components/freedns/test_init.py | 4 +- tests/components/sensor/test_darksky.py | 15 +++-- tests/helpers/test_config_validation.py | 18 +++++- 15 files changed, 249 insertions(+), 120 deletions(-) diff --git a/homeassistant/components/fastdotcom/__init__.py b/homeassistant/components/fastdotcom/__init__.py index a63fab76861..2e092e527c5 100644 --- a/homeassistant/components/fastdotcom/__init__.py +++ b/homeassistant/components/fastdotcom/__init__.py @@ -5,7 +5,8 @@ from datetime import timedelta import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.const import CONF_UPDATE_INTERVAL +from homeassistant.const import CONF_UPDATE_INTERVAL, CONF_SCAN_INTERVAL, \ + CONF_UPDATE_INTERVAL_INVALIDATION_VERSION from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.event import async_track_time_interval @@ -22,13 +23,21 @@ CONF_MANUAL = 'manual' DEFAULT_INTERVAL = timedelta(hours=1) CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_UPDATE_INTERVAL, default=DEFAULT_INTERVAL): - vol.All( - cv.time_period, cv.positive_timedelta - ), - vol.Optional(CONF_MANUAL, default=False): cv.boolean, - }) + DOMAIN: vol.All( + vol.Schema({ + vol.Optional(CONF_UPDATE_INTERVAL): + vol.All(cv.time_period, cv.positive_timedelta), + vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL): + vol.All(cv.time_period, cv.positive_timedelta), + vol.Optional(CONF_MANUAL, default=False): cv.boolean, + }), + cv.deprecated( + CONF_UPDATE_INTERVAL, + replacement_key=CONF_SCAN_INTERVAL, + invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION, + default=DEFAULT_INTERVAL + ) + ) }, extra=vol.ALLOW_EXTRA) @@ -39,7 +48,7 @@ async def async_setup(hass, config): if not conf[CONF_MANUAL]: async_track_time_interval( - hass, data.update, conf[CONF_UPDATE_INTERVAL] + hass, data.update, conf[CONF_SCAN_INTERVAL] ) def update(call=None): diff --git a/homeassistant/components/freedns/__init__.py b/homeassistant/components/freedns/__init__.py index 7da51cd42e4..edb3a57c28c 100644 --- a/homeassistant/components/freedns/__init__.py +++ b/homeassistant/components/freedns/__init__.py @@ -13,7 +13,8 @@ import async_timeout import voluptuous as vol from homeassistant.const import (CONF_URL, CONF_ACCESS_TOKEN, - CONF_UPDATE_INTERVAL, CONF_SCAN_INTERVAL) + CONF_UPDATE_INTERVAL, CONF_SCAN_INTERVAL, + CONF_UPDATE_INTERVAL_INVALIDATION_VERSION) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -32,13 +33,13 @@ CONFIG_SCHEMA = vol.Schema({ vol.Exclusive(CONF_ACCESS_TOKEN, DOMAIN): cv.string, vol.Optional(CONF_UPDATE_INTERVAL): vol.All(cv.time_period, cv.positive_timedelta), - vol.Optional(CONF_SCAN_INTERVAL): + vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL): vol.All(cv.time_period, cv.positive_timedelta), }), cv.deprecated( CONF_UPDATE_INTERVAL, replacement_key=CONF_SCAN_INTERVAL, - invalidation_version='1.0.0', + invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION, default=DEFAULT_INTERVAL ) ) diff --git a/homeassistant/components/mythicbeastsdns/__init__.py b/homeassistant/components/mythicbeastsdns/__init__.py index f34b2736710..3d0d250557b 100644 --- a/homeassistant/components/mythicbeastsdns/__init__.py +++ b/homeassistant/components/mythicbeastsdns/__init__.py @@ -5,7 +5,9 @@ import logging import voluptuous as vol from homeassistant.const import ( - CONF_DOMAIN, CONF_HOST, CONF_PASSWORD, CONF_UPDATE_INTERVAL) + CONF_HOST, CONF_DOMAIN, CONF_PASSWORD, CONF_UPDATE_INTERVAL, + CONF_SCAN_INTERVAL, CONF_UPDATE_INTERVAL_INVALIDATION_VERSION +) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_time_interval @@ -19,13 +21,23 @@ DOMAIN = 'mythicbeastsdns' DEFAULT_INTERVAL = timedelta(minutes=10) CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_DOMAIN): cv.string, - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_UPDATE_INTERVAL, default=DEFAULT_INTERVAL): vol.All( - cv.time_period, cv.positive_timedelta), - }) + DOMAIN: vol.All( + vol.Schema({ + vol.Required(CONF_DOMAIN): cv.string, + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_UPDATE_INTERVAL): + vol.All(cv.time_period, cv.positive_timedelta), + vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL): + vol.All(cv.time_period, cv.positive_timedelta), + }), + cv.deprecated( + CONF_UPDATE_INTERVAL, + replacement_key=CONF_SCAN_INTERVAL, + invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION, + default=DEFAULT_INTERVAL + ) + ) }, extra=vol.ALLOW_EXTRA) @@ -36,7 +48,7 @@ async def async_setup(hass, config): domain = config[DOMAIN][CONF_DOMAIN] password = config[DOMAIN][CONF_PASSWORD] host = config[DOMAIN][CONF_HOST] - update_interval = config[DOMAIN][CONF_UPDATE_INTERVAL] + update_interval = config[DOMAIN][CONF_SCAN_INTERVAL] session = async_get_clientsession(hass) diff --git a/homeassistant/components/sensor/broadlink.py b/homeassistant/components/sensor/broadlink.py index 50f9f955148..5720201b3f2 100644 --- a/homeassistant/components/sensor/broadlink.py +++ b/homeassistant/components/sensor/broadlink.py @@ -14,7 +14,8 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_HOST, CONF_MAC, CONF_MONITORED_CONDITIONS, CONF_NAME, TEMP_CELSIUS, - CONF_TIMEOUT, CONF_UPDATE_INTERVAL) + CONF_TIMEOUT, CONF_UPDATE_INTERVAL, CONF_SCAN_INTERVAL, + CONF_UPDATE_INTERVAL_INVALIDATION_VERSION) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv @@ -25,6 +26,7 @@ _LOGGER = logging.getLogger(__name__) DEVICE_DEFAULT_NAME = 'Broadlink sensor' DEFAULT_TIMEOUT = 10 +SCAN_INTERVAL = timedelta(seconds=300) SENSOR_TYPES = { 'temperature': ['Temperature', TEMP_CELSIUS], @@ -34,16 +36,24 @@ SENSOR_TYPES = { 'noise': ['Noise', ' '], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): vol.Coerce(str), - vol.Optional(CONF_MONITORED_CONDITIONS, default=[]): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), - vol.Optional(CONF_UPDATE_INTERVAL, default=timedelta(seconds=300)): ( - vol.All(cv.time_period, cv.positive_timedelta)), - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_MAC): cv.string, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int -}) +PLATFORM_SCHEMA = vol.All( + PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): vol.Coerce(str), + vol.Optional(CONF_MONITORED_CONDITIONS, default=[]): + vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), + vol.Optional(CONF_UPDATE_INTERVAL): + vol.All(cv.time_period, cv.positive_timedelta), + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_MAC): cv.string, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int + }), + cv.deprecated( + CONF_UPDATE_INTERVAL, + replacement_key=CONF_SCAN_INTERVAL, + invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION, + default=SCAN_INTERVAL + ) +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -53,7 +63,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): mac_addr = binascii.unhexlify(mac) name = config.get(CONF_NAME) timeout = config.get(CONF_TIMEOUT) - update_interval = config.get(CONF_UPDATE_INTERVAL) + update_interval = config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL) broadlink_data = BroadlinkData(update_interval, host, mac_addr, timeout) dev = [] for variable in config[CONF_MONITORED_CONDITIONS]: diff --git a/homeassistant/components/sensor/darksky.py b/homeassistant/components/sensor/darksky.py index 6e2ca2dc6c5..c68bb2cd3a3 100644 --- a/homeassistant/components/sensor/darksky.py +++ b/homeassistant/components/sensor/darksky.py @@ -14,7 +14,8 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, - CONF_MONITORED_CONDITIONS, CONF_NAME, UNIT_UV_INDEX, CONF_UPDATE_INTERVAL) + CONF_MONITORED_CONDITIONS, CONF_NAME, UNIT_UV_INDEX, CONF_UPDATE_INTERVAL, + CONF_SCAN_INTERVAL, CONF_UPDATE_INTERVAL_INVALIDATION_VERSION) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -30,8 +31,8 @@ CONF_LANGUAGE = 'language' CONF_UNITS = 'units' DEFAULT_LANGUAGE = 'en' - DEFAULT_NAME = 'Dark Sky' +SCAN_INTERVAL = timedelta(seconds=300) DEPRECATED_SENSOR_TYPES = { 'apparent_temperature_max', @@ -167,23 +168,39 @@ LANGUAGE_CODES = [ 'tet', 'tr', 'uk', 'x-pig-latin', 'zh', 'zh-tw', ] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_MONITORED_CONDITIONS): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), - vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_UNITS): vol.In(['auto', 'si', 'us', 'ca', 'uk', 'uk2']), - vol.Optional(CONF_LANGUAGE, - default=DEFAULT_LANGUAGE): vol.In(LANGUAGE_CODES), - vol.Inclusive(CONF_LATITUDE, 'coordinates', - 'Latitude and longitude must exist together'): cv.latitude, - vol.Inclusive(CONF_LONGITUDE, 'coordinates', - 'Latitude and longitude must exist together'): cv.longitude, - vol.Optional(CONF_UPDATE_INTERVAL, default=timedelta(seconds=300)): ( - vol.All(cv.time_period, cv.positive_timedelta)), - vol.Optional(CONF_FORECAST): - vol.All(cv.ensure_list, [vol.Range(min=0, max=7)]), -}) +ALLOWED_UNITS = ['auto', 'si', 'us', 'ca', 'uk', 'uk2'] + +PLATFORM_SCHEMA = vol.All( + PLATFORM_SCHEMA.extend({ + vol.Required(CONF_MONITORED_CONDITIONS): + vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_UNITS): vol.In(ALLOWED_UNITS), + vol.Optional(CONF_LANGUAGE, + default=DEFAULT_LANGUAGE): vol.In(LANGUAGE_CODES), + vol.Inclusive( + CONF_LATITUDE, + 'coordinates', + 'Latitude and longitude must exist together' + ): cv.latitude, + vol.Inclusive( + CONF_LONGITUDE, + 'coordinates', + 'Latitude and longitude must exist together' + ): cv.longitude, + vol.Optional(CONF_UPDATE_INTERVAL): + vol.All(cv.time_period, cv.positive_timedelta), + vol.Optional(CONF_FORECAST): + vol.All(cv.ensure_list, [vol.Range(min=0, max=7)]), + }), + cv.deprecated( + CONF_UPDATE_INTERVAL, + replacement_key=CONF_SCAN_INTERVAL, + invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION, + default=SCAN_INTERVAL + ) +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -191,7 +208,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) language = config.get(CONF_LANGUAGE) - interval = config.get(CONF_UPDATE_INTERVAL) + interval = config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL) if CONF_UNITS in config: units = config[CONF_UNITS] diff --git a/homeassistant/components/sensor/fedex.py b/homeassistant/components/sensor/fedex.py index 02938ff837b..54c319e6441 100644 --- a/homeassistant/components/sensor/fedex.py +++ b/homeassistant/components/sensor/fedex.py @@ -12,7 +12,9 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import (CONF_NAME, CONF_USERNAME, CONF_PASSWORD, - ATTR_ATTRIBUTION, CONF_UPDATE_INTERVAL) + ATTR_ATTRIBUTION, CONF_UPDATE_INTERVAL, + CONF_SCAN_INTERVAL, + CONF_UPDATE_INTERVAL_INVALIDATION_VERSION) from homeassistant.helpers.entity import Entity from homeassistant.util import slugify from homeassistant.util import Throttle @@ -31,13 +33,23 @@ ICON = 'mdi:package-variant-closed' STATUS_DELIVERED = 'delivered' -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_UPDATE_INTERVAL, default=timedelta(seconds=1800)): - vol.All(cv.time_period, cv.positive_timedelta), -}) +SCAN_INTERVAL = timedelta(seconds=1800) + +PLATFORM_SCHEMA = vol.All( + PLATFORM_SCHEMA.extend({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_UPDATE_INTERVAL): + vol.All(cv.time_period, cv.positive_timedelta), + }), + cv.deprecated( + CONF_UPDATE_INTERVAL, + replacement_key=CONF_SCAN_INTERVAL, + invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION, + default=SCAN_INTERVAL + ) +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -45,7 +57,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): import fedexdeliverymanager name = config.get(CONF_NAME) - update_interval = config.get(CONF_UPDATE_INTERVAL) + update_interval = config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL) try: cookie = hass.config.path(COOKIE) diff --git a/homeassistant/components/sensor/ups.py b/homeassistant/components/sensor/ups.py index 44ecdc433c5..e4aab555050 100644 --- a/homeassistant/components/sensor/ups.py +++ b/homeassistant/components/sensor/ups.py @@ -12,7 +12,9 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import (CONF_NAME, CONF_USERNAME, CONF_PASSWORD, - ATTR_ATTRIBUTION, CONF_UPDATE_INTERVAL) + ATTR_ATTRIBUTION, CONF_UPDATE_INTERVAL, + CONF_SCAN_INTERVAL, + CONF_UPDATE_INTERVAL_INVALIDATION_VERSION) from homeassistant.helpers.entity import Entity from homeassistant.util import slugify from homeassistant.util import Throttle @@ -28,13 +30,23 @@ COOKIE = 'upsmychoice_cookies.pickle' ICON = 'mdi:package-variant-closed' STATUS_DELIVERED = 'delivered' -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_UPDATE_INTERVAL, default=timedelta(seconds=1800)): ( - vol.All(cv.time_period, cv.positive_timedelta)), -}) +SCAN_INTERVAL = timedelta(seconds=1800) + +PLATFORM_SCHEMA = vol.All( + PLATFORM_SCHEMA.extend({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_UPDATE_INTERVAL): ( + vol.All(cv.time_period, cv.positive_timedelta)), + }), + cv.deprecated( + CONF_UPDATE_INTERVAL, + replacement_key=CONF_SCAN_INTERVAL, + invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION, + default=SCAN_INTERVAL + ) +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -49,8 +61,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.exception("Could not connect to UPS My Choice") return False - add_entities([UPSSensor(session, config.get(CONF_NAME), - config.get(CONF_UPDATE_INTERVAL))], True) + add_entities([UPSSensor( + session, + config.get(CONF_NAME), + config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL) + )], True) class UPSSensor(Entity): diff --git a/homeassistant/components/speedtestdotnet/__init__.py b/homeassistant/components/speedtestdotnet/__init__.py index 3b8d2964f83..4eae738b0d3 100644 --- a/homeassistant/components/speedtestdotnet/__init__.py +++ b/homeassistant/components/speedtestdotnet/__init__.py @@ -8,7 +8,8 @@ import homeassistant.helpers.config_validation as cv from homeassistant.components.speedtestdotnet.const import DOMAIN, \ DATA_UPDATED, SENSOR_TYPES from homeassistant.const import CONF_MONITORED_CONDITIONS, \ - CONF_UPDATE_INTERVAL + CONF_UPDATE_INTERVAL, CONF_SCAN_INTERVAL, \ + CONF_UPDATE_INTERVAL_INVALIDATION_VERSION from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.event import async_track_time_interval @@ -24,16 +25,26 @@ CONF_MANUAL = 'manual' DEFAULT_INTERVAL = timedelta(hours=1) CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_SERVER_ID): cv.positive_int, - vol.Optional(CONF_UPDATE_INTERVAL, default=DEFAULT_INTERVAL): - vol.All( - cv.time_period, cv.positive_timedelta - ), - vol.Optional(CONF_MANUAL, default=False): cv.boolean, - vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): - vol.All(cv.ensure_list, [vol.In(list(SENSOR_TYPES))]) - }) + DOMAIN: vol.All( + vol.Schema({ + vol.Optional(CONF_SERVER_ID): cv.positive_int, + vol.Optional(CONF_UPDATE_INTERVAL): + vol.All(cv.time_period, cv.positive_timedelta), + vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL): + vol.All(cv.time_period, cv.positive_timedelta), + vol.Optional(CONF_MANUAL, default=False): cv.boolean, + vol.Optional( + CONF_MONITORED_CONDITIONS, + default=list(SENSOR_TYPES) + ): vol.All(cv.ensure_list, [vol.In(list(SENSOR_TYPES))]) + }), + cv.deprecated( + CONF_UPDATE_INTERVAL, + replacement_key=CONF_SCAN_INTERVAL, + invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION, + default=DEFAULT_INTERVAL + ) + ) }, extra=vol.ALLOW_EXTRA) @@ -44,7 +55,7 @@ async def async_setup(hass, config): if not conf[CONF_MANUAL]: async_track_time_interval( - hass, data.update, conf[CONF_UPDATE_INTERVAL] + hass, data.update, conf[CONF_SCAN_INTERVAL] ) def update(call=None): diff --git a/homeassistant/components/tellduslive/__init__.py b/homeassistant/components/tellduslive/__init__.py index 1a6f35fe8d8..397e21922d9 100644 --- a/homeassistant/components/tellduslive/__init__.py +++ b/homeassistant/components/tellduslive/__init__.py @@ -6,7 +6,8 @@ import logging import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_UPDATE_INTERVAL +from homeassistant.const import CONF_UPDATE_INTERVAL, CONF_SCAN_INTERVAL, \ + CONF_UPDATE_INTERVAL_INVALIDATION_VERSION import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_call_later @@ -23,17 +24,23 @@ REQUIREMENTS = ['tellduslive==0.10.10'] _LOGGER = logging.getLogger(__name__) -CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.All( vol.Schema({ vol.Optional(CONF_HOST, default=DOMAIN): cv.string, - vol.Optional(CONF_UPDATE_INTERVAL, default=SCAN_INTERVAL): - (vol.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL))) + vol.Optional(CONF_UPDATE_INTERVAL): + vol.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL)), + vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): + vol.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL)), }), - }, - extra=vol.ALLOW_EXTRA, -) + cv.deprecated( + CONF_UPDATE_INTERVAL, + replacement_key=CONF_SCAN_INTERVAL, + invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION, + default=SCAN_INTERVAL + ) + ) +}, extra=vol.ALLOW_EXTRA) DATA_CONFIG_ENTRY_LOCK = 'tellduslive_config_entry_lock' CONFIG_ENTRY_IS_SETUP = 'telldus_config_entry_is_setup' @@ -102,7 +109,7 @@ async def async_setup(hass, config): context={'source': config_entries.SOURCE_IMPORT}, data={ KEY_HOST: config[DOMAIN].get(CONF_HOST), - KEY_SCAN_INTERVAL: config[DOMAIN].get(CONF_UPDATE_INTERVAL), + KEY_SCAN_INTERVAL: config[DOMAIN][CONF_SCAN_INTERVAL], })) return True diff --git a/homeassistant/components/volvooncall/__init__.py b/homeassistant/components/volvooncall/__init__.py index 9dbaadf9bee..7e72607c2f3 100644 --- a/homeassistant/components/volvooncall/__init__.py +++ b/homeassistant/components/volvooncall/__init__.py @@ -6,7 +6,8 @@ import voluptuous as vol from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, CONF_NAME, CONF_RESOURCES, - CONF_UPDATE_INTERVAL) + CONF_UPDATE_INTERVAL, CONF_SCAN_INTERVAL, + CONF_UPDATE_INTERVAL_INVALIDATION_VERSION) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -83,20 +84,30 @@ RESOURCES = [ ] CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_UPDATE_INTERVAL, default=DEFAULT_UPDATE_INTERVAL): ( - vol.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL))), - vol.Optional(CONF_NAME, default={}): - cv.schema_with_slug_keys(cv.string), - vol.Optional(CONF_RESOURCES): vol.All( - cv.ensure_list, [vol.In(RESOURCES)]), - vol.Optional(CONF_REGION): cv.string, - vol.Optional(CONF_SERVICE_URL): cv.string, - vol.Optional(CONF_MUTABLE, default=True): cv.boolean, - vol.Optional(CONF_SCANDINAVIAN_MILES, default=False): cv.boolean, - }), + DOMAIN: vol.All( + vol.Schema({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_UPDATE_INTERVAL): + vol.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL)), + vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_UPDATE_INTERVAL): + vol.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL)), + vol.Optional(CONF_NAME, default={}): + cv.schema_with_slug_keys(cv.string), + vol.Optional(CONF_RESOURCES): vol.All( + cv.ensure_list, [vol.In(RESOURCES)]), + vol.Optional(CONF_REGION): cv.string, + vol.Optional(CONF_SERVICE_URL): cv.string, + vol.Optional(CONF_MUTABLE, default=True): cv.boolean, + vol.Optional(CONF_SCANDINAVIAN_MILES, default=False): cv.boolean, + }), + cv.deprecated( + CONF_UPDATE_INTERVAL, + replacement_key=CONF_SCAN_INTERVAL, + invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION, + default=DEFAULT_UPDATE_INTERVAL + ) + ) }, extra=vol.ALLOW_EXTRA) @@ -112,7 +123,7 @@ async def async_setup(hass, config): service_url=config[DOMAIN].get(CONF_SERVICE_URL), region=config[DOMAIN].get(CONF_REGION)) - interval = config[DOMAIN].get(CONF_UPDATE_INTERVAL) + interval = config[DOMAIN][CONF_SCAN_INTERVAL] data = hass.data[DATA_KEY] = VolvoData(config) diff --git a/homeassistant/const.py b/homeassistant/const.py index 12f394b9e03..43caf61aa72 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -147,7 +147,11 @@ CONF_TTL = 'ttl' CONF_TYPE = 'type' CONF_UNIT_OF_MEASUREMENT = 'unit_of_measurement' CONF_UNIT_SYSTEM = 'unit_system' + +# Deprecated in 0.88.0, invalidated in 0.91.0, remove in 0.92.0 CONF_UPDATE_INTERVAL = 'update_interval' +CONF_UPDATE_INTERVAL_INVALIDATION_VERSION = '0.91.0' + CONF_URL = 'url' CONF_USERNAME = 'username' CONF_VALUE_TEMPLATE = 'value_template' diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 3b01a01fc96..ab385019b10 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -606,7 +606,8 @@ def deprecated(key: str, else: value = default if (replacement_key - and replacement_key not in config + and (replacement_key not in config + or default == config.get(replacement_key)) and value is not None): config[replacement_key] = value diff --git a/tests/components/freedns/test_init.py b/tests/components/freedns/test_init.py index 784926912cd..1996b02d8d0 100644 --- a/tests/components/freedns/test_init.py +++ b/tests/components/freedns/test_init.py @@ -24,7 +24,7 @@ def setup_freedns(hass, aioclient_mock): hass.loop.run_until_complete(async_setup_component(hass, freedns.DOMAIN, { freedns.DOMAIN: { 'access_token': ACCESS_TOKEN, - 'update_interval': UPDATE_INTERVAL, + 'scan_interval': UPDATE_INTERVAL, } })) @@ -62,7 +62,7 @@ def test_setup_fails_if_wrong_token(hass, aioclient_mock): result = yield from async_setup_component(hass, freedns.DOMAIN, { freedns.DOMAIN: { 'access_token': ACCESS_TOKEN, - 'update_interval': UPDATE_INTERVAL, + 'scan_interval': UPDATE_INTERVAL, } }) assert not result diff --git a/tests/components/sensor/test_darksky.py b/tests/components/sensor/test_darksky.py index 33a13f013de..58ce932020a 100644 --- a/tests/components/sensor/test_darksky.py +++ b/tests/components/sensor/test_darksky.py @@ -21,7 +21,7 @@ VALID_CONFIG_MINIMAL = { 'api_key': 'foo', 'forecast': [1, 2], 'monitored_conditions': ['summary', 'icon', 'temperature_high'], - 'update_interval': timedelta(seconds=120), + 'scan_interval': timedelta(seconds=120), } } @@ -31,7 +31,7 @@ INVALID_CONFIG_MINIMAL = { 'api_key': 'foo', 'forecast': [1, 2], 'monitored_conditions': ['sumary', 'iocn', 'temperature_high'], - 'update_interval': timedelta(seconds=120), + 'scan_interval': timedelta(seconds=120), } } @@ -45,7 +45,7 @@ VALID_CONFIG_LANG_DE = { 'monitored_conditions': ['summary', 'icon', 'temperature_high', 'minutely_summary', 'hourly_summary', 'daily_summary', 'humidity', ], - 'update_interval': timedelta(seconds=120), + 'scan_interval': timedelta(seconds=120), } } @@ -56,7 +56,7 @@ INVALID_CONFIG_LANG = { 'forecast': [1, 2], 'language': 'yz', 'monitored_conditions': ['summary', 'icon', 'temperature_high'], - 'update_interval': timedelta(seconds=120), + 'scan_interval': timedelta(seconds=120), } } @@ -138,8 +138,11 @@ class TestDarkSkySetup(unittest.TestCase): msg = '400 Client Error: Bad Request for url: {}'.format(url) mock_get_forecast.side_effect = HTTPError(msg,) - response = darksky.setup_platform(self.hass, VALID_CONFIG_MINIMAL, - MagicMock()) + response = darksky.setup_platform( + self.hass, + VALID_CONFIG_MINIMAL['sensor'], + MagicMock() + ) assert not response @requests_mock.Mocker() diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index cefde564035..d83d32c88e3 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -713,7 +713,7 @@ def test_deprecated_with_default(caplog, schema): def test_deprecated_with_replacement_key_and_default(caplog, schema): """ - Test deprecation behaves correctly when only a replacement key is provided. + Test deprecation with a replacement key and default. Expected behavior: - Outputs the appropriate deprecation warning if key is detected @@ -748,6 +748,22 @@ def test_deprecated_with_replacement_key_and_default(caplog, schema): assert len(caplog.records) == 0 assert {'venus': True, 'jupiter': False} == output + deprecated_schema_with_default = vol.All( + vol.Schema({ + 'venus': cv.boolean, + vol.Optional('mars', default=False): cv.boolean, + vol.Optional('jupiter', default=False): cv.boolean + }), + cv.deprecated('mars', replacement_key='jupiter', default=False) + ) + + test_data = {'mars': True} + output = deprecated_schema_with_default(test_data.copy()) + assert len(caplog.records) == 1 + assert ("The 'mars' option (with value 'True') is deprecated, " + "please replace it with 'jupiter'") in caplog.text + assert {'jupiter': True} == output + def test_deprecated_with_replacement_key_invalidation_version_default( caplog, schema, version From 0023da778a93fdd7d893a64363c5e58f63054a02 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 14 Feb 2019 20:55:51 +0100 Subject: [PATCH 212/242] Add legacy PLATFORM_SCHEMA config validation --- homeassistant/helpers/config_validation.py | 51 +++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index ab385019b10..ef9781f480b 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -25,6 +25,8 @@ from homeassistant.helpers import template as template_helper from homeassistant.helpers.logging import KeywordStyleAdapter from homeassistant.util import slugify as util_slugify +_LOGGER = logging.getLogger(__name__) + # pylint: disable=invalid-name TIME_PERIOD_ERROR = "offset {} should be format 'HH:MM' or 'HH:MM:SS'" @@ -633,8 +635,55 @@ def key_dependency(key, dependency): # Schemas +class HASchema(vol.Schema): + """Schema class that allows us to mark PREVENT_EXTRA errors as warnings.""" + def __call__(self, data): + try: + return super().__call__(data) + except vol.Invalid as orig_err: + if self.extra != vol.PREVENT_EXTRA: + raise -PLATFORM_SCHEMA = vol.Schema({ + # orig_error is of type vol.MultipleInvalid (see super __call__) + assert isinstance(orig_err, vol.MultipleInvalid) + # If it fails with PREVENT_EXTRA, try with ALLOW_EXTRA + self.extra = vol.ALLOW_EXTRA + # In case it still fails the following will raise + try: + validated = super().__call__(data) + finally: + self.extra = vol.PREVENT_EXTRA + + # This is a legacy config, print warning + extra_key_errs = [err for err in orig_err.errors + if err.error_message == 'extra keys not allowed'] + if extra_key_errs: + msg = "Your configuration contains extra keys " \ + "that the platform does not support. The keys " + msg += ', '.join('[{}]'.format(err.path[-1]) for err in + extra_key_errs) + msg += ' are 42.' + if hasattr(data, '__config_file__'): + msg += " (See {}, line {}). ".format(data.__config_file__, + data.__line__) + _LOGGER.warning(msg) + else: + # This should not happen (all errors should be extra key + # errors). Let's raise the original error anyway. + raise orig_err + + # Return legacy validated config + return validated + + def extend(self, schema, required=None, extra=None): + """Extend this schema and convert it to HASchema if necessary""" + ret = super().extend(schema, required=required, extra=extra) + if extra is not None: + return ret + return HASchema(ret.schema, required=required, extra=self.extra) + + +PLATFORM_SCHEMA = HASchema({ vol.Required(CONF_PLATFORM): string, vol.Optional(CONF_ENTITY_NAMESPACE): string, vol.Optional(CONF_SCAN_INTERVAL): time_period From 26a79dff99942e3090b0185dbe7973bcd6af869a Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 15 Feb 2019 10:51:52 +0100 Subject: [PATCH 213/242] Fix tests --- tests/test_setup.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/test_setup.py b/tests/test_setup.py index 8575b023d37..1a60943a72d 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -90,7 +90,7 @@ class TestSetup: } }) - def test_validate_platform_config(self): + def test_validate_platform_config(self, caplog): """Test validating platform configuration.""" platform_schema = PLATFORM_SCHEMA.extend({ 'hello': str, @@ -109,7 +109,7 @@ class TestSetup: MockPlatform('whatever', platform_schema=platform_schema)) - with assert_setup_component(0): + with assert_setup_component(1): assert setup.setup_component(self.hass, 'platform_conf', { 'platform_conf': { 'platform': 'whatever', @@ -117,11 +117,12 @@ class TestSetup: 'invalid': 'extra', } }) + assert caplog.text.count('Your configuration contains extra keys') == 1 self.hass.data.pop(setup.DATA_SETUP) self.hass.config.components.remove('platform_conf') - with assert_setup_component(1): + with assert_setup_component(2): assert setup.setup_component(self.hass, 'platform_conf', { 'platform_conf': { 'platform': 'whatever', @@ -132,6 +133,7 @@ class TestSetup: 'invalid': True } }) + assert caplog.text.count('Your configuration contains extra keys') == 2 self.hass.data.pop(setup.DATA_SETUP) self.hass.config.components.remove('platform_conf') @@ -183,7 +185,7 @@ class TestSetup: assert 'platform_conf' in self.hass.config.components assert not config['platform_conf'] # empty - def test_validate_platform_config_2(self): + def test_validate_platform_config_2(self, caplog): """Test component PLATFORM_SCHEMA_BASE prio over PLATFORM_SCHEMA.""" platform_schema = PLATFORM_SCHEMA.extend({ 'hello': str, @@ -204,7 +206,7 @@ class TestSetup: MockPlatform('whatever', platform_schema=platform_schema)) - with assert_setup_component(0): + with assert_setup_component(1): assert setup.setup_component(self.hass, 'platform_conf', { # fail: no extra keys allowed in platform schema 'platform_conf': { @@ -213,6 +215,7 @@ class TestSetup: 'invalid': 'extra', } }) + assert caplog.text.count('Your configuration contains extra keys') == 1 self.hass.data.pop(setup.DATA_SETUP) self.hass.config.components.remove('platform_conf') @@ -234,7 +237,7 @@ class TestSetup: self.hass.data.pop(setup.DATA_SETUP) self.hass.config.components.remove('platform_conf') - def test_validate_platform_config_3(self): + def test_validate_platform_config_3(self, caplog): """Test fallback to component PLATFORM_SCHEMA.""" component_schema = PLATFORM_SCHEMA_BASE.extend({ 'hello': str, @@ -255,15 +258,15 @@ class TestSetup: MockPlatform('whatever', platform_schema=platform_schema)) - with assert_setup_component(0): + with assert_setup_component(1): assert setup.setup_component(self.hass, 'platform_conf', { 'platform_conf': { - # fail: no extra keys allowed 'platform': 'whatever', 'hello': 'world', 'invalid': 'extra', } }) + assert caplog.text.count('Your configuration contains extra keys') == 1 self.hass.data.pop(setup.DATA_SETUP) self.hass.config.components.remove('platform_conf') From 219ca336a9e49669807c76e4bf6d8fe5001f25c3 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 15 Feb 2019 10:57:02 +0100 Subject: [PATCH 214/242] Lint --- homeassistant/helpers/config_validation.py | 5 ++++- tests/test_setup.py | 12 ++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index ef9781f480b..4b97409dd3d 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -637,7 +637,9 @@ def key_dependency(key, dependency): # Schemas class HASchema(vol.Schema): """Schema class that allows us to mark PREVENT_EXTRA errors as warnings.""" + def __call__(self, data): + """Override __call__ to mark PREVENT_EXTRA as warning.""" try: return super().__call__(data) except vol.Invalid as orig_err: @@ -646,6 +648,7 @@ class HASchema(vol.Schema): # orig_error is of type vol.MultipleInvalid (see super __call__) assert isinstance(orig_err, vol.MultipleInvalid) + # pylint: disable=no-member # If it fails with PREVENT_EXTRA, try with ALLOW_EXTRA self.extra = vol.ALLOW_EXTRA # In case it still fails the following will raise @@ -676,7 +679,7 @@ class HASchema(vol.Schema): return validated def extend(self, schema, required=None, extra=None): - """Extend this schema and convert it to HASchema if necessary""" + """Extend this schema and convert it to HASchema if necessary.""" ret = super().extend(schema, required=required, extra=extra) if extra is not None: return ret diff --git a/tests/test_setup.py b/tests/test_setup.py index 1a60943a72d..c6126bc4a3b 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -117,7 +117,8 @@ class TestSetup: 'invalid': 'extra', } }) - assert caplog.text.count('Your configuration contains extra keys') == 1 + assert caplog.text.count('Your configuration contains ' + 'extra keys') == 1 self.hass.data.pop(setup.DATA_SETUP) self.hass.config.components.remove('platform_conf') @@ -133,7 +134,8 @@ class TestSetup: 'invalid': True } }) - assert caplog.text.count('Your configuration contains extra keys') == 2 + assert caplog.text.count('Your configuration contains ' + 'extra keys') == 2 self.hass.data.pop(setup.DATA_SETUP) self.hass.config.components.remove('platform_conf') @@ -215,7 +217,8 @@ class TestSetup: 'invalid': 'extra', } }) - assert caplog.text.count('Your configuration contains extra keys') == 1 + assert caplog.text.count('Your configuration contains ' + 'extra keys') == 1 self.hass.data.pop(setup.DATA_SETUP) self.hass.config.components.remove('platform_conf') @@ -266,7 +269,8 @@ class TestSetup: 'invalid': 'extra', } }) - assert caplog.text.count('Your configuration contains extra keys') == 1 + assert caplog.text.count('Your configuration contains ' + 'extra keys') == 1 self.hass.data.pop(setup.DATA_SETUP) self.hass.config.components.remove('platform_conf') From 9696a8b8a907de28fa31d180c6801d541d6209ad Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 15 Feb 2019 18:40:46 +0100 Subject: [PATCH 215/242] Add persistent notification --- homeassistant/bootstrap.py | 17 +++++++++++++++++ homeassistant/helpers/config_validation.py | 20 +++++++++++--------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 90a74f23598..7e12a516478 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -192,6 +192,23 @@ async def async_from_config_dict(config: Dict[str, Any], '\n\n'.join(msg), "Config Warning", "config_warning" ) + # TEMP: warn users for invalid slugs + # Remove after 0.92 + if cv.INVALID_EXTRA_KEYS_FOUND: + msg = [] + msg.append( + "Your configuration contains extra keys " + "that the platform does not support (but were silently " + "accepted before 0.88). Please find and remove the following." + "This will become a breaking change." + ) + msg.append('\n'.join('- {}'.format(it) + for it in cv.INVALID_EXTRA_KEYS_FOUND)) + + hass.components.persistent_notification.async_create( + '\n\n'.join(msg), "Config Warning", "config_warning" + ) + return hass diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 4b97409dd3d..b5716431217 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -25,8 +25,6 @@ from homeassistant.helpers import template as template_helper from homeassistant.helpers.logging import KeywordStyleAdapter from homeassistant.util import slugify as util_slugify -_LOGGER = logging.getLogger(__name__) - # pylint: disable=invalid-name TIME_PERIOD_ERROR = "offset {} should be format 'HH:MM' or 'HH:MM:SS'" @@ -36,6 +34,7 @@ OLD_ENTITY_ID_VALIDATION = r"^(\w+)\.(\w+)$" # persistent notification. Rare temporary exception to use a global. INVALID_SLUGS_FOUND = {} INVALID_ENTITY_IDS_FOUND = {} +INVALID_EXTRA_KEYS_FOUND = [] # Home Assistant types @@ -662,14 +661,17 @@ class HASchema(vol.Schema): if err.error_message == 'extra keys not allowed'] if extra_key_errs: msg = "Your configuration contains extra keys " \ - "that the platform does not support. The keys " - msg += ', '.join('[{}]'.format(err.path[-1]) for err in - extra_key_errs) - msg += ' are 42.' + "that the platform does not support.\n" \ + "Please remove " + submsg = ', '.join('[{}]'.format(err.path[-1]) for err in + extra_key_errs) + submsg += '. ' if hasattr(data, '__config_file__'): - msg += " (See {}, line {}). ".format(data.__config_file__, - data.__line__) - _LOGGER.warning(msg) + submsg += " (See {}, line {}). ".format( + data.__config_file__, data.__line__) + msg += submsg + logging.getLogger(__name__).warning(msg) + INVALID_EXTRA_KEYS_FOUND.append(submsg) else: # This should not happen (all errors should be extra key # errors). Let's raise the original error anyway. From 6f0b8535471c1c89db6e38c1c4852aeb9853d7eb Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 16 Feb 2019 14:51:30 +0100 Subject: [PATCH 216/242] Update bootstrap.py --- homeassistant/bootstrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 7e12a516478..a018d540033 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -192,7 +192,7 @@ async def async_from_config_dict(config: Dict[str, Any], '\n\n'.join(msg), "Config Warning", "config_warning" ) - # TEMP: warn users for invalid slugs + # TEMP: warn users of invalid extra keys # Remove after 0.92 if cv.INVALID_EXTRA_KEYS_FOUND: msg = [] From f881a3af8271a680d2d81ffbb228ccd10291d09e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 16 Feb 2019 23:52:04 -0800 Subject: [PATCH 217/242] Bumped version to 0.88.0b2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 43caf61aa72..c5e3e082e0b 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 88 -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 017f34770b998d7a02d49a686d7d478fddad58d4 Mon Sep 17 00:00:00 2001 From: cdce8p <30130371+cdce8p@users.noreply.github.com> Date: Sun, 17 Feb 2019 08:04:29 +0100 Subject: [PATCH 218/242] Fix battery_level error - HomeKit (#21120) --- homeassistant/components/homekit/accessories.py | 2 ++ tests/components/homekit/test_accessories.py | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 5baed0294b8..ca1b560e336 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -124,6 +124,8 @@ class HomeAccessory(Accessory): """ battery_level = convert_to_float( new_state.attributes.get(ATTR_BATTERY_LEVEL)) + if battery_level is None: + return self._char_battery.set_value(battery_level) self._char_low_battery.set_value(battery_level < 20) _LOGGER.debug('%s: Updated battery level to %d', self.entity_id, diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index 15ab6d7413e..6f3957827eb 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -100,7 +100,7 @@ async def test_home_accessory(hass, hk_driver): assert serv.get_characteristic(CHAR_MODEL).value == 'Test Model' -async def test_battery_service(hass, hk_driver): +async def test_battery_service(hass, hk_driver, caplog): """Test battery service.""" entity_id = 'homekit.accessory' hass.states.async_set(entity_id, None, {ATTR_BATTERY_LEVEL: 50}) @@ -124,6 +124,13 @@ async def test_battery_service(hass, hk_driver): assert acc._char_low_battery.value == 1 assert acc._char_charging.value == 2 + hass.states.async_set(entity_id, None, {ATTR_BATTERY_LEVEL: 'error'}) + await hass.async_block_till_done() + assert acc._char_battery.value == 15 + assert acc._char_low_battery.value == 1 + assert acc._char_charging.value == 2 + assert 'ERROR' not in caplog.text + # Test charging hass.states.async_set(entity_id, None, { ATTR_BATTERY_LEVEL: 10, ATTR_BATTERY_CHARGING: True}) From 30c8c689d869e74ef52cf193430f83dc3d3be275 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 16 Feb 2019 19:38:52 -0800 Subject: [PATCH 219/242] Handle ValueError (#21126) --- homeassistant/components/person/__init__.py | 30 +++++++++++++-------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 63e588f911b..6fb7d42e0ee 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -247,7 +247,7 @@ class PersonManager: if any(person for person in chain(self.storage_data.values(), self.config_data.values()) - if person[CONF_USER_ID] == user_id): + if person.get(CONF_USER_ID) == user_id): raise ValueError("User already taken") async def _user_removed(self, event: Event): @@ -417,7 +417,7 @@ def ws_list_person(hass: HomeAssistantType, @websocket_api.websocket_command({ vol.Required('type'): 'person/create', - vol.Required('name'): str, + vol.Required('name'): vol.All(str, vol.Length(min=1)), vol.Optional('user_id'): vol.Any(str, None), vol.Optional('device_trackers', default=[]): vol.All( cv.ensure_list, cv.entities_domain(DEVICE_TRACKER_DOMAIN)), @@ -428,18 +428,22 @@ async def ws_create_person(hass: HomeAssistantType, connection: websocket_api.ActiveConnection, msg): """Create a person.""" manager = hass.data[DOMAIN] # type: PersonManager - person = await manager.async_create_person( - name=msg['name'], - user_id=msg.get('user_id'), - device_trackers=msg['device_trackers'] - ) - connection.send_result(msg['id'], person) + try: + person = await manager.async_create_person( + name=msg['name'], + user_id=msg.get('user_id'), + device_trackers=msg['device_trackers'] + ) + connection.send_result(msg['id'], person) + except ValueError as err: + connection.send_error( + msg['id'], websocket_api.const.ERR_INVALID_FORMAT, str(err)) @websocket_api.websocket_command({ vol.Required('type'): 'person/update', vol.Required('person_id'): str, - vol.Optional('name'): str, + vol.Required('name'): vol.All(str, vol.Length(min=1)), vol.Optional('user_id'): vol.Any(str, None), vol.Optional(CONF_DEVICE_TRACKERS, default=[]): vol.All( cv.ensure_list, cv.entities_domain(DEVICE_TRACKER_DOMAIN)), @@ -455,8 +459,12 @@ async def ws_update_person(hass: HomeAssistantType, if key in msg: changes[key] = msg[key] - person = await manager.async_update_person(msg['person_id'], **changes) - connection.send_result(msg['id'], person) + try: + person = await manager.async_update_person(msg['person_id'], **changes) + connection.send_result(msg['id'], person) + except ValueError as err: + connection.send_error( + msg['id'], websocket_api.const.ERR_INVALID_FORMAT, str(err)) @websocket_api.websocket_command({ From 7d111c2b4e6657b5af1a13c3c63f669157efd2db Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 16 Feb 2019 17:48:43 -0800 Subject: [PATCH 220/242] Bump pychromecast to 2.5.2 (#21127) --- homeassistant/components/cast/__init__.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py index 5e6bd720d4b..1b3da200540 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -2,7 +2,7 @@ from homeassistant import config_entries from homeassistant.helpers import config_entry_flow -REQUIREMENTS = ['pychromecast==2.5.1'] +REQUIREMENTS = ['pychromecast==2.5.2'] DOMAIN = 'cast' diff --git a/requirements_all.txt b/requirements_all.txt index 508f7bb2d04..08f834f2fdf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -956,7 +956,7 @@ pycfdns==0.0.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==2.5.1 +pychromecast==2.5.2 # homeassistant.components.media_player.cmus pycmus==0.1.1 From 933076560b9ea34337b6699e05d4ac797541313b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 18 Feb 2019 13:25:43 -0800 Subject: [PATCH 221/242] Updated frontend to 20190218.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 3db63e65a6d..3b1d961ebe7 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -21,7 +21,7 @@ from homeassistant.loader import bind_hass from .storage import async_setup_frontend_storage -REQUIREMENTS = ['home-assistant-frontend==20190216.0'] +REQUIREMENTS = ['home-assistant-frontend==20190218.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index 08f834f2fdf..64bd64c0aac 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -535,7 +535,7 @@ hole==0.3.0 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190216.0 +home-assistant-frontend==20190218.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5830085e544..a4e042c9c43 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -116,7 +116,7 @@ hdate==0.8.7 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190216.0 +home-assistant-frontend==20190218.0 # homeassistant.components.homekit_controller homekit==0.12.2 From 834d8940a85887b8be76b00b30266fc9591ec4fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9-Marc=20Simard?= Date: Sun, 17 Feb 2019 05:46:08 -0500 Subject: [PATCH 222/242] Return None if no GTFS departures found (#20919) --- homeassistant/components/sensor/gtfs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 081361aa32e..94f21287e39 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -205,7 +205,7 @@ class GTFSDepartureSensor(Entity): self._icon = ICON self._name = '' self._unit_of_measurement = 'min' - self._state = 0 + self._state = None self._attributes = {} self.lock = threading.Lock() self.update() @@ -241,7 +241,7 @@ class GTFSDepartureSensor(Entity): self._departure = get_next_departure( self._pygtfs, self.origin, self.destination, self._offset) if not self._departure: - self._state = 0 + self._state = None self._attributes = {'Info': 'No more departures today'} if self._name == '': self._name = (self._custom_name or DEFAULT_NAME) From 7ce18146d434c32805685db127905d3cc1481f6f Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Fri, 15 Feb 2019 10:40:54 -0600 Subject: [PATCH 223/242] SmartThings Component Enhancements/Fixes (#21085) * Improve component setup error logging/notification * Prevent capabilities from being represented my multiple platforms * Improved logging of received updates * Updates based on review feedback --- .../smartthings/.translations/en.json | 3 +- .../components/smartthings/__init__.py | 47 +++++++++++++++++-- .../components/smartthings/binary_sensor.py | 15 ++++-- .../components/smartthings/climate.py | 39 +++++++++------ .../components/smartthings/config_flow.py | 13 ++++- homeassistant/components/smartthings/const.py | 8 ++-- homeassistant/components/smartthings/fan.py | 14 ++++-- homeassistant/components/smartthings/light.py | 26 +++++----- homeassistant/components/smartthings/lock.py | 18 +++---- .../components/smartthings/sensor.py | 21 ++++++--- .../components/smartthings/strings.json | 3 +- .../components/smartthings/switch.py | 25 ++++------ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/smartthings/test_climate.py | 13 ----- .../smartthings/test_config_flow.py | 44 +++++++++++++++-- tests/components/smartthings/test_fan.py | 20 -------- tests/components/smartthings/test_light.py | 19 -------- tests/components/smartthings/test_lock.py | 6 --- tests/components/smartthings/test_switch.py | 17 ------- 20 files changed, 198 insertions(+), 157 deletions(-) diff --git a/homeassistant/components/smartthings/.translations/en.json b/homeassistant/components/smartthings/.translations/en.json index f2775b30ae2..2091ddb00a2 100644 --- a/homeassistant/components/smartthings/.translations/en.json +++ b/homeassistant/components/smartthings/.translations/en.json @@ -7,7 +7,8 @@ "token_already_setup": "The token has already been setup.", "token_forbidden": "The token does not have the required OAuth scopes.", "token_invalid_format": "The token must be in the UID/GUID format", - "token_unauthorized": "The token is invalid or no longer authorized." + "token_unauthorized": "The token is invalid or no longer authorized.", + "webhook_error": "SmartThings could not validate the endpoint configured in `base_url`. Please review the [component requirements]({component_url})." }, "step": { "user": { diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index 04da29aa55e..3cf38c358bc 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -1,5 +1,6 @@ """Support for SmartThings Cloud.""" import asyncio +import importlib import logging from typing import Iterable @@ -22,7 +23,7 @@ from .const import ( from .smartapp import ( setup_smartapp, setup_smartapp_endpoint, validate_installed_app) -REQUIREMENTS = ['pysmartapp==0.3.0', 'pysmartthings==0.6.1'] +REQUIREMENTS = ['pysmartapp==0.3.0', 'pysmartthings==0.6.2'] DEPENDENCIES = ['webhook'] _LOGGER = logging.getLogger(__name__) @@ -132,9 +133,41 @@ class DeviceBroker: """Create a new instance of the DeviceBroker.""" self._hass = hass self._installed_app_id = installed_app_id + self.assignments = self._assign_capabilities(devices) self.devices = {device.device_id: device for device in devices} self.event_handler_disconnect = None + def _assign_capabilities(self, devices: Iterable): + """Assign platforms to capabilities.""" + assignments = {} + for device in devices: + capabilities = device.capabilities.copy() + slots = {} + for platform_name in SUPPORTED_PLATFORMS: + platform = importlib.import_module( + '.' + platform_name, self.__module__) + assigned = platform.get_capabilities(capabilities) + if not assigned: + continue + # Draw-down capabilities and set slot assignment + for capability in assigned: + if capability not in capabilities: + continue + capabilities.remove(capability) + slots[capability] = platform_name + assignments[device.device_id] = slots + return assignments + + def get_assigned(self, device_id: str, platform: str): + """Get the capabilities assigned to the platform.""" + slots = self.assignments.get(device_id, {}) + return [key for key, value in slots.items() if value == platform] + + def any_assigned(self, device_id: str, platform: str): + """Return True if the platform has any assigned capabilities.""" + slots = self.assignments.get(device_id, {}) + return any(value for value in slots.values() if value == platform) + async def event_handler(self, req, resp, app): """Broker for incoming events.""" from pysmartapp.event import EVENT_TYPE_DEVICE @@ -167,10 +200,18 @@ class DeviceBroker: } self._hass.bus.async_fire(EVENT_BUTTON, data) _LOGGER.debug("Fired button event: %s", data) + else: + data = { + 'location_id': evt.location_id, + 'device_id': evt.device_id, + 'component_id': evt.component_id, + 'capability': evt.capability, + 'attribute': evt.attribute, + 'value': evt.value, + } + _LOGGER.debug("Push update received: %s", data) updated_devices.add(device.device_id) - _LOGGER.debug("Update received with %s events and updated %s devices", - len(req.events), len(updated_devices)) async_dispatcher_send(self._hass, SIGNAL_SMARTTHINGS_UPDATE, updated_devices) diff --git a/homeassistant/components/smartthings/binary_sensor.py b/homeassistant/components/smartthings/binary_sensor.py index 2fbb6f719da..45101601d5f 100644 --- a/homeassistant/components/smartthings/binary_sensor.py +++ b/homeassistant/components/smartthings/binary_sensor.py @@ -1,4 +1,6 @@ """Support for binary sensors through the SmartThings cloud API.""" +from typing import Optional, Sequence + from homeassistant.components.binary_sensor import BinarySensorDevice from . import SmartThingsEntity @@ -41,12 +43,19 @@ async def async_setup_entry(hass, config_entry, async_add_entities): broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] sensors = [] for device in broker.devices.values(): - for capability, attrib in CAPABILITY_TO_ATTRIB.items(): - if capability in device.capabilities: - sensors.append(SmartThingsBinarySensor(device, attrib)) + for capability in broker.get_assigned( + device.device_id, 'binary_sensor'): + attrib = CAPABILITY_TO_ATTRIB[capability] + sensors.append(SmartThingsBinarySensor(device, attrib)) async_add_entities(sensors) +def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: + """Return all capabilities supported if minimum required are present.""" + return [capability for capability in CAPABILITY_TO_ATTRIB + if capability in capabilities] + + class SmartThingsBinarySensor(SmartThingsEntity, BinarySensorDevice): """Define a SmartThings Binary Sensor.""" diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py index 9340bcef337..ab7334f1316 100644 --- a/homeassistant/components/smartthings/climate.py +++ b/homeassistant/components/smartthings/climate.py @@ -1,13 +1,15 @@ """Support for climate devices through the SmartThings cloud API.""" import asyncio +from typing import Optional, Sequence from homeassistant.components.climate import ( ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - ATTR_TEMPERATURE, STATE_AUTO, STATE_COOL, STATE_ECO, STATE_HEAT, STATE_OFF, - SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, + STATE_AUTO, STATE_COOL, STATE_ECO, STATE_HEAT, SUPPORT_FAN_MODE, + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW, ClimateDevice) -from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.const import ( + ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT) from . import SmartThingsEntity from .const import DATA_BROKERS, DOMAIN @@ -48,30 +50,37 @@ async def async_setup_entry(hass, config_entry, async_add_entities): broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] async_add_entities( [SmartThingsThermostat(device) for device in broker.devices.values() - if is_climate(device)]) + if broker.any_assigned(device.device_id, 'climate')]) -def is_climate(device): - """Determine if the device should be represented as a climate entity.""" +def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: + """Return all capabilities supported if minimum required are present.""" from pysmartthings import Capability + supported = [ + Capability.thermostat, + Capability.temperature_measurement, + Capability.thermostat_cooling_setpoint, + Capability.thermostat_heating_setpoint, + Capability.thermostat_mode, + Capability.relative_humidity_measurement, + Capability.thermostat_operating_state, + Capability.thermostat_fan_mode + ] # Can have this legacy/deprecated capability - if Capability.thermostat in device.capabilities: - return True + if Capability.thermostat in capabilities: + return supported # Or must have all of these climate_capabilities = [ Capability.temperature_measurement, Capability.thermostat_cooling_setpoint, Capability.thermostat_heating_setpoint, Capability.thermostat_mode] - if all(capability in device.capabilities + if all(capability in capabilities for capability in climate_capabilities): - return True - # Optional capabilities: - # relative_humidity_measurement -> state attribs - # thermostat_operating_state -> state attribs - # thermostat_fan_mode -> SUPPORT_FAN_MODE - return False + return supported + + return None class SmartThingsThermostat(SmartThingsEntity, ClimateDevice): diff --git a/homeassistant/components/smartthings/config_flow.py b/homeassistant/components/smartthings/config_flow.py index b280036a615..4663222c3b4 100644 --- a/homeassistant/components/smartthings/config_flow.py +++ b/homeassistant/components/smartthings/config_flow.py @@ -1,7 +1,7 @@ """Config flow to configure SmartThings.""" import logging -from aiohttp.client_exceptions import ClientResponseError +from aiohttp import ClientResponseError import voluptuous as vol from homeassistant import config_entries @@ -50,7 +50,7 @@ class SmartThingsFlowHandler(config_entries.ConfigFlow): async def async_step_user(self, user_input=None): """Get access token and validate it.""" - from pysmartthings import SmartThings + from pysmartthings import APIResponseError, SmartThings errors = {} if not self.hass.config.api.base_url.lower().startswith('https://'): @@ -87,6 +87,14 @@ class SmartThingsFlowHandler(config_entries.ConfigFlow): app = await create_app(self.hass, self.api) setup_smartapp(self.hass, app) self.app_id = app.app_id + except APIResponseError as ex: + if ex.is_target_error(): + errors['base'] = 'webhook_error' + else: + errors['base'] = "app_setup_error" + _LOGGER.exception("API error setting up the SmartApp: %s", + ex.raw_error_response) + return self._show_step_user(errors) except ClientResponseError as ex: if ex.status == 401: errors[CONF_ACCESS_TOKEN] = "token_unauthorized" @@ -94,6 +102,7 @@ class SmartThingsFlowHandler(config_entries.ConfigFlow): errors[CONF_ACCESS_TOKEN] = "token_forbidden" else: errors['base'] = "app_setup_error" + _LOGGER.exception("Unexpected error setting up the SmartApp") return self._show_step_user(errors) except Exception: # pylint:disable=broad-except errors['base'] = "app_setup_error" diff --git a/homeassistant/components/smartthings/const.py b/homeassistant/components/smartthings/const.py index 25cd9e8305f..27260b155d1 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -18,14 +18,16 @@ SIGNAL_SMARTAPP_PREFIX = 'smartthings_smartap_' SETTINGS_INSTANCE_ID = "hassInstanceId" STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 +# Ordered 'specific to least-specific platform' in order for capabilities +# to be drawn-down and represented by the appropriate platform. SUPPORTED_PLATFORMS = [ - 'binary_sensor', 'climate', 'fan', 'light', 'lock', - 'sensor', - 'switch' + 'switch', + 'binary_sensor', + 'sensor' ] VAL_UID = "^(?:([0-9a-fA-F]{32})|([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]" \ "{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}))$" diff --git a/homeassistant/components/smartthings/fan.py b/homeassistant/components/smartthings/fan.py index 4de1744c9b8..e722cd21d65 100644 --- a/homeassistant/components/smartthings/fan.py +++ b/homeassistant/components/smartthings/fan.py @@ -1,4 +1,6 @@ """Support for fans through the SmartThings cloud API.""" +from typing import Optional, Sequence + from homeassistant.components.fan import ( SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, SUPPORT_SET_SPEED, FanEntity) @@ -29,15 +31,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities): broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] async_add_entities( [SmartThingsFan(device) for device in broker.devices.values() - if is_fan(device)]) + if broker.any_assigned(device.device_id, 'fan')]) -def is_fan(device): - """Determine if the device should be represented as a fan.""" +def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: + """Return all capabilities supported if minimum required are present.""" from pysmartthings import Capability + + supported = [Capability.switch, Capability.fan_speed] # Must have switch and fan_speed - return all(capability in device.capabilities - for capability in [Capability.switch, Capability.fan_speed]) + if all(capability in capabilities for capability in supported): + return supported class SmartThingsFan(SmartThingsEntity, FanEntity): diff --git a/homeassistant/components/smartthings/light.py b/homeassistant/components/smartthings/light.py index ce4b00ca1fe..79a5eabc20a 100644 --- a/homeassistant/components/smartthings/light.py +++ b/homeassistant/components/smartthings/light.py @@ -1,5 +1,6 @@ """Support for lights through the SmartThings cloud API.""" import asyncio +from typing import Optional, Sequence from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_TRANSITION, @@ -24,29 +25,32 @@ async def async_setup_entry(hass, config_entry, async_add_entities): broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] async_add_entities( [SmartThingsLight(device) for device in broker.devices.values() - if is_light(device)], True) + if broker.any_assigned(device.device_id, 'light')], True) -def is_light(device): - """Determine if the device should be represented as a light.""" +def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: + """Return all capabilities supported if minimum required are present.""" from pysmartthings import Capability + supported = [ + Capability.switch, + Capability.switch_level, + Capability.color_control, + Capability.color_temperature, + ] # Must be able to be turned on/off. - if Capability.switch not in device.capabilities: - return False - # Not a fan (which might also have switch_level) - if Capability.fan_speed in device.capabilities: - return False + if Capability.switch not in capabilities: + return None # Must have one of these light_capabilities = [ Capability.color_control, Capability.color_temperature, Capability.switch_level ] - if any(capability in device.capabilities + if any(capability in capabilities for capability in light_capabilities): - return True - return False + return supported + return None def convert_scale(value, value_scale, target_scale, round_digits=4): diff --git a/homeassistant/components/smartthings/lock.py b/homeassistant/components/smartthings/lock.py index 6dfff0bd02c..d3f633ed0e4 100644 --- a/homeassistant/components/smartthings/lock.py +++ b/homeassistant/components/smartthings/lock.py @@ -1,9 +1,6 @@ -""" -Support for locks through the SmartThings cloud API. +"""Support for locks through the SmartThings cloud API.""" +from typing import Optional, Sequence -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/smartthings.lock/ -""" from homeassistant.components.lock import LockDevice from . import SmartThingsEntity @@ -30,13 +27,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities): broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] async_add_entities( [SmartThingsLock(device) for device in broker.devices.values() - if is_lock(device)]) + if broker.any_assigned(device.device_id, 'lock')]) -def is_lock(device): - """Determine if the device supports the lock capability.""" +def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: + """Return all capabilities supported if minimum required are present.""" from pysmartthings import Capability - return Capability.lock in device.capabilities + + if Capability.lock in capabilities: + return [Capability.lock] + return None class SmartThingsLock(SmartThingsEntity, LockDevice): diff --git a/homeassistant/components/smartthings/sensor.py b/homeassistant/components/smartthings/sensor.py index eb83334c6b3..32047c179b4 100644 --- a/homeassistant/components/smartthings/sensor.py +++ b/homeassistant/components/smartthings/sensor.py @@ -1,5 +1,6 @@ """Support for sensors through the SmartThings cloud API.""" from collections import namedtuple +from typing import Optional, Sequence from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, @@ -164,16 +165,22 @@ async def async_setup_entry(hass, config_entry, async_add_entities): broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] sensors = [] for device in broker.devices.values(): - for capability, maps in CAPABILITY_TO_SENSORS.items(): - if capability in device.capabilities: - sensors.extend([ - SmartThingsSensor( - device, m.attribute, m.name, m.default_unit, - m.device_class) - for m in maps]) + for capability in broker.get_assigned(device.device_id, 'sensor'): + maps = CAPABILITY_TO_SENSORS[capability] + sensors.extend([ + SmartThingsSensor( + device, m.attribute, m.name, m.default_unit, + m.device_class) + for m in maps]) async_add_entities(sensors) +def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: + """Return all capabilities supported if minimum required are present.""" + return [capability for capability in CAPABILITY_TO_SENSORS + if capability in capabilities] + + class SmartThingsSensor(SmartThingsEntity): """Define a SmartThings Binary Sensor.""" diff --git a/homeassistant/components/smartthings/strings.json b/homeassistant/components/smartthings/strings.json index 1fb4e878cb4..bcbe02f6011 100644 --- a/homeassistant/components/smartthings/strings.json +++ b/homeassistant/components/smartthings/strings.json @@ -21,7 +21,8 @@ "token_already_setup": "The token has already been setup.", "app_setup_error": "Unable to setup the SmartApp. Please try again.", "app_not_installed": "Please ensure you have installed and authorized the Home Assistant SmartApp and try again.", - "base_url_not_https": "The `base_url` for the `http` component must be configured and start with `https://`." + "base_url_not_https": "The `base_url` for the `http` component must be configured and start with `https://`.", + "webhook_error": "SmartThings could not validate the endpoint configured in `base_url`. Please review the [component requirements]({component_url})." } } } \ No newline at end of file diff --git a/homeassistant/components/smartthings/switch.py b/homeassistant/components/smartthings/switch.py index 08cdb74ed77..5a1224f4fc2 100644 --- a/homeassistant/components/smartthings/switch.py +++ b/homeassistant/components/smartthings/switch.py @@ -1,4 +1,6 @@ """Support for switches through the SmartThings cloud API.""" +from typing import Optional, Sequence + from homeassistant.components.switch import SwitchDevice from . import SmartThingsEntity @@ -18,28 +20,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities): broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] async_add_entities( [SmartThingsSwitch(device) for device in broker.devices.values() - if is_switch(device)]) + if broker.any_assigned(device.device_id, 'switch')]) -def is_switch(device): - """Determine if the device should be represented as a switch.""" +def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: + """Return all capabilities supported if minimum required are present.""" from pysmartthings import Capability # Must be able to be turned on/off. - if Capability.switch not in device.capabilities: - return False - # Must not have a capability represented by other types. - non_switch_capabilities = [ - Capability.color_control, - Capability.color_temperature, - Capability.fan_speed, - Capability.switch_level - ] - if any(capability in device.capabilities - for capability in non_switch_capabilities): - return False - - return True + if Capability.switch in capabilities: + return [Capability.switch] + return None class SmartThingsSwitch(SmartThingsEntity, SwitchDevice): diff --git a/requirements_all.txt b/requirements_all.txt index 64bd64c0aac..b23c98630a1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1241,7 +1241,7 @@ pysma==0.3.1 pysmartapp==0.3.0 # homeassistant.components.smartthings -pysmartthings==0.6.1 +pysmartthings==0.6.2 # homeassistant.components.device_tracker.snmp # homeassistant.components.sensor.snmp diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a4e042c9c43..7fbe73eac5f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -217,7 +217,7 @@ pyqwikswitch==0.8 pysmartapp==0.3.0 # homeassistant.components.smartthings -pysmartthings==0.6.1 +pysmartthings==0.6.2 # homeassistant.components.sonos pysonos==0.0.6 diff --git a/tests/components/smartthings/test_climate.py b/tests/components/smartthings/test_climate.py index 0f1102e2ab1..c5646fb400f 100644 --- a/tests/components/smartthings/test_climate.py +++ b/tests/components/smartthings/test_climate.py @@ -100,19 +100,6 @@ async def test_async_setup_platform(): await climate.async_setup_platform(None, None, None) -def test_is_climate(device_factory, legacy_thermostat, - basic_thermostat, thermostat): - """Test climate devices are correctly identified.""" - other_devices = [ - device_factory('Unknown', ['Unknown']), - device_factory("Switch 1", [Capability.switch]) - ] - for device in [legacy_thermostat, basic_thermostat, thermostat]: - assert climate.is_climate(device), device.name - for device in other_devices: - assert not climate.is_climate(device), device.name - - async def test_legacy_thermostat_entity_state(hass, legacy_thermostat): """Tests the state attributes properly match the thermostat type.""" await setup_platform(hass, CLIMATE_DOMAIN, legacy_thermostat) diff --git a/tests/components/smartthings/test_config_flow.py b/tests/components/smartthings/test_config_flow.py index 4d2a43a52c7..7d335703131 100644 --- a/tests/components/smartthings/test_config_flow.py +++ b/tests/components/smartthings/test_config_flow.py @@ -1,8 +1,9 @@ """Tests for the SmartThings config flow module.""" -from unittest.mock import patch +from unittest.mock import Mock, patch from uuid import uuid4 -from aiohttp.client_exceptions import ClientResponseError +from aiohttp import ClientResponseError +from pysmartthings import APIResponseError from homeassistant import data_entry_flow from homeassistant.components.smartthings.config_flow import ( @@ -103,13 +104,50 @@ async def test_token_forbidden(hass, smartthings_mock): assert result['errors'] == {'access_token': 'token_forbidden'} +async def test_webhook_error(hass, smartthings_mock): + """Test an error is when there's an error with the webhook endpoint.""" + flow = SmartThingsFlowHandler() + flow.hass = hass + + data = {'error': {}} + error = APIResponseError(None, None, data=data, status=422) + error.is_target_error = Mock(return_value=True) + + smartthings_mock.return_value.apps.return_value = mock_coro( + exception=error) + + result = await flow.async_step_user({'access_token': str(uuid4())}) + + assert result['type'] == data_entry_flow.RESULT_TYPE_FORM + assert result['step_id'] == 'user' + assert result['errors'] == {'base': 'webhook_error'} + + +async def test_api_error(hass, smartthings_mock): + """Test an error is shown when other API errors occur.""" + flow = SmartThingsFlowHandler() + flow.hass = hass + + data = {'error': {}} + error = APIResponseError(None, None, data=data, status=400) + + smartthings_mock.return_value.apps.return_value = mock_coro( + exception=error) + + result = await flow.async_step_user({'access_token': str(uuid4())}) + + assert result['type'] == data_entry_flow.RESULT_TYPE_FORM + assert result['step_id'] == 'user' + assert result['errors'] == {'base': 'app_setup_error'} + + async def test_unknown_api_error(hass, smartthings_mock): """Test an error is shown when there is an unknown API error.""" flow = SmartThingsFlowHandler() flow.hass = hass smartthings_mock.return_value.apps.return_value = mock_coro( - exception=ClientResponseError(None, None, status=500)) + exception=ClientResponseError(None, None, status=404)) result = await flow.async_step_user({'access_token': str(uuid4())}) diff --git a/tests/components/smartthings/test_fan.py b/tests/components/smartthings/test_fan.py index 99627e866d9..db8d9b512de 100644 --- a/tests/components/smartthings/test_fan.py +++ b/tests/components/smartthings/test_fan.py @@ -39,26 +39,6 @@ async def test_async_setup_platform(): await fan.async_setup_platform(None, None, None) -def test_is_fan(device_factory): - """Test fans are correctly identified.""" - non_fans = [ - device_factory('Unknown', ['Unknown']), - device_factory("Switch 1", [Capability.switch]), - device_factory("Non-Switchable Fan", [Capability.fan_speed]), - device_factory("Color Light", - [Capability.switch, Capability.switch_level, - Capability.color_control, - Capability.color_temperature]) - ] - fan_device = device_factory( - "Fan 1", [Capability.switch, Capability.switch_level, - Capability.fan_speed]) - - assert fan.is_fan(fan_device), fan_device.name - for device in non_fans: - assert not fan.is_fan(device), device.name - - async def test_entity_state(hass, device_factory): """Tests the state attributes properly match the fan types.""" device = device_factory( diff --git a/tests/components/smartthings/test_light.py b/tests/components/smartthings/test_light.py index a4f1103f270..72bc5da9063 100644 --- a/tests/components/smartthings/test_light.py +++ b/tests/components/smartthings/test_light.py @@ -65,25 +65,6 @@ async def test_async_setup_platform(): await light.async_setup_platform(None, None, None) -def test_is_light(device_factory, light_devices): - """Test lights are correctly identified.""" - non_lights = [ - device_factory('Unknown', ['Unknown']), - device_factory("Fan 1", - [Capability.switch, Capability.switch_level, - Capability.fan_speed]), - device_factory("Switch 1", [Capability.switch]), - device_factory("Can't be turned off", - [Capability.switch_level, Capability.color_control, - Capability.color_temperature]) - ] - - for device in light_devices: - assert light.is_light(device), device.name - for device in non_lights: - assert not light.is_light(device), device.name - - async def test_entity_state(hass, light_devices): """Tests the state attributes properly match the light types.""" await _setup_platform(hass, *light_devices) diff --git a/tests/components/smartthings/test_lock.py b/tests/components/smartthings/test_lock.py index c73f4ff549e..3739a2dc9b5 100644 --- a/tests/components/smartthings/test_lock.py +++ b/tests/components/smartthings/test_lock.py @@ -20,12 +20,6 @@ async def test_async_setup_platform(): await lock.async_setup_platform(None, None, None) -def test_is_lock(device_factory): - """Test locks are correctly identified.""" - lock_device = device_factory('Lock', [Capability.lock]) - assert lock.is_lock(lock_device) - - async def test_entity_and_device_attributes(hass, device_factory): """Test the attributes of the entity are correct.""" # Arrange diff --git a/tests/components/smartthings/test_switch.py b/tests/components/smartthings/test_switch.py index 15ff3adce86..3f2bedd4f13 100644 --- a/tests/components/smartthings/test_switch.py +++ b/tests/components/smartthings/test_switch.py @@ -35,23 +35,6 @@ async def test_async_setup_platform(): await switch.async_setup_platform(None, None, None) -def test_is_switch(device_factory): - """Test switches are correctly identified.""" - switch_device = device_factory('Switch', [Capability.switch]) - non_switch_devices = [ - device_factory('Light', [Capability.switch, Capability.switch_level]), - device_factory('Fan', [Capability.switch, Capability.fan_speed]), - device_factory('Color Light', [Capability.switch, - Capability.color_control]), - device_factory('Temp Light', [Capability.switch, - Capability.color_temperature]), - device_factory('Unknown', ['Unknown']), - ] - assert switch.is_switch(switch_device) - for non_switch_device in non_switch_devices: - assert not switch.is_switch(non_switch_device) - - async def test_entity_and_device_attributes(hass, device_factory): """Test the attributes of the entity are correct.""" # Arrange From d55693762e367cd0db895c6a0d6fe34e3d85131f Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Sat, 16 Feb 2019 03:49:24 -0600 Subject: [PATCH 224/242] Fix SmartThings Translation Error (#21103) --- homeassistant/components/smartthings/.translations/en.json | 2 +- homeassistant/components/smartthings/strings.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/smartthings/.translations/en.json b/homeassistant/components/smartthings/.translations/en.json index 2091ddb00a2..e35035b8fa0 100644 --- a/homeassistant/components/smartthings/.translations/en.json +++ b/homeassistant/components/smartthings/.translations/en.json @@ -8,7 +8,7 @@ "token_forbidden": "The token does not have the required OAuth scopes.", "token_invalid_format": "The token must be in the UID/GUID format", "token_unauthorized": "The token is invalid or no longer authorized.", - "webhook_error": "SmartThings could not validate the endpoint configured in `base_url`. Please review the [component requirements]({component_url})." + "webhook_error": "SmartThings could not validate the endpoint configured in `base_url`. Please review the component requirements." }, "step": { "user": { diff --git a/homeassistant/components/smartthings/strings.json b/homeassistant/components/smartthings/strings.json index bcbe02f6011..3578bcd5138 100644 --- a/homeassistant/components/smartthings/strings.json +++ b/homeassistant/components/smartthings/strings.json @@ -22,7 +22,7 @@ "app_setup_error": "Unable to setup the SmartApp. Please try again.", "app_not_installed": "Please ensure you have installed and authorized the Home Assistant SmartApp and try again.", "base_url_not_https": "The `base_url` for the `http` component must be configured and start with `https://`.", - "webhook_error": "SmartThings could not validate the endpoint configured in `base_url`. Please review the [component requirements]({component_url})." + "webhook_error": "SmartThings could not validate the endpoint configured in `base_url`. Please review the component requirements." } } } \ No newline at end of file From c544845e29fe85e801f1e73277aa47924e7059a5 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 18 Feb 2019 04:40:51 +0000 Subject: [PATCH 225/242] Fix track_change error in utility_meter (#21134) * split validation * remove any() --- homeassistant/components/utility_meter/sensor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index a59d51d97e2..a01c53b20e3 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -83,9 +83,9 @@ class UtilityMeterSensor(RestoreEntity): @callback def async_reading(self, entity, old_state, new_state): """Handle the sensor state changes.""" - if any([old_state is None, - old_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE], - new_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE]]): + if old_state is None or new_state is None or\ + old_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE] or\ + new_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE]: return if self._unit_of_measurement is None and\ From 8b5aff63aea2b29886242ec71dfb3d440c7950b9 Mon Sep 17 00:00:00 2001 From: John Mihalic <2854333+mezz64@users.noreply.github.com> Date: Mon, 18 Feb 2019 05:20:31 -0500 Subject: [PATCH 226/242] Update pyEight for Python 3.7 Compatability (#21161) --- homeassistant/components/eight_sleep/__init__.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/eight_sleep/__init__.py b/homeassistant/components/eight_sleep/__init__.py index 851fd3d1c31..ca6c8a5a5c6 100644 --- a/homeassistant/components/eight_sleep/__init__.py +++ b/homeassistant/components/eight_sleep/__init__.py @@ -16,7 +16,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util.dt import utcnow -REQUIREMENTS = ['pyeight==0.1.0'] +REQUIREMENTS = ['pyeight==0.1.1'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index b23c98630a1..b83ef7ae084 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1001,7 +1001,7 @@ pyeconet==0.0.6 pyedimax==0.1 # homeassistant.components.eight_sleep -pyeight==0.1.0 +pyeight==0.1.1 # homeassistant.components.media_player.emby pyemby==1.6 From d1fa341a78a022f63c70dbeea4df51eaa922b2f0 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Mon, 18 Feb 2019 10:55:41 -0500 Subject: [PATCH 227/242] Add power source to device and clean up zha listeners (#21174) check available and add comments ensure order on API test --- homeassistant/components/zha/core/const.py | 3 +- homeassistant/components/zha/core/device.py | 12 +- homeassistant/components/zha/core/gateway.py | 64 +++++++--- .../components/zha/core/listeners.py | 111 ++++++++++++++---- homeassistant/components/zha/sensor.py | 5 +- tests/components/zha/common.py | 4 +- tests/components/zha/test_api.py | 14 ++- tests/components/zha/test_binary_sensor.py | 8 +- tests/components/zha/test_fan.py | 3 +- tests/components/zha/test_light.py | 6 +- tests/components/zha/test_sensor.py | 3 +- tests/components/zha/test_switch.py | 4 +- 12 files changed, 179 insertions(+), 58 deletions(-) diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 5edcadc7fce..faa423d8ac4 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -62,7 +62,6 @@ ILLUMINANCE = 'illuminance' PRESSURE = 'pressure' METERING = 'metering' ELECTRICAL_MEASUREMENT = 'electrical_measurement' -POWER_CONFIGURATION = 'power_configuration' GENERIC = 'generic' UNKNOWN = 'unknown' OPENING = 'opening' @@ -73,6 +72,7 @@ ATTR_LEVEL = 'level' LISTENER_ON_OFF = 'on_off' LISTENER_ATTRIBUTE = 'attribute' +LISTENER_BASIC = 'basic' LISTENER_COLOR = 'color' LISTENER_FAN = 'fan' LISTENER_LEVEL = ATTR_LEVEL @@ -113,6 +113,7 @@ CLUSTER_REPORT_CONFIGS = {} CUSTOM_CLUSTER_MAPPINGS = {} COMPONENT_CLUSTERS = {} EVENT_RELAY_CLUSTERS = [] +NO_SENSOR_CLUSTERS = [] REPORT_CONFIG_MAX_INT = 900 REPORT_CONFIG_MAX_INT_BATTERY_SAVE = 10800 diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 7c972988e9c..7bb39f943f6 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -15,9 +15,9 @@ from .const import ( ATTR_CLUSTER_ID, ATTR_ATTRIBUTE, ATTR_VALUE, ATTR_COMMAND, SERVER, ATTR_COMMAND_TYPE, ATTR_ARGS, CLIENT_COMMANDS, SERVER_COMMANDS, ATTR_ENDPOINT_ID, IEEE, MODEL, NAME, UNKNOWN, QUIRK_APPLIED, - QUIRK_CLASS + QUIRK_CLASS, LISTENER_BASIC ) -from .listeners import EventRelayListener +from .listeners import EventRelayListener, BasicListener _LOGGER = logging.getLogger(__name__) @@ -59,6 +59,7 @@ class ZHADevice: self._zigpy_device.__class__.__module__, self._zigpy_device.__class__.__name__ ) + self.power_source = None @property def name(self): @@ -177,6 +178,13 @@ class ZHADevice: """Initialize listeners.""" _LOGGER.debug('%s: started initialization', self.name) await self._execute_listener_tasks('async_initialize', from_cache) + self.power_source = self.cluster_listeners.get( + LISTENER_BASIC).get_power_source() + _LOGGER.debug( + '%s: power source: %s', + self.name, + BasicListener.POWER_SOURCES.get(self.power_source) + ) _LOGGER.debug('%s: completed initialization', self.name) async def _execute_listener_tasks(self, task_name, *args): diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index ff3c374a850..391b12189cf 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -18,15 +18,15 @@ from .const import ( ZHA_DISCOVERY_NEW, DEVICE_CLASS, SINGLE_INPUT_CLUSTER_DEVICE_CLASS, SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS, COMPONENT_CLUSTERS, HUMIDITY, TEMPERATURE, ILLUMINANCE, PRESSURE, METERING, ELECTRICAL_MEASUREMENT, - POWER_CONFIGURATION, GENERIC, SENSOR_TYPE, EVENT_RELAY_CLUSTERS, - LISTENER_BATTERY, UNKNOWN, OPENING, ZONE, OCCUPANCY, - CLUSTER_REPORT_CONFIGS, REPORT_CONFIG_IMMEDIATE, REPORT_CONFIG_ASAP, - REPORT_CONFIG_DEFAULT, REPORT_CONFIG_MIN_INT, REPORT_CONFIG_MAX_INT, - REPORT_CONFIG_OP, SIGNAL_REMOVE) + GENERIC, SENSOR_TYPE, EVENT_RELAY_CLUSTERS, LISTENER_BATTERY, UNKNOWN, + OPENING, ZONE, OCCUPANCY, CLUSTER_REPORT_CONFIGS, REPORT_CONFIG_IMMEDIATE, + REPORT_CONFIG_ASAP, REPORT_CONFIG_DEFAULT, REPORT_CONFIG_MIN_INT, + REPORT_CONFIG_MAX_INT, REPORT_CONFIG_OP, SIGNAL_REMOVE, NO_SENSOR_CLUSTERS) from .device import ZHADevice from ..device_entity import ZhaDeviceEntity from .listeners import ( - LISTENER_REGISTRY, AttributeListener, EventRelayListener, ZDOListener) + LISTENER_REGISTRY, AttributeListener, EventRelayListener, ZDOListener, + BasicListener) from .helpers import convert_ieee _LOGGER = logging.getLogger(__name__) @@ -165,7 +165,21 @@ class ZHAGateway: await self._component.async_add_entities([device_entity]) if is_new_join: + # because it's a new join we can immediately mark the device as + # available and we already loaded fresh state above zha_device.update_available(True) + elif not zha_device.available and zha_device.power_source is not None\ + and zha_device.power_source != BasicListener.BATTERY: + # the device is currently marked unavailable and it isn't a battery + # powered device so we should be able to update it now + _LOGGER.debug( + "attempting to request fresh state for %s %s", + zha_device.name, + "with power source: {}".format( + BasicListener.POWER_SOURCES.get(zha_device.power_source) + ) + ) + await zha_device.async_initialize(from_cache=False) async def _async_process_endpoint( self, endpoint_id, endpoint, discovery_infos, device, zha_device, @@ -312,6 +326,13 @@ async def _handle_single_cluster_matches(hass, endpoint, zha_device, is_new_join, )) + if cluster.cluster_id in NO_SENSOR_CLUSTERS: + cluster_match_tasks.append(_handle_listener_only_cluster_match( + zha_device, + cluster, + is_new_join, + )) + for cluster in endpoint.out_clusters.values(): if cluster.cluster_id not in profile_clusters[1]: cluster_match_tasks.append(_handle_single_cluster_match( @@ -338,6 +359,12 @@ async def _handle_single_cluster_matches(hass, endpoint, zha_device, return cluster_matches +async def _handle_listener_only_cluster_match( + zha_device, cluster, is_new_join): + """Handle a listener only cluster match.""" + await _create_cluster_listener(cluster, zha_device, is_new_join) + + async def _handle_single_cluster_match(hass, zha_device, cluster, device_key, device_classes, is_new_join): """Dispatch a single cluster match to a HA component.""" @@ -352,11 +379,6 @@ async def _handle_single_cluster_match(hass, zha_device, cluster, device_key, listeners = [] await _create_cluster_listener(cluster, zha_device, is_new_join, listeners=listeners) - # don't actually create entities for PowerConfiguration - # find a better way to do this without abusing single cluster reg - from zigpy.zcl.clusters.general import PowerConfiguration - if cluster.cluster_id == PowerConfiguration.cluster_id: - return cluster_key = "{}-{}".format(device_key, cluster.cluster_id) discovery_info = { @@ -405,6 +427,10 @@ def establish_device_mappings(): EVENT_RELAY_CLUSTERS.append(zcl.clusters.general.LevelControl.cluster_id) EVENT_RELAY_CLUSTERS.append(zcl.clusters.general.OnOff.cluster_id) + NO_SENSOR_CLUSTERS.append(zcl.clusters.general.Basic.cluster_id) + NO_SENSOR_CLUSTERS.append( + zcl.clusters.general.PowerConfiguration.cluster_id) + DEVICE_CLASS[zha.PROFILE_ID].update({ zha.DeviceType.ON_OFF_SWITCH: 'binary_sensor', zha.DeviceType.LEVEL_CONTROL_SWITCH: 'binary_sensor', @@ -442,7 +468,6 @@ def establish_device_mappings(): zcl.clusters.measurement.IlluminanceMeasurement: 'sensor', zcl.clusters.smartenergy.Metering: 'sensor', zcl.clusters.homeautomation.ElectricalMeasurement: 'sensor', - zcl.clusters.general.PowerConfiguration: 'sensor', zcl.clusters.security.IasZone: 'binary_sensor', zcl.clusters.measurement.OccupancySensing: 'binary_sensor', zcl.clusters.hvac.Fan: 'fan', @@ -462,8 +487,6 @@ def establish_device_mappings(): zcl.clusters.smartenergy.Metering.cluster_id: METERING, zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id: ELECTRICAL_MEASUREMENT, - zcl.clusters.general.PowerConfiguration.cluster_id: - POWER_CONFIGURATION, }) BINARY_SENSOR_TYPES.update({ @@ -473,6 +496,19 @@ def establish_device_mappings(): }) CLUSTER_REPORT_CONFIGS.update({ + zcl.clusters.general.Alarms.cluster_id: [], + zcl.clusters.general.Basic.cluster_id: [], + zcl.clusters.general.Commissioning.cluster_id: [], + zcl.clusters.general.Identify.cluster_id: [], + zcl.clusters.general.Groups.cluster_id: [], + zcl.clusters.general.Scenes.cluster_id: [], + zcl.clusters.general.Partition.cluster_id: [], + zcl.clusters.general.Ota.cluster_id: [], + zcl.clusters.general.PowerProfile.cluster_id: [], + zcl.clusters.general.ApplianceControl.cluster_id: [], + zcl.clusters.general.PollControl.cluster_id: [], + zcl.clusters.general.GreenPowerProxy.cluster_id: [], + zcl.clusters.general.OnOffConfiguration.cluster_id: [], zcl.clusters.general.OnOff.cluster_id: [{ 'attr': 'on_off', 'config': REPORT_CONFIG_IMMEDIATE diff --git a/homeassistant/components/zha/core/listeners.py b/homeassistant/components/zha/core/listeners.py index 1b240d499b4..f8d24ce903c 100644 --- a/homeassistant/components/zha/core/listeners.py +++ b/homeassistant/components/zha/core/listeners.py @@ -18,7 +18,10 @@ from .helpers import ( safe_read, get_attr_id_by_name, bind_cluster) from .const import ( CLUSTER_REPORT_CONFIGS, REPORT_CONFIG_DEFAULT, SIGNAL_ATTR_UPDATED, - SIGNAL_MOVE_LEVEL, SIGNAL_SET_LEVEL, SIGNAL_STATE_ATTR, ATTR_LEVEL + SIGNAL_MOVE_LEVEL, SIGNAL_SET_LEVEL, SIGNAL_STATE_ATTR, LISTENER_BASIC, + LISTENER_ATTRIBUTE, LISTENER_ON_OFF, LISTENER_COLOR, LISTENER_FAN, + LISTENER_LEVEL, LISTENER_ZONE, LISTENER_ACTIVE_POWER, LISTENER_BATTERY, + LISTENER_EVENT_RELAY ) LISTENER_REGISTRY = {} @@ -30,12 +33,25 @@ def populate_listener_registry(): """Populate the listener registry.""" from zigpy import zcl LISTENER_REGISTRY.update({ + zcl.clusters.general.Alarms.cluster_id: ClusterListener, + zcl.clusters.general.Commissioning.cluster_id: ClusterListener, + zcl.clusters.general.Identify.cluster_id: ClusterListener, + zcl.clusters.general.Groups.cluster_id: ClusterListener, + zcl.clusters.general.Scenes.cluster_id: ClusterListener, + zcl.clusters.general.Partition.cluster_id: ClusterListener, + zcl.clusters.general.Ota.cluster_id: ClusterListener, + zcl.clusters.general.PowerProfile.cluster_id: ClusterListener, + zcl.clusters.general.ApplianceControl.cluster_id: ClusterListener, + zcl.clusters.general.PollControl.cluster_id: ClusterListener, + zcl.clusters.general.GreenPowerProxy.cluster_id: ClusterListener, + zcl.clusters.general.OnOffConfiguration.cluster_id: ClusterListener, zcl.clusters.general.OnOff.cluster_id: OnOffListener, zcl.clusters.general.LevelControl.cluster_id: LevelListener, zcl.clusters.lighting.Color.cluster_id: ColorListener, zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id: ActivePowerListener, zcl.clusters.general.PowerConfiguration.cluster_id: BatteryListener, + zcl.clusters.general.Basic.cluster_id: BasicListener, zcl.clusters.security.IasZone.cluster_id: IASZoneListener, zcl.clusters.hvac.Fan.cluster_id: FanListener, }) @@ -92,6 +108,7 @@ class ClusterListener: def __init__(self, cluster, device): """Initialize ClusterListener.""" + self.name = 'cluster_{}'.format(cluster.cluster_id) self._cluster = cluster self._zha_device = device self._unique_id = construct_unique_id(cluster) @@ -216,11 +233,10 @@ class ClusterListener: class AttributeListener(ClusterListener): """Listener for the attribute reports cluster.""" - name = 'attribute' - def __init__(self, cluster, device): """Initialize AttributeListener.""" super().__init__(cluster, device) + self.name = LISTENER_ATTRIBUTE attr = self._report_config[0].get('attr') if isinstance(attr, str): self._value_attribute = get_attr_id_by_name(self.cluster, attr) @@ -247,13 +263,12 @@ class AttributeListener(ClusterListener): class OnOffListener(ClusterListener): """Listener for the OnOff Zigbee cluster.""" - name = 'on_off' - ON_OFF = 0 def __init__(self, cluster, device): - """Initialize ClusterListener.""" + """Initialize OnOffListener.""" super().__init__(cluster, device) + self.name = LISTENER_ON_OFF self._state = None @callback @@ -295,10 +310,13 @@ class OnOffListener(ClusterListener): class LevelListener(ClusterListener): """Listener for the LevelControl Zigbee cluster.""" - name = ATTR_LEVEL - CURRENT_LEVEL = 0 + def __init__(self, cluster, device): + """Initialize LevelListener.""" + super().__init__(cluster, device) + self.name = LISTENER_LEVEL + @callback def cluster_command(self, tsn, command_id, args): """Handle commands received to this cluster.""" @@ -350,7 +368,10 @@ class LevelListener(ClusterListener): class IASZoneListener(ClusterListener): """Listener for the IASZone Zigbee cluster.""" - name = 'zone' + def __init__(self, cluster, device): + """Initialize LevelListener.""" + super().__init__(cluster, device) + self.name = LISTENER_ZONE @callback def cluster_command(self, tsn, command_id, args): @@ -415,7 +436,10 @@ class IASZoneListener(ClusterListener): class ActivePowerListener(AttributeListener): """Listener that polls active power level.""" - name = 'active_power' + def __init__(self, cluster, device): + """Initialize ActivePowerListener.""" + super().__init__(cluster, device) + self.name = LISTENER_ACTIVE_POWER async def async_update(self): """Retrieve latest state.""" @@ -423,7 +447,7 @@ class ActivePowerListener(AttributeListener): # This is a polling listener. Don't allow cache. result = await self.get_attribute_value( - 'active_power', from_cache=False) + LISTENER_ACTIVE_POWER, from_cache=False) async_dispatcher_send( self._zha_device.hass, "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), @@ -433,14 +457,53 @@ class ActivePowerListener(AttributeListener): async def async_initialize(self, from_cache): """Initialize listener.""" await self.get_attribute_value( - 'active_power', from_cache=from_cache) + LISTENER_ACTIVE_POWER, from_cache=from_cache) await super().async_initialize(from_cache) +class BasicListener(ClusterListener): + """Listener to interact with the basic cluster.""" + + BATTERY = 3 + POWER_SOURCES = { + 0: 'Unknown', + 1: 'Mains (single phase)', + 2: 'Mains (3 phase)', + BATTERY: 'Battery', + 4: 'DC source', + 5: 'Emergency mains constantly powered', + 6: 'Emergency mains and transfer switch' + } + + def __init__(self, cluster, device): + """Initialize BasicListener.""" + super().__init__(cluster, device) + self.name = LISTENER_BASIC + self._power_source = None + + async def async_configure(self): + """Configure this listener.""" + await super().async_configure() + await self.async_initialize(False) + + async def async_initialize(self, from_cache): + """Initialize listener.""" + self._power_source = await self.get_attribute_value( + 'power_source', from_cache=from_cache) + await super().async_initialize(from_cache) + + def get_power_source(self): + """Get the power source.""" + return self._power_source + + class BatteryListener(ClusterListener): """Listener that polls active power level.""" - name = 'battery' + def __init__(self, cluster, device): + """Initialize BatteryListener.""" + super().__init__(cluster, device) + self.name = LISTENER_BATTERY @callback def attribute_updated(self, attrid, value): @@ -480,7 +543,10 @@ class BatteryListener(ClusterListener): class EventRelayListener(ClusterListener): """Event relay that can be attached to zigbee clusters.""" - name = 'event_relay' + def __init__(self, cluster, device): + """Initialize EventRelayListener.""" + super().__init__(cluster, device) + self.name = LISTENER_EVENT_RELAY @callback def attribute_updated(self, attrid, value): @@ -512,15 +578,14 @@ class EventRelayListener(ClusterListener): class ColorListener(ClusterListener): """Color listener.""" - name = 'color' - CAPABILITIES_COLOR_XY = 0x08 CAPABILITIES_COLOR_TEMP = 0x10 UNSUPPORTED_ATTRIBUTE = 0x86 def __init__(self, cluster, device): - """Initialize ClusterListener.""" + """Initialize ColorListener.""" super().__init__(cluster, device) + self.name = LISTENER_COLOR self._color_capabilities = None def get_color_capabilities(self): @@ -550,10 +615,13 @@ class ColorListener(ClusterListener): class FanListener(ClusterListener): """Fan listener.""" - name = 'fan' - _value_attribute = 0 + def __init__(self, cluster, device): + """Initialize FanListener.""" + super().__init__(cluster, device) + self.name = LISTENER_FAN + async def async_set_speed(self, value) -> None: """Set the speed of the fan.""" from zigpy.exceptions import DeliveryError @@ -595,10 +663,9 @@ class FanListener(ClusterListener): class ZDOListener: """Listener for ZDO events.""" - name = 'zdo' - def __init__(self, cluster, device): - """Initialize ClusterListener.""" + """Initialize ZDOListener.""" + self.name = 'zdo' self._cluster = cluster self._zha_device = device self._status = ListenerStatus.CREATED diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index ad566df00f4..9c00d8124bb 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -12,8 +12,8 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from .core.const import ( DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW, HUMIDITY, TEMPERATURE, ILLUMINANCE, PRESSURE, METERING, ELECTRICAL_MEASUREMENT, - POWER_CONFIGURATION, GENERIC, SENSOR_TYPE, LISTENER_ATTRIBUTE, - LISTENER_ACTIVE_POWER, SIGNAL_ATTR_UPDATED, SIGNAL_STATE_ATTR) + GENERIC, SENSOR_TYPE, LISTENER_ATTRIBUTE, LISTENER_ACTIVE_POWER, + SIGNAL_ATTR_UPDATED, SIGNAL_STATE_ATTR) from .entity import ZhaEntity _LOGGER = logging.getLogger(__name__) @@ -71,7 +71,6 @@ UNIT_REGISTRY = { ILLUMINANCE: 'lx', METERING: 'W', ELECTRICAL_MEASUREMENT: 'W', - POWER_CONFIGURATION: '%', GENERIC: None } diff --git a/tests/components/zha/common.py b/tests/components/zha/common.py index f0e1aa701e7..cd2eb53c3fe 100644 --- a/tests/components/zha/common.py +++ b/tests/components/zha/common.py @@ -174,6 +174,7 @@ async def async_test_device_join( only trigger during device joins can be tested. """ from zigpy.zcl.foundation import Status + from zigpy.zcl.clusters.general import Basic # create zigpy device mocking out the zigbee network operations with patch( 'zigpy.zcl.Cluster.configure_reporting', @@ -182,7 +183,8 @@ async def async_test_device_join( 'zigpy.zcl.Cluster.bind', return_value=mock_coro([Status.SUCCESS, Status.SUCCESS])): zigpy_device = await async_init_zigpy_device( - hass, [cluster_id], [], device_type, zha_gateway, + hass, [cluster_id, Basic.cluster_id], [], device_type, + zha_gateway, ieee="00:0d:6f:00:0a:90:69:f7", manufacturer="FakeMan{}".format(cluster_id), model="FakeMod{}".format(cluster_id), diff --git a/tests/components/zha/test_api.py b/tests/components/zha/test_api.py index ad139d81ddf..616a94e8b89 100644 --- a/tests/components/zha/test_api.py +++ b/tests/components/zha/test_api.py @@ -17,14 +17,14 @@ from .common import async_init_zigpy_device @pytest.fixture async def zha_client(hass, config_entry, zha_gateway, hass_ws_client): """Test zha switch platform.""" - from zigpy.zcl.clusters.general import OnOff + from zigpy.zcl.clusters.general import OnOff, Basic # load the ZHA API async_load_api(hass, Mock(), zha_gateway) # create zigpy device await async_init_zigpy_device( - hass, [OnOff.cluster_id], [], None, zha_gateway) + hass, [OnOff.cluster_id, Basic.cluster_id], [], None, zha_gateway) # load up switch domain await hass.config_entries.async_forward_entry_setup( @@ -44,10 +44,16 @@ async def test_device_clusters(hass, config_entry, zha_gateway, zha_client): msg = await zha_client.receive_json() - assert len(msg['result']) == 1 + assert len(msg['result']) == 2 - cluster_info = msg['result'][0] + cluster_infos = sorted(msg['result'], key=lambda k: k[ID]) + cluster_info = cluster_infos[0] + assert cluster_info[TYPE] == IN + assert cluster_info[ID] == 0 + assert cluster_info[NAME] == 'Basic' + + cluster_info = cluster_infos[1] assert cluster_info[TYPE] == IN assert cluster_info[ID] == 6 assert cluster_info[NAME] == 'OnOff' diff --git a/tests/components/zha/test_binary_sensor.py b/tests/components/zha/test_binary_sensor.py index c81f96468ce..d0763b8fb10 100644 --- a/tests/components/zha/test_binary_sensor.py +++ b/tests/components/zha/test_binary_sensor.py @@ -11,13 +11,13 @@ async def test_binary_sensor(hass, config_entry, zha_gateway): """Test zha binary_sensor platform.""" from zigpy.zcl.clusters.security import IasZone from zigpy.zcl.clusters.measurement import OccupancySensing - from zigpy.zcl.clusters.general import OnOff, LevelControl + from zigpy.zcl.clusters.general import OnOff, LevelControl, Basic from zigpy.profiles.zha import DeviceType # create zigpy devices zigpy_device_zone = await async_init_zigpy_device( hass, - [IasZone.cluster_id], + [IasZone.cluster_id, Basic.cluster_id], [], None, zha_gateway @@ -25,7 +25,7 @@ async def test_binary_sensor(hass, config_entry, zha_gateway): zigpy_device_remote = await async_init_zigpy_device( hass, - [], + [Basic.cluster_id], [OnOff.cluster_id, LevelControl.cluster_id], DeviceType.LEVEL_CONTROL_SWITCH, zha_gateway, @@ -36,7 +36,7 @@ async def test_binary_sensor(hass, config_entry, zha_gateway): zigpy_device_occupancy = await async_init_zigpy_device( hass, - [OccupancySensing.cluster_id], + [OccupancySensing.cluster_id, Basic.cluster_id], [], None, zha_gateway, diff --git a/tests/components/zha/test_fan.py b/tests/components/zha/test_fan.py index 6beafc6ca8e..a70e0e5ea40 100644 --- a/tests/components/zha/test_fan.py +++ b/tests/components/zha/test_fan.py @@ -17,11 +17,12 @@ from .common import ( async def test_fan(hass, config_entry, zha_gateway): """Test zha fan platform.""" from zigpy.zcl.clusters.hvac import Fan + from zigpy.zcl.clusters.general import Basic from zigpy.zcl.foundation import Status # create zigpy device zigpy_device = await async_init_zigpy_device( - hass, [Fan.cluster_id], [], None, zha_gateway) + hass, [Fan.cluster_id, Basic.cluster_id], [], None, zha_gateway) # load up fan domain await hass.config_entries.async_forward_entry_setup( diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index 9c5e69d1347..38d7caedaad 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -14,13 +14,13 @@ OFF = 0 async def test_light(hass, config_entry, zha_gateway): """Test zha light platform.""" - from zigpy.zcl.clusters.general import OnOff, LevelControl + from zigpy.zcl.clusters.general import OnOff, LevelControl, Basic from zigpy.profiles.zha import DeviceType # create zigpy devices zigpy_device_on_off = await async_init_zigpy_device( hass, - [OnOff.cluster_id], + [OnOff.cluster_id, Basic.cluster_id], [], DeviceType.ON_OFF_LIGHT, zha_gateway @@ -28,7 +28,7 @@ async def test_light(hass, config_entry, zha_gateway): zigpy_device_level = await async_init_zigpy_device( hass, - [OnOff.cluster_id, LevelControl.cluster_id], + [OnOff.cluster_id, LevelControl.cluster_id, Basic.cluster_id], [], DeviceType.ON_OFF_LIGHT, zha_gateway, diff --git a/tests/components/zha/test_sensor.py b/tests/components/zha/test_sensor.py index d16cafb7df8..c348ef0d0a7 100644 --- a/tests/components/zha/test_sensor.py +++ b/tests/components/zha/test_sensor.py @@ -86,6 +86,7 @@ async def async_build_devices(hass, zha_gateway, config_entry, cluster_ids): A dict containing relevant device info for testing is returned. It contains the entity id, zigpy device, and the zigbee cluster for the sensor. """ + from zigpy.zcl.clusters.general import Basic device_infos = {} counter = 0 for cluster_id in cluster_ids: @@ -93,7 +94,7 @@ async def async_build_devices(hass, zha_gateway, config_entry, cluster_ids): device_infos[cluster_id] = {"zigpy_device": None} device_infos[cluster_id]["zigpy_device"] = await \ async_init_zigpy_device( - hass, [cluster_id], [], None, zha_gateway, + hass, [cluster_id, Basic.cluster_id], [], None, zha_gateway, ieee="{}0:15:8d:00:02:32:4f:32".format(counter), manufacturer="Fake{}".format(cluster_id), model="FakeModel{}".format(cluster_id)) diff --git a/tests/components/zha/test_switch.py b/tests/components/zha/test_switch.py index 32c8ee64e67..1fc21e34cd8 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -14,12 +14,12 @@ OFF = 0 async def test_switch(hass, config_entry, zha_gateway): """Test zha switch platform.""" - from zigpy.zcl.clusters.general import OnOff + from zigpy.zcl.clusters.general import OnOff, Basic from zigpy.zcl.foundation import Status # create zigpy device zigpy_device = await async_init_zigpy_device( - hass, [OnOff.cluster_id], [], None, zha_gateway) + hass, [OnOff.cluster_id, Basic.cluster_id], [], None, zha_gateway) # load up switch domain await hass.config_entries.async_forward_entry_setup( From 1768c2b447cbf1a1e37b0bdec6a4bb995dbdc6b0 Mon Sep 17 00:00:00 2001 From: sjabby Date: Mon, 18 Feb 2019 22:05:46 +0100 Subject: [PATCH 228/242] Fix for #19072 (#21175) * Fix for #19072 PR #19072 introduced the custom_effect feature but it didnt make it optional as the documentation states. This causes error on startup and the component does not work. ``` Error while setting up platform flux_led Traceback (most recent call last): File "/srv/homeassistant/lib/python3.5/site-packages/homeassistant/helpers/entity_platform.py", line 128, in _async_setup_platform SLOW_SETUP_MAX_WAIT, loop=hass.loop) File "/usr/lib/python3.5/asyncio/tasks.py", line 400, in wait_for return fut.result() File "/usr/lib/python3.5/asyncio/futures.py", line 293, in result raise self._exception File "/usr/lib/python3.5/concurrent/futures/thread.py", line 55, in run result = self.fn(*self.args, **self.kwargs) File "/srv/homeassistant/lib/python3.5/site-packages/homeassistant/components/light/flux_led.py", line 135, in setup_platform device[CONF_CUSTOM_EFFECT] = device_config[CONF_CUSTOM_EFFECT] KeyError: 'custom_effect' ``` Changing this line to make the custom_effect optional as the original intention. * Update flux_led.py --- homeassistant/components/light/flux_led.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/light/flux_led.py b/homeassistant/components/light/flux_led.py index 088fc871fc1..5ecf3f55e10 100644 --- a/homeassistant/components/light/flux_led.py +++ b/homeassistant/components/light/flux_led.py @@ -132,7 +132,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): device['ipaddr'] = ipaddr device[CONF_PROTOCOL] = device_config.get(CONF_PROTOCOL) device[ATTR_MODE] = device_config[ATTR_MODE] - device[CONF_CUSTOM_EFFECT] = device_config[CONF_CUSTOM_EFFECT] + device[CONF_CUSTOM_EFFECT] = device_config.get(CONF_CUSTOM_EFFECT) light = FluxLight(device) lights.append(light) light_ips.append(ipaddr) From 5ad252bd3b0626aa268bdd7e5576766a20926575 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 18 Feb 2019 13:31:21 -0800 Subject: [PATCH 229/242] Bumped version to 0.88.0b3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index c5e3e082e0b..5885bf6acfe 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 88 -PATCH_VERSION = '0b2' +PATCH_VERSION = '0b3' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From dfb45c03e4f6aefced403e46f831b1b6dfc3f010 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 19 Feb 2019 10:14:33 -0800 Subject: [PATCH 230/242] Updated frontend to 20190219.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 3b1d961ebe7..dce5b78bb6d 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -21,7 +21,7 @@ from homeassistant.loader import bind_hass from .storage import async_setup_frontend_storage -REQUIREMENTS = ['home-assistant-frontend==20190218.0'] +REQUIREMENTS = ['home-assistant-frontend==20190219.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index b83ef7ae084..766d699aedc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -535,7 +535,7 @@ hole==0.3.0 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190218.0 +home-assistant-frontend==20190219.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7fbe73eac5f..e706a1428de 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -116,7 +116,7 @@ hdate==0.8.7 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190218.0 +home-assistant-frontend==20190219.0 # homeassistant.components.homekit_controller homekit==0.12.2 From 4562bdc69f00fc702a536d47643038a8bee160fb Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Tue, 19 Feb 2019 06:11:56 +0100 Subject: [PATCH 231/242] Upgrade aioimaplib for Python 3.7 compatibility (#21197) --- homeassistant/components/sensor/imap.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/imap.py b/homeassistant/components/sensor/imap.py index b8d363417c2..571d05e78e9 100644 --- a/homeassistant/components/sensor/imap.py +++ b/homeassistant/components/sensor/imap.py @@ -20,7 +20,7 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['aioimaplib==0.7.13'] +REQUIREMENTS = ['aioimaplib==0.7.15'] CONF_SERVER = 'server' CONF_FOLDER = 'folder' diff --git a/requirements_all.txt b/requirements_all.txt index 766d699aedc..68b5c971f18 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -124,7 +124,7 @@ aiohue==1.9.0 aioiliad==0.1.1 # homeassistant.components.sensor.imap -aioimaplib==0.7.13 +aioimaplib==0.7.15 # homeassistant.components.lifx aiolifx==0.6.7 From 620f23d433537d4f6b95741ab5070d2e98458048 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Tue, 19 Feb 2019 16:45:21 +0000 Subject: [PATCH 232/242] ordered by last occurence (#21200) --- homeassistant/components/system_log/__init__.py | 10 +++++++--- tests/components/system_log/test_init.py | 5 +++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/system_log/__init__.py b/homeassistant/components/system_log/__init__.py index 9e968111c9c..16786bdeba4 100644 --- a/homeassistant/components/system_log/__init__.py +++ b/homeassistant/components/system_log/__init__.py @@ -88,7 +88,7 @@ class LogEntry: def __init__(self, record, stack, source): """Initialize a log entry.""" - self.timestamp = record.created + self.first_occured = self.timestamp = record.created self.level = record.levelname self.message = record.getMessage() if record.exc_info: @@ -125,9 +125,13 @@ class DedupStore(OrderedDict): key = str(entry.hash()) if key in self: - entry.count = self[key].count + 1 + # Update stored entry + self[key].count += 1 + self[key].timestamp = entry.timestamp - self[key] = entry + self.move_to_end(key) + else: + self[key] = entry if len(self) > self.maxlen: # Removes the first record which should also be the oldest diff --git a/tests/components/system_log/test_init.py b/tests/components/system_log/test_init.py index c1d79c9f33f..14047399aff 100644 --- a/tests/components/system_log/test_init.py +++ b/tests/components/system_log/test_init.py @@ -152,6 +152,11 @@ async def test_dedup_logs(hass, hass_client): assert log[1]["count"] == 2 assert_log(log[1], '', 'error message 2', 'ERROR') + _LOGGER.error('error message 2') + log = await get_error_log(hass, hass_client, 2) + assert_log(log[0], '', 'error message 2', 'ERROR') + assert log[0]["timestamp"] > log[0]["first_occured"] + async def test_clear_logs(hass, hass_client): """Test that the log can be cleared via a service call.""" From 4500760b52ccc911a5f3b1e5a0402bea7303306d Mon Sep 17 00:00:00 2001 From: ehendrix23 Date: Tue, 19 Feb 2019 09:44:42 -0700 Subject: [PATCH 233/242] Set aioharmony version to 0.1.8 (#21213) Update aioharmony version to support latest HUB firmware (4.15.250). --- homeassistant/components/harmony/remote.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/harmony/remote.py b/homeassistant/components/harmony/remote.py index a5e4f5a8528..489fe9144f2 100644 --- a/homeassistant/components/harmony/remote.py +++ b/homeassistant/components/harmony/remote.py @@ -22,7 +22,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.exceptions import PlatformNotReady from homeassistant.util import slugify -REQUIREMENTS = ['aioharmony==0.1.5'] +REQUIREMENTS = ['aioharmony==0.1.8'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 68b5c971f18..b8d46143716 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -111,7 +111,7 @@ aiofreepybox==0.0.6 aioftp==0.12.0 # homeassistant.components.harmony.remote -aioharmony==0.1.5 +aioharmony==0.1.8 # homeassistant.components.emulated_hue # homeassistant.components.http From c0f83b41648188b679123d884300c1855fcf5f2e Mon Sep 17 00:00:00 2001 From: carstenschroeder Date: Tue, 19 Feb 2019 18:42:00 +0100 Subject: [PATCH 234/242] Push pyads to 3.0.7 (#21216) * Push to pyads 3.0.7 * Correct too long line --- homeassistant/components/ads/__init__.py | 9 +++++---- requirements_all.txt | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/ads/__init__.py b/homeassistant/components/ads/__init__.py index 48b5ea21cbc..cfd0f37caa0 100644 --- a/homeassistant/components/ads/__init__.py +++ b/homeassistant/components/ads/__init__.py @@ -9,7 +9,7 @@ from homeassistant.const import CONF_DEVICE, CONF_PORT, CONF_IP_ADDRESS, \ EVENT_HOMEASSISTANT_STOP import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyads==2.2.6'] +REQUIREMENTS = ['pyads==3.0.7'] _LOGGER = logging.getLogger(__name__) @@ -73,9 +73,10 @@ def setup(hass, config): try: ads = AdsHub(client) - except pyads.pyads.ADSError: + except pyads.ADSError: _LOGGER.error( - "Could not connect to ADS host (netid=%s, port=%s)", net_id, port) + "Could not connect to ADS host (netid=%s, ip=%s, port=%s)", + net_id, ip_address, port) return False hass.data[DATA_ADS] = ads @@ -168,7 +169,7 @@ class AdsHub: self._notification_items[hnotify] = NotificationItem( hnotify, huser, name, plc_datatype, callback) - def _device_notification_callback(self, addr, notification, huser): + def _device_notification_callback(self, notification, name): """Handle device notifications.""" contents = notification.contents diff --git a/requirements_all.txt b/requirements_all.txt index b8d46143716..30f48e6587c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -916,7 +916,7 @@ pyW800rf32==0.1 # py_noaa==0.3.0 # homeassistant.components.ads -pyads==2.2.6 +pyads==3.0.7 # homeassistant.components.sensor.aftership pyaftership==0.1.2 From 4cc90c437f8100d44d1ddd988a44eef5e269eed0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 19 Feb 2019 10:31:47 -0800 Subject: [PATCH 235/242] Bumped version to 0.88.0b4 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 5885bf6acfe..1924d145529 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 88 -PATCH_VERSION = '0b3' +PATCH_VERSION = '0b4' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From d09cf8dd17278a515fb1d0b927a2197f30f33ee6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 20 Feb 2019 08:55:42 -0800 Subject: [PATCH 236/242] Updated frontend to 20190220.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 dce5b78bb6d..caf6bbccb5c 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -21,7 +21,7 @@ from homeassistant.loader import bind_hass from .storage import async_setup_frontend_storage -REQUIREMENTS = ['home-assistant-frontend==20190219.0'] +REQUIREMENTS = ['home-assistant-frontend==20190220.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index 30f48e6587c..ac85efe3be7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -535,7 +535,7 @@ hole==0.3.0 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190219.0 +home-assistant-frontend==20190220.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e706a1428de..1ee80607150 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -116,7 +116,7 @@ hdate==0.8.7 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190219.0 +home-assistant-frontend==20190220.0 # homeassistant.components.homekit_controller homekit==0.12.2 From 9057da01bdc8faed1bde57b7d8b64cf06455286b Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Tue, 19 Feb 2019 12:58:22 -0500 Subject: [PATCH 237/242] Refactor ZHA listeners into channels (#21196) * refactor listeners to channels * update coveragerc --- .coveragerc | 2 +- homeassistant/components/zha/__init__.py | 4 +- homeassistant/components/zha/binary_sensor.py | 46 +- homeassistant/components/zha/core/__init__.py | 3 - .../components/zha/core/channels/__init__.py | 308 ++++++++ .../components/zha/core/channels/closures.py | 9 + .../components/zha/core/channels/general.py | 202 +++++ .../zha/core/channels/homeautomation.py | 40 + .../components/zha/core/channels/hvac.py | 62 ++ .../components/zha/core/channels/lighting.py | 48 ++ .../components/zha/core/channels/lightlink.py | 9 + .../zha/core/channels/manufacturerspecific.py | 9 + .../zha/core/channels/measurement.py | 9 + .../components/zha/core/channels/protocol.py | 9 + .../components/zha/core/channels/registry.py | 46 ++ .../components/zha/core/channels/security.py | 82 ++ .../zha/core/channels/smartenergy.py | 9 + homeassistant/components/zha/core/const.py | 20 +- homeassistant/components/zha/core/device.py | 77 +- homeassistant/components/zha/core/gateway.py | 95 +-- .../components/zha/core/listeners.py | 706 ------------------ homeassistant/components/zha/device_entity.py | 25 +- homeassistant/components/zha/entity.py | 26 +- homeassistant/components/zha/fan.py | 14 +- homeassistant/components/zha/light.py | 38 +- homeassistant/components/zha/sensor.py | 20 +- homeassistant/components/zha/switch.py | 12 +- tests/components/zha/conftest.py | 6 +- 28 files changed, 1037 insertions(+), 899 deletions(-) create mode 100644 homeassistant/components/zha/core/channels/__init__.py create mode 100644 homeassistant/components/zha/core/channels/closures.py create mode 100644 homeassistant/components/zha/core/channels/general.py create mode 100644 homeassistant/components/zha/core/channels/homeautomation.py create mode 100644 homeassistant/components/zha/core/channels/hvac.py create mode 100644 homeassistant/components/zha/core/channels/lighting.py create mode 100644 homeassistant/components/zha/core/channels/lightlink.py create mode 100644 homeassistant/components/zha/core/channels/manufacturerspecific.py create mode 100644 homeassistant/components/zha/core/channels/measurement.py create mode 100644 homeassistant/components/zha/core/channels/protocol.py create mode 100644 homeassistant/components/zha/core/channels/registry.py create mode 100644 homeassistant/components/zha/core/channels/security.py create mode 100644 homeassistant/components/zha/core/channels/smartenergy.py delete mode 100644 homeassistant/components/zha/core/listeners.py diff --git a/.coveragerc b/.coveragerc index 5931322f80b..8e5b61136c0 100644 --- a/.coveragerc +++ b/.coveragerc @@ -665,11 +665,11 @@ omit = homeassistant/components/zha/__init__.py homeassistant/components/zha/api.py homeassistant/components/zha/const.py + homeassistant/components/zha/core/channels/* homeassistant/components/zha/core/const.py homeassistant/components/zha/core/device.py homeassistant/components/zha/core/gateway.py homeassistant/components/zha/core/helpers.py - homeassistant/components/zha/core/listeners.py homeassistant/components/zha/device_entity.py homeassistant/components/zha/entity.py homeassistant/components/zha/light.py diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index b8ef5c40838..6c7e83689ad 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -26,7 +26,7 @@ from .core.const import ( DATA_ZHA_RADIO, DEFAULT_BAUDRATE, DEFAULT_DATABASE_NAME, DEFAULT_RADIO_TYPE, DOMAIN, RadioType, DATA_ZHA_CORE_EVENTS, ENABLE_QUIRKS) from .core.gateway import establish_device_mappings -from .core.listeners import populate_listener_registry +from .core.channels.registry import populate_channel_registry REQUIREMENTS = [ 'bellows==0.7.0', @@ -90,7 +90,7 @@ async def async_setup_entry(hass, config_entry): Will automatically load components to support devices found on the network. """ establish_device_mappings() - populate_listener_registry() + populate_channel_registry() for component in COMPONENTS: hass.data[DATA_ZHA][component] = ( diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index 1f85373eecc..a46ffdd305d 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -9,9 +9,9 @@ import logging from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice from homeassistant.helpers.dispatcher import async_dispatcher_connect from .core.const import ( - DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW, LISTENER_ON_OFF, - LISTENER_LEVEL, LISTENER_ZONE, SIGNAL_ATTR_UPDATED, SIGNAL_MOVE_LEVEL, - SIGNAL_SET_LEVEL, LISTENER_ATTRIBUTE, UNKNOWN, OPENING, ZONE, OCCUPANCY, + DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW, ON_OFF_CHANNEL, + LEVEL_CHANNEL, ZONE_CHANNEL, SIGNAL_ATTR_UPDATED, SIGNAL_MOVE_LEVEL, + SIGNAL_SET_LEVEL, ATTRIBUTE_CHANNEL, UNKNOWN, OPENING, ZONE, OCCUPANCY, ATTR_LEVEL, SENSOR_TYPE) from .entity import ZhaEntity @@ -30,9 +30,9 @@ CLASS_MAPPING = { } -async def get_ias_device_class(listener): - """Get the HA device class from the listener.""" - zone_type = await listener.get_attribute_value('zone_type') +async def get_ias_device_class(channel): + """Get the HA device class from the channel.""" + zone_type = await channel.get_attribute_value('zone_type') return CLASS_MAPPING.get(zone_type) @@ -87,10 +87,10 @@ class BinarySensor(ZhaEntity, BinarySensorDevice): """Initialize the ZHA binary sensor.""" super().__init__(**kwargs) self._device_state_attributes = {} - self._zone_listener = self.cluster_listeners.get(LISTENER_ZONE) - self._on_off_listener = self.cluster_listeners.get(LISTENER_ON_OFF) - self._level_listener = self.cluster_listeners.get(LISTENER_LEVEL) - self._attr_listener = self.cluster_listeners.get(LISTENER_ATTRIBUTE) + self._zone_channel = self.cluster_channels.get(ZONE_CHANNEL) + self._on_off_channel = self.cluster_channels.get(ON_OFF_CHANNEL) + self._level_channel = self.cluster_channels.get(LEVEL_CHANNEL) + self._attr_channel = self.cluster_channels.get(ATTRIBUTE_CHANNEL) self._zha_sensor_type = kwargs[SENSOR_TYPE] self._level = None @@ -99,31 +99,31 @@ class BinarySensor(ZhaEntity, BinarySensorDevice): device_class_supplier = DEVICE_CLASS_REGISTRY.get( self._zha_sensor_type) if callable(device_class_supplier): - listener = self.cluster_listeners.get(self._zha_sensor_type) - if listener is None: + channel = self.cluster_channels.get(self._zha_sensor_type) + if channel is None: return None - return await device_class_supplier(listener) + return await device_class_supplier(channel) return device_class_supplier async def async_added_to_hass(self): """Run when about to be added to hass.""" self._device_class = await self._determine_device_class() await super().async_added_to_hass() - if self._level_listener: + if self._level_channel: await self.async_accept_signal( - self._level_listener, SIGNAL_SET_LEVEL, self.set_level) + self._level_channel, SIGNAL_SET_LEVEL, self.set_level) await self.async_accept_signal( - self._level_listener, SIGNAL_MOVE_LEVEL, self.move_level) - if self._on_off_listener: + self._level_channel, SIGNAL_MOVE_LEVEL, self.move_level) + if self._on_off_channel: await self.async_accept_signal( - self._on_off_listener, SIGNAL_ATTR_UPDATED, + self._on_off_channel, SIGNAL_ATTR_UPDATED, self.async_set_state) - if self._zone_listener: + if self._zone_channel: await self.async_accept_signal( - self._zone_listener, SIGNAL_ATTR_UPDATED, self.async_set_state) - if self._attr_listener: + self._zone_channel, SIGNAL_ATTR_UPDATED, self.async_set_state) + if self._attr_channel: await self.async_accept_signal( - self._attr_listener, SIGNAL_ATTR_UPDATED, self.async_set_state) + self._attr_channel, SIGNAL_ATTR_UPDATED, self.async_set_state) @property def is_on(self) -> bool: @@ -160,7 +160,7 @@ class BinarySensor(ZhaEntity, BinarySensorDevice): @property def device_state_attributes(self): """Return the device state attributes.""" - if self._level_listener is not None: + if self._level_channel is not None: self._device_state_attributes.update({ ATTR_LEVEL: self._state and self._level or 0 }) diff --git a/homeassistant/components/zha/core/__init__.py b/homeassistant/components/zha/core/__init__.py index e7443e7e0b7..145b725fc79 100644 --- a/homeassistant/components/zha/core/__init__.py +++ b/homeassistant/components/zha/core/__init__.py @@ -8,6 +8,3 @@ https://home-assistant.io/components/zha/ # flake8: noqa from .device import ZHADevice from .gateway import ZHAGateway -from .listeners import ( - ClusterListener, AttributeListener, OnOffListener, LevelListener, - IASZoneListener, ActivePowerListener, BatteryListener, EventRelayListener) diff --git a/homeassistant/components/zha/core/channels/__init__.py b/homeassistant/components/zha/core/channels/__init__.py new file mode 100644 index 00000000000..0c0e1ed2173 --- /dev/null +++ b/homeassistant/components/zha/core/channels/__init__.py @@ -0,0 +1,308 @@ +""" +Channels module for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zha/ +""" +import asyncio +from enum import Enum +from functools import wraps +import logging +from random import uniform + +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_send +from ..helpers import ( + bind_configure_reporting, construct_unique_id, + safe_read, get_attr_id_by_name) +from ..const import ( + CLUSTER_REPORT_CONFIGS, REPORT_CONFIG_DEFAULT, SIGNAL_ATTR_UPDATED, + ATTRIBUTE_CHANNEL, EVENT_RELAY_CHANNEL +) + +ZIGBEE_CHANNEL_REGISTRY = {} +_LOGGER = logging.getLogger(__name__) + + +def parse_and_log_command(unique_id, cluster, tsn, command_id, args): + """Parse and log a zigbee cluster command.""" + cmd = cluster.server_commands.get(command_id, [command_id])[0] + _LOGGER.debug( + "%s: received '%s' command with %s args on cluster_id '%s' tsn '%s'", + unique_id, + cmd, + args, + cluster.cluster_id, + tsn + ) + return cmd + + +def decorate_command(channel, command): + """Wrap a cluster command to make it safe.""" + @wraps(command) + async def wrapper(*args, **kwds): + from zigpy.zcl.foundation import Status + from zigpy.exceptions import DeliveryError + try: + result = await command(*args, **kwds) + _LOGGER.debug("%s: executed command: %s %s %s %s", + channel.unique_id, + command.__name__, + "{}: {}".format("with args", args), + "{}: {}".format("with kwargs", kwds), + "{}: {}".format("and result", result)) + if isinstance(result, bool): + return result + return result[1] is Status.SUCCESS + except DeliveryError: + _LOGGER.debug("%s: command failed: %s", channel.unique_id, + command.__name__) + return False + return wrapper + + +class ChannelStatus(Enum): + """Status of a channel.""" + + CREATED = 1 + CONFIGURED = 2 + INITIALIZED = 3 + + +class ZigbeeChannel: + """Base channel for a Zigbee cluster.""" + + def __init__(self, cluster, device): + """Initialize ZigbeeChannel.""" + self.name = 'channel_{}'.format(cluster.cluster_id) + self._cluster = cluster + self._zha_device = device + self._unique_id = construct_unique_id(cluster) + self._report_config = CLUSTER_REPORT_CONFIGS.get( + self._cluster.cluster_id, + [{'attr': 0, 'config': REPORT_CONFIG_DEFAULT}] + ) + self._status = ChannelStatus.CREATED + self._cluster.add_listener(self) + + @property + def unique_id(self): + """Return the unique id for this channel.""" + return self._unique_id + + @property + def cluster(self): + """Return the zigpy cluster for this channel.""" + return self._cluster + + @property + def device(self): + """Return the device this channel is linked to.""" + return self._zha_device + + @property + def status(self): + """Return the status of the channel.""" + return self._status + + def set_report_config(self, report_config): + """Set the reporting configuration.""" + self._report_config = report_config + + async def async_configure(self): + """Set cluster binding and attribute reporting.""" + manufacturer = None + manufacturer_code = self._zha_device.manufacturer_code + if self.cluster.cluster_id >= 0xfc00 and manufacturer_code: + manufacturer = manufacturer_code + + skip_bind = False # bind cluster only for the 1st configured attr + for report_config in self._report_config: + attr = report_config.get('attr') + min_report_interval, max_report_interval, change = \ + report_config.get('config') + await bind_configure_reporting( + self._unique_id, self.cluster, attr, + min_report=min_report_interval, + max_report=max_report_interval, + reportable_change=change, + skip_bind=skip_bind, + manufacturer=manufacturer + ) + skip_bind = True + await asyncio.sleep(uniform(0.1, 0.5)) + _LOGGER.debug( + "%s: finished channel configuration", + self._unique_id + ) + self._status = ChannelStatus.CONFIGURED + + async def async_initialize(self, from_cache): + """Initialize channel.""" + self._status = ChannelStatus.INITIALIZED + + @callback + def cluster_command(self, tsn, command_id, args): + """Handle commands received to this cluster.""" + pass + + @callback + def attribute_updated(self, attrid, value): + """Handle attribute updates on this cluster.""" + pass + + @callback + def zdo_command(self, *args, **kwargs): + """Handle ZDO commands on this cluster.""" + pass + + @callback + def zha_send_event(self, cluster, command, args): + """Relay events to hass.""" + self._zha_device.hass.bus.async_fire( + 'zha_event', + { + 'unique_id': self._unique_id, + 'device_ieee': str(self._zha_device.ieee), + 'command': command, + 'args': args + } + ) + + async def async_update(self): + """Retrieve latest state from cluster.""" + pass + + async def get_attribute_value(self, attribute, from_cache=True): + """Get the value for an attribute.""" + result = await safe_read( + self._cluster, + [attribute], + allow_cache=from_cache, + only_cache=from_cache + ) + return result.get(attribute) + + def __getattr__(self, name): + """Get attribute or a decorated cluster command.""" + if hasattr(self._cluster, name) and callable( + getattr(self._cluster, name)): + command = getattr(self._cluster, name) + command.__name__ = name + return decorate_command( + self, + command + ) + return self.__getattribute__(name) + + +class AttributeListeningChannel(ZigbeeChannel): + """Channel for attribute reports from the cluster.""" + + def __init__(self, cluster, device): + """Initialize AttributeListeningChannel.""" + super().__init__(cluster, device) + self.name = ATTRIBUTE_CHANNEL + attr = self._report_config[0].get('attr') + if isinstance(attr, str): + self._value_attribute = get_attr_id_by_name(self.cluster, attr) + else: + self._value_attribute = attr + + @callback + def attribute_updated(self, attrid, value): + """Handle attribute updates on this cluster.""" + if attrid == self._value_attribute: + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), + value + ) + + async def async_initialize(self, from_cache): + """Initialize listener.""" + await self.get_attribute_value( + self._report_config[0].get('attr'), from_cache=from_cache) + await super().async_initialize(from_cache) + + +class ZDOChannel: + """Channel for ZDO events.""" + + def __init__(self, cluster, device): + """Initialize ZDOChannel.""" + self.name = 'zdo' + self._cluster = cluster + self._zha_device = device + self._status = ChannelStatus.CREATED + self._unique_id = "{}_ZDO".format(device.name) + self._cluster.add_listener(self) + + @property + def unique_id(self): + """Return the unique id for this channel.""" + return self._unique_id + + @property + def cluster(self): + """Return the aigpy cluster for this channel.""" + return self._cluster + + @property + def status(self): + """Return the status of the channel.""" + return self._status + + @callback + def device_announce(self, zigpy_device): + """Device announce handler.""" + pass + + @callback + def permit_duration(self, duration): + """Permit handler.""" + pass + + async def async_initialize(self, from_cache): + """Initialize channel.""" + self._status = ChannelStatus.INITIALIZED + + async def async_configure(self): + """Configure channel.""" + self._status = ChannelStatus.CONFIGURED + + +class EventRelayChannel(ZigbeeChannel): + """Event relay that can be attached to zigbee clusters.""" + + def __init__(self, cluster, device): + """Initialize EventRelayChannel.""" + super().__init__(cluster, device) + self.name = EVENT_RELAY_CHANNEL + + @callback + def attribute_updated(self, attrid, value): + """Handle an attribute updated on this cluster.""" + self.zha_send_event( + self._cluster, + SIGNAL_ATTR_UPDATED, + { + 'attribute_id': attrid, + 'attribute_name': self._cluster.attributes.get( + attrid, + ['Unknown'])[0], + 'value': value + } + ) + + @callback + def cluster_command(self, tsn, command_id, args): + """Handle a cluster command received on this cluster.""" + if self._cluster.server_commands is not None and \ + self._cluster.server_commands.get(command_id) is not None: + self.zha_send_event( + self._cluster, + self._cluster.server_commands.get(command_id)[0], + args + ) diff --git a/homeassistant/components/zha/core/channels/closures.py b/homeassistant/components/zha/core/channels/closures.py new file mode 100644 index 00000000000..ba3b6b2e716 --- /dev/null +++ b/homeassistant/components/zha/core/channels/closures.py @@ -0,0 +1,9 @@ +""" +Closures channels module for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zha/ +""" +import logging + +_LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zha/core/channels/general.py b/homeassistant/components/zha/core/channels/general.py new file mode 100644 index 00000000000..bc015ae47f0 --- /dev/null +++ b/homeassistant/components/zha/core/channels/general.py @@ -0,0 +1,202 @@ +""" +General channels module for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zha/ +""" +import logging +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_send +from . import ZigbeeChannel, parse_and_log_command +from ..helpers import get_attr_id_by_name +from ..const import ( + SIGNAL_ATTR_UPDATED, + SIGNAL_MOVE_LEVEL, SIGNAL_SET_LEVEL, SIGNAL_STATE_ATTR, BASIC_CHANNEL, + ON_OFF_CHANNEL, LEVEL_CHANNEL, POWER_CONFIGURATION_CHANNEL +) + +_LOGGER = logging.getLogger(__name__) + + +class OnOffChannel(ZigbeeChannel): + """Channel for the OnOff Zigbee cluster.""" + + ON_OFF = 0 + + def __init__(self, cluster, device): + """Initialize OnOffChannel.""" + super().__init__(cluster, device) + self.name = ON_OFF_CHANNEL + self._state = None + + @callback + def cluster_command(self, tsn, command_id, args): + """Handle commands received to this cluster.""" + cmd = parse_and_log_command( + self.unique_id, + self._cluster, + tsn, + command_id, + args + ) + + if cmd in ('off', 'off_with_effect'): + self.attribute_updated(self.ON_OFF, False) + elif cmd in ('on', 'on_with_recall_global_scene', 'on_with_timed_off'): + self.attribute_updated(self.ON_OFF, True) + elif cmd == 'toggle': + self.attribute_updated(self.ON_OFF, not bool(self._state)) + + @callback + def attribute_updated(self, attrid, value): + """Handle attribute updates on this cluster.""" + if attrid == self.ON_OFF: + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), + value + ) + self._state = bool(value) + + async def async_initialize(self, from_cache): + """Initialize channel.""" + self._state = bool( + await self.get_attribute_value(self.ON_OFF, from_cache=from_cache)) + await super().async_initialize(from_cache) + + +class LevelControlChannel(ZigbeeChannel): + """Channel for the LevelControl Zigbee cluster.""" + + CURRENT_LEVEL = 0 + + def __init__(self, cluster, device): + """Initialize LevelControlChannel.""" + super().__init__(cluster, device) + self.name = LEVEL_CHANNEL + + @callback + def cluster_command(self, tsn, command_id, args): + """Handle commands received to this cluster.""" + cmd = parse_and_log_command( + self.unique_id, + self._cluster, + tsn, + command_id, + args + ) + + if cmd in ('move_to_level', 'move_to_level_with_on_off'): + self.dispatch_level_change(SIGNAL_SET_LEVEL, args[0]) + elif cmd in ('move', 'move_with_on_off'): + # We should dim slowly -- for now, just step once + rate = args[1] + if args[0] == 0xff: + rate = 10 # Should read default move rate + self.dispatch_level_change( + SIGNAL_MOVE_LEVEL, -rate if args[0] else rate) + elif cmd in ('step', 'step_with_on_off'): + # Step (technically may change on/off) + self.dispatch_level_change( + SIGNAL_MOVE_LEVEL, -args[1] if args[0] else args[1]) + + @callback + def attribute_updated(self, attrid, value): + """Handle attribute updates on this cluster.""" + _LOGGER.debug("%s: received attribute: %s update with value: %i", + self.unique_id, attrid, value) + if attrid == self.CURRENT_LEVEL: + self.dispatch_level_change(SIGNAL_SET_LEVEL, value) + + def dispatch_level_change(self, command, level): + """Dispatch level change.""" + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, command), + level + ) + + async def async_initialize(self, from_cache): + """Initialize channel.""" + await self.get_attribute_value( + self.CURRENT_LEVEL, from_cache=from_cache) + await super().async_initialize(from_cache) + + +class BasicChannel(ZigbeeChannel): + """Channel to interact with the basic cluster.""" + + BATTERY = 3 + POWER_SOURCES = { + 0: 'Unknown', + 1: 'Mains (single phase)', + 2: 'Mains (3 phase)', + BATTERY: 'Battery', + 4: 'DC source', + 5: 'Emergency mains constantly powered', + 6: 'Emergency mains and transfer switch' + } + + def __init__(self, cluster, device): + """Initialize BasicChannel.""" + super().__init__(cluster, device) + self.name = BASIC_CHANNEL + self._power_source = None + + async def async_configure(self): + """Configure this channel.""" + await super().async_configure() + await self.async_initialize(False) + + async def async_initialize(self, from_cache): + """Initialize channel.""" + self._power_source = await self.get_attribute_value( + 'power_source', from_cache=from_cache) + await super().async_initialize(from_cache) + + def get_power_source(self): + """Get the power source.""" + return self._power_source + + +class PowerConfigurationChannel(ZigbeeChannel): + """Channel for the zigbee power configuration cluster.""" + + def __init__(self, cluster, device): + """Initialize PowerConfigurationChannel.""" + super().__init__(cluster, device) + self.name = POWER_CONFIGURATION_CHANNEL + + @callback + def attribute_updated(self, attrid, value): + """Handle attribute updates on this cluster.""" + attr = self._report_config[1].get('attr') + if isinstance(attr, str): + attr_id = get_attr_id_by_name(self.cluster, attr) + else: + attr_id = attr + if attrid == attr_id: + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, SIGNAL_STATE_ATTR), + 'battery_level', + value + ) + + async def async_initialize(self, from_cache): + """Initialize channel.""" + await self.async_read_state(from_cache) + await super().async_initialize(from_cache) + + async def async_update(self): + """Retrieve latest state.""" + await self.async_read_state(True) + + async def async_read_state(self, from_cache): + """Read data from the cluster.""" + await self.get_attribute_value( + 'battery_size', from_cache=from_cache) + await self.get_attribute_value( + 'battery_percentage_remaining', from_cache=from_cache) + await self.get_attribute_value( + 'active_power', from_cache=from_cache) diff --git a/homeassistant/components/zha/core/channels/homeautomation.py b/homeassistant/components/zha/core/channels/homeautomation.py new file mode 100644 index 00000000000..2518889fcb1 --- /dev/null +++ b/homeassistant/components/zha/core/channels/homeautomation.py @@ -0,0 +1,40 @@ +""" +Home automation channels module for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zha/ +""" +import logging +from homeassistant.helpers.dispatcher import async_dispatcher_send +from . import AttributeListeningChannel +from ..const import SIGNAL_ATTR_UPDATED, ELECTRICAL_MEASUREMENT_CHANNEL + +_LOGGER = logging.getLogger(__name__) + + +class ElectricalMeasurementChannel(AttributeListeningChannel): + """Channel that polls active power level.""" + + def __init__(self, cluster, device): + """Initialize ElectricalMeasurementChannel.""" + super().__init__(cluster, device) + self.name = ELECTRICAL_MEASUREMENT_CHANNEL + + async def async_update(self): + """Retrieve latest state.""" + _LOGGER.debug("%s async_update", self.unique_id) + + # This is a polling channel. Don't allow cache. + result = await self.get_attribute_value( + ELECTRICAL_MEASUREMENT_CHANNEL, from_cache=False) + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), + result + ) + + async def async_initialize(self, from_cache): + """Initialize channel.""" + await self.get_attribute_value( + ELECTRICAL_MEASUREMENT_CHANNEL, from_cache=from_cache) + await super().async_initialize(from_cache) diff --git a/homeassistant/components/zha/core/channels/hvac.py b/homeassistant/components/zha/core/channels/hvac.py new file mode 100644 index 00000000000..c62ec66588e --- /dev/null +++ b/homeassistant/components/zha/core/channels/hvac.py @@ -0,0 +1,62 @@ +""" +HVAC channels module for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zha/ +""" +import logging +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_send +from . import ZigbeeChannel +from ..const import FAN_CHANNEL, SIGNAL_ATTR_UPDATED + +_LOGGER = logging.getLogger(__name__) + + +class FanChannel(ZigbeeChannel): + """Fan channel.""" + + _value_attribute = 0 + + def __init__(self, cluster, device): + """Initialize FanChannel.""" + super().__init__(cluster, device) + self.name = FAN_CHANNEL + + async def async_set_speed(self, value) -> None: + """Set the speed of the fan.""" + from zigpy.exceptions import DeliveryError + try: + await self.cluster.write_attributes({'fan_mode': value}) + except DeliveryError as ex: + _LOGGER.error("%s: Could not set speed: %s", self.unique_id, ex) + return + + async def async_update(self): + """Retrieve latest state.""" + result = await self.get_attribute_value('fan_mode', from_cache=True) + + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), + result + ) + + @callback + def attribute_updated(self, attrid, value): + """Handle attribute update from fan cluster.""" + attr_name = self.cluster.attributes.get(attrid, [attrid])[0] + _LOGGER.debug("%s: Attribute report '%s'[%s] = %s", + self.unique_id, self.cluster.name, attr_name, value) + if attrid == self._value_attribute: + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), + value + ) + + async def async_initialize(self, from_cache): + """Initialize channel.""" + await self.get_attribute_value( + self._value_attribute, from_cache=from_cache) + await super().async_initialize(from_cache) diff --git a/homeassistant/components/zha/core/channels/lighting.py b/homeassistant/components/zha/core/channels/lighting.py new file mode 100644 index 00000000000..ee88a30e828 --- /dev/null +++ b/homeassistant/components/zha/core/channels/lighting.py @@ -0,0 +1,48 @@ +""" +Lighting channels module for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zha/ +""" +import logging +from . import ZigbeeChannel +from ..const import COLOR_CHANNEL + +_LOGGER = logging.getLogger(__name__) + + +class ColorChannel(ZigbeeChannel): + """Color channel.""" + + CAPABILITIES_COLOR_XY = 0x08 + CAPABILITIES_COLOR_TEMP = 0x10 + UNSUPPORTED_ATTRIBUTE = 0x86 + + def __init__(self, cluster, device): + """Initialize ColorChannel.""" + super().__init__(cluster, device) + self.name = COLOR_CHANNEL + self._color_capabilities = None + + def get_color_capabilities(self): + """Return the color capabilities.""" + return self._color_capabilities + + async def async_initialize(self, from_cache): + """Initialize channel.""" + capabilities = await self.get_attribute_value( + 'color_capabilities', from_cache=from_cache) + + if capabilities is None: + # ZCL Version 4 devices don't support the color_capabilities + # attribute. In this version XY support is mandatory, but we + # need to probe to determine if the device supports color + # temperature. + capabilities = self.CAPABILITIES_COLOR_XY + result = await self.get_attribute_value( + 'color_temperature', from_cache=from_cache) + + if result is not self.UNSUPPORTED_ATTRIBUTE: + capabilities |= self.CAPABILITIES_COLOR_TEMP + self._color_capabilities = capabilities + await super().async_initialize(from_cache) diff --git a/homeassistant/components/zha/core/channels/lightlink.py b/homeassistant/components/zha/core/channels/lightlink.py new file mode 100644 index 00000000000..83fca6e80c2 --- /dev/null +++ b/homeassistant/components/zha/core/channels/lightlink.py @@ -0,0 +1,9 @@ +""" +Lightlink channels module for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zha/ +""" +import logging + +_LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zha/core/channels/manufacturerspecific.py b/homeassistant/components/zha/core/channels/manufacturerspecific.py new file mode 100644 index 00000000000..a0eebd78343 --- /dev/null +++ b/homeassistant/components/zha/core/channels/manufacturerspecific.py @@ -0,0 +1,9 @@ +""" +Manufacturer specific channels module for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zha/ +""" +import logging + +_LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zha/core/channels/measurement.py b/homeassistant/components/zha/core/channels/measurement.py new file mode 100644 index 00000000000..51146289e69 --- /dev/null +++ b/homeassistant/components/zha/core/channels/measurement.py @@ -0,0 +1,9 @@ +""" +Measurement channels module for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zha/ +""" +import logging + +_LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zha/core/channels/protocol.py b/homeassistant/components/zha/core/channels/protocol.py new file mode 100644 index 00000000000..2cae156aec5 --- /dev/null +++ b/homeassistant/components/zha/core/channels/protocol.py @@ -0,0 +1,9 @@ +""" +Protocol channels module for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zha/ +""" +import logging + +_LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zha/core/channels/registry.py b/homeassistant/components/zha/core/channels/registry.py new file mode 100644 index 00000000000..f0363ac8330 --- /dev/null +++ b/homeassistant/components/zha/core/channels/registry.py @@ -0,0 +1,46 @@ +""" +Channel registry module for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zha/ +""" +from . import ZigbeeChannel +from .general import ( + OnOffChannel, LevelControlChannel, PowerConfigurationChannel, BasicChannel +) +from .homeautomation import ElectricalMeasurementChannel +from .hvac import FanChannel +from .lighting import ColorChannel +from .security import IASZoneChannel + + +ZIGBEE_CHANNEL_REGISTRY = {} + + +def populate_channel_registry(): + """Populate the channel registry.""" + from zigpy import zcl + ZIGBEE_CHANNEL_REGISTRY.update({ + zcl.clusters.general.Alarms.cluster_id: ZigbeeChannel, + zcl.clusters.general.Commissioning.cluster_id: ZigbeeChannel, + zcl.clusters.general.Identify.cluster_id: ZigbeeChannel, + zcl.clusters.general.Groups.cluster_id: ZigbeeChannel, + zcl.clusters.general.Scenes.cluster_id: ZigbeeChannel, + zcl.clusters.general.Partition.cluster_id: ZigbeeChannel, + zcl.clusters.general.Ota.cluster_id: ZigbeeChannel, + zcl.clusters.general.PowerProfile.cluster_id: ZigbeeChannel, + zcl.clusters.general.ApplianceControl.cluster_id: ZigbeeChannel, + zcl.clusters.general.PollControl.cluster_id: ZigbeeChannel, + zcl.clusters.general.GreenPowerProxy.cluster_id: ZigbeeChannel, + zcl.clusters.general.OnOffConfiguration.cluster_id: ZigbeeChannel, + zcl.clusters.general.OnOff.cluster_id: OnOffChannel, + zcl.clusters.general.LevelControl.cluster_id: LevelControlChannel, + zcl.clusters.lighting.Color.cluster_id: ColorChannel, + zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id: + ElectricalMeasurementChannel, + zcl.clusters.general.PowerConfiguration.cluster_id: + PowerConfigurationChannel, + zcl.clusters.general.Basic.cluster_id: BasicChannel, + zcl.clusters.security.IasZone.cluster_id: IASZoneChannel, + zcl.clusters.hvac.Fan.cluster_id: FanChannel, + }) diff --git a/homeassistant/components/zha/core/channels/security.py b/homeassistant/components/zha/core/channels/security.py new file mode 100644 index 00000000000..e8c0e71a263 --- /dev/null +++ b/homeassistant/components/zha/core/channels/security.py @@ -0,0 +1,82 @@ +""" +Security channels module for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zha/ +""" +import logging +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_send +from . import ZigbeeChannel +from ..helpers import bind_cluster +from ..const import SIGNAL_ATTR_UPDATED, ZONE_CHANNEL + +_LOGGER = logging.getLogger(__name__) + + +class IASZoneChannel(ZigbeeChannel): + """Channel for the IASZone Zigbee cluster.""" + + def __init__(self, cluster, device): + """Initialize IASZoneChannel.""" + super().__init__(cluster, device) + self.name = ZONE_CHANNEL + + @callback + def cluster_command(self, tsn, command_id, args): + """Handle commands received to this cluster.""" + if command_id == 0: + state = args[0] & 3 + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), + state + ) + _LOGGER.debug("Updated alarm state: %s", state) + elif command_id == 1: + _LOGGER.debug("Enroll requested") + res = self._cluster.enroll_response(0, 0) + self._zha_device.hass.async_create_task(res) + + async def async_configure(self): + """Configure IAS device.""" + from zigpy.exceptions import DeliveryError + _LOGGER.debug("%s: started IASZoneChannel configuration", + self._unique_id) + + await bind_cluster(self.unique_id, self._cluster) + ieee = self._cluster.endpoint.device.application.ieee + + try: + res = await self._cluster.write_attributes({'cie_addr': ieee}) + _LOGGER.debug( + "%s: wrote cie_addr: %s to '%s' cluster: %s", + self.unique_id, str(ieee), self._cluster.ep_attribute, + res[0] + ) + except DeliveryError as ex: + _LOGGER.debug( + "%s: Failed to write cie_addr: %s to '%s' cluster: %s", + self.unique_id, str(ieee), self._cluster.ep_attribute, str(ex) + ) + _LOGGER.debug("%s: finished IASZoneChannel configuration", + self._unique_id) + + await self.get_attribute_value('zone_type', from_cache=False) + + @callback + def attribute_updated(self, attrid, value): + """Handle attribute updates on this cluster.""" + if attrid == 2: + value = value & 3 + async_dispatcher_send( + self._zha_device.hass, + "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), + value + ) + + async def async_initialize(self, from_cache): + """Initialize channel.""" + await self.get_attribute_value('zone_status', from_cache=from_cache) + await self.get_attribute_value('zone_state', from_cache=from_cache) + await super().async_initialize(from_cache) diff --git a/homeassistant/components/zha/core/channels/smartenergy.py b/homeassistant/components/zha/core/channels/smartenergy.py new file mode 100644 index 00000000000..d17eae30a96 --- /dev/null +++ b/homeassistant/components/zha/core/channels/smartenergy.py @@ -0,0 +1,9 @@ +""" +Smart energy channels module for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/zha/ +""" +import logging + +_LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index faa423d8ac4..d1001682c7b 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -70,16 +70,16 @@ OCCUPANCY = 'occupancy' ATTR_LEVEL = 'level' -LISTENER_ON_OFF = 'on_off' -LISTENER_ATTRIBUTE = 'attribute' -LISTENER_BASIC = 'basic' -LISTENER_COLOR = 'color' -LISTENER_FAN = 'fan' -LISTENER_LEVEL = ATTR_LEVEL -LISTENER_ZONE = 'zone' -LISTENER_ACTIVE_POWER = 'active_power' -LISTENER_BATTERY = 'battery' -LISTENER_EVENT_RELAY = 'event_relay' +ON_OFF_CHANNEL = 'on_off' +ATTRIBUTE_CHANNEL = 'attribute' +BASIC_CHANNEL = 'basic' +COLOR_CHANNEL = 'color' +FAN_CHANNEL = 'fan' +LEVEL_CHANNEL = ATTR_LEVEL +ZONE_CHANNEL = 'zone' +ELECTRICAL_MEASUREMENT_CHANNEL = 'active_power' +POWER_CONFIGURATION_CHANNEL = 'battery' +EVENT_RELAY_CHANNEL = 'event_relay' SIGNAL_ATTR_UPDATED = 'attribute_updated' SIGNAL_MOVE_LEVEL = "move_level" diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 7bb39f943f6..3a012ed7895 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -11,13 +11,14 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send ) from .const import ( - ATTR_MANUFACTURER, LISTENER_BATTERY, SIGNAL_AVAILABLE, IN, OUT, + ATTR_MANUFACTURER, POWER_CONFIGURATION_CHANNEL, SIGNAL_AVAILABLE, IN, OUT, ATTR_CLUSTER_ID, ATTR_ATTRIBUTE, ATTR_VALUE, ATTR_COMMAND, SERVER, ATTR_COMMAND_TYPE, ATTR_ARGS, CLIENT_COMMANDS, SERVER_COMMANDS, ATTR_ENDPOINT_ID, IEEE, MODEL, NAME, UNKNOWN, QUIRK_APPLIED, - QUIRK_CLASS, LISTENER_BASIC + QUIRK_CLASS, BASIC_CHANNEL ) -from .listeners import EventRelayListener, BasicListener +from .channels import EventRelayChannel +from .channels.general import BasicChannel _LOGGER = logging.getLogger(__name__) @@ -38,9 +39,9 @@ class ZHADevice: self._manufacturer = zigpy_device.endpoints[ept_id].manufacturer self._model = zigpy_device.endpoints[ept_id].model self._zha_gateway = zha_gateway - self.cluster_listeners = {} - self._relay_listeners = [] - self._all_listeners = [] + self.cluster_channels = {} + self._relay_channels = [] + self._all_channels = [] self._name = "{} {}".format( self.manufacturer, self.model @@ -113,9 +114,9 @@ class ZHADevice: return self._zha_gateway @property - def all_listeners(self): - """Return cluster listeners and relay listeners for device.""" - return self._all_listeners + def all_channels(self): + """Return cluster channels and relay channels for device.""" + return self._all_channels @property def available_signal(self): @@ -156,59 +157,59 @@ class ZHADevice: QUIRK_CLASS: self.quirk_class } - def add_cluster_listener(self, cluster_listener): - """Add cluster listener to device.""" - # only keep 1 power listener - if cluster_listener.name is LISTENER_BATTERY and \ - LISTENER_BATTERY in self.cluster_listeners: + def add_cluster_channel(self, cluster_channel): + """Add cluster channel to device.""" + # only keep 1 power configuration channel + if cluster_channel.name is POWER_CONFIGURATION_CHANNEL and \ + POWER_CONFIGURATION_CHANNEL in self.cluster_channels: return - self._all_listeners.append(cluster_listener) - if isinstance(cluster_listener, EventRelayListener): - self._relay_listeners.append(cluster_listener) + self._all_channels.append(cluster_channel) + if isinstance(cluster_channel, EventRelayChannel): + self._relay_channels.append(cluster_channel) else: - self.cluster_listeners[cluster_listener.name] = cluster_listener + self.cluster_channels[cluster_channel.name] = cluster_channel async def async_configure(self): """Configure the device.""" _LOGGER.debug('%s: started configuration', self.name) - await self._execute_listener_tasks('async_configure') + await self._execute_channel_tasks('async_configure') _LOGGER.debug('%s: completed configuration', self.name) async def async_initialize(self, from_cache=False): - """Initialize listeners.""" + """Initialize channels.""" _LOGGER.debug('%s: started initialization', self.name) - await self._execute_listener_tasks('async_initialize', from_cache) - self.power_source = self.cluster_listeners.get( - LISTENER_BASIC).get_power_source() + await self._execute_channel_tasks('async_initialize', from_cache) + self.power_source = self.cluster_channels.get( + BASIC_CHANNEL).get_power_source() _LOGGER.debug( '%s: power source: %s', self.name, - BasicListener.POWER_SOURCES.get(self.power_source) + BasicChannel.POWER_SOURCES.get(self.power_source) ) _LOGGER.debug('%s: completed initialization', self.name) - async def _execute_listener_tasks(self, task_name, *args): - """Gather and execute a set of listener tasks.""" - listener_tasks = [] - for listener in self.all_listeners: - listener_tasks.append( - self._async_create_task(listener, task_name, *args)) - await asyncio.gather(*listener_tasks) + async def _execute_channel_tasks(self, task_name, *args): + """Gather and execute a set of CHANNEL tasks.""" + channel_tasks = [] + for channel in self.all_channels: + channel_tasks.append( + self._async_create_task(channel, task_name, *args)) + await asyncio.gather(*channel_tasks) - async def _async_create_task(self, listener, func_name, *args): - """Configure a single listener on this device.""" + async def _async_create_task(self, channel, func_name, *args): + """Configure a single channel on this device.""" try: - await getattr(listener, func_name)(*args) - _LOGGER.debug('%s: listener: %s %s stage succeeded', + await getattr(channel, func_name)(*args) + _LOGGER.debug('%s: channel: %s %s stage succeeded', self.name, "{}-{}".format( - listener.name, listener.unique_id), + channel.name, channel.unique_id), func_name) except Exception as ex: # pylint: disable=broad-except _LOGGER.warning( - '%s listener: %s %s stage failed ex: %s', + '%s channel: %s %s stage failed ex: %s', self.name, - "{}-{}".format(listener.name, listener.unique_id), + "{}-{}".format(channel.name, channel.unique_id), func_name, ex ) diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 391b12189cf..4fbf96a22b6 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -18,15 +18,18 @@ from .const import ( ZHA_DISCOVERY_NEW, DEVICE_CLASS, SINGLE_INPUT_CLUSTER_DEVICE_CLASS, SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS, COMPONENT_CLUSTERS, HUMIDITY, TEMPERATURE, ILLUMINANCE, PRESSURE, METERING, ELECTRICAL_MEASUREMENT, - GENERIC, SENSOR_TYPE, EVENT_RELAY_CLUSTERS, LISTENER_BATTERY, UNKNOWN, + GENERIC, SENSOR_TYPE, EVENT_RELAY_CLUSTERS, UNKNOWN, OPENING, ZONE, OCCUPANCY, CLUSTER_REPORT_CONFIGS, REPORT_CONFIG_IMMEDIATE, REPORT_CONFIG_ASAP, REPORT_CONFIG_DEFAULT, REPORT_CONFIG_MIN_INT, - REPORT_CONFIG_MAX_INT, REPORT_CONFIG_OP, SIGNAL_REMOVE, NO_SENSOR_CLUSTERS) + REPORT_CONFIG_MAX_INT, REPORT_CONFIG_OP, SIGNAL_REMOVE, NO_SENSOR_CLUSTERS, + POWER_CONFIGURATION_CHANNEL) from .device import ZHADevice from ..device_entity import ZhaDeviceEntity -from .listeners import ( - LISTENER_REGISTRY, AttributeListener, EventRelayListener, ZDOListener, - BasicListener) +from .channels import ( + AttributeListeningChannel, EventRelayChannel, ZDOChannel +) +from .channels.general import BasicChannel +from .channels.registry import ZIGBEE_CHANNEL_REGISTRY from .helpers import convert_ieee _LOGGER = logging.getLogger(__name__) @@ -34,7 +37,7 @@ _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = {} BINARY_SENSOR_TYPES = {} EntityReference = collections.namedtuple( - 'EntityReference', 'reference_id zha_device cluster_listeners device_info') + 'EntityReference', 'reference_id zha_device cluster_channels device_info') class ZHAGateway: @@ -106,14 +109,14 @@ class ZHAGateway: return self._device_registry def register_entity_reference( - self, ieee, reference_id, zha_device, cluster_listeners, + self, ieee, reference_id, zha_device, cluster_channels, device_info): """Record the creation of a hass entity associated with ieee.""" self._device_registry[ieee].append( EntityReference( reference_id=reference_id, zha_device=zha_device, - cluster_listeners=cluster_listeners, + cluster_channels=cluster_channels, device_info=device_info ) ) @@ -169,14 +172,14 @@ class ZHAGateway: # available and we already loaded fresh state above zha_device.update_available(True) elif not zha_device.available and zha_device.power_source is not None\ - and zha_device.power_source != BasicListener.BATTERY: + and zha_device.power_source != BasicChannel.BATTERY: # the device is currently marked unavailable and it isn't a battery # powered device so we should be able to update it now _LOGGER.debug( "attempting to request fresh state for %s %s", zha_device.name, "with power source: {}".format( - BasicListener.POWER_SOURCES.get(zha_device.power_source) + BasicChannel.POWER_SOURCES.get(zha_device.power_source) ) ) await zha_device.async_initialize(from_cache=False) @@ -188,11 +191,11 @@ class ZHAGateway: import zigpy.profiles if endpoint_id == 0: # ZDO - await _create_cluster_listener( + await _create_cluster_channel( endpoint, zha_device, is_new_join, - listener_class=ZDOListener + channel_class=ZDOChannel ) return @@ -234,18 +237,18 @@ class ZHAGateway: )) -async def _create_cluster_listener(cluster, zha_device, is_new_join, - listeners=None, listener_class=None): - """Create a cluster listener and attach it to a device.""" - if listener_class is None: - listener_class = LISTENER_REGISTRY.get(cluster.cluster_id, - AttributeListener) - listener = listener_class(cluster, zha_device) +async def _create_cluster_channel(cluster, zha_device, is_new_join, + channels=None, channel_class=None): + """Create a cluster channel and attach it to a device.""" + if channel_class is None: + channel_class = ZIGBEE_CHANNEL_REGISTRY.get(cluster.cluster_id, + AttributeListeningChannel) + channel = channel_class(cluster, zha_device) if is_new_join: - await listener.async_configure() - zha_device.add_cluster_listener(listener) - if listeners is not None: - listeners.append(listener) + await channel.async_configure() + zha_device.add_cluster_channel(channel) + if channels is not None: + channels.append(channel) async def _dispatch_discovery_info(hass, is_new_join, discovery_info): @@ -272,23 +275,23 @@ async def _handle_profile_match(hass, endpoint, profile_clusters, zha_device, for c in profile_clusters[1] if c in endpoint.out_clusters] - listeners = [] + channels = [] cluster_tasks = [] for cluster in in_clusters: - cluster_tasks.append(_create_cluster_listener( - cluster, zha_device, is_new_join, listeners=listeners)) + cluster_tasks.append(_create_cluster_channel( + cluster, zha_device, is_new_join, channels=channels)) for cluster in out_clusters: - cluster_tasks.append(_create_cluster_listener( - cluster, zha_device, is_new_join, listeners=listeners)) + cluster_tasks.append(_create_cluster_channel( + cluster, zha_device, is_new_join, channels=channels)) await asyncio.gather(*cluster_tasks) discovery_info = { 'unique_id': device_key, 'zha_device': zha_device, - 'listeners': listeners, + 'channels': channels, 'component': component } @@ -314,7 +317,7 @@ async def _handle_single_cluster_matches(hass, endpoint, zha_device, """Dispatch single cluster matches to HA components.""" cluster_matches = [] cluster_match_tasks = [] - event_listener_tasks = [] + event_channel_tasks = [] for cluster in endpoint.in_clusters.values(): if cluster.cluster_id not in profile_clusters[0]: cluster_match_tasks.append(_handle_single_cluster_match( @@ -327,7 +330,7 @@ async def _handle_single_cluster_matches(hass, endpoint, zha_device, )) if cluster.cluster_id in NO_SENSOR_CLUSTERS: - cluster_match_tasks.append(_handle_listener_only_cluster_match( + cluster_match_tasks.append(_handle_channel_only_cluster_match( zha_device, cluster, is_new_join, @@ -345,13 +348,13 @@ async def _handle_single_cluster_matches(hass, endpoint, zha_device, )) if cluster.cluster_id in EVENT_RELAY_CLUSTERS: - event_listener_tasks.append(_create_cluster_listener( + event_channel_tasks.append(_create_cluster_channel( cluster, zha_device, is_new_join, - listener_class=EventRelayListener + channel_class=EventRelayChannel )) - await asyncio.gather(*event_listener_tasks) + await asyncio.gather(*event_channel_tasks) cluster_match_results = await asyncio.gather(*cluster_match_tasks) for cluster_match in cluster_match_results: if cluster_match is not None: @@ -359,10 +362,10 @@ async def _handle_single_cluster_matches(hass, endpoint, zha_device, return cluster_matches -async def _handle_listener_only_cluster_match( +async def _handle_channel_only_cluster_match( zha_device, cluster, is_new_join): - """Handle a listener only cluster match.""" - await _create_cluster_listener(cluster, zha_device, is_new_join) + """Handle a channel only cluster match.""" + await _create_cluster_channel(cluster, zha_device, is_new_join) async def _handle_single_cluster_match(hass, zha_device, cluster, device_key, @@ -376,15 +379,15 @@ async def _handle_single_cluster_match(hass, zha_device, cluster, device_key, if component is None or component not in COMPONENTS: return - listeners = [] - await _create_cluster_listener(cluster, zha_device, is_new_join, - listeners=listeners) + channels = [] + await _create_cluster_channel(cluster, zha_device, is_new_join, + channels=channels) cluster_key = "{}-{}".format(device_key, cluster.cluster_id) discovery_info = { 'unique_id': cluster_key, 'zha_device': zha_device, - 'listeners': listeners, + 'channels': channels, 'entity_suffix': '_{}'.format(cluster.cluster_id), 'component': component } @@ -403,11 +406,11 @@ async def _handle_single_cluster_match(hass, zha_device, cluster, device_key, def _create_device_entity(zha_device): """Create ZHADeviceEntity.""" - device_entity_listeners = [] - if LISTENER_BATTERY in zha_device.cluster_listeners: - listener = zha_device.cluster_listeners.get(LISTENER_BATTERY) - device_entity_listeners.append(listener) - return ZhaDeviceEntity(zha_device, device_entity_listeners) + device_entity_channels = [] + if POWER_CONFIGURATION_CHANNEL in zha_device.cluster_channels: + channel = zha_device.cluster_channels.get(POWER_CONFIGURATION_CHANNEL) + device_entity_channels.append(channel) + return ZhaDeviceEntity(zha_device, device_entity_channels) def establish_device_mappings(): diff --git a/homeassistant/components/zha/core/listeners.py b/homeassistant/components/zha/core/listeners.py deleted file mode 100644 index f8d24ce903c..00000000000 --- a/homeassistant/components/zha/core/listeners.py +++ /dev/null @@ -1,706 +0,0 @@ -""" -Cluster listeners for Zigbee Home Automation. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/zha/ -""" - -import asyncio -from enum import Enum -from functools import wraps -import logging -from random import uniform - -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_send -from .helpers import ( - bind_configure_reporting, construct_unique_id, - safe_read, get_attr_id_by_name, bind_cluster) -from .const import ( - CLUSTER_REPORT_CONFIGS, REPORT_CONFIG_DEFAULT, SIGNAL_ATTR_UPDATED, - SIGNAL_MOVE_LEVEL, SIGNAL_SET_LEVEL, SIGNAL_STATE_ATTR, LISTENER_BASIC, - LISTENER_ATTRIBUTE, LISTENER_ON_OFF, LISTENER_COLOR, LISTENER_FAN, - LISTENER_LEVEL, LISTENER_ZONE, LISTENER_ACTIVE_POWER, LISTENER_BATTERY, - LISTENER_EVENT_RELAY -) - -LISTENER_REGISTRY = {} - -_LOGGER = logging.getLogger(__name__) - - -def populate_listener_registry(): - """Populate the listener registry.""" - from zigpy import zcl - LISTENER_REGISTRY.update({ - zcl.clusters.general.Alarms.cluster_id: ClusterListener, - zcl.clusters.general.Commissioning.cluster_id: ClusterListener, - zcl.clusters.general.Identify.cluster_id: ClusterListener, - zcl.clusters.general.Groups.cluster_id: ClusterListener, - zcl.clusters.general.Scenes.cluster_id: ClusterListener, - zcl.clusters.general.Partition.cluster_id: ClusterListener, - zcl.clusters.general.Ota.cluster_id: ClusterListener, - zcl.clusters.general.PowerProfile.cluster_id: ClusterListener, - zcl.clusters.general.ApplianceControl.cluster_id: ClusterListener, - zcl.clusters.general.PollControl.cluster_id: ClusterListener, - zcl.clusters.general.GreenPowerProxy.cluster_id: ClusterListener, - zcl.clusters.general.OnOffConfiguration.cluster_id: ClusterListener, - zcl.clusters.general.OnOff.cluster_id: OnOffListener, - zcl.clusters.general.LevelControl.cluster_id: LevelListener, - zcl.clusters.lighting.Color.cluster_id: ColorListener, - zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id: - ActivePowerListener, - zcl.clusters.general.PowerConfiguration.cluster_id: BatteryListener, - zcl.clusters.general.Basic.cluster_id: BasicListener, - zcl.clusters.security.IasZone.cluster_id: IASZoneListener, - zcl.clusters.hvac.Fan.cluster_id: FanListener, - }) - - -def parse_and_log_command(unique_id, cluster, tsn, command_id, args): - """Parse and log a zigbee cluster command.""" - cmd = cluster.server_commands.get(command_id, [command_id])[0] - _LOGGER.debug( - "%s: received '%s' command with %s args on cluster_id '%s' tsn '%s'", - unique_id, - cmd, - args, - cluster.cluster_id, - tsn - ) - return cmd - - -def decorate_command(listener, command): - """Wrap a cluster command to make it safe.""" - @wraps(command) - async def wrapper(*args, **kwds): - from zigpy.zcl.foundation import Status - from zigpy.exceptions import DeliveryError - try: - result = await command(*args, **kwds) - _LOGGER.debug("%s: executed command: %s %s %s %s", - listener.unique_id, - command.__name__, - "{}: {}".format("with args", args), - "{}: {}".format("with kwargs", kwds), - "{}: {}".format("and result", result)) - if isinstance(result, bool): - return result - return result[1] is Status.SUCCESS - except DeliveryError: - _LOGGER.debug("%s: command failed: %s", listener.unique_id, - command.__name__) - return False - return wrapper - - -class ListenerStatus(Enum): - """Status of a listener.""" - - CREATED = 1 - CONFIGURED = 2 - INITIALIZED = 3 - - -class ClusterListener: - """Listener for a Zigbee cluster.""" - - def __init__(self, cluster, device): - """Initialize ClusterListener.""" - self.name = 'cluster_{}'.format(cluster.cluster_id) - self._cluster = cluster - self._zha_device = device - self._unique_id = construct_unique_id(cluster) - self._report_config = CLUSTER_REPORT_CONFIGS.get( - self._cluster.cluster_id, - [{'attr': 0, 'config': REPORT_CONFIG_DEFAULT}] - ) - self._status = ListenerStatus.CREATED - self._cluster.add_listener(self) - - @property - def unique_id(self): - """Return the unique id for this listener.""" - return self._unique_id - - @property - def cluster(self): - """Return the zigpy cluster for this listener.""" - return self._cluster - - @property - def device(self): - """Return the device this listener is linked to.""" - return self._zha_device - - @property - def status(self): - """Return the status of the listener.""" - return self._status - - def set_report_config(self, report_config): - """Set the reporting configuration.""" - self._report_config = report_config - - async def async_configure(self): - """Set cluster binding and attribute reporting.""" - manufacturer = None - manufacturer_code = self._zha_device.manufacturer_code - if self.cluster.cluster_id >= 0xfc00 and manufacturer_code: - manufacturer = manufacturer_code - - skip_bind = False # bind cluster only for the 1st configured attr - for report_config in self._report_config: - attr = report_config.get('attr') - min_report_interval, max_report_interval, change = \ - report_config.get('config') - await bind_configure_reporting( - self._unique_id, self.cluster, attr, - min_report=min_report_interval, - max_report=max_report_interval, - reportable_change=change, - skip_bind=skip_bind, - manufacturer=manufacturer - ) - skip_bind = True - await asyncio.sleep(uniform(0.1, 0.5)) - _LOGGER.debug( - "%s: finished listener configuration", - self._unique_id - ) - self._status = ListenerStatus.CONFIGURED - - async def async_initialize(self, from_cache): - """Initialize listener.""" - self._status = ListenerStatus.INITIALIZED - - @callback - def cluster_command(self, tsn, command_id, args): - """Handle commands received to this cluster.""" - pass - - @callback - def attribute_updated(self, attrid, value): - """Handle attribute updates on this cluster.""" - pass - - @callback - def zdo_command(self, *args, **kwargs): - """Handle ZDO commands on this cluster.""" - pass - - @callback - def zha_send_event(self, cluster, command, args): - """Relay events to hass.""" - self._zha_device.hass.bus.async_fire( - 'zha_event', - { - 'unique_id': self._unique_id, - 'device_ieee': str(self._zha_device.ieee), - 'command': command, - 'args': args - } - ) - - async def async_update(self): - """Retrieve latest state from cluster.""" - pass - - async def get_attribute_value(self, attribute, from_cache=True): - """Get the value for an attribute.""" - result = await safe_read( - self._cluster, - [attribute], - allow_cache=from_cache, - only_cache=from_cache - ) - return result.get(attribute) - - def __getattr__(self, name): - """Get attribute or a decorated cluster command.""" - if hasattr(self._cluster, name) and callable( - getattr(self._cluster, name)): - command = getattr(self._cluster, name) - command.__name__ = name - return decorate_command( - self, - command - ) - return self.__getattribute__(name) - - -class AttributeListener(ClusterListener): - """Listener for the attribute reports cluster.""" - - def __init__(self, cluster, device): - """Initialize AttributeListener.""" - super().__init__(cluster, device) - self.name = LISTENER_ATTRIBUTE - attr = self._report_config[0].get('attr') - if isinstance(attr, str): - self._value_attribute = get_attr_id_by_name(self.cluster, attr) - else: - self._value_attribute = attr - - @callback - def attribute_updated(self, attrid, value): - """Handle attribute updates on this cluster.""" - if attrid == self._value_attribute: - async_dispatcher_send( - self._zha_device.hass, - "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), - value - ) - - async def async_initialize(self, from_cache): - """Initialize listener.""" - await self.get_attribute_value( - self._report_config[0].get('attr'), from_cache=from_cache) - await super().async_initialize(from_cache) - - -class OnOffListener(ClusterListener): - """Listener for the OnOff Zigbee cluster.""" - - ON_OFF = 0 - - def __init__(self, cluster, device): - """Initialize OnOffListener.""" - super().__init__(cluster, device) - self.name = LISTENER_ON_OFF - self._state = None - - @callback - def cluster_command(self, tsn, command_id, args): - """Handle commands received to this cluster.""" - cmd = parse_and_log_command( - self.unique_id, - self._cluster, - tsn, - command_id, - args - ) - - if cmd in ('off', 'off_with_effect'): - self.attribute_updated(self.ON_OFF, False) - elif cmd in ('on', 'on_with_recall_global_scene', 'on_with_timed_off'): - self.attribute_updated(self.ON_OFF, True) - elif cmd == 'toggle': - self.attribute_updated(self.ON_OFF, not bool(self._state)) - - @callback - def attribute_updated(self, attrid, value): - """Handle attribute updates on this cluster.""" - if attrid == self.ON_OFF: - async_dispatcher_send( - self._zha_device.hass, - "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), - value - ) - self._state = bool(value) - - async def async_initialize(self, from_cache): - """Initialize listener.""" - self._state = bool( - await self.get_attribute_value(self.ON_OFF, from_cache=from_cache)) - await super().async_initialize(from_cache) - - -class LevelListener(ClusterListener): - """Listener for the LevelControl Zigbee cluster.""" - - CURRENT_LEVEL = 0 - - def __init__(self, cluster, device): - """Initialize LevelListener.""" - super().__init__(cluster, device) - self.name = LISTENER_LEVEL - - @callback - def cluster_command(self, tsn, command_id, args): - """Handle commands received to this cluster.""" - cmd = parse_and_log_command( - self.unique_id, - self._cluster, - tsn, - command_id, - args - ) - - if cmd in ('move_to_level', 'move_to_level_with_on_off'): - self.dispatch_level_change(SIGNAL_SET_LEVEL, args[0]) - elif cmd in ('move', 'move_with_on_off'): - # We should dim slowly -- for now, just step once - rate = args[1] - if args[0] == 0xff: - rate = 10 # Should read default move rate - self.dispatch_level_change( - SIGNAL_MOVE_LEVEL, -rate if args[0] else rate) - elif cmd in ('step', 'step_with_on_off'): - # Step (technically may change on/off) - self.dispatch_level_change( - SIGNAL_MOVE_LEVEL, -args[1] if args[0] else args[1]) - - @callback - def attribute_updated(self, attrid, value): - """Handle attribute updates on this cluster.""" - _LOGGER.debug("%s: received attribute: %s update with value: %i", - self.unique_id, attrid, value) - if attrid == self.CURRENT_LEVEL: - self.dispatch_level_change(SIGNAL_SET_LEVEL, value) - - def dispatch_level_change(self, command, level): - """Dispatch level change.""" - async_dispatcher_send( - self._zha_device.hass, - "{}_{}".format(self.unique_id, command), - level - ) - - async def async_initialize(self, from_cache): - """Initialize listener.""" - await self.get_attribute_value( - self.CURRENT_LEVEL, from_cache=from_cache) - await super().async_initialize(from_cache) - - -class IASZoneListener(ClusterListener): - """Listener for the IASZone Zigbee cluster.""" - - def __init__(self, cluster, device): - """Initialize LevelListener.""" - super().__init__(cluster, device) - self.name = LISTENER_ZONE - - @callback - def cluster_command(self, tsn, command_id, args): - """Handle commands received to this cluster.""" - if command_id == 0: - state = args[0] & 3 - async_dispatcher_send( - self._zha_device.hass, - "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), - state - ) - _LOGGER.debug("Updated alarm state: %s", state) - elif command_id == 1: - _LOGGER.debug("Enroll requested") - res = self._cluster.enroll_response(0, 0) - self._zha_device.hass.async_create_task(res) - - async def async_configure(self): - """Configure IAS device.""" - from zigpy.exceptions import DeliveryError - _LOGGER.debug("%s: started IASZoneListener configuration", - self._unique_id) - - await bind_cluster(self.unique_id, self._cluster) - ieee = self._cluster.endpoint.device.application.ieee - - try: - res = await self._cluster.write_attributes({'cie_addr': ieee}) - _LOGGER.debug( - "%s: wrote cie_addr: %s to '%s' cluster: %s", - self.unique_id, str(ieee), self._cluster.ep_attribute, - res[0] - ) - except DeliveryError as ex: - _LOGGER.debug( - "%s: Failed to write cie_addr: %s to '%s' cluster: %s", - self.unique_id, str(ieee), self._cluster.ep_attribute, str(ex) - ) - _LOGGER.debug("%s: finished IASZoneListener configuration", - self._unique_id) - - await self.get_attribute_value('zone_type', from_cache=False) - - @callback - def attribute_updated(self, attrid, value): - """Handle attribute updates on this cluster.""" - if attrid == 2: - value = value & 3 - async_dispatcher_send( - self._zha_device.hass, - "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), - value - ) - - async def async_initialize(self, from_cache): - """Initialize listener.""" - await self.get_attribute_value('zone_status', from_cache=from_cache) - await self.get_attribute_value('zone_state', from_cache=from_cache) - await super().async_initialize(from_cache) - - -class ActivePowerListener(AttributeListener): - """Listener that polls active power level.""" - - def __init__(self, cluster, device): - """Initialize ActivePowerListener.""" - super().__init__(cluster, device) - self.name = LISTENER_ACTIVE_POWER - - async def async_update(self): - """Retrieve latest state.""" - _LOGGER.debug("%s async_update", self.unique_id) - - # This is a polling listener. Don't allow cache. - result = await self.get_attribute_value( - LISTENER_ACTIVE_POWER, from_cache=False) - async_dispatcher_send( - self._zha_device.hass, - "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), - result - ) - - async def async_initialize(self, from_cache): - """Initialize listener.""" - await self.get_attribute_value( - LISTENER_ACTIVE_POWER, from_cache=from_cache) - await super().async_initialize(from_cache) - - -class BasicListener(ClusterListener): - """Listener to interact with the basic cluster.""" - - BATTERY = 3 - POWER_SOURCES = { - 0: 'Unknown', - 1: 'Mains (single phase)', - 2: 'Mains (3 phase)', - BATTERY: 'Battery', - 4: 'DC source', - 5: 'Emergency mains constantly powered', - 6: 'Emergency mains and transfer switch' - } - - def __init__(self, cluster, device): - """Initialize BasicListener.""" - super().__init__(cluster, device) - self.name = LISTENER_BASIC - self._power_source = None - - async def async_configure(self): - """Configure this listener.""" - await super().async_configure() - await self.async_initialize(False) - - async def async_initialize(self, from_cache): - """Initialize listener.""" - self._power_source = await self.get_attribute_value( - 'power_source', from_cache=from_cache) - await super().async_initialize(from_cache) - - def get_power_source(self): - """Get the power source.""" - return self._power_source - - -class BatteryListener(ClusterListener): - """Listener that polls active power level.""" - - def __init__(self, cluster, device): - """Initialize BatteryListener.""" - super().__init__(cluster, device) - self.name = LISTENER_BATTERY - - @callback - def attribute_updated(self, attrid, value): - """Handle attribute updates on this cluster.""" - attr = self._report_config[1].get('attr') - if isinstance(attr, str): - attr_id = get_attr_id_by_name(self.cluster, attr) - else: - attr_id = attr - if attrid == attr_id: - async_dispatcher_send( - self._zha_device.hass, - "{}_{}".format(self.unique_id, SIGNAL_STATE_ATTR), - 'battery_level', - value - ) - - async def async_initialize(self, from_cache): - """Initialize listener.""" - await self.async_read_state(from_cache) - await super().async_initialize(from_cache) - - async def async_update(self): - """Retrieve latest state.""" - await self.async_read_state(True) - - async def async_read_state(self, from_cache): - """Read data from the cluster.""" - await self.get_attribute_value( - 'battery_size', from_cache=from_cache) - await self.get_attribute_value( - 'battery_percentage_remaining', from_cache=from_cache) - await self.get_attribute_value( - 'active_power', from_cache=from_cache) - - -class EventRelayListener(ClusterListener): - """Event relay that can be attached to zigbee clusters.""" - - def __init__(self, cluster, device): - """Initialize EventRelayListener.""" - super().__init__(cluster, device) - self.name = LISTENER_EVENT_RELAY - - @callback - def attribute_updated(self, attrid, value): - """Handle an attribute updated on this cluster.""" - self.zha_send_event( - self._cluster, - SIGNAL_ATTR_UPDATED, - { - 'attribute_id': attrid, - 'attribute_name': self._cluster.attributes.get( - attrid, - ['Unknown'])[0], - 'value': value - } - ) - - @callback - def cluster_command(self, tsn, command_id, args): - """Handle a cluster command received on this cluster.""" - if self._cluster.server_commands is not None and \ - self._cluster.server_commands.get(command_id) is not None: - self.zha_send_event( - self._cluster, - self._cluster.server_commands.get(command_id)[0], - args - ) - - -class ColorListener(ClusterListener): - """Color listener.""" - - CAPABILITIES_COLOR_XY = 0x08 - CAPABILITIES_COLOR_TEMP = 0x10 - UNSUPPORTED_ATTRIBUTE = 0x86 - - def __init__(self, cluster, device): - """Initialize ColorListener.""" - super().__init__(cluster, device) - self.name = LISTENER_COLOR - self._color_capabilities = None - - def get_color_capabilities(self): - """Return the color capabilities.""" - return self._color_capabilities - - async def async_initialize(self, from_cache): - """Initialize listener.""" - capabilities = await self.get_attribute_value( - 'color_capabilities', from_cache=from_cache) - - if capabilities is None: - # ZCL Version 4 devices don't support the color_capabilities - # attribute. In this version XY support is mandatory, but we - # need to probe to determine if the device supports color - # temperature. - capabilities = self.CAPABILITIES_COLOR_XY - result = await self.get_attribute_value( - 'color_temperature', from_cache=from_cache) - - if result is not self.UNSUPPORTED_ATTRIBUTE: - capabilities |= self.CAPABILITIES_COLOR_TEMP - self._color_capabilities = capabilities - await super().async_initialize(from_cache) - - -class FanListener(ClusterListener): - """Fan listener.""" - - _value_attribute = 0 - - def __init__(self, cluster, device): - """Initialize FanListener.""" - super().__init__(cluster, device) - self.name = LISTENER_FAN - - async def async_set_speed(self, value) -> None: - """Set the speed of the fan.""" - from zigpy.exceptions import DeliveryError - try: - await self.cluster.write_attributes({'fan_mode': value}) - except DeliveryError as ex: - _LOGGER.error("%s: Could not set speed: %s", self.unique_id, ex) - return - - async def async_update(self): - """Retrieve latest state.""" - result = await self.get_attribute_value('fan_mode', from_cache=True) - - async_dispatcher_send( - self._zha_device.hass, - "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), - result - ) - - def attribute_updated(self, attrid, value): - """Handle attribute update from fan cluster.""" - attr_name = self.cluster.attributes.get(attrid, [attrid])[0] - _LOGGER.debug("%s: Attribute report '%s'[%s] = %s", - self.unique_id, self.cluster.name, attr_name, value) - if attrid == self._value_attribute: - async_dispatcher_send( - self._zha_device.hass, - "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), - value - ) - - async def async_initialize(self, from_cache): - """Initialize listener.""" - await self.get_attribute_value( - self._value_attribute, from_cache=from_cache) - await super().async_initialize(from_cache) - - -class ZDOListener: - """Listener for ZDO events.""" - - def __init__(self, cluster, device): - """Initialize ZDOListener.""" - self.name = 'zdo' - self._cluster = cluster - self._zha_device = device - self._status = ListenerStatus.CREATED - self._unique_id = "{}_ZDO".format(device.name) - self._cluster.add_listener(self) - - @property - def unique_id(self): - """Return the unique id for this listener.""" - return self._unique_id - - @property - def cluster(self): - """Return the aigpy cluster for this listener.""" - return self._cluster - - @property - def status(self): - """Return the status of the listener.""" - return self._status - - @callback - def device_announce(self, zigpy_device): - """Device announce handler.""" - pass - - @callback - def permit_duration(self, duration): - """Permit handler.""" - pass - - async def async_initialize(self, from_cache): - """Initialize listener.""" - self._status = ListenerStatus.INITIALIZED - - async def async_configure(self): - """Configure listener.""" - self._status = ListenerStatus.CONFIGURED diff --git a/homeassistant/components/zha/device_entity.py b/homeassistant/components/zha/device_entity.py index e8b765a07a6..5632c849d59 100644 --- a/homeassistant/components/zha/device_entity.py +++ b/homeassistant/components/zha/device_entity.py @@ -11,7 +11,7 @@ import time from homeassistant.core import callback from homeassistant.util import slugify from .entity import ZhaEntity -from .const import LISTENER_BATTERY, SIGNAL_STATE_ATTR +from .const import POWER_CONFIGURATION_CHANNEL, SIGNAL_STATE_ATTR _LOGGER = logging.getLogger(__name__) @@ -38,7 +38,7 @@ STATE_OFFLINE = 'offline' class ZhaDeviceEntity(ZhaEntity): """A base class for ZHA devices.""" - def __init__(self, zha_device, listeners, keepalive_interval=7200, + def __init__(self, zha_device, channels, keepalive_interval=7200, **kwargs): """Init ZHA endpoint entity.""" ieee = zha_device.ieee @@ -55,7 +55,7 @@ class ZhaDeviceEntity(ZhaEntity): unique_id = str(ieeetail) kwargs['component'] = 'zha' - super().__init__(unique_id, zha_device, listeners, skip_entity_id=True, + super().__init__(unique_id, zha_device, channels, skip_entity_id=True, **kwargs) self._keepalive_interval = keepalive_interval @@ -66,7 +66,8 @@ class ZhaDeviceEntity(ZhaEntity): 'rssi': zha_device.rssi, }) self._should_poll = True - self._battery_listener = self.cluster_listeners.get(LISTENER_BATTERY) + self._battery_channel = self.cluster_channels.get( + POWER_CONFIGURATION_CHANNEL) @property def state(self) -> str: @@ -97,9 +98,9 @@ class ZhaDeviceEntity(ZhaEntity): async def async_added_to_hass(self): """Run when about to be added to hass.""" await super().async_added_to_hass() - if self._battery_listener: + if self._battery_channel: await self.async_accept_signal( - self._battery_listener, SIGNAL_STATE_ATTR, + self._battery_channel, SIGNAL_STATE_ATTR, self.async_update_state_attribute) # only do this on add to HA because it is static await self._async_init_battery_values() @@ -114,7 +115,7 @@ class ZhaDeviceEntity(ZhaEntity): self._zha_device.update_available(False) else: self._zha_device.update_available(True) - if self._battery_listener: + if self._battery_channel: await self.async_get_latest_battery_reading() @callback @@ -127,14 +128,14 @@ class ZhaDeviceEntity(ZhaEntity): super().async_set_available(available) async def _async_init_battery_values(self): - """Get initial battery level and battery info from listener cache.""" - battery_size = await self._battery_listener.get_attribute_value( + """Get initial battery level and battery info from channel cache.""" + battery_size = await self._battery_channel.get_attribute_value( 'battery_size') if battery_size is not None: self._device_state_attributes['battery_size'] = BATTERY_SIZES.get( battery_size, 'Unknown') - battery_quantity = await self._battery_listener.get_attribute_value( + battery_quantity = await self._battery_channel.get_attribute_value( 'battery_quantity') if battery_quantity is not None: self._device_state_attributes['battery_quantity'] = \ @@ -142,8 +143,8 @@ class ZhaDeviceEntity(ZhaEntity): await self.async_get_latest_battery_reading() async def async_get_latest_battery_reading(self): - """Get the latest battery reading from listeners cache.""" - battery = await self._battery_listener.get_attribute_value( + """Get the latest battery reading from channels cache.""" + battery = await self._battery_channel.get_attribute_value( 'battery_percentage_remaining') if battery is not None: self._device_state_attributes['battery_level'] = battery diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index d914a76c4ce..2f5aed4ca29 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -27,7 +27,7 @@ class ZhaEntity(entity.Entity): _domain = None # Must be overridden by subclasses - def __init__(self, unique_id, zha_device, listeners, + def __init__(self, unique_id, zha_device, channels, skip_entity_id=False, **kwargs): """Init ZHA entity.""" self._force_update = False @@ -48,25 +48,25 @@ class ZhaEntity(entity.Entity): slugify(zha_device.manufacturer), slugify(zha_device.model), ieeetail, - listeners[0].cluster.endpoint.endpoint_id, + channels[0].cluster.endpoint.endpoint_id, kwargs.get(ENTITY_SUFFIX, ''), ) else: self.entity_id = "{}.zha_{}_{}{}".format( self._domain, ieeetail, - listeners[0].cluster.endpoint.endpoint_id, + channels[0].cluster.endpoint.endpoint_id, kwargs.get(ENTITY_SUFFIX, ''), ) self._state = None self._device_state_attributes = {} self._zha_device = zha_device - self.cluster_listeners = {} + self.cluster_channels = {} self._available = False self._component = kwargs['component'] self._unsubs = [] - for listener in listeners: - self.cluster_listeners[listener.name] = listener + for channel in channels: + self.cluster_channels[channel.name] = channel @property def name(self): @@ -147,7 +147,7 @@ class ZhaEntity(entity.Entity): ) self._zha_device.gateway.register_entity_reference( self._zha_device.ieee, self.entity_id, self._zha_device, - self.cluster_listeners, self.device_info) + self.cluster_channels, self.device_info) async def async_will_remove_from_hass(self) -> None: """Disconnect entity object when removed.""" @@ -156,13 +156,13 @@ class ZhaEntity(entity.Entity): async def async_update(self): """Retrieve latest state.""" - for listener in self.cluster_listeners: - if hasattr(listener, 'async_update'): - await listener.async_update() + for channel in self.cluster_channels: + if hasattr(channel, 'async_update'): + await channel.async_update() - async def async_accept_signal(self, listener, signal, func, + async def async_accept_signal(self, channel, signal, func, signal_override=False): - """Accept a signal from a listener.""" + """Accept a signal from a channel.""" unsub = None if signal_override: unsub = async_dispatcher_connect( @@ -173,7 +173,7 @@ class ZhaEntity(entity.Entity): else: unsub = async_dispatcher_connect( self.hass, - "{}_{}".format(listener.unique_id, signal), + "{}_{}".format(channel.unique_id, signal), func ) self._unsubs.append(unsub) diff --git a/homeassistant/components/zha/fan.py b/homeassistant/components/zha/fan.py index dfe3c8cdd23..761dfaede1e 100644 --- a/homeassistant/components/zha/fan.py +++ b/homeassistant/components/zha/fan.py @@ -11,7 +11,7 @@ from homeassistant.components.fan import ( FanEntity) from homeassistant.helpers.dispatcher import async_dispatcher_connect from .core.const import ( - DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW, LISTENER_FAN, + DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW, FAN_CHANNEL, SIGNAL_ATTR_UPDATED ) from .entity import ZhaEntity @@ -81,16 +81,16 @@ class ZhaFan(ZhaEntity, FanEntity): _domain = DOMAIN - def __init__(self, unique_id, zha_device, listeners, **kwargs): + def __init__(self, unique_id, zha_device, channels, **kwargs): """Init this sensor.""" - super().__init__(unique_id, zha_device, listeners, **kwargs) - self._fan_listener = self.cluster_listeners.get(LISTENER_FAN) + super().__init__(unique_id, zha_device, channels, **kwargs) + self._fan_channel = self.cluster_channels.get(FAN_CHANNEL) async def async_added_to_hass(self): """Run when about to be added to hass.""" await super().async_added_to_hass() await self.async_accept_signal( - self._fan_listener, SIGNAL_ATTR_UPDATED, self.async_set_state) + self._fan_channel, SIGNAL_ATTR_UPDATED, self.async_set_state) @property def supported_features(self) -> int: @@ -120,7 +120,7 @@ class ZhaFan(ZhaEntity, FanEntity): return self.state_attributes def async_set_state(self, state): - """Handle state update from listener.""" + """Handle state update from channel.""" self._state = VALUE_TO_SPEED.get(state, self._state) self.async_schedule_update_ha_state() @@ -137,5 +137,5 @@ class ZhaFan(ZhaEntity, FanEntity): async def async_set_speed(self, speed: str) -> None: """Set the speed of the fan.""" - await self._fan_listener.async_set_speed(SPEED_TO_VALUE[speed]) + await self._fan_channel.async_set_speed(SPEED_TO_VALUE[speed]) self.async_set_state(speed) diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index 09f1812cd76..efa6f679ae8 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -10,8 +10,8 @@ from homeassistant.components import light from homeassistant.helpers.dispatcher import async_dispatcher_connect import homeassistant.util.color as color_util from .const import ( - DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW, LISTENER_COLOR, - LISTENER_ON_OFF, LISTENER_LEVEL, SIGNAL_ATTR_UPDATED, SIGNAL_SET_LEVEL + DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW, COLOR_CHANNEL, + ON_OFF_CHANNEL, LEVEL_CHANNEL, SIGNAL_ATTR_UPDATED, SIGNAL_SET_LEVEL ) from .entity import ZhaEntity @@ -67,24 +67,24 @@ class Light(ZhaEntity, light.Light): _domain = light.DOMAIN - def __init__(self, unique_id, zha_device, listeners, **kwargs): + def __init__(self, unique_id, zha_device, channels, **kwargs): """Initialize the ZHA light.""" - super().__init__(unique_id, zha_device, listeners, **kwargs) + super().__init__(unique_id, zha_device, channels, **kwargs) self._supported_features = 0 self._color_temp = None self._hs_color = None self._brightness = None - self._on_off_listener = self.cluster_listeners.get(LISTENER_ON_OFF) - self._level_listener = self.cluster_listeners.get(LISTENER_LEVEL) - self._color_listener = self.cluster_listeners.get(LISTENER_COLOR) + self._on_off_channel = self.cluster_channels.get(ON_OFF_CHANNEL) + self._level_channel = self.cluster_channels.get(LEVEL_CHANNEL) + self._color_channel = self.cluster_channels.get(COLOR_CHANNEL) - if self._level_listener: + if self._level_channel: self._supported_features |= light.SUPPORT_BRIGHTNESS self._supported_features |= light.SUPPORT_TRANSITION self._brightness = 0 - if self._color_listener: - color_capabilities = self._color_listener.get_color_capabilities() + if self._color_channel: + color_capabilities = self._color_channel.get_color_capabilities() if color_capabilities & CAPABILITIES_COLOR_TEMP: self._supported_features |= light.SUPPORT_COLOR_TEMP @@ -139,10 +139,10 @@ class Light(ZhaEntity, light.Light): """Run when about to be added to hass.""" await super().async_added_to_hass() await self.async_accept_signal( - self._on_off_listener, SIGNAL_ATTR_UPDATED, self.async_set_state) - if self._level_listener: + self._on_off_channel, SIGNAL_ATTR_UPDATED, self.async_set_state) + if self._level_channel: await self.async_accept_signal( - self._level_listener, SIGNAL_SET_LEVEL, self.set_level) + self._level_channel, SIGNAL_SET_LEVEL, self.set_level) async def async_turn_on(self, **kwargs): """Turn the entity on.""" @@ -152,7 +152,7 @@ class Light(ZhaEntity, light.Light): if light.ATTR_COLOR_TEMP in kwargs and \ self.supported_features & light.SUPPORT_COLOR_TEMP: temperature = kwargs[light.ATTR_COLOR_TEMP] - success = await self._color_listener.move_to_color_temp( + success = await self._color_channel.move_to_color_temp( temperature, duration) if not success: return @@ -162,7 +162,7 @@ class Light(ZhaEntity, light.Light): self.supported_features & light.SUPPORT_COLOR: hs_color = kwargs[light.ATTR_HS_COLOR] xy_color = color_util.color_hs_to_xy(*hs_color) - success = await self._color_listener.move_to_color( + success = await self._color_channel.move_to_color( int(xy_color[0] * 65535), int(xy_color[1] * 65535), duration, @@ -174,7 +174,7 @@ class Light(ZhaEntity, light.Light): if self._brightness is not None: brightness = kwargs.get( light.ATTR_BRIGHTNESS, self._brightness or 255) - success = await self._level_listener.move_to_level_with_on_off( + success = await self._level_channel.move_to_level_with_on_off( brightness, duration ) @@ -185,7 +185,7 @@ class Light(ZhaEntity, light.Light): self.async_schedule_update_ha_state() return - success = await self._on_off_listener.on() + success = await self._on_off_channel.on() if not success: return @@ -198,12 +198,12 @@ class Light(ZhaEntity, light.Light): supports_level = self.supported_features & light.SUPPORT_BRIGHTNESS success = None if duration and supports_level: - success = await self._level_listener.move_to_level_with_on_off( + success = await self._level_channel.move_to_level_with_on_off( 0, duration*10 ) else: - success = await self._on_off_listener.off() + success = await self._on_off_channel.off() _LOGGER.debug("%s was turned off: %s", self.entity_id, success) if not success: return diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 9c00d8124bb..6dcdbb845dc 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -12,7 +12,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from .core.const import ( DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW, HUMIDITY, TEMPERATURE, ILLUMINANCE, PRESSURE, METERING, ELECTRICAL_MEASUREMENT, - GENERIC, SENSOR_TYPE, LISTENER_ATTRIBUTE, LISTENER_ACTIVE_POWER, + GENERIC, SENSOR_TYPE, ATTRIBUTE_CHANNEL, ELECTRICAL_MEASUREMENT_CHANNEL, SIGNAL_ATTR_UPDATED, SIGNAL_STATE_ATTR) from .entity import ZhaEntity @@ -74,8 +74,8 @@ UNIT_REGISTRY = { GENERIC: None } -LISTENER_REGISTRY = { - ELECTRICAL_MEASUREMENT: LISTENER_ACTIVE_POWER, +CHANNEL_REGISTRY = { + ELECTRICAL_MEASUREMENT: ELECTRICAL_MEASUREMENT_CHANNEL, } POLLING_REGISTRY = { @@ -130,9 +130,9 @@ class Sensor(ZhaEntity): _domain = DOMAIN - def __init__(self, unique_id, zha_device, listeners, **kwargs): + def __init__(self, unique_id, zha_device, channels, **kwargs): """Init this sensor.""" - super().__init__(unique_id, zha_device, listeners, **kwargs) + super().__init__(unique_id, zha_device, channels, **kwargs) sensor_type = kwargs.get(SENSOR_TYPE, GENERIC) self._unit = UNIT_REGISTRY.get(sensor_type) self._formatter_function = FORMATTER_FUNC_REGISTRY.get( @@ -147,17 +147,17 @@ class Sensor(ZhaEntity): sensor_type, False ) - self._listener = self.cluster_listeners.get( - LISTENER_REGISTRY.get(sensor_type, LISTENER_ATTRIBUTE) + self._channel = self.cluster_channels.get( + CHANNEL_REGISTRY.get(sensor_type, ATTRIBUTE_CHANNEL) ) async def async_added_to_hass(self): """Run when about to be added to hass.""" await super().async_added_to_hass() await self.async_accept_signal( - self._listener, SIGNAL_ATTR_UPDATED, self.async_set_state) + self._channel, SIGNAL_ATTR_UPDATED, self.async_set_state) await self.async_accept_signal( - self._listener, SIGNAL_STATE_ATTR, + self._channel, SIGNAL_STATE_ATTR, self.async_update_state_attribute) @property @@ -175,6 +175,6 @@ class Sensor(ZhaEntity): return self._state def async_set_state(self, state): - """Handle state update from listener.""" + """Handle state update from channel.""" self._state = self._formatter_function(state) self.async_schedule_update_ha_state() diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py index 4eee3d5da35..bdbdd7a6a76 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -9,7 +9,7 @@ import logging from homeassistant.components.switch import DOMAIN, SwitchDevice from homeassistant.helpers.dispatcher import async_dispatcher_connect from .core.const import ( - DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW, LISTENER_ON_OFF, + DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW, ON_OFF_CHANNEL, SIGNAL_ATTR_UPDATED ) from .entity import ZhaEntity @@ -60,7 +60,7 @@ class Switch(ZhaEntity, SwitchDevice): def __init__(self, **kwargs): """Initialize the ZHA switch.""" super().__init__(**kwargs) - self._on_off_listener = self.cluster_listeners.get(LISTENER_ON_OFF) + self._on_off_channel = self.cluster_channels.get(ON_OFF_CHANNEL) @property def is_on(self) -> bool: @@ -71,14 +71,14 @@ class Switch(ZhaEntity, SwitchDevice): async def async_turn_on(self, **kwargs): """Turn the entity on.""" - await self._on_off_listener.on() + await self._on_off_channel.on() async def async_turn_off(self, **kwargs): """Turn the entity off.""" - await self._on_off_listener.off() + await self._on_off_channel.off() def async_set_state(self, state): - """Handle state update from listener.""" + """Handle state update from channel.""" self._state = bool(state) self.async_schedule_update_ha_state() @@ -91,4 +91,4 @@ class Switch(ZhaEntity, SwitchDevice): """Run when about to be added to hass.""" await super().async_added_to_hass() await self.async_accept_signal( - self._on_off_listener, SIGNAL_ATTR_UPDATED, self.async_set_state) + self._on_off_channel, SIGNAL_ATTR_UPDATED, self.async_set_state) diff --git a/tests/components/zha/conftest.py b/tests/components/zha/conftest.py index c806b1a2217..bd594941da1 100644 --- a/tests/components/zha/conftest.py +++ b/tests/components/zha/conftest.py @@ -7,8 +7,8 @@ from homeassistant.components.zha.core.const import ( ) from homeassistant.components.zha.core.gateway import ZHAGateway from homeassistant.components.zha.core.gateway import establish_device_mappings -from homeassistant.components.zha.core.listeners \ - import populate_listener_registry +from homeassistant.components.zha.core.channels.registry \ + import populate_channel_registry from .common import async_setup_entry @@ -28,7 +28,7 @@ def zha_gateway_fixture(hass): Create a ZHAGateway object that can be used to interact with as if we had a real zigbee network running. """ - populate_listener_registry() + populate_channel_registry() establish_device_mappings() for component in COMPONENTS: hass.data[DATA_ZHA][component] = ( From be26fc896d4bac88561ea2e26d5b8e68eef762ce Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 20 Feb 2019 04:10:42 -0700 Subject: [PATCH 238/242] Fix an Ambient PWS exception when location info is missing (#21220) --- homeassistant/components/ambient_station/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index 4a7864d3f7f..4464992e5fa 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -339,8 +339,10 @@ class AmbientStation: self.stations[station['macAddress']] = { ATTR_LAST_DATA: station['lastData'], - ATTR_LOCATION: station['info']['location'], - ATTR_NAME: station['info']['name'], + ATTR_LOCATION: station.get('info', {}).get('location'), + ATTR_NAME: + station.get('info', {}).get( + 'name', station['macAddress']), } for component in ('binary_sensor', 'sensor'): From fd3bea177bf34c6b5489bc0139c407c1a7cbdb98 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 19 Feb 2019 23:02:56 -0800 Subject: [PATCH 239/242] Prevent invalid context from crashing (#21231) * Prevent invalid context from crashing * Lint --- homeassistant/core.py | 5 +- tests/test_core.py | 105 ++++++++++++++++++++++++------------------ 2 files changed, 64 insertions(+), 46 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 5cd23e9f9a2..e7f654f5184 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -744,7 +744,10 @@ class State: context = json_dict.get('context') if context: - context = Context(**context) + context = Context( + id=context.get('id'), + user_id=context.get('user_id'), + ) return cls(json_dict['entity_id'], json_dict['state'], json_dict.get('attributes'), last_changed, last_updated, diff --git a/tests/test_core.py b/tests/test_core.py index 3cb5b87b4bb..4acb1de6677 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -460,61 +460,76 @@ class TestEventBus(unittest.TestCase): assert len(coroutine_calls) == 1 -class TestState(unittest.TestCase): - """Test State methods.""" +def test_state_init(): + """Test state.init.""" + with pytest.raises(InvalidEntityFormatError): + ha.State('invalid_entity_format', 'test_state') - def test_init(self): - """Test state.init.""" - with pytest.raises(InvalidEntityFormatError): - ha.State('invalid_entity_format', 'test_state') + with pytest.raises(InvalidStateError): + ha.State('domain.long_state', 't' * 256) - with pytest.raises(InvalidStateError): - ha.State('domain.long_state', 't' * 256) - def test_domain(self): - """Test domain.""" - state = ha.State('some_domain.hello', 'world') - assert 'some_domain' == state.domain +def test_state_domain(): + """Test domain.""" + state = ha.State('some_domain.hello', 'world') + assert 'some_domain' == state.domain - def test_object_id(self): - """Test object ID.""" - state = ha.State('domain.hello', 'world') - assert 'hello' == state.object_id - def test_name_if_no_friendly_name_attr(self): - """Test if there is no friendly name.""" - state = ha.State('domain.hello_world', 'world') - assert 'hello world' == state.name +def test_state_object_id(): + """Test object ID.""" + state = ha.State('domain.hello', 'world') + assert 'hello' == state.object_id - def test_name_if_friendly_name_attr(self): - """Test if there is a friendly name.""" - name = 'Some Unique Name' - state = ha.State('domain.hello_world', 'world', - {ATTR_FRIENDLY_NAME: name}) - assert name == state.name - def test_dict_conversion(self): - """Test conversion of dict.""" - state = ha.State('domain.hello', 'world', {'some': 'attr'}) - assert state == ha.State.from_dict(state.as_dict()) +def test_state_name_if_no_friendly_name_attr(): + """Test if there is no friendly name.""" + state = ha.State('domain.hello_world', 'world') + assert 'hello world' == state.name - def test_dict_conversion_with_wrong_data(self): - """Test conversion with wrong data.""" - assert ha.State.from_dict(None) is None - assert ha.State.from_dict({'state': 'yes'}) is None - assert ha.State.from_dict({'entity_id': 'yes'}) is None - def test_repr(self): - """Test state.repr.""" - assert "" == \ - str(ha.State( - "happy.happy", "on", - last_changed=datetime(1984, 12, 8, 12, 0, 0))) +def test_state_name_if_friendly_name_attr(): + """Test if there is a friendly name.""" + name = 'Some Unique Name' + state = ha.State('domain.hello_world', 'world', + {ATTR_FRIENDLY_NAME: name}) + assert name == state.name - assert "" == \ - str(ha.State("happy.happy", "on", {"brightness": 144}, - datetime(1984, 12, 8, 12, 0, 0))) + +def test_state_dict_conversion(): + """Test conversion of dict.""" + state = ha.State('domain.hello', 'world', {'some': 'attr'}) + assert state == ha.State.from_dict(state.as_dict()) + + +def test_state_dict_conversion_with_wrong_data(): + """Test conversion with wrong data.""" + assert ha.State.from_dict(None) is None + assert ha.State.from_dict({'state': 'yes'}) is None + assert ha.State.from_dict({'entity_id': 'yes'}) is None + # Make sure invalid context data doesn't crash + wrong_context = ha.State.from_dict({ + 'entity_id': 'light.kitchen', + 'state': 'on', + 'context': { + 'id': '123', + 'non-existing': 'crash' + } + }) + assert wrong_context is not None + assert wrong_context.context.id == '123' + + +def test_state_repr(): + """Test state.repr.""" + assert "" == \ + str(ha.State( + "happy.happy", "on", + last_changed=datetime(1984, 12, 8, 12, 0, 0))) + + assert "" == \ + str(ha.State("happy.happy", "on", {"brightness": 144}, + datetime(1984, 12, 8, 12, 0, 0))) class TestStateMachine(unittest.TestCase): From 7dd3fc7ca7b497259d8748901f26db2f6026ed14 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 20 Feb 2019 08:58:37 -0800 Subject: [PATCH 240/242] Bumped version to 0.88.0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 1924d145529..9dbb06e8adf 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 88 -PATCH_VERSION = '0b4' +PATCH_VERSION = '0' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From 9f99e173def241fbc179393feff050ba21265fbc Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Wed, 20 Feb 2019 10:27:03 -0500 Subject: [PATCH 241/242] Don't dispatch to components when there are no channels for ZHA sensors (#21223) * don't dispatch when channels don't exist * review comment --- homeassistant/components/zha/core/gateway.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 4fbf96a22b6..cd549afc819 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -253,6 +253,10 @@ async def _create_cluster_channel(cluster, zha_device, is_new_join, async def _dispatch_discovery_info(hass, is_new_join, discovery_info): """Dispatch or store discovery information.""" + if not discovery_info['channels']: + _LOGGER.warning( + "there are no channels in the discovery info: %s", discovery_info) + return component = discovery_info['component'] if is_new_join: async_dispatcher_send( From 67008e094788ed6f6248753230d6451f9b2f40dc Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Wed, 20 Feb 2019 10:33:29 -0500 Subject: [PATCH 242/242] Fix bug in ZHA and tweak non sensor channel logic (#21234) * fix race condition and prevent profiles from stealing channels * fix battery voltage --- .../components/zha/core/channels/general.py | 2 +- homeassistant/components/zha/core/device.py | 10 +++++++++ homeassistant/components/zha/core/gateway.py | 21 +++++++++++-------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/zha/core/channels/general.py b/homeassistant/components/zha/core/channels/general.py index bc015ae47f0..a29b23d340b 100644 --- a/homeassistant/components/zha/core/channels/general.py +++ b/homeassistant/components/zha/core/channels/general.py @@ -199,4 +199,4 @@ class PowerConfigurationChannel(ZigbeeChannel): await self.get_attribute_value( 'battery_percentage_remaining', from_cache=from_cache) await self.get_attribute_value( - 'active_power', from_cache=from_cache) + 'battery_voltage', from_cache=from_cache) diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 3a012ed7895..12bb397fbc3 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -5,6 +5,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/zha/ """ import asyncio +from enum import Enum import logging from homeassistant.helpers.dispatcher import ( @@ -23,6 +24,13 @@ from .channels.general import BasicChannel _LOGGER = logging.getLogger(__name__) +class DeviceStatus(Enum): + """Status of a device.""" + + CREATED = 1 + INITIALIZED = 2 + + class ZHADevice: """ZHA Zigbee device object.""" @@ -61,6 +69,7 @@ class ZHADevice: self._zigpy_device.__class__.__name__ ) self.power_source = None + self.status = DeviceStatus.CREATED @property def name(self): @@ -186,6 +195,7 @@ class ZHADevice: self.name, BasicChannel.POWER_SOURCES.get(self.power_source) ) + self.status = DeviceStatus.INITIALIZED _LOGGER.debug('%s: completed initialization', self.name) async def _execute_channel_tasks(self, task_name, *args): diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index cd549afc819..a50bfeae1be 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -23,7 +23,7 @@ from .const import ( REPORT_CONFIG_ASAP, REPORT_CONFIG_DEFAULT, REPORT_CONFIG_MIN_INT, REPORT_CONFIG_MAX_INT, REPORT_CONFIG_OP, SIGNAL_REMOVE, NO_SENSOR_CLUSTERS, POWER_CONFIGURATION_CHANNEL) -from .device import ZHADevice +from .device import ZHADevice, DeviceStatus from ..device_entity import ZhaDeviceEntity from .channels import ( AttributeListeningChannel, EventRelayChannel, ZDOChannel @@ -139,7 +139,9 @@ class ZHAGateway: """Update device that has just become available.""" if sender.ieee in self.devices: device = self.devices[sender.ieee] - device.update_available(True) + # avoid a race condition during new joins + if device.status is DeviceStatus.INITIALIZED: + device.update_available(True) async def async_device_initialized(self, device, is_new_join): """Handle device joined and basic information discovered (async).""" @@ -323,6 +325,14 @@ async def _handle_single_cluster_matches(hass, endpoint, zha_device, cluster_match_tasks = [] event_channel_tasks = [] for cluster in endpoint.in_clusters.values(): + # don't let profiles prevent these channels from being created + if cluster.cluster_id in NO_SENSOR_CLUSTERS: + cluster_match_tasks.append(_handle_channel_only_cluster_match( + zha_device, + cluster, + is_new_join, + )) + if cluster.cluster_id not in profile_clusters[0]: cluster_match_tasks.append(_handle_single_cluster_match( hass, @@ -333,13 +343,6 @@ async def _handle_single_cluster_matches(hass, endpoint, zha_device, is_new_join, )) - if cluster.cluster_id in NO_SENSOR_CLUSTERS: - cluster_match_tasks.append(_handle_channel_only_cluster_match( - zha_device, - cluster, - is_new_join, - )) - for cluster in endpoint.out_clusters.values(): if cluster.cluster_id not in profile_clusters[1]: cluster_match_tasks.append(_handle_single_cluster_match(