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 8701c68292084..ba9d32e0daf47 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 8cda30c7171a5..64583a5ab385e 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 9c4c127f52e1f..c72f0f22827aa 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -30,7 +30,7 @@ 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 cc931b797d410..5c7ac9a5d00df 100644 --- a/homeassistant/components/device_tracker/synology_srm.py +++ b/homeassistant/components/device_tracker/synology_srm.py @@ -13,7 +13,7 @@ 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 de8ad8c7914c4..2031eeaec1935 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 ed3b409c49d42..8b7d78aa038d9 100644 --- a/homeassistant/components/sensor/dsmr.py +++ b/homeassistant/components/sensor/dsmr.py @@ -24,9 +24,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 @@ 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 connect_and_reconnect(): 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 @@ def state(self): 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 673cadd620835..8d9df11116a71 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 | 139 ++++++++---------- homeassistant/components/googlehome.py | 86 +++++++++++ requirements_all.txt | 6 +- 5 files changed, 161 insertions(+), 79 deletions(-) create mode 100644 homeassistant/components/googlehome.py diff --git a/.coveragerc b/.coveragerc index f1ff771558019..722f74a0b6a04 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 98eaca900764d..a0d67c6191df8 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 daa36d1d2c71d..ba6d708295acb 100644 --- a/homeassistant/components/device_tracker/googlehome.py +++ b/homeassistant/components/device_tracker/googlehome.py @@ -5,91 +5,82 @@ 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.last_results = {} - - 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): + 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 + + 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 + + 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 0000000000000..78bd2d7df3f51 --- /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 2031eeaec1935..5faf6f2c6d0a0 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 0000000000000..290682549f58d --- /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 980655bac78a8..fe1fa31801690 100644 --- a/tests/components/sensor/test_mqtt_room.py +++ b/tests/components/sensor/test_mqtt_room.py @@ -10,7 +10,7 @@ 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 977cd96698158..7bcfb11ff5c60 100644 --- a/tests/components/test_snips.py +++ b/tests/components/test_snips.py @@ -10,11 +10,13 @@ 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 528ad19519579..c2e8eb1eb2867 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,7 +14,7 @@ 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 6d4e28243ea78..947e291630097 100644 --- a/homeassistant/components/alarm_control_panel/abode.py +++ b/homeassistant/components/alarm_control_panel/abode.py @@ -57,11 +57,6 @@ def state(self): 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 0000000000000..b78d981c6954d --- /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 c3c6c1e211400..010612acc7d89 100644 --- a/homeassistant/components/velux.py +++ b/homeassistant/components/velux.py @@ -14,10 +14,10 @@ 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 @@ def __init__(self, hass, config): 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 5faf6f2c6d0a0..8908e251afc97 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 25e7a72db90ba..ca2d8c1a1691f 100644 --- a/homeassistant/components/xiaomi_aqara.py +++ b/homeassistant/components/xiaomi_aqara.py @@ -324,7 +324,7 @@ def gateway(sid): # 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 e97972dae3bd0..f69d0b285ba1f 100644 --- a/homeassistant/components/sensor/cpuspeed.py +++ b/homeassistant/components/sensor/cpuspeed.py @@ -21,6 +21,9 @@ 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 @@ def unit_of_measurement(self): 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 @@ def update(self): 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 195eacf17c22c..1f7f9c3a6861f 100644 --- a/homeassistant/components/locative/__init__.py +++ b/homeassistant/components/locative/__init__.py @@ -48,8 +48,8 @@ def _validate_test_mode(obj: Dict) -> Dict: 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 57500fcc8a63e..82815d11a6e86 100644 --- a/homeassistant/components/blink/__init__.py +++ b/homeassistant/components/blink/__init__.py @@ -15,7 +15,7 @@ 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 8908e251afc97..f9e4d5f9352fb 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 249f542325f23..b678fcd2799cd 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 168cde4a792e9..0609b75f98db8 100644 --- a/homeassistant/components/media_player/pjlink.py +++ b/homeassistant/components/media_player/pjlink.py @@ -93,15 +93,31 @@ def projector(self): 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 50463c28bd143..67bc97da9924d 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -345,14 +345,21 @@ def _comp_error(ex, domain, config): _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 9ab7b56c0ca6e..1bf7235713afb 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 9f34580344cff..35229c2a80549 100644 --- a/homeassistant/components/sensor/influxdb.py +++ b/homeassistant/components/sensor/influxdb.py @@ -111,7 +111,7 @@ def __init__(self, hass, influx_conf, query): 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 0000000000000..a406a7ec2d827 --- /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 0bb34aee7e190..67c55dad0d1fd 100644 --- a/homeassistant/components/media_player/yamaha.py +++ b/homeassistant/components/media_player/yamaha.py @@ -21,7 +21,7 @@ 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 f9e4d5f9352fb..b6027a4b47836 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 a56626734eb1f..e95d1ec35f55e 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 5613bdff3ff69..8f3afef16cd99 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,7 +24,7 @@ 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 b6027a4b47836..5fa560e9258b9 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 e95d1ec35f55e..50a61ee2acc6c 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 412b339caf3a1..91f873f5a2d79 100644 --- a/homeassistant/components/sensor/miflora.py +++ b/homeassistant/components/sensor/miflora.py @@ -30,13 +30,13 @@ 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 @@ def unit_of_measurement(self): """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 239af14add85c..f58580b83c79b 100644 --- a/homeassistant/components/geofency/__init__.py +++ b/homeassistant/components/geofency/__init__.py @@ -68,8 +68,8 @@ def _address(value: str) -> str: 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 722f74a0b6a04..448adfcee3f5b 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 21360e71c42a0..d454fe3c75ec4 100644 --- a/homeassistant/components/light/lutron_caseta.py +++ b/homeassistant/components/light/lutron_caseta.py @@ -8,7 +8,7 @@ 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 5fa560e9258b9..2d74da98a6c7d 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 0121cd1c79475..9f8e07809cb3f 100644 --- a/tests/components/cast/test_init.py +++ b/tests/components/cast/test_init.py @@ -10,7 +10,7 @@ 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 8fd1ae1884118..2e0fe9d152926 100644 --- a/tests/components/media_player/test_cast.py +++ b/tests/components/cast/test_media_player.py @@ -10,11 +10,11 @@ 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 abfa32ca06bbd..3e584aa541cfe 100644 --- a/tests/components/device_tracker/test_mqtt.py +++ b/tests/components/device_tracker/test_mqtt.py @@ -30,7 +30,7 @@ async def mock_setup_scanner(hass, config, see, discovery_info=None): """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 79d0bdb6f73ce..5e65e0a75c77b 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 33adff9adf816..b3ce1e93e4cf6 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 ad1ab32832506..7141c8c9d3a74 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 11a5028842b8a..aaf22ffce65cb 100644 --- a/tests/components/weather/test_smhi.py +++ b/tests/components/smhi/test_weather.py @@ -9,8 +9,8 @@ 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 8d46f4d57a39a..a09fa7d2615c4 100644 --- a/tests/components/sonos/test_init.py +++ b/tests/components/sonos/test_init.py @@ -10,7 +10,7 @@ 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 bf81aee598265..79eab1c16996e 100644 --- a/tests/components/media_player/test_sonos.py +++ b/tests/components/sonos/test_media_player.py @@ -8,8 +8,9 @@ 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 67cacb298807e..a739325847f03 100644 --- a/tests/components/sensor/test_history_stats.py +++ b/tests/components/sensor/test_history_stats.py @@ -70,9 +70,9 @@ def test_period_parsing(self): 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 448adfcee3f5b..d9632103f6747 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 0000000000000..b5df45216e553 --- /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 0000000000000..4d105cebf449f --- /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 0000000000000..fe6cb1ac12dba --- /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 8e975c4857423..0000000000000 --- 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 2d74da98a6c7d..b0bf06292e816 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 | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/tests/components/sensor/test_history_stats.py b/tests/components/sensor/test_history_stats.py index a739325847f03..28d01de4b3478 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 @@ 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 @@ def test_setup(self): 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) - - 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 + 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.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 @@ def test_period_parsing(self): 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 d43ba989f288b..02b9affefd417 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -25,8 +25,7 @@ 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 @@ def __init__(self, hass, api_password, 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 8b28a7cf28807..54e72c88ff30f 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 @@ async def sendfile(request, fobj, count): # 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 28d01de4b3478..eda0034c922ab 100644 --- a/tests/components/sensor/test_history_stats.py +++ b/tests/components/sensor/test_history_stats.py @@ -54,7 +54,6 @@ def test_period_parsing(self): """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 9a6d96bfab973..a9f47fc7c7221 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -18,6 +18,7 @@ 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 0000000000000..8495be62a73c6 --- /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 0000000000000..a4f1103f270f5 --- /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 7bf8b15af51b9..a8013105291f6 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 d9632103f6747..65e4656297f4a 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 0000000000000..5358060ea8a9d --- /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 0000000000000..de9de74054c82 --- /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 0000000000000..2f9bee9d70243 --- /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 61dc49ce2ec5f..8fd3f7d053e73 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -18,7 +18,7 @@ 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 @@ 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 b0bf06292e816..5a16f8b75b1da 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 50a61ee2acc6c..e531754ec711e 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 0000000000000..82c69bf3755af --- /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 732e47099c4bb..ffb879571dcd9 100644 --- a/tests/components/sensor/test_arlo.py +++ b/tests/components/arlo/test_sensor.py @@ -4,7 +4,7 @@ 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 0000000000000..389dc7101f936 --- /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 8a03cbcd191b3..965fb37dcb814 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 0000000000000..24bb7c3a1810e --- /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 1cd15e3655f03..95361170a2ce2 100644 --- a/tests/components/climate/test_fritzbox.py +++ b/tests/components/fritzbox/test_climate.py @@ -4,7 +4,7 @@ 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 0000000000000..d5524765d077a --- /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 ec4089677d8a5..6329c2c1d145e 100644 --- a/tests/components/calendar/test_google.py +++ b/tests/components/google/test_calendar.py @@ -7,7 +7,7 @@ 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 @@ def tearDown(self): 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 @@ def test_all_day_event(self, mock_next_event): '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 @@ def test_future_event(self, mock_next_event): '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 @@ def test_in_progress_event(self, mock_next_event): '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 @@ def test_offset_in_progress_event(self, mock_next_event): } @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 @@ def test_all_day_offset_in_progress_event(self, mock_next_event): '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 f328e3e9f16a3..2b5346bf639ca 100644 --- a/tests/components/tts/test_google.py +++ b/tests/components/google/test_tts.py @@ -12,7 +12,7 @@ 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 0000000000000..b92ba05bdb16b --- /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 74c8e2854d0fa..afa5f20142220 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 76aba46d51446..5fe4ca2ee0aa6 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 0000000000000..12584aba23940 --- /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 d96bf8f5abb2b..33bf1fd333bf6 100644 --- a/tests/components/light/test_mochad.py +++ b/tests/components/mochad/test_light.py @@ -5,7 +5,7 @@ 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 @@ def tearDown(self): """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 76640f8872391..e5216b276fa70 100644 --- a/tests/components/switch/test_mochad.py +++ b/tests/components/mochad/test_switch.py @@ -6,7 +6,7 @@ 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 @@ def tearDown(self): """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 0000000000000..0382661dbe3ed --- /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 03dd202e8381c..20af71cfca578 100644 --- a/tests/components/lock/test_verisure.py +++ b/tests/components/verisure/test_lock.py @@ -46,7 +46,7 @@ @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 0000000000000..adef8e9b86abb --- /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 8017ad6cd541e..c552775c02315 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 0000000000000..9f162e02f28d4 --- /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 c4c1fb0e1b48e..a1e937cb244e0 100644 --- a/tests/components/vacuum/test_xiaomi_miio.py +++ b/tests/components/xiaomi_miio/test_vacuum.py @@ -11,7 +11,7 @@ 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 a0d67c6191df8..4f0727e11018d 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 f3371a267250b..b148a875398f8 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -16,7 +16,7 @@ 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 validator(value): 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 6d2cc77001362..6d0d2a358471c 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -259,6 +259,7 @@ def test_validate_platform_config_3(self): 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 @@ def test_validate_platform_config_3(self): 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 a9f47fc7c7221..2834de4dcf194 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -18,12 +18,14 @@ 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 0000000000000..7862736e60b1c --- /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 0000000000000..99627e866d9ff --- /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 0000000000000..045944ccfa92f --- /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 2834de4dcf194..f545f84832d3d 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -18,16 +18,26 @@ 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 0000000000000..2e0c46842b0ec --- /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 0000000000000..d3c451f3e3ff8 --- /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 0000000000000..51a09514159ef --- /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 0000000000000..0f0d60d445863 --- /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 0000000000000..d1264010b75c1 --- /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 0000000000000..7e3ed3ef88850 --- /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 6adaaa019c5e9..f0e9f7b71ea44 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 223dc91a4800a..f70bb81e7003c 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 3d4d9a5b151ff..f826075078e7b 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 0000000000000..1330e3a932d66 --- /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 0c60953db5648..d8bcc95a115a3 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 5dd87d9e46214..5a8b710c006d9 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 597e65a7658db..18a476b6870eb 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 6eecbed54acc9..8a542dd0d6293 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 0000000000000..3491c784c19e5 --- /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 54c3e079386ab..ddee892039f4f 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 0000000000000..11d1aa3ff7a7b --- /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 0000000000000..0ed3cc3d14af6 --- /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 0000000000000..768feb83747e8 --- /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 0000000000000..9cb4cc3343112 --- /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 0000000000000..40b4307ae02de --- /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 0000000000000..8010b330b88fa --- /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 514acbbbf1813..24f84851254cf 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 4f2a8b0e1bbce..19fb581eb3fd3 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 0000000000000..8e5ca59fcef83 --- /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 0000000000000..cd14e21db1060 --- /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 0000000000000..09d93e6911e5d --- /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 0000000000000..e56d41d4f1aac --- /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 0000000000000..7ab8a1289806a --- /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 0000000000000..bec33c26d100b --- /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 0000000000000..2d3b08d236ee7 --- /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 d5641ef5db819..ad8f978bc5925 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 0000000000000..cd14e21db1060 --- /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 0000000000000..a65e51d7cae5c --- /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 0000000000000..78df911c8689b --- /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 0000000000000..836b5c8bc687e --- /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 0000000000000..3d82ac6fa5a3c --- /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 0000000000000..34b7e9072887f --- /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 0000000000000..8e205bef437af --- /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 0000000000000..dd5db73f5824e --- /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 0000000000000..c9d98da1afcf0 --- /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 eabb31ac8335c..cdde0f12d7856 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 629ee4347fe5f..4c2b6268eec35 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 784fa0d99a6d1..63cbbe016a21e 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 05d52d5c37e80..7ad7a2e6ade03 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 f5cc1dc572ebb..efb966880ebe3 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 0000000000000..a08907a51ef92 --- /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 b2a538a0fa5b4..052557408d810 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 0000000000000..a57b27cdd7551 --- /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 0000000000000..25db0ecef8159 --- /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 0000000000000..00e3337dfe1ee --- /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 0000000000000..89f6881593aa0 --- /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 0000000000000..d8b8d55a608db --- /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 0000000000000..0b0bd45b7d6ff --- /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 0000000000000..d98793d96e5b9 --- /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 0000000000000..62bb6bb9d962a --- /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 4eb12d7343ce9..2f526826d3189 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 06c1d3624f40b..5dd0a7aeabf31 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 d8d331449a216..0050d1b040d36 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 05ba5bdf15525..0b5cbc989fd26 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 5cb9a8ce5a5ba..ad2f391886a23 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 6b5cb91cfeb25..ebd2b88b10ef9 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 7c6f07a7edd3c..e7171ca28672b 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 2316f5c7454cd..4c57baea77f84 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 0000000000000..3c0ca05a8d5cd --- /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 1fb4e878cb463..f2775b30ae23b 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 0000000000000..fd59d187314b5 --- /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 0000000000000..379cdf699b7c4 --- /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 0000000000000..952eafec60c5b --- /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 db97b1ad6d8b2..759157358822a 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 4ed9ef597f489..c2b0056185874 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 0000000000000..4e7de72edc408 --- /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 a7b68bbf8be84..29f64a87cb3b7 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 85de49776c16c..5eb4d1b978af1 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 5c3d343dd036c..2c6439b364f4e 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 5ee9ac221a7f7..9d791e0e78653 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 2e319b9400b0b..80dff6dc88ace 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 f4b9f0fda9832..16e6ddcb5f4b6 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 0000000000000..f707b1f15f891 --- /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 a5e3c652c0c82..c632b54363485 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 0321cb054521a..86d2c44f11cfa 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 e108fe12498fc..6fda9f0143c97 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 4bf6501bd2af6..4c019d8f20767 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 b16172e97d742..2194a2dc264c2 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 8befb2ee114d7..ce458fa32f130 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 8f3afef16cd99..b5836e67ffcc9 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,7 +24,7 @@ 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 5a16f8b75b1da..388498d487aea 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 e531754ec711e..c46596e209eaa 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 c705a3df73e94..d86524ef62ba5 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -19,7 +19,7 @@ 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 @@ async def event_handler(self, req, resp, app): 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 f545f84832d3d..3d0e5cb95f852 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -12,6 +12,7 @@ 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_CAPABILITIES = [ 'accelerationSensor', + 'button', 'colorControl', 'colorTemperature', 'contactSensor', diff --git a/tests/components/smartthings/conftest.py b/tests/components/smartthings/conftest.py index 56bb5a628881d..7358e05f346b6 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -254,14 +254,16 @@ def _factory(label, capabilities, status: dict = None): @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 _factory(device_id, event_type="DEVICE_EVENT"): @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 2e0c46842b0ec..92d891c06d6b2 100644 --- a/tests/components/smartthings/test_binary_sensor.py +++ b/tests/components/smartthings/test_binary_sensor.py @@ -6,9 +6,10 @@ """ 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 d20d2d4e047de..4aef42c1b6fe6 100644 --- a/tests/components/smartthings/test_init.py +++ b/tests/components/smartthings/test_init.py @@ -8,7 +8,8 @@ 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 @@ def signal(ids): 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 c46596e209eaa..5feb416515558 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 398b2791848f6..4a99ef84bc9ad 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -124,6 +124,8 @@ '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 0000000000000..ea0e5f43467bb --- /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 0000000000000..59509e2ab032d --- /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 0000000000000..e86eb7fdd9b8f --- /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 c04ed96d6e01d..3b8f296eec01e 100644 --- a/homeassistant/components/media_player/firetv.py +++ b/homeassistant/components/media_player/firetv.py @@ -12,7 +12,7 @@ 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 @@ 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 ea0e5f43467bb..c7a9c786054cc 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 @@ def __init__(self): 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 @@ def __init__(self): 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 @@ def __init__(self): 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 59509e2ab032d..624c6a0296496 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 0000000000000..3933f416e3d81 --- /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 e86eb7fdd9b8f..d3415bde59b3a 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -4,7 +4,8 @@ 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 cddcd1f26eefb..c4635f8dc431a 100644 --- a/homeassistant/components/device_tracker/mikrotik.py +++ b/homeassistant/components/device_tracker/mikrotik.py @@ -174,7 +174,7 @@ def _update_info(self): 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 c04ed96d6e01d..6e9b223817a1e 100644 --- a/homeassistant/components/media_player/firetv.py +++ b/homeassistant/components/media_player/firetv.py @@ -171,6 +171,11 @@ def available(self): """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 173e115cbaf03..ceec57f77557c 100644 --- a/homeassistant/components/camera/foscam.py +++ b/homeassistant/components/camera/foscam.py @@ -15,7 +15,7 @@ _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 388498d487aea..072ec06f2c753 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 788927a2700f2..0991336f42a2c 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -11,7 +11,7 @@ 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 .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 @@ 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 @@ 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 @@ def __init__( 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 df2c5462e6660..75606a1c699fc 100644 --- a/homeassistant/components/ambient_station/const.py +++ b/homeassistant/components/ambient_station/const.py @@ -8,6 +8,3 @@ 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 d2d89233472cf..9e0833e344132 100644 --- a/homeassistant/components/ambient_station/sensor.py +++ b/homeassistant/components/ambient_station/sensor.py @@ -12,14 +12,11 @@ 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 @@ def __init__( 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 @@ def state(self): @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 b5836e67ffcc9..46652b4d7b014 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,7 +24,7 @@ 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 072ec06f2c753..93e5b45fcf5a1 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 5feb416515558..deee1efca3fd2 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 b5df45216e553..aef8e4c48f92e 100644 --- a/homeassistant/components/fastdotcom/__init__.py +++ b/homeassistant/components/fastdotcom/__init__.py @@ -41,9 +41,12 @@ 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 @@ def update(call=None): 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 65e4656297f4a..e7454ccfa9c41 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 f834b51b064ae..0000000000000 --- 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 0000000000000..ce6f376d1b084 --- /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 0000000000000..7f19d796fd02c --- /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 0000000000000..33557ddacab5f --- /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 0000000000000..db813affe76ab --- /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 93e5b45fcf5a1..4ce0ae263aa87 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 03a77c08d4b68..a2825eb6d7baa 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -107,13 +107,16 @@ async def error_handler(view, request, *args, **kwargs): 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 84d35f4bdd834..06de6bf0b59c2 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 0000000000000..7c0c8b5350fc1 --- /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 0000000000000..c19225bf31051 --- /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 0000000000000..d9063b4885ac6 --- /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 78bd2d7df3f51..f2d5ad09350f3 100644 --- a/homeassistant/components/googlehome/__init__.py +++ b/homeassistant/components/googlehome/__init__.py @@ -24,6 +24,7 @@ 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 @@ [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 @@ def __init__(self, hass): 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 ba6d708295acb..c4b490ab316b2 100644 --- a/homeassistant/components/googlehome/device_tracker.py +++ b/homeassistant/components/googlehome/device_tracker.py @@ -45,7 +45,7 @@ def __init__(self, hass, client, config, async_see): 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 @@ async def async_init(self): 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 0000000000000..f2a0f822dbf9e --- /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 4ba527b480528..3b3368c2f5c4b 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -82,7 +82,7 @@ async def handle_async_mjpeg_stream(self, request): 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 d56616218e769..7857995b4af3a 100644 --- a/homeassistant/components/arlo/camera.py +++ b/homeassistant/components/arlo/camera.py @@ -104,7 +104,7 @@ async def handle_async_mjpeg_stream(self, request): 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 7a83e2da4d135..eb0c8f3fc6d7a 100644 --- a/homeassistant/components/camera/canary.py +++ b/homeassistant/components/camera/canary.py @@ -101,7 +101,7 @@ async def handle_async_mjpeg_stream(self, request): 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 6bd68b05bb589..db9e73f3e1bca 100644 --- a/homeassistant/components/camera/ffmpeg.py +++ b/homeassistant/components/camera/ffmpeg.py @@ -68,7 +68,7 @@ async def handle_async_mjpeg_stream(self, request): 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 d1afd39ca7b1f..da0bae7c50bac 100644 --- a/homeassistant/components/camera/onvif.py +++ b/homeassistant/components/camera/onvif.py @@ -213,7 +213,8 @@ async def handle_async_mjpeg_stream(self, request): 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 @@ async def handle_async_mjpeg_stream(self, request): 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 ad351fb59cfb2..da1119281b344 100644 --- a/homeassistant/components/camera/ring.py +++ b/homeassistant/components/camera/ring.py @@ -142,7 +142,7 @@ async def handle_async_mjpeg_stream(self, request): 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 207dd17ed9b31..93e9dd4a07c8e 100644 --- a/homeassistant/components/camera/xiaomi.py +++ b/homeassistant/components/camera/xiaomi.py @@ -161,6 +161,6 @@ async def handle_async_mjpeg_stream(self, request): 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 8b5b865ee57cc..7d731d2a43397 100644 --- a/homeassistant/components/camera/yi.py +++ b/homeassistant/components/camera/yi.py @@ -147,6 +147,6 @@ async def handle_async_mjpeg_stream(self, request): 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 a2f0ca1923107..3184b5a5d54ab 100644 --- a/homeassistant/components/ffmpeg.py +++ b/homeassistant/components/ffmpeg.py @@ -5,6 +5,7 @@ https://home-assistant.io/components/ffmpeg/ """ import logging +import re import voluptuous as vol @@ -16,7 +17,7 @@ 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 @@ def __init__(self, hass, ffmpeg_bin): 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 4ce0ae263aa87..f8085401d989a 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 deee1efca3fd2..3ec1c7d35fa31 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 9a2daf388cb13..688912070bdc6 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -11,7 +11,7 @@ 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 @@ '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 @@ '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 @@ '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 @@ '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_device_message_received(topic, payload, qos): 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 47bd912fbc8bb..ffc385021d72f 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 2a9f8f6483508..5dbf226ca2535 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 ed24fe48d40b1..d725cb309bcc6 100644 --- a/homeassistant/components/cloud/iot.py +++ b/homeassistant/components/cloud/iot.py @@ -12,9 +12,10 @@ 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 0000000000000..da1d3809989e7 --- /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 d648ed43110f2..16fea1295731e 100644 --- a/homeassistant/util/aiohttp.py +++ b/homeassistant/util/aiohttp.py @@ -3,7 +3,6 @@ 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 @@ async def post(self) -> 'MultiDict[str]': 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 0000000000000..24de4ce6214c8 --- /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 8f528376cce74..5df1582da3214 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 cdf55c8e04948..b14881fccca48 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -15,7 +15,8 @@ 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 @@ '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 @@ 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 refresh(event_time): """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 efe32b07fc095..84c7d54306e60 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -4,10 +4,12 @@ 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 @@ 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 3ce3c7a98f9ce..8e6c0a8cb446c 100644 --- a/homeassistant/components/transmission/switch.py +++ b/homeassistant/components/transmission/switch.py @@ -4,10 +4,12 @@ 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 @@ 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 bdff232fec915..b2587c0651262 100644 --- a/homeassistant/components/cover/myq.py +++ b/homeassistant/components/cover/myq.py @@ -15,7 +15,7 @@ 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 f8085401d989a..05d9c07603c83 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 0121f6e98d3a7..802b2b597fc04 100644 --- a/homeassistant/components/media_player/dlna_dmr.py +++ b/homeassistant/components/media_player/dlna_dmr.py @@ -26,7 +26,7 @@ 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 494c39c06bab6..2a1b8c52d7998 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -29,7 +29,7 @@ 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 f8085401d989a..fc4adb0309c98 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 d86524ef62ba5..bdbbfcf25904a 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -23,7 +23,7 @@ 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 @@ def __init__(self, hass: HomeAssistantType, devices: Iterable, 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 @@ async def event_handler(self, req, resp, app): 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 fc4adb0309c98..791e6d5ddd653 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 3ec1c7d35fa31..611b20d6a0c90 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 5f770e84b37df..66af51efcb183 100644 --- a/homeassistant/components/air_quality/__init__.py +++ b/homeassistant/components/air_quality/__init__.py @@ -8,7 +8,8 @@ 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 e02e074189cc1..86bb3e73bdab9 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -14,7 +14,7 @@ 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 7b2da21ff6a68..9972e4dca3b4c 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -13,7 +13,8 @@ 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 9d105fb02d0ff..024dc1ac9de99 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -13,7 +13,8 @@ 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 653d0315ad4e3..474f9594610c9 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -25,7 +25,8 @@ 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 6d7f9432e39a2..362c4ee93e405 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -14,7 +14,8 @@ 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 b5b2a91b09789..bd003f1ad6703 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -13,7 +13,8 @@ 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 202883713c740..af33453c9d55f 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -22,10 +22,10 @@ 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 @@ 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 3525b95c007ad..50d6802c4d241 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -16,7 +16,8 @@ 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 4597a56c61aa4..9095ce617aab1 100644 --- a/homeassistant/components/geo_location/__init__.py +++ b/homeassistant/components/geo_location/__init__.py @@ -9,7 +9,8 @@ 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 b2cbb2b2391af..f854384bb0307 100644 --- a/homeassistant/components/image_processing/__init__.py +++ b/homeassistant/components/image_processing/__init__.py @@ -60,6 +60,7 @@ 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 a16d1aaf87e88..816f93b58815e 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -20,7 +20,8 @@ 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 750977fac874c..c5c1ce1339d4d 100644 --- a/homeassistant/components/lock/__init__.py +++ b/homeassistant/components/lock/__init__.py @@ -13,7 +13,8 @@ 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 b526d1659ba9e..6f74380728cab 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -30,7 +30,8 @@ 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 ed2a3cd6c521c..e430b1fbc9f50 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -234,7 +234,7 @@ def validate_device_has_at_least_one_identifier(value: ConfigType) -> \ 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 be176a39a255f..e5fb8fc66b0f9 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -13,7 +13,8 @@ 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 @@ async def async_added_to_hass(self): 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 db46f11b88e7e..c028ca5a6f694 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -17,9 +17,9 @@ 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 @@ async def async_added_to_hass(self): 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 162cb41d92e17..e04b0ef27583f 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -18,7 +18,8 @@ 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 2800b689dc651..50549f28fd7be 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -11,7 +11,8 @@ 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 39c202ef01c3e..b52f039281cea 100644 --- a/homeassistant/components/sensor/mqtt_room.py +++ b/homeassistant/components/sensor/mqtt_room.py @@ -38,12 +38,11 @@ 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 513ebbcb5ea2e..d517f635a9241 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -12,7 +12,8 @@ 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 063ba428d4a4a..3f8d1b6fdec09 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -68,6 +68,7 @@ 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 6341c9661ed3a..3fdc7cb1a51a3 100644 --- a/homeassistant/components/vacuum/__init__.py +++ b/homeassistant/components/vacuum/__init__.py @@ -16,7 +16,8 @@ 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 fee2846e8d56b..07acb15b76512 100644 --- a/homeassistant/components/water_heater/__init__.py +++ b/homeassistant/components/water_heater/__init__.py @@ -14,7 +14,8 @@ 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 b9cb300ee214b..d479725657bcf 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -10,7 +10,8 @@ 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 b148a875398f8..36dca2bbcaf92 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -556,16 +556,9 @@ def validator(value): 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 d3df1a32ac708..befeca0725115 100644 --- a/tests/components/binary_sensor/test_rest.py +++ b/tests/components/binary_sensor/test_rest.py @@ -99,7 +99,6 @@ def test_setup_get(self, mock_req): '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 @@ def test_setup_post(self, mock_req): '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 4f88d24d97f44..9c6d41a26c05e 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 c9b7c748ea502..ecbdc39e22bca 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 29718314ef4da..b43d38da5e83c 100644 --- a/tests/components/sensor/test_filter.py +++ b/tests/components/sensor/test_filter.py @@ -59,7 +59,6 @@ def test_chain(self): '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 1bae84b532080..53ed8004f7d59 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 6d0d2a358471c..8575b023d3730 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -15,7 +15,7 @@ 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 d938dd20e67ea..98e649e1742ee 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 @@ def load_config(): 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 954d28b803fab..6019dac87b95e 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 @@ class UnknownError(CloudError): } +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 d725cb309bcc6..055c4dbaa6417 100644 --- a/homeassistant/components/cloud/iot.py +++ b/homeassistant/components/cloud/iot.py @@ -62,12 +62,18 @@ def __init__(self, cloud): # 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 @@ def _handle_hass_stop(event): # 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 @@ def _handle_connection(self): 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 a50a4d796aa4d..bdf9939cb2b02 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 1a528f8cedfe0..10a94f46833af 100644 --- a/tests/components/cloud/test_iot.py +++ b/tests/components/cloud/test_iot.py @@ -10,9 +10,8 @@ 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 20a44c0e910e9..c66f74f74a9a7 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 297e98e548a7c..f59c922577b01 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 a9916ed54fe33..2b5f8fcb13f16 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 6ddefd2022dc3..5cd23e9f9a2d4 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -778,7 +778,7 @@ def __init__(self, bus: EventBus, 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 @@ def async_entity_ids( 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 af256efc7092f..d99d878ef6381 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 611b20d6a0c90..cb6d000dca88b 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 8d9df11116a71..dbf1e1fe7dd03 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 85cca89eefcd9..212d3e0280298 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -214,7 +214,7 @@ def mock_connect(receiver, signal, *args, **kwargs): 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 sleep(duration, loop=None): 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 @@ def mock_connect(receiver, signal, *args, **kwargs): 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 a6ea7cbd53454..82a1ec8bb681b 100644 --- a/homeassistant/components/sensor/ted5000.py +++ b/homeassistant/components/sensor/ted5000.py @@ -114,7 +114,4 @@ def update(self): 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 f2a0f822dbf9e..90b9cda80bbfa 100644 --- a/homeassistant/components/googlehome/sensor.py +++ b/homeassistant/components/googlehome/sensor.py @@ -7,11 +7,10 @@ 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 @@ def __init__(self, client, condition, config): 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 0ca3bac3e35db..ba7ccc2f68216 100644 --- a/homeassistant/components/smhi/__init__.py +++ b/homeassistant/components/smhi/__init__.py @@ -11,7 +11,7 @@ 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 94873b03bd6dd..79fd1166a4b66 100644 --- a/homeassistant/components/smhi/weather.py +++ b/homeassistant/components/smhi/weather.py @@ -22,7 +22,7 @@ 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 @@ def attribution(self) -> str: @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 68a7bc3e931ee..35019f74408ec 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 cb6d000dca88b..dd455ef88fd3b 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 aaf22ffce65cb..f0acd231ebe19 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 d0e3d52b363e0..1b2a4dbf05d2a 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -113,6 +113,7 @@ 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 2b4e55c4c8dc5..dcc93b7cf9e49 100644 --- a/homeassistant/components/homekit/type_fans.py +++ b/homeassistant/components/homekit/type_fans.py @@ -4,17 +4,20 @@ 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 @@ def __init__(self, *args): 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 @@ def __init__(self, *args): 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 @@ def set_oscillating(self, value): 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 @@ def update_state(self, new_state): 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 10fdc07e7b425..7ad0cea48e7e7 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 27b6cec0790ff..b620ef50e0ffd 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 a2849a7739669..c86b1353c4893 100644 --- a/tests/components/homekit/test_util.py +++ b/tests/components/homekit/test_util.py @@ -3,15 +3,14 @@ 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 fe3cca4876ea3..d77c0c9bfe20a 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 d9cd1be59dafd..2a57a78ee9ee2 100644 --- a/homeassistant/components/tellduslive/__init__.py +++ b/homeassistant/components/tellduslive/__init__.py @@ -5,27 +5,26 @@ 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 @@ 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 @@ def __init__(self, hass, config_entry, session): 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 @@ async def _discover(self, device_id): 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 35019f74408ec..ed107f224c899 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 362c4ee93e405..e1d3093995c4c 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -22,25 +22,46 @@ 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_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 0000000000000..2f84ee27bbdcc --- /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 0000000000000..3259e4084cf68 --- /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 b6dcd65fc2c89..d1cd88a8438d6 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -23,6 +23,8 @@ 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 0000000000000..1cf1793e6f6f2 --- /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 6f74380728cab..840b745eebd1d 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -36,10 +36,44 @@ 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 @@ 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 0000000000000..b926d89341403 --- /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 0000000000000..cbe9870461545 --- /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 8fd3f7d053e73..7d69defed480e 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -10,41 +10,27 @@ 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 @@ # 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_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 0000000000000..c16151320b447 --- /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 0000000000000..43bc2a03fe95c --- /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 0000000000000..f39733178b1d6 --- /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 076547444926c..5c04f085c8622 100644 --- a/tests/helpers/test_state.py +++ b/tests/helpers/test_state.py @@ -15,8 +15,6 @@ 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 @@ def test_reproduce_complex_data(self): 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 @@ def test_reproduce_bad_state(self): 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 8c2abc0f875d1..62f4638820f57 100644 --- a/homeassistant/components/wink/sensor.py +++ b/homeassistant/components/wink/sensor.py @@ -8,7 +8,6 @@ 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 @@ def state(self): 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 e7454ccfa9c41..488ea50298aeb 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 4f0727e11018d..b9f5cec0d64c0 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 0000000000000..ad0bb076d90b4 --- /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 0000000000000..1cdd1f02e7eed --- /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 0000000000000..01e26a5630b76 --- /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 0000000000000..db7690730dd91 --- /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 0000000000000..05c9c4fb18060 --- /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 0000000000000..3d15a9583f64f --- /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 0000000000000..33574c5dd713b --- /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 0000000000000..bb0f5f932aeaa --- /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 0000000000000..e49d46c9b8604 --- /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 0000000000000..650abc056a97c --- /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 0000000000000..8084e56b731e7 --- /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 0000000000000..219eecf2fd6d8 --- /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 0000000000000..79b11c129af63 --- /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 d7ec49e00968f..f15020a5d72f6 100644 --- a/homeassistant/components/lirc.py +++ b/homeassistant/components/lirc/__init__.py @@ -4,7 +4,7 @@ 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 0000000000000..4d1ba649d3651 --- /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 0000000000000..386f7083f92a9 --- /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 0000000000000..0fd9e5a49e738 --- /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 e10e626464378..0015c83342d06 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 0000000000000..9269326ece6fa --- /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 8988021a5b60e..40c7637653101 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 0000000000000..1d667e43fa6eb --- /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 0000000000000..cca39062ce95e --- /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 0000000000000..405f0c5d57df6 --- /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 0000000000000..e20dd64396f4d --- /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 0000000000000..0c5b89dc2cbb3 --- /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 0000000000000..80e51236d534d --- /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 0000000000000..c72fd03f7dedb --- /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 98147ff7e6fe5..c14107438b341 100644 --- a/tests/components/binary_sensor/test_rflink.py +++ b/tests/components/binary_sensor/test_rflink.py @@ -16,7 +16,7 @@ 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 a4b243e7420e6..924ed01f9e8ce 100644 --- a/tests/components/binary_sensor/test_ring.py +++ b/tests/components/binary_sensor/test_ring.py @@ -6,7 +6,7 @@ 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 3f3d69ad9d399..524093d76b357 100644 --- a/tests/components/binary_sensor/test_sleepiq.py +++ b/tests/components/binary_sensor/test_sleepiq.py @@ -7,7 +7,7 @@ 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 ee19cf941a3f5..3dcba033d730e 100644 --- a/tests/components/binary_sensor/test_vultr.py +++ b/tests/components/binary_sensor/test_vultr.py @@ -16,7 +16,7 @@ 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 0000000000000..cc85edd806a9c --- /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 0000000000000..a533a39a93c1a --- /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 0000000000000..ea244c00df8ee --- /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 9c6d41a26c05e..d531574d34f06 100644 --- a/tests/components/cover/test_rflink.py +++ b/tests/components/cover/test_rflink.py @@ -14,7 +14,7 @@ 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 0000000000000..4f29b839b858f --- /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 0000000000000..68d228076b9ae --- /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 0000000000000..4400ace7cd8f9 --- /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 0000000000000..b5744b42d6b8d --- /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 0000000000000..d8304b7cf6890 --- /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 0000000000000..d4c814a37db35 --- /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 0000000000000..3667f7c75ea32 --- /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 0000000000000..c69f1dac44a81 --- /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 0000000000000..8508216226f58 --- /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 0000000000000..ab0f8df64114a --- /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 0000000000000..3466a3be48969 --- /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 0000000000000..e62487ad79ea7 --- /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 0000000000000..662e70a7bff81 --- /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 0000000000000..2cb34499938e0 --- /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 0000000000000..79602ecfb44dd --- /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 0000000000000..7a215bea1979d --- /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 0000000000000..b935cf060c817 --- /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 0000000000000..164d6a0ba5e85 --- /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 0000000000000..b408528a4ffa0 --- /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 0000000000000..e40cd77455c02 --- /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 0000000000000..2d817e8a59cfd --- /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 0000000000000..b035eed0c9ec8 --- /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 0000000000000..5328df0b0b123 --- /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 0000000000000..99cea29581c7c --- /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 17898bd24516f..e2b80d12ce0d0 100644 --- a/tests/components/light/test_rflink.py +++ b/tests/components/light/test_rflink.py @@ -12,7 +12,7 @@ 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 0000000000000..9a01fbe5114fd --- /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 0000000000000..31f9305a21377 --- /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 0000000000000..c01e58c9d40a5 --- /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 0000000000000..8fb7f0dab0295 --- /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 0000000000000..c4caf0fe671e5 --- /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 0000000000000..0b07c35b51565 --- /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 0000000000000..e5c1f19d09468 --- /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 0000000000000..cc104a104c292 --- /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 0000000000000..b12d296455cea --- /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 0000000000000..db064f4405df6 --- /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 0000000000000..ed901068c6934 --- /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 0000000000000..d2c1c62b17ede --- /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 0000000000000..c238d9b0c72b4 --- /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 0000000000000..543978d21fb80 --- /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 0000000000000..df7115d9e9761 --- /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 0000000000000..028273059366b --- /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 0000000000000..43a00130db9c5 --- /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 0000000000000..d60de3cf49c99 --- /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 0000000000000..659d609edb684 --- /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 0000000000000..893f5a7eccd61 --- /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 0000000000000..e0617b4163b09 --- /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 0000000000000..c5cc359ab7613 --- /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 0000000000000..7fbc4588ccbe0 --- /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 0000000000000..fac6bf58dd8d9 --- /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 0000000000000..81b2db8f4df04 --- /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 0000000000000..b159d356d5ba4 --- /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 0000000000000..4200aea1e3210 --- /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 0000000000000..67b9b4e3670bf --- /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 7908e22e579eb..dde8f4e0f941f 100644 --- a/tests/components/sensor/test_canary.py +++ b/tests/components/sensor/test_canary.py @@ -9,7 +9,7 @@ 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 8ab568905f9a3..4cf75857a9a61 100644 --- a/tests/components/sensor/test_rflink.py +++ b/tests/components/sensor/test_rflink.py @@ -9,7 +9,7 @@ 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 3844079172aab..c54f22af8dc33 100644 --- a/tests/components/sensor/test_ring.py +++ b/tests/components/sensor/test_ring.py @@ -6,7 +6,7 @@ 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 96787473abfe8..c667a6a2cdf55 100644 --- a/tests/components/sensor/test_sleepiq.py +++ b/tests/components/sensor/test_sleepiq.py @@ -7,7 +7,7 @@ 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 294657c22eca2..333e4938ba43c 100644 --- a/tests/components/sensor/test_vultr.py +++ b/tests/components/sensor/test_vultr.py @@ -13,7 +13,7 @@ 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 0000000000000..5effcdb3ccea4 --- /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 0000000000000..26be3c9073621 --- /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 0000000000000..751f227a003e0 --- /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 0000000000000..d7ac8b5f822b6 --- /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 0000000000000..0b24e36acb2a3 --- /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 0000000000000..a86adf13be679 --- /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 0000000000000..709483291e325 --- /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 0000000000000..f72ec8d5be139 --- /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 0000000000000..11448700dcd36 --- /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 b50a223fe8b77..a91d18ce19e33 100644 --- a/tests/components/switch/test_rflink.py +++ b/tests/components/switch/test_rflink.py @@ -11,7 +11,7 @@ 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 699da34319a4c..f5e94e3e1b123 100644 --- a/tests/components/switch/test_vultr.py +++ b/tests/components/switch/test_vultr.py @@ -16,7 +16,7 @@ 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 0000000000000..691a4f221cabe --- /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 0000000000000..31a19cb3bf701 --- /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 0000000000000..fb25b7e145e76 --- /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 0000000000000..f691e3973f3c8 --- /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 0000000000000..7064c578b1c1b --- /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 0000000000000..1d58e9c24d6c7 --- /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 9c17767f033d5..2237a0a297788 100644 --- a/homeassistant/components/switch/broadlink.py +++ b/homeassistant/components/switch/broadlink.py @@ -235,7 +235,7 @@ def _sendpacket(self, packet, retry=2): 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 @@ def _auth(self, retry=2): 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 @@ def _sendpacket(self, packet, retry=2): 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 @@ def _update(self, retry=2): 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 @@ def _sendpacket(self, packet, retry=2): 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 @@ def _update(self, retry=2): 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 | 58 ++++++++++++++++++++-- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/light/flux_led.py b/homeassistant/components/light/flux_led.py index cab6957c2655c..088fc871fc10c 100644 --- a/homeassistant/components/light/flux_led.py +++ b/homeassistant/components/light/flux_led.py @@ -23,6 +23,10 @@ _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_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_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 } - -FLUX_EFFECT_LIST = [ - EFFECT_RANDOM, - ] + list(EFFECT_MAP) +EFFECT_CUSTOM_CODE = 0x60 + +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 @@ 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 @@ def __init__(self, device): 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 @@ def white_value(self): @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 @@ def turn_on(self, **kwargs): 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 c55c229f54972..ae38c529fe254 100644 --- a/homeassistant/components/sensor/waze_travel_time.py +++ b/homeassistant/components/sensor/waze_travel_time.py @@ -16,6 +16,7 @@ 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 @@ 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 @@ def _resolve_zone(self, friendly_name): 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 943b487857fbb..a6613f7c3c36f 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -287,9 +287,8 @@ async def async_start(self): 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 20808c773f0bd..78090914b2c98 100644 --- a/homeassistant/components/locative/device_tracker.py +++ b/homeassistant/components/locative/device_tracker.py @@ -11,6 +11,7 @@ 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 | 306 +++------ 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 | 595 ++++++++++++------ .../components/zha/core/listeners.py | 554 +++++++++++++++- homeassistant/components/zha/device_entity.py | 134 ++-- homeassistant/components/zha/entity.py | 386 ++++-------- homeassistant/components/zha/event.py | 99 --- homeassistant/components/zha/fan.py | 65 +- homeassistant/components/zha/light.py | 277 +++----- homeassistant/components/zha/sensor.py | 365 ++++------- homeassistant/components/zha/switch.py | 77 +-- 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, 1744 insertions(+), 1561 deletions(-) delete mode 100644 homeassistant/components/zha/event.py diff --git a/.coveragerc b/.coveragerc index 488ea50298aeb..1f467c93c2d72 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 4f9b5b0436209..2e69390776984 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -4,6 +4,7 @@ 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 @@ # 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 @@ def zha_send_event(self, cluster, command, args): 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 @@ def zha_send_event(self, cluster, command, args): 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 0312a40967fbd..c412cb9fef0f1 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -11,8 +11,7 @@ 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 @@ }) -@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 @@ async def set_zigbee_cluster_attributes(service): 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 @@ async def issue_zigbee_cluster_command(service): 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 @@ async def issue_zigbee_cluster_command(service): 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 @@ async def websocket_reconfigure_node(hass, connection, msg): 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 @@ async def websocket_entities_by_ieee(hass, connection, msg): 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 @@ async def websocket_entity_clusters(hass, connection, msg): 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 d0f23ff3dd202..1f85373eecc56 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -7,16 +7,13 @@ 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 @@ 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,258 +70,98 @@ async def async_discover(discovery_info): 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.""" +class BinarySensor(ZhaEntity, BinarySensorDevice): + """ZHA BinarySensor.""" _domain = DOMAIN + _device_class = None - def __init__(self, device_class, **kwargs): + def __init__(self, **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) + 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 + + 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) - 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] - ) - }) + 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: - """Return true if the binary sensor is on.""" + """Return if the switch is on based on the statemachine.""" + if self._state is None: + return False 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 + def device_class(self) -> str: + """Return device class from component DEVICE_CLASSES.""" + return self._device_class - @property - def zcl_reporting_config(self): - """Return ZCL attribute reporting configuration.""" - return self._zcl_reporting + 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: - self._level = 0 - self._level = min(255, max(0, self._level + change)) + 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(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._state = bool(level) 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.""" - - _domain = DOMAIN - _device_class = None - value_attribute = 0 - - def __init__(self, device_class, **kwargs): - """Initialize the ZHA binary sensor.""" - super().__init__(**kwargs) - self._device_class = device_class - self._cluster = list(kwargs['in_clusters'].values())[0] - - 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 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) - 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}} - - @property - def is_on(self) -> bool: - """Return if the switch is on based on the statemachine.""" - if self._state is None: - return False - return self._state - @property - def device_class(self) -> str: - """Return device class from component DEVICE_CLASSES.""" - return self._device_class + 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 47e6ed2b0eea8..e7443e7e0b7fe 100644 --- a/homeassistant/components/zha/core/__init__.py +++ b/homeassistant/components/zha/core/__init__.py @@ -4,3 +4,10 @@ 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 3069ebf02dbca..cb3a311c985d4 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -55,10 +55,38 @@ 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 @@ def list(cls): 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 c7dabced24be5..292f9817671ee 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -14,7 +14,7 @@ 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 @@ def __init__(self, hass, zigpy_device, zha_gateway): 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 @@ def gateway(self): """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 @@ 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: + 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 479b2f79b2684..2722f6720ce7b 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -5,7 +5,9 @@ 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 . 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 @@ def __init__(self, hass, config): 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 @@ def device_left(self, device): 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) + 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, + "{}_{}".format(SIGNAL_REMOVE, str(device.ieee)) + ) - def get_device_entity(self, ieee_str): - """Return ZHADeviceEntity for given ieee.""" + def get_device(self, ieee_str): + """Return ZHADevice 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 [] + 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 device_registry(self) -> str: + def devices(self): """Return devices.""" - return self._device_registry + return self._devices - async def async_device_initialized(self, device, join): - """Handle device joined and basic information discovered (async).""" - import zigpy.profiles + @property + def device_registry(self): + """Return entities by ieee.""" + return self._device_registry - device_manufacturer = device_model = None + 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(): - 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]) + endpoint_tasks.append(self._async_process_endpoint( + endpoint_id, endpoint, discovery_infos, device, zha_device, + is_new_join + )) + await asyncio.gather(*endpoint_tasks) - 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 + await zha_device.async_initialize(not is_new_join) - component = sub_component = None - for cluster_type, candidate_component in device_classes.items(): - if isinstance(cluster, cluster_type): - component = candidate_component - break + 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) - 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 + device_entity = _create_device_entity(zha_device) + await self._component.async_add_entities([device_entity]) - if component is None: + 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 - 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: - async_dispatcher_send( - self._hass, - ZHA_DISCOVERY_NEW.format(component), - discovery_info + 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, {} ) - else: - self._hass.data[DATA_ZHA][component][cluster_key] = discovery_info + + 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 4f60ea83d6f2d..916319b2d98a3 100644 --- a/homeassistant/components/zha/core/listeners.py +++ b/homeassistant/components/zha/core/listeners.py @@ -5,20 +5,48 @@ 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 @@ def cluster_command(self, tsn, command_id, args): ) 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 @@ def cluster_command(self, tsn, command_id, args): ) 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 @@ def cluster_command(self, tsn, command_id, args): 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 2d2a5d76b817d..cf2156b76c3ae 100644 --- a/homeassistant/components/zha/device_entity.py +++ b/homeassistant/components/zha/device_entity.py @@ -5,78 +5,134 @@ 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__) -class ZhaDeviceEntity(entity.Entity): +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(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) - self._device = device - self._state = 'offline' - self._keepalive_interval = keepalive_interval + kwargs['component'] = 'zha' + super().__init__(unique_id, zha_device, listeners, skip_entity_id=True, + **kwargs) - application_listener.register_entity(ieee, self) - - @property - def unique_id(self) -> str: - """Return a unique ID.""" - return self._unique_id + self._keepalive_interval = keepalive_interval + 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 e112e32d59262..5a78d91553fa8 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -4,20 +4,18 @@ 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 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 - ) - _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 + self._name = None + if zha_device.manufacturer and zha_device.model is not None: + self._name = "{} {}".format( + zha_device.manufacturer, + zha_device.model ) - - 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 should_poll(self) -> bool: - """Let ZHA handle polling.""" - return False + def force_update(self) -> bool: + """Force update this entity.""" + return self._force_update - @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 + @property + def should_poll(self) -> bool: + """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 7828a695a7ba9..0000000000000 --- 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 f6dbef5092311..dfe3c8cdd232a 100644 --- a/homeassistant/components/zha/fan.py +++ b/homeassistant/components/zha/fan.py @@ -10,9 +10,10 @@ 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 @@ def is_on(self) -> bool: 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 @@ async def async_turn_off(self, **kwargs) -> None: 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 49a09112b31eb..1d1b4c5f921d1 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -9,14 +9,12 @@ 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] - ), - } - - if LevelControl.cluster_id in self._in_clusters: + 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 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,181 +92,120 @@ def __init__(self, **kwargs): 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) + return self._state + + @property + def brightness(self): + """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.""" + value = max(0, min(255, value)) + self._brightness = value + self.async_set_state(value) + + @property + def hs_color(self): + """Return the hs color value [int, int].""" + return self._hs_color + + @property + def color_temp(self): + """Return the CT color value in mireds.""" + return self._color_temp + + @property + def supported_features(self): + """Flag supported features.""" + return self._supported_features - def set_state(self, state): + def async_set_state(self, state): """Set the state.""" - self._state = state + self._state = bool(state) self.async_schedule_update_ha_state() + 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) + 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) + 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: - 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) + 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) - # 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) + success = await self._level_listener.move_to_level_with_on_off( + brightness, + duration + ) + if not success: return - self._state = 1 + self._state = True 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) + success = await self._on_off_listener.on() + if not success: return - self._state = 1 + self._state = True 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) + 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 = 0 + self._state = False self.async_schedule_update_ha_state() - - @property - def brightness(self): - """Return the brightness of this light between 0..255.""" - return self._brightness - - def set_level(self, value): - """Set the brightness of this light between 0..255.""" - if value < 0 or value > 255: - return - self._brightness = value - self.async_schedule_update_ha_state() - - @property - def hs_color(self): - """Return the hs color value [int, int].""" - return self._hs_color - - @property - def color_temp(self): - """Return the CT color value in mireds.""" - return self._color_temp - - @property - def supported_features(self): - """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) - - 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) - - 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) - - 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) diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index ae45fad082653..ad566df00f44e 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -9,11 +9,11 @@ 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 @@ 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] - - @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 - - @property - def state(self) -> str: - """Return the state of the entity.""" - 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) + 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._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._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) ) - 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.""" + 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 unit_of_measurement(self): """Return the unit of measurement of this entity.""" - return 'hPa' + return self._unit @property - def state(self): + def state(self) -> str: """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.""" + if isinstance(self._state, float): + return str(round(self._state, 2)) 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 09c20acd088e5..4eee3d5da35b5 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -8,9 +8,10 @@ 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 - - self._state = 0 + await self._on_off_listener.off() + + 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 624c6a0296496..c806b1a2217cd 100644 --- a/tests/components/zha/conftest.py +++ b/tests/components/zha/conftest.py @@ -3,9 +3,12 @@ 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 7c0c8b5350fc1..ba72398704259 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 c19225bf31051..14da94bdf52ac 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 d9063b4885ac6..e94e53c293d93 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 3933f416e3d81..18d2e152beb15 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 d3415bde59b3a..4e6ff6da6baac 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 36dca2bbcaf92..3a4b9ced0abf6 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 53ed8004f7d59..119725b06ddc4 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 18b953a037246..a0c99dbec4596 100644 --- a/homeassistant/components/media_player/universal.py +++ b/homeassistant/components/media_player/universal.py @@ -333,8 +333,7 @@ def supported_features(self): 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 589117768360e..ee86735a06399 100644 --- a/tests/components/media_player/test_universal.py +++ b/tests/components/media_player/test_universal.py @@ -584,7 +584,8 @@ def test_supported_features_children_and_cmds(self): 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 0000000000000..9cec34c134079 --- /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 0000000000000..f22958e7e384e --- /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 0000000000000..0e689e338f1dc --- /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 daa85a2425e31..28c88bf5c296c 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -23,7 +23,7 @@ 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 ed107f224c899..d52ef878b19eb 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 23794abeba49d..be80ef19169af 100644 --- a/homeassistant/components/switch/switchmate.py +++ b/homeassistant/components/switch/switchmate.py @@ -13,7 +13,7 @@ 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 d52ef878b19eb..577de8e5de8ef 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 62f4638820f57..3e228c4b40b3d 100644 --- a/homeassistant/components/wink/sensor.py +++ b/homeassistant/components/wink/sensor.py @@ -91,10 +91,9 @@ def unit_of_measurement(self): 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 b9443c287a1ef..1f0fb9d1a3b97 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 577de8e5de8ef..46d71c6c4e9a8 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 0ceaa7d55b384..ef4c010d42fc7 100755 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ 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 5a78d91553fa8..d914a76c4ce1d 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -62,9 +62,7 @@ def __init__(self, unique_id, zha_device, listeners, 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 c7a9c786054cc..f0b03a4b40b94 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 ba72398704259..c81f96468ce68 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 14da94bdf52ac..a67c857207279 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 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 e94e53c293d93..4712cac28d055 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 18d2e152beb15..bc367e155339c 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 4e6ff6da6baac..8b2553aae7a0f 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 2e69390776984..ae08b2cac40c2 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -147,8 +147,6 @@ def zha_send_event(self, cluster, command, args): ) 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 292f9817671ee..2322df5452c06 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -170,10 +170,6 @@ async def async_initialize(self, from_cache): 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 2722f6720ce7b..dca6b60ccc570 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -126,13 +126,6 @@ async def _get_or_create_device(self, zigpy_device): 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 916319b2d98a3..d7c46bdfb3a2d 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 @@ def __init__(self, cluster, device): [{'attr': 0, 'config': REPORT_CONFIG_DEFAULT}] ) self._status = ListenerStatus.CREATED + self._cluster.add_listener(self) @property def unique_id(self): @@ -156,11 +156,6 @@ 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.""" @@ -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 @@ async def async_initialize(self, 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.""" @@ -625,6 +610,7 @@ def __init__(self, cluster, device): 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 @@ 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 diff --git a/tests/components/zha/common.py b/tests/components/zha/common.py index f0b03a4b40b94..1a923849ce531 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 a67c857207279..6beafc6ca8e32 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 4712cac28d055..9c5e69d1347ef 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 bc367e155339c..d16cafb7df89b 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 8b2553aae7a0f..32c8ee64e6781 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 d7c46bdfb3a2d..035c752c42bae 100644 --- a/homeassistant/components/zha/core/listeners.py +++ b/homeassistant/components/zha/core/listeners.py @@ -178,6 +178,7 @@ def zha_send_event(self, cluster, command, args): '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 1f467c93c2d72..557567e7aaf6e 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 0000000000000..335dbc668d924 --- /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 46d71c6c4e9a8..7f618e0920ab0 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 8ab6bd752efb6..c59aee56a5117 100644 --- a/homeassistant/components/system_log/__init__.py +++ b/homeassistant/components/system_log/__init__.py @@ -4,8 +4,7 @@ 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 @@ def __init__(self, hass, maxlen, fire_event): """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 @@ def emit(self, record): 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 @@ def __init__(self, handler): 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 6afd792be9c6b..c1d79c9f33f4d 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 ba9d32e0daf47..1a3d4e2e455af 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,8 +7,8 @@ __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 cd22a69dab12a..d02d22cc8d28d 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 3642c5da6ec74..409b020f7286b 100644 --- a/tests/common.py +++ b/tests/common.py @@ -486,6 +486,8 @@ def __init__(self, domain=None, dependencies=None, setup=None, 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 5bd273ea16a05..6fecd5086b102 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 0000000000000..0023aa8a1f204 --- /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 b2669312e384b..6b1dd10bd5bd1 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.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 @@ @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_close_websession(event): @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 19ad52534cb89..bc8c05ed0a67b 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 @@ def async_update(self, area_id: str, name: str) -> AreaEntry: 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 @@ def _data_to_save(self) -> dict: @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 d240149cff88f..1dfa77c14f162 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 0991336f42a2c..c5ddd2734cb81 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -12,51 +12,85 @@ 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 @@ def on_subscribed(data): 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 @@ def on_subscribed(data): 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 @@ def on_subscribed(data): 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 0000000000000..c9c0160cf7c55 --- /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 75606a1c699fc..27ec7afefaa4a 100644 --- a/homeassistant/components/ambient_station/const.py +++ b/homeassistant/components/ambient_station/const.py @@ -8,3 +8,6 @@ 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 9e0833e344132..2699975cfb598 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,51 +18,38 @@ 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 - self._unit = unit - - @property - def name(self): - """Return the name of the sensor.""" - return '{0}_{1}'.format(self._station_name, self._sensor_name) + super().__init__( + ambient, mac_address, station_name, sensor_type, sensor_name) - @property - def should_poll(self): - """Disable polling.""" - return False + self._unit = unit @property def state(self): @@ -76,26 +61,6 @@ def unit_of_measurement(self): """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 0000000000000..7a1aae357ad12 --- /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 f33e8a83e1ef9..786afb1b9ce9a 100644 --- a/tests/components/binary_sensor/test_zwave.py +++ b/tests/components/zwave/test_binary_sensor.py @@ -4,7 +4,7 @@ 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 39a85ab493f54..e8be6d7382b3c 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 b870075d39fa6..cf3bcdf993b6a 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 b7d7e497c031b..af3d16f628891 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 212d3e0280298..66011f3e6ee00 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -10,7 +10,7 @@ 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 5805c8eb2fbbe..5e85f28da392c 100644 --- a/tests/components/light/test_zwave.py +++ b/tests/components/zwave/test_light.py @@ -3,8 +3,9 @@ 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 07095e4fe3e57..98734db8d7ce4 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 e3792c27d77d2..cce6bf9deaa1f 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 3769eef828b87..943be9fc4ea7e 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 c2e8eb1eb2867..1dc5733cf40a0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,7 @@ 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 @@ 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 5dd620056094e..90a74f2359839 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -10,7 +10,8 @@ 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 d02d22cc8d28d..962b168aa9765 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -18,7 +18,6 @@ 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 @@ 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() - - dep_load_order = _load_order_component( - hass, dependency, load_order, loading) + raise CircularDependency(comp_name, dependency) - # 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() + dep_loaded = _component_dependencies( + hass, dependency, loaded, loading) - load_order.update(dep_load_order) + loaded.update(dep_loaded) - 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 49aae2178fc66..33c5d5311b14d 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -106,12 +106,18 @@ def log_error(msg: str, link: bool = True) -> None: 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 b4d45b4807984..12cd543a872ed 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 @@ def __lt__(self, other: ENUM_T) -> bool: 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 6fecd5086b102..cceb9839d99c5 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 98fe8774b96a7..af957582ec0c6 100644 --- a/tests/util/test_init.py +++ b/tests/util/test_init.py @@ -105,67 +105,6 @@ class TestEnum(util.OrderedEnum): 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 557567e7aaf6e..e0f797c4d041c 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 0000000000000..14656737f5cf2 --- /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 0000000000000..0417d3bcde071 --- /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 0000000000000..b4d9bfe5ff987 --- /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 0000000000000..e6855865845f0 --- /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 7f618e0920ab0..452275bfcdcf9 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 035c752c42bae..3605f4a988582 100644 --- a/homeassistant/components/zha/core/listeners.py +++ b/homeassistant/components/zha/core/listeners.py @@ -69,6 +69,8 @@ async def wrapper(*args, **kwds): "{}: {}".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 1d1b4c5f921d1..09f1812cd7602 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -113,7 +113,7 @@ def set_level(self, value): """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 0000000000000..2e8b10c457d40 --- /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 0000000000000..217189a78a9a1 --- /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 0000000000000..4b10846ee3ceb --- /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 786afb1b9ce9a..ee68971bc3ec6 100644 --- a/tests/components/zwave/test_binary_sensor.py +++ b/tests/components/zwave/test_binary_sensor.py @@ -3,8 +3,7 @@ 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 e8be6d7382b3c..9a9ed41381f7b 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 cf3bcdf993b6a..ce34111c6129f 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 af3d16f628891..57a60cfa3030a 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 5e85f28da392c..61e960077c914 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 98734db8d7ce4..2c49c79f4a8a1 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 cce6bf9deaa1f..73613424d84f4 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 943be9fc4ea7e..e68f765ae384e 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 c5ddd2734cb81..ff9538738ee93 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -254,6 +254,17 @@ def __init__( 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 0000000000000..3a99757b54bfe --- /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 d8198ba303362..87b89ddb44c09 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -105,7 +105,7 @@ 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 15df690746807..e337a2ec251c7 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -124,7 +124,7 @@ async def service_handler(service): 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 5dbf226ca2535..3310cd3e160d9 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -65,49 +65,16 @@ (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 -introduction: - -# Enables the frontend -frontend: +# Configure a default setup of Home Assistant (frontend, api, etc) +default_config: -# Enables configuration UI -config: +# Show the introduction message on startup. +introduction: # 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 @@ 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 33c5d5311b14d..29c8e22d45d90 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 0000000000000..7ee4658fed5ca --- /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 0000000000000..94adf53cb2d5f --- /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 ff9538738ee93..4aa19dbc69ea6 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -96,12 +96,9 @@ 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 @@ def on_subscribed(data): _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 | 75 ++++-- 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, 453 insertions(+), 61 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 bdbbfcf25904a..b7b5436da3e73 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -23,7 +23,7 @@ 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 3d0e5cb95f852..9391c871b25d1 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -22,25 +22,9 @@ '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 0000000000000..5539703e77ebd --- /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 9d9dacf8460c9..89043d4f76c5e 100644 --- a/homeassistant/components/smartthings/smartapp.py +++ b/homeassistant/components/smartthings/smartapp.py @@ -22,8 +22,7 @@ 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,40 +189,58 @@ async def setup_smartapp_endpoint(hass: HomeAssistantType): } -async def smartapp_install(hass: HomeAssistantType, req, resp, app): - """ - Handle when a SmartApp is installed by the user into a location. +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) - Setup subscriptions using the access token SmartThings provided in the - event. An explicit subscription is required for each 'capability' in order - to receive the related attribute updates. Finally, create a config entry - representing the installation if this is not the first installation under - the account. - """ - from pysmartthings import SmartThings, Subscription, SourceType + 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) - # 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) + # 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 = req.installed_app_id - sub.location_id = req.location_id + 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, req.installed_app_id) + target, 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) + "app '%s'", target, installed_app_id) - tasks = [create_subscription(c) for c in SUPPORTED_CAPABILITIES] + tasks = [create_subscription(c) for c in capabilities] await asyncio.gather(*tasks) - _LOGGER.debug("SmartApp '%s' under parent app '%s' was installed", - req.installed_app_id, app.app_id) + + +async def smartapp_install(hass: HomeAssistantType, req, resp, app): + """ + Handle when a SmartApp is installed by the user into a location. + + Setup subscriptions using the access token SmartThings provided in the + event. An explicit subscription is required for each 'capability' in order + to receive the related attribute updates. Finally, create a config entry + representing the installation if this is not the first installation under + the account. + """ + 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 create_subscription(target): }) +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 452275bfcdcf9..6328e7f713582 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 dd455ef88fd3b..df750d69972d0 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 7358e05f346b6..c1a1769f04c3d 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -10,9 +10,10 @@ 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 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 92d891c06d6b2..4b47537fa19e0 100644 --- a/tests/components/smartthings/test_binary_sensor.py +++ b/tests/components/smartthings/test_binary_sensor.py @@ -4,12 +4,12 @@ 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 4aef42c1b6fe6..014cfe7da9820 100644 --- a/tests/components/smartthings/test_init.py +++ b/tests/components/smartthings/test_init.py @@ -162,7 +162,7 @@ def signal(ids): 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 0000000000000..773f157dd877e --- /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 0f517222c4a86..162a8f9a4e5c7 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 366ec4405cd76..fae18bf8b77d5 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -17,7 +17,7 @@ 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 a1423cc66826b..bb92f1d0ce034 100644 --- a/homeassistant/components/knx/light.py +++ b/homeassistant/components/knx/light.py @@ -4,28 +4,50 @@ 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 @@ 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 @@ def __init__(self, device): """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 @@ def should_poll(self): @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 @@ def supported_features(self): 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 6328e7f713582..7033b48b414eb 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 b9f5cec0d64c0..a10be84472a35 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 0000000000000..d138675730531 --- /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 0000000000000..87f62371b5557 --- /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 0000000000000..bb42a00742e43 --- /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 0000000000000..dbb192945419b --- /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 0000000000000..f22d1b62fe44c --- /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 fda0fef4f2595..ec9b6fec2e8b5 100644 --- a/homeassistant/components/weather/ipma.py +++ b/homeassistant/components/ipma/weather.py @@ -20,7 +20,7 @@ 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 @@ 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 @@ async def async_update(self): 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 @@ def condition(self): @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 c72f0f22827aa..7c6da2644f660 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -149,6 +149,7 @@ async def async_step_discovery(info): 'hue', 'ifttt', 'ios', + 'ipma', 'lifx', 'locative', 'luftdaten', diff --git a/requirements_all.txt b/requirements_all.txt index 7033b48b414eb..8b2a0a7cc6ba4 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 0000000000000..35099c405bb97 --- /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 0000000000000..4a72318128ebd --- /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 0000000000000..c141fbae7f1fb --- /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 c7c89ecdbdbff..0000000000000 --- 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 72b7a502aa2ca..77d0825ef0b4e 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -224,7 +224,12 @@ def setup(self): 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 | 113 ++++++- homeassistant/helpers/logging.py | 49 +++ tests/components/freedns/test_init.py | 2 +- tests/helpers/test_config_validation.py | 329 ++++++++++++++++++- 5 files changed, 486 insertions(+), 39 deletions(-) create mode 100644 homeassistant/helpers/logging.py diff --git a/homeassistant/components/freedns/__init__.py b/homeassistant/components/freedns/__init__.py index ec38bb59cc782..7da51cd42e4bf 100644 --- a/homeassistant/components/freedns/__init__.py +++ b/homeassistant/components/freedns/__init__.py @@ -13,7 +13,7 @@ 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 @@ 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 3a4b9ced0abf6..3b01a01fc96bd 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 validate(obj: Dict) -> Dict: 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]) - - return config + 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 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 0000000000000..ea596eb3c158e --- /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 b8e38e9c3a8f0..784926912cdfe 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 119725b06ddc4..cefde564035a3 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -5,6 +5,7 @@ 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 + 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 - deprecated_schema({'mars': True}) + 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 + "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 b0ea1a3b348f1..f048a50d1d0e4 100644 --- a/homeassistant/components/homematicip_cloud/__init__.py +++ b/homeassistant/components/homematicip_cloud/__init__.py @@ -19,7 +19,7 @@ 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 77feda20be12e..911c00e45bc13 100644 --- a/homeassistant/components/homematicip_cloud/sensor.py +++ b/homeassistant/components/homematicip_cloud/sensor.py @@ -7,11 +7,10 @@ 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 @@ def state(self): 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 8b2a0a7cc6ba4..9f43fc82a5ddf 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 df750d69972d0..9d768779ccc48 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 333f62a9aa7d7..482f961765fce 100644 --- a/homeassistant/components/media_extractor/__init__.py +++ b/homeassistant/components/media_extractor/__init__.py @@ -14,7 +14,7 @@ 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 9f43fc82a5ddf..62efa76cac23d 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 cf7d58b17a8a6..be9a0a24feee7 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -80,7 +80,7 @@ def update(self): "$.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 b14881fccca48..dd10c4ecfdf86 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -19,6 +19,7 @@ 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 @@ _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 refresh(event_time): 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 @@ def update(self): 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 @@ def set_alt_speed_enabled(self, is_enabled): 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 84c7d54306e60..cb592a74758e6 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -9,10 +9,11 @@ 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 @@ 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 @@ def state(self): """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 @@ def available(self): """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 8e6c0a8cb446c..aac946dee8ba6 100644 --- a/homeassistant/components/transmission/switch.py +++ b/homeassistant/components/transmission/switch.py @@ -4,16 +4,15 @@ 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 @@ 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 @@ def state(self): @property def should_poll(self): """Poll for status regularly.""" - return True + return False @property def is_on(self): @@ -75,8 +76,21 @@ def turn_off(self, **kwargs): _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 7240912883a88..156dfa84a8a78 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -27,9 +27,9 @@ 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 @@ def interfaces(self): _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 @@ def interfaces(self): 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 bff8834639d57..0b38a256e4070 100644 --- a/homeassistant/components/apple_tv/media_player.py +++ b/homeassistant/components/apple_tv/media_player.py @@ -8,11 +8,11 @@ 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 c66f74f74a9a7..b80a8ce5e0f2a 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -14,10 +14,12 @@ 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 815e28b4fa463..174ee38715ad8 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -12,7 +12,7 @@ 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 d69d8a74ce6fb..6e691cad94fc7 100644 --- a/homeassistant/components/hdmi_cec/media_player.py +++ b/homeassistant/components/hdmi_cec/media_player.py @@ -7,10 +7,11 @@ 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 7ad0cea48e7e7..f1327f8b5270f 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 482f961765fce..8e00949da07bd 100644 --- a/homeassistant/components/media_extractor/__init__.py +++ b/homeassistant/components/media_extractor/__init__.py @@ -9,9 +9,12 @@ 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 840b745eebd1d..ad29a645765bb 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -68,6 +68,19 @@ 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 @@ 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 a0bc3d05dcb4c..d48f90d2bd7eb 100644 --- a/homeassistant/components/media_player/anthemav.py +++ b/homeassistant/components/media_player/anthemav.py @@ -9,8 +9,10 @@ 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 5c1994e65fc8b..59723b4752260 100644 --- a/homeassistant/components/media_player/aquostv.py +++ b/homeassistant/components/media_player/aquostv.py @@ -9,10 +9,12 @@ 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 2c78bb24bbdc3..2daa2656e8306 100644 --- a/homeassistant/components/media_player/blackbird.py +++ b/homeassistant/components/media_player/blackbird.py @@ -10,8 +10,10 @@ 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 998f559bc8aa5..c6a8c51ca58bf 100644 --- a/homeassistant/components/media_player/bluesound.py +++ b/homeassistant/components/media_player/bluesound.py @@ -16,12 +16,13 @@ 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 04dc013108fc6..7efb7abd569d7 100644 --- a/homeassistant/components/media_player/braviatv.py +++ b/homeassistant/components/media_player/braviatv.py @@ -10,10 +10,12 @@ 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 43259c40f65d3..2f7b169601c9e 100644 --- a/homeassistant/components/media_player/channels.py +++ b/homeassistant/components/media_player/channels.py @@ -9,11 +9,12 @@ 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 2add2bd682a60..24df7c24611a5 100644 --- a/homeassistant/components/media_player/clementine.py +++ b/homeassistant/components/media_player/clementine.py @@ -11,9 +11,11 @@ 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 2711ac1ff11c8..20b292749b4ec 100644 --- a/homeassistant/components/media_player/cmus.py +++ b/homeassistant/components/media_player/cmus.py @@ -9,10 +9,11 @@ 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 b926d89341403..bf7e6b4e0cef6 100644 --- a/homeassistant/components/media_player/const.py +++ b/homeassistant/components/media_player/const.py @@ -29,7 +29,34 @@ 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 8a88e3bd74e42..de455879d3d5a 100644 --- a/homeassistant/components/media_player/demo.py +++ b/homeassistant/components/media_player/demo.py @@ -6,12 +6,13 @@ """ 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 79b69b551cec0..3dc4e550d9b9d 100644 --- a/homeassistant/components/media_player/denon.py +++ b/homeassistant/components/media_player/denon.py @@ -10,10 +10,11 @@ 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 c565a161b101e..d6caf361961c7 100644 --- a/homeassistant/components/media_player/denonavr.py +++ b/homeassistant/components/media_player/denonavr.py @@ -11,11 +11,13 @@ 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 707014328c63b..9c5a3bf07b82b 100644 --- a/homeassistant/components/media_player/directv.py +++ b/homeassistant/components/media_player/directv.py @@ -9,10 +9,11 @@ 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 802b2b597fc04..03015cd5c01a7 100644 --- a/homeassistant/components/media_player/dlna_dmr.py +++ b/homeassistant/components/media_player/dlna_dmr.py @@ -14,9 +14,11 @@ 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 00c8ff3f4df6a..796aea86414dc 100644 --- a/homeassistant/components/media_player/dunehd.py +++ b/homeassistant/components/media_player/dunehd.py @@ -7,9 +7,11 @@ 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 dd43d48ee6a46..b1259db913d97 100644 --- a/homeassistant/components/media_player/emby.py +++ b/homeassistant/components/media_player/emby.py @@ -9,9 +9,11 @@ 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 bb1618f23513f..38c0ffacc3231 100644 --- a/homeassistant/components/media_player/epson.py +++ b/homeassistant/components/media_player/epson.py @@ -9,10 +9,11 @@ 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 6e7d431c9ae98..58f1913b9f983 100644 --- a/homeassistant/components/media_player/firetv.py +++ b/homeassistant/components/media_player/firetv.py @@ -10,7 +10,9 @@ 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 67c84bd7b1b89..ed7041a3b8293 100644 --- a/homeassistant/components/media_player/frontier_silicon.py +++ b/homeassistant/components/media_player/frontier_silicon.py @@ -9,11 +9,12 @@ 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 b4ede671d5222..c72d14ebb8a6a 100644 --- a/homeassistant/components/media_player/gpmdp.py +++ b/homeassistant/components/media_player/gpmdp.py @@ -12,9 +12,10 @@ 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 fa8545bae03c4..c6571894472cb 100644 --- a/homeassistant/components/media_player/gstreamer.py +++ b/homeassistant/components/media_player/gstreamer.py @@ -9,8 +9,10 @@ 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 46d1dd4d69837..334757c086dba 100644 --- a/homeassistant/components/media_player/harman_kardon_avr.py +++ b/homeassistant/components/media_player/harman_kardon_avr.py @@ -10,9 +10,10 @@ 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 058796ea46dbb..e7cfcfe62b132 100644 --- a/homeassistant/components/media_player/horizon.py +++ b/homeassistant/components/media_player/horizon.py @@ -11,9 +11,11 @@ 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 e2ae179676b3c..f8380032aea4c 100644 --- a/homeassistant/components/media_player/itunes.py +++ b/homeassistant/components/media_player/itunes.py @@ -10,10 +10,12 @@ 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 a83287eb617a0..f8d0cdc5a12cf 100644 --- a/homeassistant/components/media_player/kodi.py +++ b/homeassistant/components/media_player/kodi.py @@ -16,13 +16,14 @@ 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 c2f63c71f89cf..7c5d978937240 100644 --- a/homeassistant/components/media_player/lg_netcast.py +++ b/homeassistant/components/media_player/lg_netcast.py @@ -12,10 +12,11 @@ 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 38b27bd074a34..b45baf88bca2a 100644 --- a/homeassistant/components/media_player/lg_soundbar.py +++ b/homeassistant/components/media_player/lg_soundbar.py @@ -7,8 +7,10 @@ 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 3f8ea2cfd48cb..f69c3c67aec9a 100644 --- a/homeassistant/components/media_player/liveboxplaytv.py +++ b/homeassistant/components/media_player/liveboxplaytv.py @@ -11,10 +11,12 @@ 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 345b58cbbe413..29cc733293605 100644 --- a/homeassistant/components/media_player/mediaroom.py +++ b/homeassistant/components/media_player/mediaroom.py @@ -9,10 +9,12 @@ 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 96b8dcbcf26c7..e98ad47a6e7fa 100644 --- a/homeassistant/components/media_player/monoprice.py +++ b/homeassistant/components/media_player/monoprice.py @@ -9,9 +9,11 @@ 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 e6bc1f2699dfe..61d89c6d0b15d 100644 --- a/homeassistant/components/media_player/mpchc.py +++ b/homeassistant/components/media_player/mpchc.py @@ -11,9 +11,11 @@ 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 09d0a976b82e4..9d8015109b200 100644 --- a/homeassistant/components/media_player/mpd.py +++ b/homeassistant/components/media_player/mpd.py @@ -11,12 +11,14 @@ 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 57dca116f6b09..127be02dac4aa 100644 --- a/homeassistant/components/media_player/nad.py +++ b/homeassistant/components/media_player/nad.py @@ -10,9 +10,10 @@ 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 5ff54201b3c0a..df30c7e078278 100644 --- a/homeassistant/components/media_player/onkyo.py +++ b/homeassistant/components/media_player/onkyo.py @@ -12,9 +12,11 @@ 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 ab23f8a7f9aba..d828284a563e2 100644 --- a/homeassistant/components/media_player/openhome.py +++ b/homeassistant/components/media_player/openhome.py @@ -7,10 +7,11 @@ 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 041efed74bf1d..36a3160d3b52f 100644 --- a/homeassistant/components/media_player/panasonic_bluray.py +++ b/homeassistant/components/media_player/panasonic_bluray.py @@ -10,8 +10,10 @@ 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 bff108d70d7f7..e5ce22e952431 100644 --- a/homeassistant/components/media_player/panasonic_viera.py +++ b/homeassistant/components/media_player/panasonic_viera.py @@ -9,10 +9,12 @@ 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 231ea5302aef8..ca78f7a43182b 100644 --- a/homeassistant/components/media_player/pandora.py +++ b/homeassistant/components/media_player/pandora.py @@ -12,14 +12,14 @@ 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 506e5a9e47933..4f8a133978193 100644 --- a/homeassistant/components/media_player/philips_js.py +++ b/homeassistant/components/media_player/philips_js.py @@ -10,10 +10,11 @@ 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 171343d4adbb0..00fa453100ae7 100644 --- a/homeassistant/components/media_player/pioneer.py +++ b/homeassistant/components/media_player/pioneer.py @@ -10,9 +10,10 @@ 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 0609b75f98db8..c1b883a029589 100644 --- a/homeassistant/components/media_player/pjlink.py +++ b/homeassistant/components/media_player/pjlink.py @@ -9,8 +9,10 @@ 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 2110c42d37187..c67849edee991 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -13,10 +13,11 @@ 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 19cc2228d3266..972594e07e606 100644 --- a/homeassistant/components/media_player/russound_rio.py +++ b/homeassistant/components/media_player/russound_rio.py @@ -9,9 +9,10 @@ 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 7f4d04eb63456..6d919cdf7a8a9 100644 --- a/homeassistant/components/media_player/russound_rnet.py +++ b/homeassistant/components/media_player/russound_rnet.py @@ -9,8 +9,10 @@ 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 9def9875ab4e6..db6bd317c40a8 100644 --- a/homeassistant/components/media_player/samsungtv.py +++ b/homeassistant/components/media_player/samsungtv.py @@ -12,10 +12,11 @@ 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 cfe2f99729558..74b17ae5ff1a6 100644 --- a/homeassistant/components/media_player/snapcast.py +++ b/homeassistant/components/media_player/snapcast.py @@ -10,8 +10,10 @@ 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 e67578539adc2..7665b409d1d23 100644 --- a/homeassistant/components/media_player/songpal.py +++ b/homeassistant/components/media_player/songpal.py @@ -11,9 +11,11 @@ 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 037a9b88fc63d..b2045b9b65eb7 100644 --- a/homeassistant/components/media_player/soundtouch.py +++ b/homeassistant/components/media_player/soundtouch.py @@ -10,10 +10,11 @@ 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 4fbd43f3f1602..9965487ded9b7 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -11,10 +11,11 @@ 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 73b6a070419ad..5f6fd525a112e 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -14,11 +14,13 @@ 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 75f6d92a98c28..2261aadc2f68f 100644 --- a/homeassistant/components/media_player/ue_smart_radio.py +++ b/homeassistant/components/media_player/ue_smart_radio.py @@ -11,10 +11,11 @@ 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 a0c99dbec4596..5730a0867311b 100644 --- a/homeassistant/components/media_player/universal.py +++ b/homeassistant/components/media_player/universal.py @@ -10,6 +10,8 @@ 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 @@ 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 5aae8661bd888..395f5bb369e4d 100644 --- a/homeassistant/components/media_player/vizio.py +++ b/homeassistant/components/media_player/vizio.py @@ -11,10 +11,11 @@ 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 5cc4196d4e11c..592243938d767 100644 --- a/homeassistant/components/media_player/vlc.py +++ b/homeassistant/components/media_player/vlc.py @@ -9,9 +9,10 @@ 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 bd43e6c371039..a72f34fac1de6 100644 --- a/homeassistant/components/media_player/volumio.py +++ b/homeassistant/components/media_player/volumio.py @@ -15,11 +15,12 @@ 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 09d36f82db07a..e3b25c3c31f80 100644 --- a/homeassistant/components/media_player/xiaomi_tv.py +++ b/homeassistant/components/media_player/xiaomi_tv.py @@ -9,8 +9,9 @@ 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 67c55dad0d1fd..f652d95e713af 100644 --- a/homeassistant/components/media_player/yamaha.py +++ b/homeassistant/components/media_player/yamaha.py @@ -10,11 +10,13 @@ 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 535a2ad01ca5d..6aa06b604c5d1 100644 --- a/homeassistant/components/media_player/yamaha_musiccast.py +++ b/homeassistant/components/media_player/yamaha_musiccast.py @@ -9,10 +9,11 @@ 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 57ef69c923e05..abad22d89eb5b 100644 --- a/homeassistant/components/media_player/ziggo_mediabox_xl.py +++ b/homeassistant/components/media_player/ziggo_mediabox_xl.py @@ -10,9 +10,11 @@ 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 9dc1151064d3e..7d1977aadc14f 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -7,10 +7,11 @@ 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 ef6b02514f0a5..fd8c228d3965e 100644 --- a/homeassistant/components/sisyphus/media_player.py +++ b/homeassistant/components/sisyphus/media_player.py @@ -6,10 +6,11 @@ """ 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 b34aabd4c5150..2106e46cb5dd1 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -15,11 +15,13 @@ 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 3f8d1b6fdec09..475ed2c68921f 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -18,10 +18,10 @@ 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 f80e29d35a0e0..d34dbe8077801 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -14,10 +14,12 @@ 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 2b5346bf639ca..78bdd50b6d75b 100644 --- a/tests/components/google/test_tts.py +++ b/tests/components/google/test_tts.py @@ -5,7 +5,7 @@ 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 d39609b079a7e..e395402b9582f 100644 --- a/tests/components/homekit/test_get_accessories.py +++ b/tests/components/homekit/test_get_accessories.py @@ -6,7 +6,7 @@ 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 6b23b3cc58eca..065d1845fdb5e 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -4,7 +4,7 @@ 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 2174967eae53f..4a53920d75865 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 38a63f294f964..6ab3b69b558ee 100644 --- a/tests/components/media_player/test_blackbird.py +++ b/tests/components/media_player/test_blackbird.py @@ -4,7 +4,7 @@ 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 d8e561d8d2a7e..5918946fe6c70 100644 --- a/tests/components/media_player/test_directv.py +++ b/tests/components/media_player/test_directv.py @@ -5,10 +5,14 @@ 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 c6a6b3036d97f..d6374cf9dd7f6 100644 --- a/tests/components/media_player/test_monoprice.py +++ b/tests/components/media_player/test_monoprice.py @@ -4,7 +4,7 @@ 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 c2f5d28fd5d58..a74f476981ac3 100644 --- a/tests/components/media_player/test_samsungtv.py +++ b/tests/components/media_player/test_samsungtv.py @@ -8,7 +8,7 @@ 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 79eab1c16996e..55ff96f202a20 100644 --- a/tests/components/sonos/test_media_player.py +++ b/tests/components/sonos/test_media_player.py @@ -9,7 +9,7 @@ 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 977b0669880c9..4786370f24f65 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -10,7 +10,7 @@ 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 110473d75a813..7520ba2fbaad8 100644 --- a/tests/components/tts/test_marytts.py +++ b/tests/components/tts/test_marytts.py @@ -5,7 +5,7 @@ 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 334e35a9386a2..af4bdf3976c39 100644 --- a/tests/components/tts/test_voicerss.py +++ b/tests/components/tts/test_voicerss.py @@ -4,7 +4,7 @@ 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 2675e3225071a..70c75b1f2ed47 100644 --- a/tests/components/tts/test_yandextts.py +++ b/tests/components/tts/test_yandextts.py @@ -5,7 +5,7 @@ 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 3ccc60457b6ab..cc1137e23fdea 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -29,6 +29,16 @@ 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 @@ def __init__(self, pygtfs, name, origin, destination, offset): 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 @@ def device_state_attributes(self): @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 @@ def update(self): 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 cc1137e23fdea..081361aa32ed1 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() - minutes_until = int(seconds_until / 60) + depart_time = datetime.datetime.strptime(origin_depart_time, TIME_FORMAT) + arrival_time = datetime.datetime.strptime(dest_arrival_time, TIME_FORMAT) - route = sched.routes_by_id(item[1])[0] + seconds_until = (depart_time - datetime.datetime.now()).total_seconds() + minutes_until = int(seconds_until / 60) - 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 4591e14a00682..2cefa2e10491a 100644 --- a/homeassistant/components/zoneminder/__init__.py +++ b/homeassistant/components/zoneminder/__init__.py @@ -16,7 +16,7 @@ _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 62efa76cac23d..c67bebac1a911 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 d99d878ef6381..b9da9890c611c 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 9d768779ccc48..ff63edada638b 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 f58580b83c79b..f265bd3492a1d 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 d415090022306..39d795dcd2515 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 1f7f9c3a6861f..5a27fbaec6398 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 4116a791b01cd..581b7a3e0df97 100644 --- a/homeassistant/components/august/binary_sensor.py +++ b/homeassistant/components/august/binary_sensor.py @@ -181,4 +181,4 @@ def update(self): """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 28a51bd8ef2a0..6e2ca2dc6c505 100644 --- a/homeassistant/components/sensor/darksky.py +++ b/homeassistant/components/sensor/darksky.py @@ -162,9 +162,9 @@ # 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 2e8b10c457d40..8ad03e3f0ff33 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -4,26 +4,37 @@ 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 @@ }) 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)) - - if not entities: - _LOGGER.error("No persons could be set up") - return False + conf_persons = config.get(DOMAIN, []) + manager = hass.data[DOMAIN] = PersonManager(hass, component, conf_persons) + await manager.async_initialize() - 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 @@ def state(self): @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 @@ async def async_added_to_hass(self): 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() + + self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, + person_start_hass) + + @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) - _LOGGER.debug( - "Subscribe to device trackers for %s", self.entity_id) + latest = max( + [self.hass.states.get(entity_id) for entity_id in trackers], + key=sort_key + ) - for tracker in self._trackers: - async_track_state_change( - self.hass, tracker, async_handle_tracker_update) + @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 60e2caa54acd5..041aad3969e6d 100644 --- a/homeassistant/components/websocket_api/connection.py +++ b/homeassistant/components/websocket_api/connection.py @@ -31,6 +31,16 @@ def context(self, msg): 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 2d4ad68dbbed9..c13ebe7cfab1e 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -319,7 +319,7 @@ def schedule_update_ha_state(self, force_refresh=False): @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 4b10846ee3ceb..9b76135f743fe 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 f1900979bec4b..3cb5b87b4bb57 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 @@ async def job(): 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 job(): 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 0000000000000..4a3201d7768b4 --- /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 9a496d914fc57..3439a23adb3bc 100644 --- a/homeassistant/components/homematic/__init__.py +++ b/homeassistant/components/homematic/__init__.py @@ -90,7 +90,7 @@ '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 e5fb8fc66b0f9..569d69a9ad875 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -110,7 +110,8 @@ def message_received(topic, payload, qos): 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 15b4ed223784e..5726a64ba11a8 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 ef389bb84f9bc..726433b4f7030 100644 --- a/homeassistant/components/light/rflink.py +++ b/homeassistant/components/light/rflink.py @@ -185,6 +185,14 @@ def brightness(self): """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 @@ def brightness(self): """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 b91ff251bd112..ce9777151cffc 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -345,6 +345,7 @@ def _availability_callback(self, availability): 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 e2b80d12ce0d0..901a2d3a286fd 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 a10fa129fe5de..5b991e0d3e228 100644 --- a/homeassistant/components/spider/__init__.py +++ b/homeassistant/components/spider/__init__.py @@ -14,7 +14,7 @@ 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 a9d966bd499c3..cfef50e825529 100644 --- a/homeassistant/components/spider/climate.py +++ b/homeassistant/components/spider/climate.py @@ -9,12 +9,23 @@ 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 @@ def supported_features(self): 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 @@ def set_operation_mode(self, operation_mode): 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 c67bebac1a911..9f2f8ec636633 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 cb3a311c985d4..5edcadc7fce91 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -88,6 +88,9 @@ 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 2322df5452c06..6bbfcd43a9457 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -14,7 +14,8 @@ 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 @@ def __init__(self, hass, zigpy_device, zha_gateway): 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 @@ def device_info(self): 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 c907d5101a9bf..b19b06761c4b1 100644 --- a/homeassistant/components/zwave/lock.py +++ b/homeassistant/components/zwave/lock.py @@ -42,7 +42,10 @@ # 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 156dfa84a8a78..acb23fe42780d 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -63,6 +63,7 @@ (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 ebe209c745ea6..16be7d4582511 100644 --- a/homeassistant/components/light/hyperion.py +++ b/homeassistant/components/light/hyperion.py @@ -177,6 +177,11 @@ def turn_on(self, **kwargs): 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 6957edc4f3fef..643e44ada1bff 100644 --- a/homeassistant/components/zha/core/helpers.py +++ b/homeassistant/components/zha/core/helpers.py @@ -6,7 +6,7 @@ """ 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 3605f4a988582..1b240d499b485 100644 --- a/homeassistant/components/zha/core/listeners.py +++ b/homeassistant/components/zha/core/listeners.py @@ -15,7 +15,7 @@ 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 @@ async def async_configure(self): 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 e0f797c4d041c..b615c4250f8c0 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 a10be84472a35..6426359812113 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 0000000000000..32c77b93eeabf --- /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 bc0b9bd52ee5b..14c22cefbe902 100644 --- a/homeassistant/components/climate/demo.py +++ b/homeassistant/components/climate/demo.py @@ -186,22 +186,22 @@ def set_temperature(self, **kwargs): 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 9f2f8ec636633..5911eb938b9e0 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 2837edbd5b7c9..5d604d2c66568 100644 --- a/homeassistant/components/homematicip_cloud/light.py +++ b/homeassistant/components/homematicip_cloud/light.py @@ -7,10 +7,10 @@ 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 @@ async def async_turn_on(self, **kwargs): 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 0000000000000..af8bb60f8b1dc --- /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 5911eb938b9e0..683f69ba29c68 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 0000000000000..b97dc33f8b14a --- /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 6616d6b24ec10..66044678e2836 100644 --- a/homeassistant/components/point/__init__.py +++ b/homeassistant/components/point/__init__.py @@ -26,7 +26,7 @@ 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 9413cf163d9cc..eb320de0efd7d 100644 --- a/homeassistant/components/point/sensor.py +++ b/homeassistant/components/point/sensor.py @@ -67,6 +67,8 @@ def icon(self): @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 683f69ba29c68..3d6a40a31b33f 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 0f1220f9b07a0..13b51d505c37b 100644 --- a/homeassistant/components/thethingsnetwork/sensor.py +++ b/homeassistant/components/thethingsnetwork/sensor.py @@ -153,7 +153,7 @@ async def async_update(self): 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 17288a881aae3..3f33cbe9fb393 100644 --- a/homeassistant/components/tplink_lte/__init__.py +++ b/homeassistant/components/tplink_lte/__init__.py @@ -13,7 +13,8 @@ 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 @@ 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 9bb80e2591c3a..f80345a43628c 100644 --- a/homeassistant/components/tplink_lte/notify.py +++ b/homeassistant/components/tplink_lte/notify.py @@ -10,6 +10,7 @@ from homeassistant.components.notify import ( ATTR_TARGET, BaseNotificationService) +from homeassistant.const import CONF_RECIPIENT from ..tplink_lte import DATA_KEY @@ -40,7 +41,7 @@ async def async_send_message(self, message="", **kwargs): _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 b615c4250f8c0..8ef830c8a8afb 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 0000000000000..16ab79fc58263 --- /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 0000000000000..d0b95daaafa99 --- /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 0000000000000..951e6f13b91b1 --- /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 0000000000000..c36981c5278f5 --- /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 0000000000000..24ca263898c25 --- /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 0000000000000..0f64533f7f156 --- /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 0000000000000..ee62df8ddad5f --- /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 3d6a40a31b33f..a025998e877fb 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 d6caf361961c7..380484add53f5 100644 --- a/homeassistant/components/media_player/denonavr.py +++ b/homeassistant/components/media_player/denonavr.py @@ -23,7 +23,7 @@ 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 a025998e877fb..7f21c6048271c 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 4aa19dbc69ea6..cf5625620a226 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -33,6 +33,16 @@ 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_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 @@ 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 @@ 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 c9c0160cf7c55..9d3b90a08a157 100644 --- a/homeassistant/components/ambient_station/binary_sensor.py +++ b/homeassistant/components/ambient_station/binary_sensor.py @@ -7,7 +7,9 @@ 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 @@ def device_class(self): @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 27f26805e81db..80fc8f7b43065 100644 --- a/homeassistant/components/homematicip_cloud/cover.py +++ b/homeassistant/components/homematicip_cloud/cover.py @@ -15,6 +15,9 @@ _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 @@ def current_cover_position(self): 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 b0f4c6d1a3c12..bd1621a0b358f 100644 --- a/homeassistant/components/light/tplink.py +++ b/homeassistant/components/light/tplink.py @@ -19,7 +19,7 @@ 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 01d5c2cc72c92..67c8094a1f208 100644 --- a/homeassistant/components/switch/tplink.py +++ b/homeassistant/components/switch/tplink.py @@ -14,7 +14,7 @@ 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 7f21c6048271c..229529173844e 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 eac20c62797fb..462dd007d530f 100644 --- a/homeassistant/components/notify/xmpp.py +++ b/homeassistant/components/notify/xmpp.py @@ -21,7 +21,7 @@ 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 229529173844e..0ad580ddd5d0c 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 1f0fb9d1a3b97..b94e383ec9ee0 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 0ad580ddd5d0c..182268212beed 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 ef4c010d42fc7..cdd377b76731e 100755 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ '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 174ee38715ad8..fdf1d35201e57 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -19,6 +19,16 @@ 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 @@ async def put(self, request, username, entity_number): # 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 @@ async def put(self, request, username, entity_number): 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 @@ async def put(self, request, username, entity_number): 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 70fe894debf80..5e3d6d1019c0b 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -11,13 +11,17 @@ 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 | 120 +++++++++++------- .../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, 161 insertions(+), 92 deletions(-) diff --git a/homeassistant/components/climate/flexit.py b/homeassistant/components/climate/flexit.py index de74d2facb57b..e0453b8bf90fa 100644 --- a/homeassistant/components/climate/flexit.py +++ b/homeassistant/components/climate/flexit.py @@ -20,13 +20,15 @@ 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 @@ def __init__(self, modbus_slave, name): 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 40ede019c1015..77a62103f80b1 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -12,19 +12,27 @@ 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 @@ 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_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(hass, config): - """Set up Modbus component.""" - # Modbus connection type - client_type = config[DOMAIN][CONF_TYPE] - - # Connect to Modbus network - # pylint: disable=import-error +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 - 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': + 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 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.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 - client = ModbusClient(host=config[DOMAIN][CONF_HOST], - port=config[DOMAIN][CONF_PORT], - timeout=config[DOMAIN][CONF_TIMEOUT]) - elif client_type == 'udp': + 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 - client = ModbusClient(host=config[DOMAIN][CONF_HOST], - port=config[DOMAIN][CONF_PORT], - timeout=config[DOMAIN][CONF_TIMEOUT]) - else: - return False + return ModbusClient(host=client_config[CONF_HOST], + port=client_config[CONF_PORT], + timeout=client_config[CONF_TIMEOUT]) + assert False - global HUB - HUB = ModbusHub(client) + +def setup(hass, config): + """Set up Modbus component.""" + # Modbus connection type + hass.data[DOMAIN] = hub_collect = {} + + 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 write_register(service): 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 write_coil(service): 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 write_coil(service): 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 f9f2597593e64..7089439a7e18c 100644 --- a/homeassistant/components/modbus/binary_sensor.py +++ b/homeassistant/components/modbus/binary_sensor.py @@ -7,7 +7,8 @@ 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 @@ 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 @@ def is_on(self): 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 1c5c03e4502a1..2305189867920 100644 --- a/homeassistant/components/modbus/climate.py +++ b/homeassistant/components/modbus/climate.py @@ -17,8 +17,8 @@ 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_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 @@ def set_temperature(self, **kwargs): 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 @@ def read_register(self, register): 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 833cb0c5a628e..b263bad5318b2 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -9,11 +9,12 @@ 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 @@ 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 @@ def __init__(self, name, slave, register, register_type, 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 @@ def unit_of_measurement(self): 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 @@ def update(self): 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 a8c8358f0cf19..04c73d7d3721d 100644 --- a/homeassistant/components/modbus/switch.py +++ b/homeassistant/components/modbus/switch.py @@ -7,10 +7,12 @@ 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_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 @@ }) 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 @@ def name(self): 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 @@ def __init__(self, name, slave, register, command_on, 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 @@ def turn_on(self, **kwargs): 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 @@ def update(self): 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 @@ def update(self): 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 @@ def update(self): 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 c412cb9fef0f1..75a099562a69d 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -14,14 +14,13 @@ 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 @@ 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 @@ async def issue_zigbee_cluster_command(service): 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 4a3201d7768b4..87621ea548bf7 100644 --- a/tests/components/zha/test_api.py +++ b/tests/components/zha/test_api.py @@ -5,10 +5,12 @@ 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 7658015ea67a2..77b9783b23988 100644 --- a/homeassistant/components/netgear_lte/__init__.py +++ b/homeassistant/components/netgear_lte/__init__.py @@ -8,9 +8,9 @@ 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 9ba804e193d81..3f39ccfb43f7d 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 3c17750d6adeb..99907a21b5722 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 581b7a3e0df97..c059f4c020aa3 100644 --- a/homeassistant/components/august/binary_sensor.py +++ b/homeassistant/components/august/binary_sensor.py @@ -144,6 +144,13 @@ def update(self): 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 @@ def update(self): 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 dcce5e13588c1..c62a32342dcea 100644 --- a/homeassistant/components/august/camera.py +++ b/homeassistant/components/august/camera.py @@ -74,3 +74,8 @@ def camera_image(self): 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 ce6792ceb39d3..e83641709800c 100644 --- a/homeassistant/components/august/lock.py +++ b/homeassistant/components/august/lock.py @@ -95,3 +95,8 @@ def device_state_attributes(self): 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 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/mythicbeastsdns/__init__.py b/homeassistant/components/mythicbeastsdns/__init__.py index d73e4619c789e..6b5ce2e8597bb 100644 --- a/homeassistant/components/mythicbeastsdns/__init__.py +++ b/homeassistant/components/mythicbeastsdns/__init__.py @@ -6,13 +6,14 @@ """ from datetime import timedelta import logging + import voluptuous as vol +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.const import CONF_HOST, CONF_DOMAIN, CONF_PASSWORD, \ - CONF_UPDATE_INTERVAL from homeassistant.helpers.event import async_track_time_interval -from homeassistant.helpers.aiohttp_client import async_get_clientsession REQUIREMENTS = ['mbddns==0.1.2'] @@ -24,8 +25,8 @@ 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 1ff2c10c82887..8d113b6ab9dff 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -31,7 +31,7 @@ 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 1f71d8d66b5fd..e509455c12e43 100644 --- a/homeassistant/components/esphome/config_flow.py +++ b/homeassistant/components/esphome/config_flow.py @@ -55,11 +55,10 @@ async def async_step_user(self, user_input: Optional[ConfigType] = None, 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 @@ async def async_step_discovery(self, user_input: ConfigType): # 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 182268212beed..cae373390ba12 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 1291aa5312300..8c870c6ad735f 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 cf5625620a226..bbde0c849721a 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -28,6 +28,8 @@ 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 | 35 ++++++++++++------- .../components/sensor/test_jewish_calendar.py | 9 +++++ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/sensor/jewish_calendar.py b/homeassistant/components/sensor/jewish_calendar.py index cc226337f02fc..65aeaf7fba9ba 100644 --- a/homeassistant/components/sensor/jewish_calendar.py +++ b/homeassistant/components/sensor/jewish_calendar.py @@ -4,7 +4,6 @@ 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 @@ async def async_update(self): _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 @@ def make_zmanim(date): 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) + times = make_zmanim(lagging_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 - .previous_day.gdate) + 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 639364164e00c..7243874a41d09 100644 --- a/tests/components/sensor/test_jewish_calendar.py +++ b/tests/components/sensor/test_jewish_calendar.py @@ -158,6 +158,14 @@ def test_jewish_calendar_sensor(self, cur_time, tzname, latitude, '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 @@ def test_jewish_calendar_sensor(self, cur_time, tzname, latitude, 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 b7b5436da3e73..0d86289e11cb7 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -23,7 +23,7 @@ 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 0000000000000..5a79270307ced --- /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 9391c871b25d1..df14ab6805507 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -20,6 +20,7 @@ STORAGE_VERSION = 1 SUPPORTED_PLATFORMS = [ 'binary_sensor', + 'climate', 'fan', 'light', 'sensor', diff --git a/requirements_all.txt b/requirements_all.txt index cae373390ba12..a1a5cf3a2cc7a 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 ff63edada638b..c70de8da50bf0 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 c1a1769f04c3d..ee892fb03b9f9 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 0000000000000..0f1102e2ab105 --- /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 8ef830c8a8afb..ad3189e512683 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 0000000000000..bade1bd6315a1 --- /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 a1a5cf3a2cc7a..eeb216897fbfc 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 7882cdc5a1510..f6d653360a951 100644 --- a/homeassistant/components/feedreader/__init__.py +++ b/homeassistant/components/feedreader/__init__.py @@ -16,7 +16,7 @@ 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 eeb216897fbfc..dacfe4fd6d50d 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 c70de8da50bf0..0d2e0cd88bf3d 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 4a99ef84bc9ad..47028ef3530f3 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -51,7 +51,7 @@ '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 0dcd0f666c7c8..c863ff9433cac 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 c01706782a008..de460319bc216 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 50bd290797d69..ce39ad9d55e13 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -16,7 +16,7 @@ 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 d593d93729b5b..f4dad6a3b6be4 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -164,146 +164,152 @@ def update(self): 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 @@ def update(self): 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 dacfe4fd6d50d..9f2df6894b3ac 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 46652b4d7b014..1efda81dfe08f 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,6 +24,8 @@ 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 0000000000000..f01abc79e8e98 --- /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 0000000000000..97b132cfd1345 --- /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 ad3189e512683..5931322f80bba 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 0000000000000..d4bfbebe47a2a --- /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 bab6624e9d0fd..c905e6b6ce341 100644 --- a/homeassistant/components/weather/met.py +++ b/homeassistant/components/weather/met.py @@ -18,7 +18,7 @@ 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 9f2df6894b3ac..271d0d5b70d4c 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 66044678e2836..e0f1e6651c6ca 100644 --- a/homeassistant/components/point/__init__.py +++ b/homeassistant/components/point/__init__.py @@ -12,7 +12,6 @@ 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 @@ async def update(self, *args): 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 @@ async def async_added_to_hass(self): _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 1bd97ce274787..5f4834894bcf0 100644 --- a/homeassistant/components/point/binary_sensor.py +++ b/homeassistant/components/point/binary_sensor.py @@ -75,8 +75,7 @@ async def async_will_remove_from_hass(self): 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 eb320de0efd7d..902c1a6c981f2 100644 --- a/homeassistant/components/point/sensor.py +++ b/homeassistant/components/point/sensor.py @@ -13,7 +13,6 @@ 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 @@ def __init__(self, point_client, device_id, device_class): 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 1ee56cac9d3f4..c83837dcd5fee 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 @@ def name(self): """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 10df4c0bf7287..a2d54fe7d4a1a 100644 --- a/homeassistant/components/ads/light.py +++ b/homeassistant/components/ads/light.py @@ -46,6 +46,7 @@ def __init__(self, ads_hub, ads_var_enable, ads_var_brightness, name): 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 @@ def name(self): """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 24515357f5eee..50f3fd08d5c23 100644 --- a/homeassistant/components/ads/sensor.py +++ b/homeassistant/components/ads/sensor.py @@ -55,6 +55,7 @@ def __init__(self, ads_hub, ads_var, ads_type, name, unit_of_measurement, """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 @@ def name(self): """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 ecd1e7edc318c..ab9e54adaa727 100644 --- a/homeassistant/components/ads/switch.py +++ b/homeassistant/components/ads/switch.py @@ -44,6 +44,7 @@ def __init__(self, ads_hub, name, ads_var): 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 @@ def name(self): """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 b94e383ec9ee0..27bb10d99f514 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 271d0d5b70d4c..b2f6237406e0f 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 cdd377b76731e..285757ce7100e 100755 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ '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 75a099562a69d..0dd6dd7840042 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -9,12 +9,11 @@ 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_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 @@ 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 @@ 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 @@ }), } -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 @@ 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 @@ async def remove(service): 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 @@ async def set_zigbee_cluster_attributes(service): _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 @@ async def set_zigbee_cluster_attributes(service): 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 @@ async def issue_zigbee_cluster_command(service): _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 @@ async def websocket_reconfigure_node(hass, connection, msg): ) @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 @@ async def websocket_entity_cluster_attributes(hass, connection, msg): _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 @@ async def websocket_entity_cluster_attributes(hass, connection, msg): )) 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 @@ async def websocket_entity_cluster_commands(hass, connection, msg): _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 @@ async def websocket_entity_cluster_commands(hass, connection, msg): )) 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 @@ async def websocket_read_zigbee_cluster_attributes(hass, connection, msg): _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 6bbfcd43a9457..0119b65167543 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -220,24 +220,24 @@ async def get_clusters(self): 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 @@ async def get_cluster_commands(self, endpooint_id, cluster_id, 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 @@ async def write_zigbee_attribute(self, endpooint_id, cluster_id, value, attribute, cluster_id, - endpooint_id, + endpoint_id, response ) return response @@ -276,17 +276,17 @@ async def write_zigbee_attribute(self, endpooint_id, cluster_id, '{}: {}'.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 @@ async def issue_cluster_command(self, endpooint_id, cluster_id, command, '{}: {}'.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 87621ea548bf7..ad139d81ddf08 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 | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/system_health/__init__.py b/homeassistant/components/system_health/__init__.py index fca433550d736..05e5b76fb5b60 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 @@ import async_timeout import voluptuous as vol +from homeassistant.components import websocket_api from homeassistant.core import callback +from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.loader import bind_hass -from homeassistant.helpers.typing import HomeAssistantType, ConfigType -from homeassistant.components import websocket_api + +_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 8d113b6ab9dff..1c7d713d82746 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -325,6 +325,7 @@ async def try_connect(tries: int = 0, is_disconnect: bool = True) -> None: # 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 3cfa069664499..3d0952b89fbac 100644 --- a/homeassistant/components/python_script/__init__.py +++ b/homeassistant/components/python_script/__init__.py @@ -18,7 +18,7 @@ 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 b2f6237406e0f..89ebd8da51db1 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 0d2e0cd88bf3d..433654911828c 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 d4bfbebe47a2a..372f3ec079dde 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 @@ def __init__(self, name, coordinates, forecast, session): 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 1efda81dfe08f..f89596c0c02b6 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -26,7 +26,7 @@ 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 89ebd8da51db1..4d07cfd47331f 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 433654911828c..1daff29d9158a 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 b898c577bb20c..a4809369d9be5 100644 --- a/homeassistant/components/timer/__init__.py +++ b/homeassistant/components/timer/__init__.py @@ -43,11 +43,11 @@ 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 0000000000000..25a33929c1a80 --- /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 0000000000000..267370dbcd7cf --- /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 4d07cfd47331f..ea8caac47a8ac 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 53f5e70401900..bc2f52139e2d7 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -4,7 +4,7 @@ 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 b80a8ce5e0f2a..a58bab92e9d6c 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -57,6 +57,10 @@ # 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 internal_callback(name): )) _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 @@ def __init__(self, cast_info): """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 @@ def async_cast_discovered(discover: ChromecastInfo): 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 @@ async def async_will_remove_from_hass(self) -> None: # 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 @@ async def async_set_cast_info(self, cast_info): 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 @@ def new_connection_status(self, connection_status): 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 @@ def new_connection_status(self, connection_status): # 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 ea8caac47a8ac..ba09538df8864 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 2e0fe9d152926..b5d6220904ff7 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -12,8 +12,7 @@ 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 0119b65167543..7c972988e9c34 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -173,7 +173,7 @@ async def async_configure(self): 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 dca6b60ccc570..16d834bdecb0b 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -138,7 +138,7 @@ async def async_device_initialized(self, device, is_new_join): )) 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 cf2156b76c3ae..e8b765a07a693 100644 --- a/homeassistant/components/zha/device_entity.py +++ b/homeassistant/components/zha/device_entity.py @@ -8,6 +8,7 @@ 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 @@ 255: 'Unknown' } +STATE_ONLINE = 'online' +STATE_OFFLINE = 'offline' + class ZhaDeviceEntity(ZhaEntity): """A base class for ZHA devices.""" @@ -108,13 +112,20 @@ async def async_update(self): 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 ad46f3b494f77..7b4cd67bd7070 100644 --- a/homeassistant/components/sensor/co2signal.py +++ b/homeassistant/components/sensor/co2signal.py @@ -15,7 +15,7 @@ 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 ba09538df8864..f4ac7f34125a9 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 af186fb1341ee..28f4059d60da8 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.icon + :members: + :undoc-members: + :show-inheritance: + +homeassistant.helpers.intent module +----------------------------------- -.. automodule:: homeassistant.helpers.event_decorators +.. 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 73a104b1ffe53..1efe929d666b9 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 c5c1ce1339d4d..71c838679fbf9 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 bbde0c849721a..71d946b4bf590 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -25,7 +25,7 @@ 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 f4ac7f34125a9..2d660e732a12a 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 1daff29d9158a..9c7bce22a8626 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 eb97d197e3ed1..cafae2b0b0814 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -33,7 +33,7 @@ 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 136e3cac23bf2..f780158dd4ee7 100644 --- a/homeassistant/components/sensor/sql.py +++ b/homeassistant/components/sensor/sql.py @@ -20,7 +20,7 @@ _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 2d660e732a12a..c9f6b7858b354 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 9c7bce22a8626..726d471f7bc49 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 ae08b2cac40c2..b8ef5c408386e 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -152,10 +152,14 @@ def zha_send_event(self, cluster, command, args): 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 16d834bdecb0b..02ed1d736991a 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -126,6 +126,18 @@ async def _get_or_create_device(self, zigpy_device): 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 3bc284627fcff..53cc6960fc305 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 82815d11a6e86..bcf95ee205550 100644 --- a/homeassistant/components/blink/__init__.py +++ b/homeassistant/components/blink/__init__.py @@ -29,7 +29,7 @@ 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 75fae0e9c1544..829be266b09ec 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -10,8 +10,8 @@ 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.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 @@ def validate_options(value): 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 @@ def current_cover_tilt_position(self): """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 343bb3643c6d5..47681e0de101b 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -1051,6 +1051,34 @@ def test_availability_by_custom_payload(self): 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 | 32 +++++----- 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 | 62 ++++++++++--------- .../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, 1011 insertions(+), 3493 deletions(-) diff --git a/homeassistant/components/abode/__init__.py b/homeassistant/components/abode/__init__.py index 8a1a39a726f0d..71a1dcdd590bb 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 947e291630097..ec5038a7a8440 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 a821abf445b67..47baef1d7e5d7 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 39681760d4d27..99613d07c47e8 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.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 3ba3fb118f321..03d6219ebce4b 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 397d61f307382..aabf5fbccdc23 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 a8777ccb50334..ce6634268e96e 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 4695a5f0471ad..fa6cb9323bf28 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 e3f993e5413e6..d5303a27cd2fb 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 360236790f880..48b5ea21cbcd0 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 c83837dcd5fee..6771e99cd77d1 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 a2d54fe7d4a1a..e5299821e39c9 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 50f3fd08d5c23..2972f50d8040a 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 ab9e54adaa727..e3aee023f21f2 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 @@ async def async_added_to_hass(self): """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 92eab728210c8..1f74d72809b29 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 16e82280433d7..986907622b1c7 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 d8fddeaa540b2..c5af6ea79cb7c 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 546d09299dcdd..b2f697ea83f22 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 579a19c1b521f..f92fd6b187b61 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 8491268dfd6de..062d698d5122a 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 @@ 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 978cb61189557..6918ec1e54f04 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 02f47b0561794..537f04b20be4d 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 85cb4f105cd41..b30a7238b3e04 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 acb23fe42780d..4e2383bb43d01 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 @@ (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 71d946b4bf590..5972660c6e648 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 @@ 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 9d3b90a08a157..2defa032809a1 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 .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 56e747ce5e096..f01bfd8f791d7 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 2699975cfb598..fa3222bf0e44e 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 .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 bcd0c38c3bdff..49f11570b21de 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 3b3368c2f5c4b..7c943b897343a 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 22e13d05e20e0..4869dfffa6e5b 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 @@ 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 @@ def __init__(self, name, camera, sensor_type): 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 4eb2030885065..3c1f03f01452a 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 @@ 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 1cf46174371e5..c5424b3d0fa70 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 085bafd3ae3f6..e33e22f3778ac 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 @@ 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 0f795f85dcd49..e98ce7951b8aa 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 @@ 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 f770b9d5ebf34..73a94acbcdd9e 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 @@ 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 79b8837816941..aab6f6dda018c 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 f876b8cc34b1c..445dab9b0744a 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 90c1f2e6795c1..4ebe0ac8aaf50 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 961350bfa8977..7639ac621feea 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 73cabdfbae615..b265dc533eb85 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 0b38a256e4070..03ac5bd2549b9 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 72696143bfedc..2d80ded686116 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 abb61d42ca38b..a4f83b573f73b 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 @@ _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 @@ def run(self): 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 f10fd05b83f8e..9e061ba91bfe8 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 @@ }) -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 48c4702aca0c8..ee040fa1ba5b8 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 @@ }) -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 785f8c57f943e..351122e74f0e0 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 f46eebce1b291..ff758ea58470b 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 ee8f0e878a307..947c5188766cd 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 aebd57098b55c..7e81836e522af 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 66f11fab83f74..8c21a448a23cb 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 7857995b4af3a..6f20ecdadcd3f 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 be940cc4f5103..3ad7b70a9479a 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 406774d5fadbe..d8d3b194cd7f1 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 @@ def _discover_platform(self, component): @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 @@ def handle_data(self, command, msg): 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 087018084f2c7..9da4bd1a21a62 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 0069b3c0d73e9..3fc0e9d6476ca 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 @@ _LOGGER = logging.getLogger(__name__) -DOMAIN = "asuswrt" -DATA_ASUSWRT = DOMAIN - CONF_PUB_KEY = 'pub_key' -CONF_SSH_KEY = 'ssh_key' CONF_REQUIRE_IP = 'require_ip' +CONF_SENSORS = 'sensors' +CONF_SSH_KEY = 'ssh_key' + +DOMAIN = "asuswrt" +DATA_ASUSWRT = DOMAIN 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 2073f680e00bd..8e749dca46e5f 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 c059f4c020aa3..1ad2d80cea8c0 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 c62a32342dcea..7420bb040674d 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 e83641709800c..ff355bbf87bf3 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 836901cde303e..35cf695f1e3a6 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 ec47479eac849..6cc7e3dae7df3 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 33ef00da38068..8f838ea6d6b6c 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 6d7a44291c99a..1b022316676fb 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 70e01174078a7..20c689d74cf43 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 1fa0d54061057..5f52da745ee14 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 aa51e6310269f..bf45abb88f0e0 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 4e47026d8d16d..f4d7f69c07a73 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 509195689a15a..07fbf716e1c2d 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 347b3f94e7da3..ed86d52584f87 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.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 d57e190490fe9..ce6d6eb444689 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 8b6e907f7b8d1..da8bc9f8629ce 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 f4afc8a601af2..f65b5cf885c65 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 0c3c0941a9e31..e2d79eede8d72 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 fd2e603445c38..df723272a7acd 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 671bbc730d0fa..11014dc4bc97c 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 9846ae85fb2d1..b9e969efec101 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 e3f327f1d5cf1..7749af8f335c4 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 8968b68036983..1ee371dcc2a6a 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 9e120beb44268..3ad46fd61aedf 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 @@ }) 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 bcf95ee205550..8e95f967396e9 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 77267fd7516c8..b9bf4a5250fc5 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 cd558f0368487..fe0b95b1517f5 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 e904791445630..2e5c024d6e509 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 a1a07cb2a7326..c1fdf9252ddf7 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 55fb15c686d85..a42eb34004be9 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 971941f4dd66e..c8763524de763 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 1c9266ca3a724..5cb2e1adfe16b 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 02cd456107ff3..7e6847f0e7ec2 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 40f2b91045afd..e1ac30120d211 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 execute_service(call): 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 execute_service(call): # 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 @@ def update(self, *_): 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 f8855b2e28b63..1843f647df8d8 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 02a1265318070..21121b069af2c 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 b13665610d80c..8a5eddaa86a11 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 a7ee5724d1998..a01142c53ed22 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 @@ '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 @@ '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 @@ def update(self) -> None: 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 041a0f9cdc647..1c002f21f5fb4 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 024dc1ac9de99..aa9e3153fe5bb 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 04c33d83f3db8..e53c7e22d2d6d 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 bc2f52139e2d7..94b926795e785 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 a58bab92e9d6c..432290482f158 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 98e649e1742ee..c427657c76d47 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 ae400ca638569..363e7c5eeb11d 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 98c321b9f5a43..40d04eadb3a79 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 69d88274f2965..64ebec1854590 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 0e0ac8c80b64b..a396dd276a544 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 9ae6a4de09145..ac5de866cfb71 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 7836cba6cf9da..47ac9d3a4b226 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -7,7 +7,6 @@ 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 b7a8c9c070ab6..ec9d9d0ff27cd 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 3adc6f1423388..640e267d06613 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 57123ee12de38..f29dde4594c2a 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 d8d386f5ca048..7082cb367264f 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 aeef2818f6368..ab7ada618fed8 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 f16d6a87d55c6..ce4a58162c7f3 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 45589c128644e..d97b506e2732c 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 @@ 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 f887ccf64a0f2..6065a18227422 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 80c36b6f0c63f..d6123a25f235f 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 58503d7187b37..3b519514d17b4 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 4d3e2cbc6a926..8015324be13fd 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 286b310c1a99a..77d01c5c40be9 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 @@ 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 f7bc71a23987e..8f90f303fcaad 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 99bdd20a2954b..48f06a894bb0c 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 @@ 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 f7c777b810077..50e22c84d6fe3 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 @@ 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 05845a022886a..d3a6df810bae8 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 @@ 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 1913e3d50879a..3083f0c673256 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_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 64d933896705d..c48c7205e01d1 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 .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 2b9854fbcc74c..2699ade0b1d00 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 3f3fbe7c14e2b..210aebe80d5cb 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 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 c0c9d95586c4a..d061dad67261c 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 0f604c525e016..255f43b67bab3 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 c17df81abba02..a10c961b8e43a 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 87b89ddb44c09..85e3164d08bdc 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -26,28 +26,28 @@ DOMAIN = 'discovery' SCAN_INTERVAL = timedelta(seconds=300) -SERVICE_NETGEAR = 'netgear_router' -SERVICE_WEMO = 'belkin_wemo' +SERVICE_APPLE_TV = 'apple_tv' +SERVICE_AXIS = 'axis' +SERVICE_DAIKIN = 'daikin' +SERVICE_DECONZ = 'deconz' +SERVICE_DLNA_DMR = 'dlna_dmr' +SERVICE_FREEBOX = 'freebox' 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_HOMEKIT = 'homekit' SERVICE_HUE = 'philips_hue' +SERVICE_IGD = 'igd' +SERVICE_IKEA_TRADFRI = 'ikea_tradfri' SERVICE_KONNECTED = 'konnected' -SERVICE_DECONZ = 'deconz' -SERVICE_DAIKIN = 'daikin' -SERVICE_SABNZBD = 'sabnzbd' -SERVICE_SAMSUNG_PRINTER = 'samsung_printer' -SERVICE_HOMEKIT = 'homekit' +SERVICE_NETGEAR = 'netgear_router' SERVICE_OCTOPRINT = 'octoprint' -SERVICE_FREEBOX = 'freebox' -SERVICE_IGD = 'igd' -SERVICE_DLNA_DMR = 'dlna_dmr' SERVICE_ROKU = 'roku' +SERVICE_SABNZBD = 'sabnzbd' +SERVICE_SAMSUNG_PRINTER = 'samsung_printer' +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 2c9f763aaa843..1c8966f3b4b5d 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 @@ 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 28747bbe8bea3..d477836425d5e 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_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 8982e6d08473e..e201837aaf626 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 7a50ac815b1be..df2eed3011a87 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 00036378a7848..ea6ba2b455fa5 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 b89275b179558..eb0016ed29816 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 @@ 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 @@ def __init__(self, data, sensor): 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 0d57740a83d0e..5af367ef92d45 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 3420bbed1bcc0..9899a0af98ec1 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 d5f94bb2c7bde..f8e5b1811632e 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 5f85164f35d42..d1a64201e6dc4 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 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 791f990d9ad4c..c2e56436bd8b7 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 @@ }) }, 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 951e6f13b91b1..bc1b3aa9595f9 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 24ca263898c25..942ba107509a9 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 bd08024e64a88..6ab9fc3181cb9 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 @@ 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 de81d16470c4c..47ed2d6ebdf6d 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 d8d6c98bb8b8b..f113125194a17 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 3829c2caebd46..167132a5f41f1 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 @@ 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 37f25476bd012..ca8e551bf5e6f 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 46fc5c297526b..aa6440894e1e7 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 31e4c4751c800..9824d20b85e98 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 ae22401a6182b..1f9fd5cbde854 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 7382e5c18151a..2ba5f362b7d82 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 @@ 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 8cbe95ee685b5..124cae3ca4719 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 7f05554c496b0..9d2af73031570 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 1078010361336..9b8bfaa437a12 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 0f259ec673a55..926a073832c5b 100644 --- a/homeassistant/components/edp_redy/sensor.py +++ b/homeassistant/components/edp_redy/sensor.py @@ -13,8 +13,8 @@ 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 1576361da332e..ad4ce8fe72867 100644 --- a/homeassistant/components/edp_redy/switch.py +++ b/homeassistant/components/edp_redy/switch.py @@ -12,8 +12,8 @@ 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 3547f4fc76e62..fe613824c9512 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.helpers import discovery -import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_PORT, CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_NAME, + 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 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_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 dfd60c4abde14..e202a46f9f15d 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 @@ 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 56d7dda17badb..74a048a86c0f6 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 8c36830817e16..851fd3d1c313a 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 34d3a7a13ca35..2a9cb19a327a3 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 0fc793d31cae4..2bb03c8d4f209 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 8ac3cec641131..a0c08bf54299f 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.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 c6405f953fdeb..63b38c1d321fa 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 6bd33b382dc9c..467d542ee6d49 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 707aedbb16135..3a282595d5854 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 @@ 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 47dd17a56ae2d..c8583b1d8bfec 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 @@ 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 3fd57b190a6ed..63da6ea537658 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 a838e1b948e05..7badd6ee5dc2f 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 @@ 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 6a92ab6404443..45fb358cecc63 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 07ecb9d265a98..c8ed263a2dc20 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 @@ 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 fdf1d35201e57..95b3c470d9e00 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.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 548b6f3d77197..a163d4b2e91f4 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 4dec1d5602a6f..ef87e14ec434b 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 f2d56f846813a..d08ad09f1c0b9 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 f4a034e31accc..25ea3adaa84e5 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 75e456f62bde4..8b3c27025cd26 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 c883897c2eac3..1fde8c79e401d 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 ebe2c40979679..f574f89f951f7 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 02e6812d5d5d9..d2e88ed3825e7 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 ab979604f5043..4dfbafd36b16f 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 8b89b307db9e8..b7590341f788c 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 9b772d9bdf0f4..a4cc5864fc4c5 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 @@ }) -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 276ace8dd5117..26b54e16cc8c2 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 aed2b056d2ff2..cc6a8b8723298 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 @@ 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 1c7d713d82746..4710967a625dc 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 @@ 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 @@ '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 c1166f8cf7b97..d5a0938bf66f3 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 @@ 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 7a44a58cd81d0..62bc058f1555a 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 a226797c0f8b1..96d6819410721 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 40ba5b9b70ff0..52bb77516e671 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 @@ 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 fd58e6c01e868..ef82a3dc81cf1 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 @@ _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_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 @@ def _handle_requests_exceptions(self, err): _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 @@ def _set_temperature(self, temperature, until=None): 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 @@ def _set_operation_mode(self, operation_mode): 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 @@ def turn_away_mode_off(self): 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 @@ def update(self): 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 @@ def update(self): 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 aef8e4c48f92e..a63fab7686152 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 4d105cebf449f..0f17179f9185b 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 f6d653360a951..86744bfd39c77 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 3184b5a5d54ab..7b7e3a8129408 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 715cc036265f0..9a6ccccb5e3fb 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 @@ REQUIREMENTS = ['fiblary3==0.1.7'] _LOGGER = logging.getLogger(__name__) + +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_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_DEVICES = 'fibaro_devices' -FIBARO_COMPONENTS = ['binary_sensor', 'cover', 'light', - 'scene', 'sensor', 'switch'] +FIBARO_COMPONENTS = [ + 'binary_sensor', + 'cover', + 'light', + 'scene', + 'sensor', + 'switch', +] FIBARO_TYPEMAP = { 'com.fibaro.multilevelSensor': "sensor", @@ -78,8 +82,7 @@ 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 @@ def __init__(self, config): """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 @@ def connect(self): 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 1934580c58ea3..2c2d9c30a794a 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 d47dbb2031541..aa34fcc36a965 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 9b3b3850f39ac..5ee3e83b95fd7 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 a0bd4e7ff40ae..620f095b733a4 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 e437ef8710d64..01452d8b394a7 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 8b59aabec722c..04b8aba1cf43a 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 098b34ac948cf..babfbd9e9aa61 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 a4a7395adc432..0c5a48049ecc9 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 99efbae09843d..41e60d884ceb9 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 @@ 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 f4e1ce5bd8a03..fb94f7f56f5ff 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 2f8ccbc745d0c..49e68dc2c414d 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 ad3c7bc192979..81ba019acbc0b 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 ab58e6e84bc62..c68c79f1e774b 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 f2d13ee92f6d0..64d99ebf133fd 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 @@ 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 66c515c2bfd26..a1736fb985752 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 55fa8a0479684..be9212793ab3a 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 f89596c0c02b6..bf4df366ae356 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 0d4b19da03009..36e9c61b1ba1e 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 4eb9e390d7139..0591af8acfa06 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 5e834cdf7ec2d..69b03a36769e6 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 feef5396d8893..04885402e722a 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 @@ _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 b422efea2ffe4..10bb2f748c4a1 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 cafae2b0b0814..9b852b4a00a1e 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 a94e8e95c6f6d..82619e35a0ebc 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 e04b0ef27583f..b792359603989 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 c1ccc73b81c2d..01c5d837ca96a 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 ce9777151cffc..98e80580fea60 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 f2c82842bc116..a7b703ef2ab9c 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 1e88c72e19d5a..9a49bd02b97fc 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 @@ 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 d486b60197779..5a657923683b3 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 1028877348654..d0b75c2f9627e 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 c2eff8d7c5df0..74c6463556334 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 c0f057da138af..141cf2c2c1a61 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 2e048caa52f80..526388a0918d2 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 5ceebb3dee5eb..89bb1a9acb8fb 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 7d1977aadc14f..3cf27af067433 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 86a7105dafedd..5529918010cf4 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 4c35983feed87..725dec8b8e54b 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 5f52341f1cb30..b5bd0796f160b 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 2fe4e0766ed7e..559ae9584049b 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 828f5e8e0fee0..403f7ec6867af 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 3dec1135ec887..bdb79d03eec81 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 286be87bce902..b096d9fe98ab7 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 61d1f8ac285f6..677ec3bb16f12 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 8b0871ca80087..fc158bd666f97 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 34bee1ec5fcf9..3c93fe2ac8359 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 25fce22c6414b..d070872f85c44 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 7e94f9026a8c7..ca8fc64eea4a5 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 @@ _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 @@ def __init__(self, sensor_type, sabnzbd_api_data, client_name): 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 25295c6f2ce0d..bff365a079fc6 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 b704677800f7c..360acdb24977a 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 @@ 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 b4541cf2c4457..34ced6287123e 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 @@ _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 b3ab522887557..802512dbf5d95 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 @@ 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 5812512ccef07..96e24138b4a9d 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 7676deb1a9cd3..7f0709aa6c1cd 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 c1dda86343d3b..c877bddbe53d7 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 e12643fa651bf..2563c9ceb0c48 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 e337a2ec251c7..fceedb5742892 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 2d85c1fe3c3ca..fc1c16e1ff36d 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 c218e19479141..87d7e02b383d1 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 9344aeab7ed1b..2b2bf2de94fc5 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 2fac282023023..11c45991400e7 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 a85a0c889d17a..80fb8f2634d71 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 769b3a9e148cf..2995b860e5b5c 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 f9ec8da54e3c5..e27870e2d86cc 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.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 17a46be47344c..438bc36b1bf2d 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 2ebd80c3de09d..1a036f3661aa3 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 7f1f8f539eba5..fcd9d15839b7c 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 @@ 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 626a819b0b99c..9fdeea73da8d8 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 0a59dcb3e1d7c..66e26bd520452 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 f875e3a91c7cb..b1bec15d40e8a 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 75cc86a0154fc..c9d209596968c 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 fd8c228d3965e..463ac2b6cd1c2 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 ee384fd709497..8724f7d3d667c 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 7d8b3a84a2a2b..169e1b51a4e28 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 3ad95e40d62f9..c22489aa65401 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 ecb240f2ef345..02be279f609f4 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 de8c3a5694d3b..89841ae74ef62 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 9d791aa3df397..32f1d7f939269 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 @@ def __init__(self, device, switch_type): """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 4d4ecf0160b56..7a23c6c46095f 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 d8b7a68a506a5..7a495d7b89a37 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 65f2181596078..67213ab15bfc2 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 fc2716b8019ed..3b9bee081f7b6 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 0d86289e11cb7..04da29aa55e69 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 045944ccfa92f..2fbb6f719da76 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 @@ 'soundSensor': 'sound', 'tamperAlert': 'tamper', 'valve': 'valve', - 'waterSensor': 'water' + 'waterSensor': 'water', } ATTRIB_TO_CLASS = { 'acceleration': 'moving', @@ -31,7 +26,7 @@ '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 5a79270307ced..9340bcef337bc 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 7862736e60b1c..4de1744c9b8bb 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 8495be62a73c6..ce4b00ca1fe8c 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 5539703e77ebd..eb83334c6b3d1 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 1fccfcd361976..08cdb74ed774e 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 @@ 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 ba7ccc2f68216..6af8c14843b18 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 79fd1166a4b66..75a0c51d01060 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 1aebeae59cba2..9a5508c8f3222 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 b4f507a60dd62..69d5a9bfc3319 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 2106e46cb5dd1..2a7eafaf835be 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 fa2e5e8e1eafb..fb76718f2d5f0 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 dd1931e27f1fc..8aafb6f12107b 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 ce6f376d1b084..3b8d2964f8347 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 33557ddacab5f..4deb6550444f3 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 @@ 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 5b991e0d3e228..b565f1834577f 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 cfef50e825529..08af44ad1ad11 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 @@ 'High', 'Boost 10', 'Boost 20', - 'Boost 30' + 'Boost 30', ] OPERATION_LIST = [ @@ -34,7 +29,7 @@ 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 f4bf74ad01029..227e4748515e4 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 37f56ed3b1cdf..fed05fe3498af 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 6b5287336018d..a8c34d0a8433e 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 250c6a2ed2fa5..92cdcb0a2e4c5 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 05e5b76fb5b60..9a171296ce957 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 c59aee56a5117..9e968111c9c5c 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 7c0455181326b..767e29ba0b9c3 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 @@ 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 1e52c1636244b..1812d36b7cdb5 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 ef816338ce970..7812bbd812bbd 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 @@ 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 46ad6206fff53..a1eb918ac5ded 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 5e30b8458639d..e76cadc7ce3da 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 73035a2da0d78..69855f7cb57de 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 baf32073c444d..6dbf9a3980710 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 5846d97c7f910..643cc65aa1918 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 5918bd7c9f8b6..8a2ea976ba7a9 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 bcac038d43b53..779bff9ce0d4c 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 28bc7a1ad0dea..18f206541dfa6 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 7cfcc272a3390..eb52ab496d7bc 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 @@ 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 d1dea05198509..5bca4321a5f22 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 5406ba60b1329..41a206944e799 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 2a57a78ee9ee2..1a6f35fe8d8b9 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 @@ { 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 f6ed85db132bd..85faeca96d4ec 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_discover_binary_sensor(device_id): 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 1879c88c83c1f..5a22311d7f096 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 d6e56329699ba..9255f9da6458b 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 3f14b34ea78c8..10eaee1ad8b6c 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 f024f62109b4f..48133fd69e6c5 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 5c04e8726237e..63d1512698ca9 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 8f1c45d7312a6..c35d2f790273a 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 88608ac42e9a2..d0c9c031435c2 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 cf9dd545e99b6..5deee5e08a62a 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 aac97580f2c17..c6d281772a5f6 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 51a04e9f5b327..56d563e494c62 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 76b5c00d9d418..fc433ae18b16a 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 f7613d74dfbba..2c037140f0a6d 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 ef5f2227c11c2..302c0006bcf2d 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 c08ddb4047b6a..0aeab5b1c7d2c 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 2ffb996aec349..34d660ac83c6a 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 51b7ea2325d63..1d4505ed9a482 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 30972b1014b89..a1787e9993ed9 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 61f9843be454a..952755d289e67 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 13b51d505c37b..05da90bf7ac77 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 @@ }) -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 9a876a87683b2..0fa15e7efb4bf 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 @@ 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 5358060ea8a9d..a72cda45fd5c1 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 17e2f717f5af9..f8462435a451b 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 89586465b4363..38a96eb029865 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 update_devices(): 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 c2d1daa584cea..ba9ae43f13bcf 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 ddbcb3f6c6507..6ae22c342095e 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 @@ 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 1c3ef6016330e..f3e0c39a1e6d2 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 @@ 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 a4809369d9be5..04d9acc06af10 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 01f170f0b3174..96d8b4e6d150c 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 022a509ce06bd..3397e3dacc2a6 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 fb057603a1ac3..ebd25e02cde3f 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 087ca673e85ba..08ccec588b4c9 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 3f33cbe9fb393..d0f6e600a0dee 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 @@ 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 @@ 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 f80345a43628c..519641ed34bf2 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 ba13b8d511a80..b14bc81175435 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 @@ 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 @@ }) }, 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 50e92f15e3c3c..e5e27ecbed1e1 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 45167874de282..97c7dc9627d60 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 b247858b0629c..23e6cb20c8fdd 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 dd10c4ecfdf86..25e21dc3d8a09 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 cb592a74758e6..061ed2c0c641f 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 @@ 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 @@ def available(self): 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 aac946dee8ba6..373397eddd607 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 @@ 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 22a82dec8e2c7..117424fd55e17 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 4548867a45ea9..97ff18ba911b4 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 a3a3db972e9f9..ac2309cbf9e54 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 9cb7cdc3f2c8e..b6e2cb6950c5c 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 0a1468a6a5101..1cf2f811872dc 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 2e03e5dba9a28..33d207d85456e 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 9fc1f92016e9d..1e8fab2cc1ba0 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 9fcba4da817aa..ce8c272165f3f 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 8476aade7d799..7e236789a5c58 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 7250feec799bc..8fe90823c492a 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 11529cbe171ab..abd102f6187bf 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 c1f8a96946f49..425b9878f6de3 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 ca0f554bd3923..7981cf948bb9e 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 @@ 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 c7b8a284dc97f..3fd54b349a299 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 f2818e59a9b09..0b44d787f6fbe 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 28c88bf5c296c..cb2646ea942b5 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 2a1b8c52d7998..efa3ee73af892 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 LOGGER as _LOGGER from .device import Device - REQUIREMENTS = ['async-upnp-client==0.14.4'] NOTIFICATION_ID = 'upnp_notification' @@ -41,8 +35,7 @@ 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 41aa240492bea..8a7d7d52255fd 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 @@ 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 d23359d8c57ab..d4769102d1442 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 17fa11fe8d337..1603715861d3e 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 8a8e669ba886b..7d8e4ddf71b9e 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 @@ _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 cd86f9c0bd01d..d3edf7d501bf3 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 @@ 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 @@ async def async_added_to_hass(self): 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 15ca8584a4e26..38d8b6c3f1cc5 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 @@ 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 b123b958560fd..43ffa232b40e7 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 @@ 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 0b0205acefb48..ae7a282849215 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 7e5099cecf805..72a5a7af79ba9 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 8e9aafd360528..10ad89ab847f5 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 @@ 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 300ff43d67626..7104bb0750d0d 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 @@ 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 010612acc7d89..1018f72fdbc5b 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 b78d981c6954d..1c3192961af85 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 @@ 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 77ba30158e41f..db1e9450daf34 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 127cd008a3ae3..3f4c66d238a89 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 @@ 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 bb1e7331de826..837422dbc7c45 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 5e016b8666be6..7cd3129bc1485 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 279e4a4307d21..1168cca842509 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 702236ac748f8..93e54b915c747 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 21287b6328ead..61d5f0baf287b 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 6cae1195f8739..0960512f6d19b 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.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 c9b5a36afa36b..8b68cc9190f59 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.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 4742b75594443..2f4d18e34e13e 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.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 481aa331e41f3..393a4066002db 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 160f152ef8ac0..adcdcd668cb6f 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 e040da959eaa1..4c9e79724fee1 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 01e4e82f3bcc1..7112e535a95fc 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 be9a0a24feee7..cdd230ea7f77c 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 @@ 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 b6ea75ae8cc87..13706d8408f77 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 11ccd82696ef9..a418eec6bc557 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 0d89537b8e83f..9dbaadf9bee6b 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 @@ '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 e7092ff16d527..7158e4df69b0d 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 @@ _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 395b539a065cb..d4838c0150583 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 83301aa3d4eca..f281ea644619c 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 @@ _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 65b996a5bd5a1..07f16e580bd83 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 @@ _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 81abf7d0e6cd4..d3985557cffb3 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 @@ _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 b28189444ee5e..9f2efabd412b0 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 4b23727254608..d2c0cf6b968fb 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 @@ 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 48ac6f41a1273..855a5f3ed0a8f 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 @@ 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 dba99bf7e3d4e..e6e12ef0afe3d 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 07acb15b76512..6c3cc7405ba4c 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 89b86c12af4dc..a0220927f1647 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 @@ 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 6af8ea43fa689..93ae98ed94b9f 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 bbae6170048e3..38fd44cd1c7d3 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.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 @@ 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 889984eb223bf..e9a907ee6d2e1 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 @@ 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 9ec6d0298ea50..59be3ab189066 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.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 cd87bd838fafd..608328c659b79 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 de9de74054c82..f0b3c2c5f7e05 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 d34dbe8077801..a6cbfbae99d15 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 92762b03aea54..5887586df65d4 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 @@ 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 48c8f27996a17..3734f46abb7a5 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 ff928b4387398..34bb04cb394c7 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -9,7 +9,6 @@ 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 3ec9b8920c3d3..709b3ec867275 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 @@ 'Maker': 'switch', 'Motion': 'binary_sensor', 'Sensor': 'binary_sensor', - 'Socket': 'switch' + 'Socket': 'switch', } SUBSCRIPTION_REGISTRY = None @@ -68,8 +63,7 @@ def coerce_host_port(value): 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 discovery_dispatch(service, discovery_info): # 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 discover_wemo_devices(now): 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 discover_wemo_devices(now): 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 discover_wemo_devices(now): _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 e44cbb31e6681..d6c1ad721b9ab 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 fbf72185ac241..29a493bf5bc25 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 38044b7a7362d..e0f729fb165e8 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 0815f307a9afb..0a583e49e966a 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 3cedb0b126b65..2b03d7711acbd 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 @@ 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 @@ 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 @@ 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 b2ae3578133fa..594198ddd121a 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 a950289789e78..12e1b557a733d 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 7e5230ba3c702..8d946bf03dfe1 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 3cf9c753e3a27..19ff792592fee 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 eca985a8d1e9f..5bc6e03c49b96 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 96c8f20679e09..14a983154f82f 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 68cc7a79ae66d..0ef4f30dc5a66 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 35db96c3b8b3e..d05fe58a4271c 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 3e228c4b40b3d..ab61769c94d89 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 9dea93488afde..cd55026879ade 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 a840baf980ae4..34cd86a50f4e1 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 77b4c48b41b79..28c8cb4d5156c 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 @@ _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 @@ 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 190b408abf38d..6f2c24a7788d7 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 eb9ce2970653b..3703e214d835a 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_TEMPERATURE, SENSOR_HUMIDITY, SENSOR_MOISTURE, - SENSOR_LIGHT + SENSOR_LIGHT, ] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -69,9 +63,9 @@ def __init__(self, api, tag, sensor_type, config): # 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 @@ def _sensor(self): @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 cbe62d107da54..913438e9d8c86 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 @@ def __init__(self, api, tag, switch_type): 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 f64d97dfc0d5c..d67cf089b5e75 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 @@ 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_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 ca2d8c1a1691f..ce943fb2c937e 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 15415a7328477..f19492664b1a0 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 @@ 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 1859b7be16a60..133814e216eef 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 166f51fb09113..c3cde8ede6d1d 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 @@ def is_on(self): 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 2f9bee9d70243..9abc871b9b462 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 c5c6ebcbc35ec..1aec3647d617e 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 2e0b1657d234d..c4cfa6bbe6b76 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 62433ca9f9738..3ae6bfe6cd7fc 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 c8ffd04332145..1c4ae85a0d8d9 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 @@ 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 ef5ed1d5c3805..bd5f8642e5434 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 4ead90ca4ecc6..eb7f09f95e679 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 a6613f7c3c36f..82a92dd317cc8 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 14656737f5cf2..f67eb8fd15aff 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 @@ 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 0417d3bcde071..92a2c75895ce2 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 @@ 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 b4d9bfe5ff987..9de91a6b0123c 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 @@ _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 e6855865845f0..35568587b1969 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 ea5a6d85d6bed..f33c60b1c3930 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 @@ 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 7a468a9b90630..ae2e70ede2c42 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 5d6161da904eb..bf743eaf370cd 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 8829a3bb9284c..0e2d3587829c9 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 @@ 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 67c05f470948e..eec1832f07d90 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 42dc95d11631d..e5016900be701 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 a0a1b8bb7fd32..48503e396a4b4 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 81fb8348c4e2e..ef36e17d74b48 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 370b52d13603c..242f0362088d2 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 ee8b53d6ee4d8..21084e18f06cd 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 2cefa2e10491a..a4d90d523aacc 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 e206ffa80f121..f20f09e70a6ee 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 @@ 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 8556fbf1e35f1..7f74712335cdb 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 abecc99e27869..9eb6beb491c77 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 b039d2e3ce91a..b411a148d43ff 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 093e6071bb4ad..be76eca4efd44 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 ca07986976d0d..478bfcbda7b6b 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 @@ 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 @@ def async_add_binary_sensor(binary_sensor): """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 561af9c9f57b6..bf7b64549acec 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 @@ } -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 835305449e825..e40a885ede188 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 @@ 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 56d63d658a939..0141f4392dd88 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 4b4204aa454f1..b2731f7d9a7ed 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 @@ } -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 da712a2f183c2..0af85b84177da 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_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 b19b06761c4b1..7c0958e596af9 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 ce25b61146b20..44fc132cf7780 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 @@ _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 54a2a729d04bc..ef544222546df 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 0a882a093c602..ff4db3c070694 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 b3e4d452b5c6f..8e1b62414b7af 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -19,8 +19,8 @@ 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 @@ 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 @@ 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 @@ async def _subscribe_topics(self): 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 @@ async def async_alarm_arm_away(self, code=None): 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 572cbdb0e10c3..81c993ed311dd 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -6,9 +6,9 @@ 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 @@ def test_update_state_via_state_topic(self): 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 @@ def test_arm_away_not_publishes_mqtt_with_invalid_code(self): 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 4710967a625dc..004162341b110 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 bf4df366ae356..be2551457d0ea 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -21,7 +21,7 @@ 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 c9f6b7858b354..71e4df18d17df 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 726d471f7bc49..a4ebc2301e16b 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 0000000000000..ac3d86a995bd0 --- /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 0000000000000..ede25d0bd4b7d --- /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 51a09514159ef..c316741d36b8e 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 0000000000000..a070128eefe41 --- /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 0000000000000..0b9d377718ba2 --- /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 0000000000000..2140b4e29fe27 --- /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 0000000000000..01078bbddfea9 --- /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 0000000000000..f461f376d166c --- /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 7efc50d534cb2..c5278c63f798c 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 0000000000000..783defdca258a --- /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 0000000000000..856bb1445c71b --- /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 7f9aad83160d9..e4e5f098a4d88 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 a501951540b3e..6a527ab0a0b11 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 3ff60254a6a50..c92f1562157a6 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 0000000000000..2fb203450a5eb --- /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 cf53f81bdb8e9..33c465bf0e74e 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 0000000000000..88b76539deb80 --- /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 0000000000000..00b499e2a39d2 --- /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 0000000000000..347c6e6eeb56a --- /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 0000000000000..abe5fff8fec88 --- /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 0000000000000..dd70cfd2c6e2b --- /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 0000000000000..5a302af79e1fc --- /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 0000000000000..624744de470d3 --- /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 0000000000000..0c926a0335cd1 --- /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 0000000000000..7b013a4d7bcd1 --- /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 0000000000000..1d2851acb6b6f --- /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 0000000000000..0479dee437d6e --- /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 0000000000000..5da2d437a3586 --- /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 0000000000000..fe26cda31e264 --- /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 0000000000000..286cd58dd8966 --- /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 611b56472330c..c7b85c195929d 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 0000000000000..20224ec0d15bc --- /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 a021f1fe9f408..cebe6848f1bd5 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 70e21e1466680..ea1e25c3024df 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 0000000000000..94dafeb3c2e5c --- /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 0000000000000..1390dfb504a61 --- /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 0000000000000..142f40754b9dd --- /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 8a857acbdc6c3..db60ec18fe195 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 0000000000000..04aec33b5d686 --- /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 34290b35f4286..2460e28393ac5 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 0000000000000..6d5c2185718a3 --- /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 0000000000000..ae2b217771216 --- /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 a65e51d7cae5c..2c8881034ff5f 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 0000000000000..d0dece65a0f18 --- /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 34b7e9072887f..ac9c1c2d43ebe 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 0000000000000..079b57722e213 --- /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 af0e76829e542..b1bcf5725bef6 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 7473b4a7b8674..4b8371fc748ac 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 19e60b073d380..08bad3e91ea5a 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 0000000000000..25c502ed05efa --- /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 bb54f7ef6cba1..75bdd0d99c8ec 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 0000000000000..4a900097b148e --- /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 fdcc964a0e6ba..282715ebb3b35 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 0000000000000..29dbaa4f58dac --- /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 0000000000000..080c41429ba21 --- /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 0000000000000..9e717b77843ae --- /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 d138675730531..15459b91f2a40 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 0000000000000..828733c9195ae --- /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 0000000000000..c9eb3a01941dc --- /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 0000000000000..bc10eb3573ecd --- /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 0000000000000..735f5a4a12628 --- /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 0000000000000..2ddeb9a4b3341 --- /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 0000000000000..f49852d5c0c0b --- /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 0000000000000..25c832e51c652 --- /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 0000000000000..ffd8e20ce427b --- /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 0000000000000..8211d52fa5dea --- /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 0000000000000..81950c49b4c20 --- /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 a57b27cdd7551..92e6775ea27fd 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 0000000000000..237d21c46eefa --- /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 d8b8d55a608db..ff07393da04b0 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 d98793d96e5b9..96626a57c5b8a 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 0000000000000..d43fc1128ae5f --- /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 0402f352c5c0a..9ed3611da27ff 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 0000000000000..0e25974031d75 --- /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 0000000000000..905715de72711 --- /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 95897a25f1535..ae973bdc93d64 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 0000000000000..ebe5696f514b8 --- /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 9757716b1bffa..663d79f3c14e6 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 5edf3a00af41e..7dfd1c8b250f6 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 5cda5c6e66322..a783c8646e0e5 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 bb054f0b3a658..5e06be81d31b9 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 f6c52ffd04e8f..2c4c47e8da44e 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 0000000000000..7f4053f8ead71 --- /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 ba264ad4b473f..d70ca8b114ec6 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 0000000000000..109bcbe6c3701 --- /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 6d24d56233c1a..874f0832b6c35 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 0000000000000..61d29894fe251 --- /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 0885c7e9e669c..5ce254c4026ae 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 97b9f84f26c81..12e77ed8e4623 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 330df6d0cf735..c2406de68bbfa 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 0000000000000..3ec3d7b456cf6 --- /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 0000000000000..de05edea8c9ce --- /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 5426c564e03bd..8fb056e3f9397 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 0000000000000..1c571b4e6392d --- /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 0000000000000..f65c338bf03d7 --- /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 0000000000000..6503634b92c5b --- /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 0000000000000..e4131543d50b9 --- /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 0000000000000..93150b2ae7d43 --- /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 0000000000000..4d7df8bd65d17 --- /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 379cdf699b7c4..570f11303833c 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 0000000000000..d805cfc563da1 --- /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 0000000000000..334e5d8cb2310 --- /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 0000000000000..b43fef7ec45c2 --- /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 d1378f183d536..aa4589e558d6b 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 012bb74c56870..3496d19f5f4c5 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 0000000000000..717e3ec5ac925 --- /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 9a3121c589604..2dd1c03022a8f 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 29f64a87cb3b7..8a68e303aff22 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 d0d38a42caffb..90da12451df2c 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 0000000000000..f0e5acf9d9c45 --- /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 0000000000000..3c1ab7c01b52b --- /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 0000000000000..09ca0f63cfd7e --- /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 8790c70800883..618c91e6a6597 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 0000000000000..4155658d7deae --- /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 68e90811a3e1b..767962e37eba3 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 0000000000000..1d0097c2f1f9f --- /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 3eac957789022..d1ff04d4824e6 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 5c5693e6a0c78..d559a05ff23a9 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 0000000000000..e336c14dcce02 --- /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 0259403fabc08..c1de13b5381e0 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 908ef9dc43aa7..c6981f242d2b0 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 0000000000000..e9049026a4fa5 --- /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 c667965bebc81..797a64b207613 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 df14ab6805507..25cd9e8305f20 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -23,6 +23,7 @@ '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 0000000000000..6dfff0bd02c68 --- /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 0000000000000..c73f4ff549ee5 --- /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 a8013105291f6..15ff3adce86c7 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 f7802f0f29de8..97889ea749723 100644 --- a/homeassistant/components/binary_sensor/bayesian.py +++ b/homeassistant/components/binary_sensor/bayesian.py @@ -4,29 +4,27 @@ 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 @@ 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 @@ def __init__(self, name, prior, observations, probability_threshold, 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 @@ def async_threshold_sensor_state_listener(entity, old_state, 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 @@ def _process_state(self, entity_observation): 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 3a99757b54bfe..d56cf9a4ee896 100644 --- a/homeassistant/components/default_config/__init__.py +++ b/homeassistant/components/default_config/__init__.py @@ -11,6 +11,7 @@ 'history', 'logbook', 'map', + 'person', 'script', 'sun', 'system_health', diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py index 497fa827f083d..804589200fa5d 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -94,6 +94,10 @@ async def post(self, request, data): }) 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 8ad03e3f0ff33..162d8f97a73ba 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -24,6 +24,7 @@ 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 @@ _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 d6a4030190de4..5b303943747fe 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 162d8f97a73ba..df19d86bb5cbd 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -5,6 +5,7 @@ https://home-assistant.io/components/person/ """ from collections import OrderedDict +from itertools import chain import logging import uuid @@ -15,7 +16,8 @@ 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 @@ async def async_initialize(self): 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 @@ async def async_create_person(self, *, name, device_trackers=None, 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 @@ def _data_to_save(self) -> dict: '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 9b76135f743fe..4cef84746ed79 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 @@ 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 df19d86bb5cbd..20014c3414b47 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -15,7 +15,7 @@ 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.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 @@ 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 @@ def person_updated(self): 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 4cef84746ed79..2eacb162f8e32 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 9426730be359b..9250c8dde055e 100644 --- a/homeassistant/components/sensor/integration.py +++ b/homeassistant/components/sensor/integration.py @@ -26,6 +26,12 @@ 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 @@ 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 @@ def calc_integration(entity, old_state, new_state): 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 bb4a02c042bf5..7f02d59f5913d 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 e0f1e6651c6ca..fa0217c023b6f 100644 --- a/homeassistant/components/point/__init__.py +++ b/homeassistant/components/point/__init__.py @@ -25,7 +25,7 @@ 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 5f4834894bcf0..2c79bf21c6198 100644 --- a/homeassistant/components/point/binary_sensor.py +++ b/homeassistant/components/point/binary_sensor.py @@ -91,7 +91,8 @@ def _webhook_event(self, data, webhook): 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 71e4df18d17df..5925ff1359666 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 27bb10d99f514..d0a192a9dc7b8 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 5925ff1359666..b91036fa1f6e9 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 285757ce7100e..52be310574a6c 100755 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ '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 | 16 +++--- .../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, 481 insertions(+), 1345 deletions(-) diff --git a/homeassistant/components/hydrawise/__init__.py b/homeassistant/components/hydrawise/__init__.py index 5a045a083b30c..800d19d7efe21 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 38b660c506fd5..bfe7cbd5531ed 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 bff99ddc50140..575686b92cdd5 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 6b73333431d67..a6a8b9c54cff5 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 90c07c487b6b9..8ec6f49b95d3c 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 209bbcef607ac..7dee93b22608a 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 fe9c96a0083da..bbb9a02c8a130 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 2b9a5d9e19307..b421960b51fbc 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 896de61130cb6..246af2613a786 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 63dcc364c9c94..34faffd202821 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 8cfa7abaf202c..d9d3ac8bbc067 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 fc858e7539759..fd3e4335c337b 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 580337a3af35d..48a467b54a2de 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 003714d0f944e..f73c46746f02c 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 b3011e9d7bdd9..5ff492b6f6c39 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 17de7fcd6ca26..8a2d72ebbddaf 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 717213da9acca..e28de61aad017 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 961cd119d789e..5cd5db6169be0 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 @@ 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 267370dbcd7cf..de43b41fdb745 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 e7f23dfcd13ea..beb773838fb17 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 @@ 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 b5bcb1e1a8a1b..adc856bdd3a24 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 @@ _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 @@ 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 a75ff9cd165b7..c586147d632d4 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 55567d4587901..f62331d1502ea 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 @@ 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 18725394a1f50..e378662707588 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 16253ba271a5b..44accca2f56a7 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 e02c2ee5475d8..e786fe458a846 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 3a5ee25f05ebd..d60d8e0cfeb10 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 @@ 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 @@ 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 24fc54ee78c5b..8ddf0858e1617 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 @@ _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 ced17281cc85f..8885ebcbe2409 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 fae18bf8b77d5..fdaba5e570900 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 @@ REQUIREMENTS = ['xknx==0.9.4'] +_LOGGER = logging.getLogger(__name__) + DOMAIN = "knx" DATA_KNX = "data_knx" CONF_KNX_CONFIG = "config_file" @@ -39,8 +35,6 @@ 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 a89d5d1c94579..ca7037fe81d16 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 @@ 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 77d995af2a19f..82eaa52ae5a49 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 4173db5f45049..9423983f9f758 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 @@ }) -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 bb92f1d0ce034..f2a6f15e08b9e 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 750e39455696e..2488114aa418b 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 @@ 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 cd333ba79b446..008e81508b921 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 @@ }) -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 c096e15192d2e..6a2d8144b1e81 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 26b9f77028d5f..305234e1eec8b 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 0cba855234640..e3f9a46743d67 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 e91d3f6136ad0..cb15e44e7985e 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 @@ 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 @@ def __init__(self, device_id, pin_num, data): 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 84016dac28d29..897933e6d800f 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 @@ 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 @@ def __init__(self, device_id, pin_num, data): 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 96ea3781566cd..0c3c8b08dd732 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 61bc5172af058..e5e6a5bd52296 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 @@ 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 8efdcc997947f..941160b63970f 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 b9457b7b7d920..2b7f4ed4074c4 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 @@ 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 468afe178b514..60eda2ea77995 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 a2ae6266a8d62..82802bab4af75 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.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 f0cd7b7a7fe62..c0b6158f18609 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 e1aa1664eba49..a9e5dcf9823c3 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.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 50c664d90463d..1dfbac37c889a 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 @@ 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 b612cd8dec74c..d6c00b7fddb66 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 @@ 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 c98ef16c7ed66..8bbd98c0acf77 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 24abc3dd8be40..a05681497de16 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 47bba280e1cb2..0cab2f4d0f25f 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 f15020a5d72f6..0f00eda20072c 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 e7c8452b27b7f..b4e8e45fa0b74 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 @@ 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 5a27fbaec6398..e6a5b56ecda40 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 0c6608e3572dd..74a90f0f5f0ca 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 @@ 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 6dc76d8d932d8..383fa0005141e 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 logentries_event_listener(event): _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 21ae7595ab8f5..2bfc665694575 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 c0a7f4c262178..50500f47e421b 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 1dae58ad0f7af..51bd7c124a3ff 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 104de68ce0321..74c2039c12052 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 @@ # 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 b4cb2b18dcaf2..03b1cf06d6884 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 45d75b90f7f25..125cefb90265d 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 d07928834578c..398ec30a3f5a1 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 94cb3abc4a279..8a5f098f74192 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.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 @@ 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 21eefc238a051..de62e5bfac2cb 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 df8210df02615..8a5e103db0dd3 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 35744160f24ac..8a30d65fec308 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 435039ce4bd45..e4ebec4cc5a17 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 7ea7abf882d2c..cc7a57a552224 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 359ef0114c5b7..c0b3b99114705 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 bdb8bc344fec6..f9002f2a8398c 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 @@ def activate(self): @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 4146ba5a43b0a..bfdb06be33c99 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 eb4010e43a1ae..61c005f60b2d2 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 @@ 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 37b7c1be42c4e..5e09dcc3c8582 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 @@ 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 d454fe3c75ec4..3bab781f3b609 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 @@ 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 0ef974e277864..c6ca7bad3ac96 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 @@ 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 f983050cffab5..0ef0595187b20 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 @@ 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 2ed12b231649a..1907a1e9e978a 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 ae0939c3da5d6..db5d4e8d6eef1 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.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 2aabde42b36e4..885988adb6b51 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 @@ _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 7fa08bb0f2206..3903bd14e258d 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 @@ 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 d4052678d360c..05137254fcccb 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.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 @@ # 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 d30a756845295..df8ac49a6d569 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 5f6c30aaeba5f..4b3c1bf4d7696 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 @@ 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 @@ 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 fc29ad91dc915..f1f53268c2ba8 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 9980d554232cb..c398ccbde4f41 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_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 850a416acc540..8d5ab84f6d375 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 328cdabde626c..f5c4533123f17 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 8e00949da07bd..efc3e8bddc8f4 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 638d8c55bd565..2037caa11c334 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 @@ _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 9be2f8eadf590..9b3ee960fb23c 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 @@ 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 7e6738b95f8c2..e10adf693fe70 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 2e68c369ba644..d2e1a567d2750 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 @@ 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 @@ def __init__(self, hass, ctrl, dev): 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 b703d91be3479..03fd2db07bf2f 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 @@ 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 @@ def __init__(self, hass, ctrl, dev): 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 77a62103f80b1..f42423bf9a871 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 7089439a7e18c..38511ffed7ec0 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 @@ 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 @@ def update(self): 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 2305189867920..ed8cbda863f7f 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 @@ 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 @@ 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 b263bad5318b2..6ba8d92d15533 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 @@ 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 04c73d7d3721d..47ad8e98958ba 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.components.sensor import PLATFORM_SCHEMA _LOGGER = logging.getLogger(__name__) + DEPENDENCIES = ['modbus'] CONF_COIL = "coil" @@ -59,7 +55,7 @@ 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 @@ def update(self): 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 @@ def __init__(self, hub, name, slave, register, command_on, 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 @@ def update(self): 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 @@ def update(self): 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 2cde7825734f7..6e545d19fe26f 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 @@ 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 3a0e5d39ff0d2..18a70bf75bb3a 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 @@ CONF_BASE_TOPIC = 'base_topic' CONF_PUBLISH_ATTRIBUTES = 'publish_attributes' CONF_PUBLISH_TIMESTAMPS = 'publish_timestamps' + DEPENDENCIES = ['mqtt'] DOMAIN = 'mqtt_statestream' @@ -28,16 +24,16 @@ 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 209027ad4727c..e6fd7f19c2a3a 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 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 @@ 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 c1e3b6f0aacbf..67f12a14359dd 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 @@ ] -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 @@ def __init__(self, connection, config, car_vid): 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 b478e2ef3ca7d..c7d140e0c4c76 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.icon import icon_for_battery_level from homeassistant.util import slugify +_LOGGER = logging.getLogger(__name__) + BATTERY_SENSOR = "batteryLevel" SENSORS = [ @@ -28,8 +25,6 @@ ["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 @@ def __init__(self, connection, config, car_vid): 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 834572bc551f1..29f6383f686b1 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 @@ _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 49f8560c6b336..7ca21ac582a00 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 f0b7832cf2581..57e8f1c1ef8ff 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 66c634d8cd9b1..20d608e1ca5f4 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 60ff7aeef1d6c..01605bb9afe42 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 705dc9968c959..b50286585a46b 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 23d602c5d400d..56511b73dfe79 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 71ce7fb0b74ee..ab198bc21bc08 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 160d4b4784b93..ce6d5da2b4ccd 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 20e50518df8e5..0ad9be1d50835 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 @@ async def async_turn_on(self, **kwargs): 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 @@ async def async_turn_off(self, **kwargs): 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 @@ async def async_turn_on(self, **kwargs): 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 @@ async def async_turn_off(self, **kwargs): 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 6b5ce2e8597bb..f34b2736710df 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 f817544ca77be..f86e7d1855678 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 31410d1c9b2cb..2b4af3e1e9129 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.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 4df423344bb31..530aa8fc6f197 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 0b49cb71ba273..fcc72762b8d05 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 9ec9fe688b705..45cfd273aca42 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 e97ee903abccf..dae244ece3fd9 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_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 7f0fe27df73ea..fe6a34cf4044b 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_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 @@ 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 7f7278d9789a1..1077fdb073e7b 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 @@ 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 @@ '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 @@ def get_binary_sensors(): 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 @@ def get_binary_sensors(): 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 158123989c021..8b450e02b4677 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 bd6bb2991ccfe..8746a1959ae2d 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 5514203c6ea48..10fa83d23e0fa 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 ce39ad9d55e13..495e22aae24fe 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 2cafacf401c59..727ed0a68c7b5 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 93ad2cd055b7c..a3a5461631d3c 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 8849ada5ccc78..2b9bcbebaf29e 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 f4dad6a3b6be4..78a118528b97f 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 @@ } 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 77b9783b23988..5f8c680b7f02b 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 3f39ccfb43f7d..20a20b2129182 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 99907a21b5722..339fa678d61cf 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 @@ 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 beb11ed738f46..6a714747484a6 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 fb14f119dbde5..4ea37339ef35f 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 @@ _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 0f8fbb3907316..70509469d2bcf 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 869f3bd7d6e95..35740a7be0dcf 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 @@ _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 285495c03a026..cb86017779617 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 8170b97c4c8b1..2df307f02ef52 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 @@ _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 25aca9f8afaaf..6bbe546dcb198 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 cf74aae75777a..7676806cfdfb9 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 @@ 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 @@ }), }, 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 8c5ff8c44d178..b35998c807bce 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.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 6dc52e6acc7f1..ff6acc1a8845d 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 @@ TEMP_CELSIUS) from homeassistant.helpers.dispatcher import async_dispatcher_connect +_LOGGER = logging.getLogger(__name__) + DEPENDENCIES = ['opentherm_gw'] -SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE) -_LOGGER = logging.getLogger(__name__) +SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE -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_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 9ae557654cebe..070f847e5e53c 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.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 @@ 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 32c3da0d3e58d..52383366c4dcf 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 @@ 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 .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 3e9bb0b0bc3f5..b790427b22800 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 0f566e5a9ef41..7150a8499d82a 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 63527db42a6b8..489a100a5e528 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 @@ 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 d0ba27aeddd0e..cc918dcf674e6 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 @@ 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 @@ } }, 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 740a28a9dec4a..f6602169eb21f 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 @@ })]) }, 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 030fbbf93248f..b82f9fa978942 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 d38501b9b07e4..0a648f6eff797 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 20014c3414b47..d11d4208dc86b 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 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.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 @@ def __init__(self, hass: HomeAssistantType, component: EntityComponent, 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 @@ async def async_initialize(self): 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 @@ async def async_added_to_hass(self): 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 ed43562d22167..27324ad57a39a 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 @@ 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 @@ def state_changed(self, entity_id, _, new_state): 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 @@ async def async_added_to_hass(self): # 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 @@ async def _load_history_from_db(self): 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 @@ async def _load_history_from_db(self): 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 @@ def add_measurement(self, value, timestamp=None): 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 979f257f25fa7..5b99223d25aed 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 fa15c842debc8..43cceaa671f85 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 @@ 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 @@ async def async_turn_off(self, **kwargs): 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 @@ def __init__(self, lightpad): 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 @@ def is_on(self) -> bool: @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 @@ async def async_turn_off(self, **kwargs): 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 fa0217c023b6f..f223ded998f1a 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 @@ 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 2c79bf21c6198..c29ce42168219 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 @@ def __init__(self, point_client, device_id, device_class): 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 902c1a6c981f2..90b83ba42e3ee 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 dc868530f88b9..4508611e51b46 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 e8d86d480e524..0a617bcec9011 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 8d33e011b9438..fe9fc4b77523e 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.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 @@ def __init__(self, hass, config_entry): 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 @@ async def async_setup(self, tries=0): 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 @@ async def async_reset(self): 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 37507a1fca83d..9af6669652d9c 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 @@ def __init__(self, hass, config_entry): 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 @@ async def async_setup(self, tries=0): 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 @@ async def async_connect(self): 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 93241622f0bab..6e3d818db6826 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -5,6 +5,7 @@ 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 @@ def __init__(self, hass, config_entry, allow_unreachable, allow_groups): self.allow_groups = allow_groups self.available = True self.api = None - self._cancel_retry_setup = None @property def host(self): @@ -59,20 +59,7 @@ async def async_setup(self, tries=0): 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 @@ async def async_reset(self): # 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 abd102f6187bf..2b9aa89fef24a 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -4,7 +4,7 @@ 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 @@ def __init__(self, hass, config_entry): self.available = True self.api = None self.progress = None - self._cancel_retry_setup = None @property def host(self): @@ -47,20 +46,7 @@ async def async_setup(self, tries=0): 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 @@ async def async_reset(self): 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 7c6da2644f660..c7dfc0c889b9e 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -127,6 +127,7 @@ async def async_step_discovery(info): _LOGGER = logging.getLogger(__name__) +_UNDEF = object() SOURCE_USER = 'user' SOURCE_DISCOVERY = 'discovery' @@ -441,9 +442,10 @@ async def async_load(self) -> None: 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 3411f96b981e1..dbc45c955b564 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 5fa8ddcfe38e7..cbba47eb43154 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -4,6 +4,7 @@ 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 521920b92815b..c39e7d4e26bd0 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 ceb30091970f1..855a12e26208f 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 b3b222d902ae6..e5e1d84bfcdf9 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 59777e2e6bbc3..496ad7852754e 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 1a3d4e2e455af..84d1350a80f21 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 be2551457d0ea..d35514160c914 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -21,7 +21,7 @@ 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 b91036fa1f6e9..689fe739eba4e 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 a4ebc2301e16b..a5b45a585b9f6 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 02ed1d736991a..ff3c374a85094 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -164,6 +164,9 @@ async def async_device_initialized(self, device, is_new_join): 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 1a923849ce531..f0e1aa701e766 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 3ec0b27e7c4e0..b249ca804b302 100644 --- a/homeassistant/components/notify/pushover.py +++ b/homeassistant/components/notify/pushover.py @@ -10,7 +10,7 @@ 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 @@ 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 c863ff9433cac..aa9415fd1e080 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 de460319bc216..03d6ab47c2402 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 d11d4208dc86b..63e588f911b9c 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -134,22 +134,26 @@ async def async_initialize(self): 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 2eacb162f8e32..f2d796fb20400 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 7618e702d04d5..0871d961a933e 100644 --- a/homeassistant/components/hue/__init__.py +++ b/homeassistant/components/hue/__init__.py @@ -28,6 +28,8 @@ 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 1fcc092dd3083..6c89995a1a108 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 94b926795e785..5e6bd720d4bee 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 689fe739eba4e..cb23edaa232a6 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 5972660c6e648..4a7864d3f7f3c 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -20,7 +20,7 @@ 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 cb23edaa232a6..cc4e5d3d896e3 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 a5b45a585b9f6..fc66a4b72f662 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 84d1350a80f21..12f394b9e03d7 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 d35514160c914..3db63e65a6d62 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -21,7 +21,7 @@ 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 cc4e5d3d896e3..af11e32e516cc 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 fc66a4b72f662..5830085e544d4 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 e81a3974176ce..0c6cddd9fcd73 100644 --- a/homeassistant/components/sensor/thermoworks_smoke.py +++ b/homeassistant/components/sensor/thermoworks_smoke.py @@ -17,7 +17,7 @@ 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 af11e32e516cc..508f7bb2d04d4 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 7d8e4ddf71b9e..3cf1b2fea6190 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 @@ def state_attributes(self): 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 d3edf7d501bf3..a59d51d97e2ba 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -189,6 +189,7 @@ def async_source_tracking(event): 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 a63fab7686152..2e092e527c571 100644 --- a/homeassistant/components/fastdotcom/__init__.py +++ b/homeassistant/components/fastdotcom/__init__.py @@ -5,7 +5,8 @@ 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 @@ 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 7da51cd42e4bf..edb3a57c28ca5 100644 --- a/homeassistant/components/freedns/__init__.py +++ b/homeassistant/components/freedns/__init__.py @@ -13,7 +13,8 @@ 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 @@ 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 f34b2736710df..3d0d250557bf4 100644 --- a/homeassistant/components/mythicbeastsdns/__init__.py +++ b/homeassistant/components/mythicbeastsdns/__init__.py @@ -5,7 +5,9 @@ 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 @@ 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 50f9f9551483d..5720201b3f2ea 100644 --- a/homeassistant/components/sensor/broadlink.py +++ b/homeassistant/components/sensor/broadlink.py @@ -14,7 +14,8 @@ 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 @@ DEVICE_DEFAULT_NAME = 'Broadlink sensor' DEFAULT_TIMEOUT = 10 +SCAN_INTERVAL = timedelta(seconds=300) SENSOR_TYPES = { 'temperature': ['Temperature', TEMP_CELSIUS], @@ -34,16 +36,24 @@ '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 6e2ca2dc6c505..c68bb2cd3a383 100644 --- a/homeassistant/components/sensor/darksky.py +++ b/homeassistant/components/sensor/darksky.py @@ -14,7 +14,8 @@ 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_UNITS = 'units' DEFAULT_LANGUAGE = 'en' - DEFAULT_NAME = 'Dark Sky' +SCAN_INTERVAL = timedelta(seconds=300) DEPRECATED_SENSOR_TYPES = { 'apparent_temperature_max', @@ -167,23 +168,39 @@ '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 02938ff837b3e..54c319e6441e2 100644 --- a/homeassistant/components/sensor/fedex.py +++ b/homeassistant/components/sensor/fedex.py @@ -12,7 +12,9 @@ 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 @@ 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 44ecdc433c5e7..e4aab55505087 100644 --- a/homeassistant/components/sensor/ups.py +++ b/homeassistant/components/sensor/ups.py @@ -12,7 +12,9 @@ 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 @@ 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 3b8d2964f8347..4eae738b0d324 100644 --- a/homeassistant/components/speedtestdotnet/__init__.py +++ b/homeassistant/components/speedtestdotnet/__init__.py @@ -8,7 +8,8 @@ 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 @@ 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 1a6f35fe8d8b9..397e21922d91d 100644 --- a/homeassistant/components/tellduslive/__init__.py +++ b/homeassistant/components/tellduslive/__init__.py @@ -6,7 +6,8 @@ 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 @@ _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 9dbaadf9bee6b..7e72607c2f3e8 100644 --- a/homeassistant/components/volvooncall/__init__.py +++ b/homeassistant/components/volvooncall/__init__.py @@ -6,7 +6,8 @@ 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 @@ ] 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 12f394b9e03d7..43caf61aa7281 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -147,7 +147,11 @@ 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 3b01a01fc96bd..ab385019b1049 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -606,7 +606,8 @@ def validator(config: Dict): 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 784926912cdfe..1996b02d8d0a3 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 33a13f013dedf..58ce932020af5 100644 --- a/tests/components/sensor/test_darksky.py +++ b/tests/components/sensor/test_darksky.py @@ -21,7 +21,7 @@ '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 @@ '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 @@ '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 @@ '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 @@ def test_setup_bad_api_key(self, mock_get_forecast): 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 cefde564035a3..d83d32c88e37e 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 | 53 +++++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index ab385019b1049..ef9781f480be6 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -25,6 +25,8 @@ 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 validator(value): # Schemas - -PLATFORM_SCHEMA = vol.Schema({ +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 + + # 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 8575b023d3730..1a60943a72d5e 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -90,7 +90,7 @@ def test_validate_component_config(self): } }) - 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 @@ def test_validate_platform_config(self): 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 @@ def test_validate_platform_config(self): '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 @@ def test_validate_platform_config(self): '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 @@ def test_validate_platform_config(self): 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 @@ def test_validate_platform_config_2(self): 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 @@ def test_validate_platform_config_2(self): '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 @@ def test_validate_platform_config_2(self): 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 @@ def test_validate_platform_config_3(self): 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 ef9781f480be6..4b97409dd3d90 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -637,7 +637,9 @@ def validator(value): # 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 @@ def __call__(self, data): # 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 @@ def __call__(self, data): 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 1a60943a72d5e..c6126bc4a3bbf 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -117,7 +117,8 @@ def test_validate_platform_config(self, caplog): '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 @@ def test_validate_platform_config(self, caplog): '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 @@ def test_validate_platform_config_2(self, caplog): '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 @@ def test_validate_platform_config_3(self, caplog): '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 90a74f2359839..7e12a516478f8 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 4b97409dd3d90..b5716431217f7 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -25,8 +25,6 @@ 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 @@ # 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 @@ def __call__(self, data): 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 7e12a516478f8..a018d5400338b 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 43caf61aa7281..c5e3e082e0b89 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 5baed0294b852..ca1b560e336c4 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -124,6 +124,8 @@ def update_battery(self, new_state): """ 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 15ab6d7413e1f..6f3957827eb63 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 63e588f911b9c..6fb7d42e0ee76 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -247,7 +247,7 @@ async def _validate_user_id(self, user_id): 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 5e6bd720d4bee..1b3da2005406a 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 508f7bb2d04d4..08f834f2fdfe8 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 3db63e65a6d62..3b1d961ebe74b 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -21,7 +21,7 @@ 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 08f834f2fdfe8..64bd64c0aac35 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 5830085e544d4..a4e042c9c43ae 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 081361aa32ed1..94f21287e395a 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -205,7 +205,7 @@ def __init__(self, pygtfs, name, origin, destination, offset): 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 @@ def update(self): 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 f2775b30ae23b..2091ddb00a265 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 04da29aa55e69..3cf38c358bc76 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 .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 @@ def __init__(self, hass: HomeAssistantType, devices: Iterable, """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 @@ async def event_handler(self, req, resp, app): } 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 2fbb6f719da76..45101601d5ffb 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 9340bcef337bc..ab7334f13163e 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 b280036a6152b..4663222c3b440 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 @@ async def async_step_import(self, user_input=None): 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 @@ async def async_step_user(self, user_input=None): 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 @@ async def async_step_user(self, user_input=None): 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 25cd9e8305f20..27260b155d138 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -18,14 +18,16 @@ 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 4de1744c9b8bb..e722cd21d65a6 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 ce4b00ca1fe8c..79a5eabc20a3c 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 6dfff0bd02c68..d3f633ed0e4ac 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 eb83334c6b3d1..32047c179b411 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 1fb4e878cb463..bcbe02f6011b4 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 08cdb74ed774e..5a1224f4fc2d9 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 64bd64c0aac35..b23c98630a1a8 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 a4e042c9c43ae..7fbe73eac5fcd 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 0f1102e2ab105..c5646fb400ff2 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 4d2a43a52c750..7d3357031319c 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 99627e866d9ff..db8d9b512de39 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 a4f1103f270f5..72bc5da906319 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 c73f4ff549ee5..3739a2dc9b517 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 15ff3adce86c7..3f2bedd4f1315 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 2091ddb00a265..e35035b8fa072 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 bcbe02f6011b4..3578bcd5138da 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 a59d51d97e2ba..a01c53b20e345 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -83,9 +83,9 @@ def __init__(self, source_entity, name, meter_type, meter_offset=0, @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 851fd3d1c313a..ca6c8a5a5c607 100644 --- a/homeassistant/components/eight_sleep/__init__.py +++ b/homeassistant/components/eight_sleep/__init__.py @@ -16,7 +16,7 @@ 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 b23c98630a1a8..b83ef7ae08436 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 5edcadc7fce91..faa423d8ac439 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -62,7 +62,6 @@ PRESSURE = 'pressure' METERING = 'metering' ELECTRICAL_MEASUREMENT = 'electrical_measurement' -POWER_CONFIGURATION = 'power_configuration' GENERIC = 'generic' UNKNOWN = 'unknown' OPENING = 'opening' @@ -73,6 +72,7 @@ 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 @@ def list(cls): 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 7c972988e9c34..7bb39f943f661 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -15,9 +15,9 @@ 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 @@ def __init__(self, hass, zigpy_device, zha_gateway): self._zigpy_device.__class__.__module__, self._zigpy_device.__class__.__name__ ) + self.power_source = None @property def name(self): @@ -177,6 +178,13 @@ 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) + 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 ff3c374a85094..391b12189cf2f 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -18,15 +18,15 @@ 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 @@ async def async_device_initialized(self, device, is_new_join): 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 1b240d499b485..f8d24ce903c81 100644 --- a/homeassistant/components/zha/core/listeners.py +++ b/homeassistant/components/zha/core/listeners.py @@ -18,7 +18,10 @@ 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 @@ def __getattr__(self, name): 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 @@ async def async_initialize(self, from_cache): 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 @@ async def async_initialize(self, from_cache): 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 @@ async def async_initialize(self, from_cache): 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 @@ async def async_initialize(self, from_cache): 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 @@ async def async_update(self): # 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 @@ async def async_update(self): 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 @@ async def async_read_state(self, from_cache): 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 @@ def cluster_command(self, tsn, command_id, 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.""" + """Initialize ColorListener.""" super().__init__(cluster, device) + self.name = LISTENER_COLOR self._color_capabilities = None def get_color_capabilities(self): @@ -550,10 +615,13 @@ async def async_initialize(self, from_cache): 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 @@ async def async_initialize(self, from_cache): 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 ad566df00f44e..9c00d8124bb33 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -12,8 +12,8 @@ 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 @@ def pressure_formatter(value): 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 f0e1aa701e766..cd2eb53c3fe76 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 ad139d81ddf08..616a94e8b8992 100644 --- a/tests/components/zha/test_api.py +++ b/tests/components/zha/test_api.py @@ -17,14 +17,14 @@ @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 c81f96468ce68..d0763b8fb1026 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 6beafc6ca8e32..a70e0e5ea4007 100644 --- a/tests/components/zha/test_fan.py +++ b/tests/components/zha/test_fan.py @@ -17,11 +17,12 @@ 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 9c5e69d1347ef..38d7caedaad58 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -14,13 +14,13 @@ 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 d16cafb7df89b..c348ef0d0a718 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 32c8ee64e6781..1fc21e34cd832 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -14,12 +14,12 @@ 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 088fc871fc10c..5ecf3f55e10a8 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 c5e3e082e0b89..5885bf6acfe8f 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 3b1d961ebe74b..dce5b78bb6d38 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -21,7 +21,7 @@ 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 b83ef7ae08436..766d699aedc91 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 7fbe73eac5fcd..e706a1428ded9 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 b8d363417c211..571d05e78e953 100644 --- a/homeassistant/components/sensor/imap.py +++ b/homeassistant/components/sensor/imap.py @@ -20,7 +20,7 @@ _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 766d699aedc91..68b5c971f18c5 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 9e968111c9c5c..16786bdeba484 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 @@ def add_entry(self, entry): 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 c1d79c9f33f4d..14047399aff80 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 a5e4f5a8528b5..489fe9144f2b5 100644 --- a/homeassistant/components/harmony/remote.py +++ b/homeassistant/components/harmony/remote.py @@ -22,7 +22,7 @@ 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 68b5c971f18c5..b8d461437166b 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 48b5ea21cbcd0..cfd0f37caa0d6 100644 --- a/homeassistant/components/ads/__init__.py +++ b/homeassistant/components/ads/__init__.py @@ -9,7 +9,7 @@ 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 @@ def add_device_notification(self, name, plc_datatype, callback): 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 b8d461437166b..30f48e6587cfe 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 5885bf6acfe8f..1924d145529d4 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 dce5b78bb6d38..caf6bbccb5c3f 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -21,7 +21,7 @@ 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 30f48e6587cfe..ac85efe3be7d0 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 e706a1428ded9..1ee8060715006 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 5931322f80bba..8e5b61136c02c 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 b8ef5c408386e..6c7e83689ad4c 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -26,7 +26,7 @@ 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 1f85373eecc56..a46ffdd305d42 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -9,9 +9,9 @@ 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 @@ } -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 @@ def __init__(self, **kwargs): """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 @@ async def _determine_device_class(self): 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 @@ def set_level(self, level): @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 e7443e7e0b7fe..145b725fc7968 100644 --- a/homeassistant/components/zha/core/__init__.py +++ b/homeassistant/components/zha/core/__init__.py @@ -8,6 +8,3 @@ # 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 0000000000000..0c0e1ed217323 --- /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 0000000000000..ba3b6b2e71617 --- /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 0000000000000..bc015ae47f027 --- /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 0000000000000..2518889fcb14d --- /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 0000000000000..c62ec66588e80 --- /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 0000000000000..ee88a30e8288d --- /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 0000000000000..83fca6e80c2a2 --- /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 0000000000000..a0eebd7834356 --- /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 0000000000000..51146289e69d6 --- /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 0000000000000..2cae156aec5f1 --- /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 0000000000000..f0363ac833025 --- /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 0000000000000..e8c0e71a26378 --- /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 0000000000000..d17eae30a96f1 --- /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 faa423d8ac439..d1001682c7b4e 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -70,16 +70,16 @@ 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 7bb39f943f661..3a012ed789509 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -11,13 +11,14 @@ 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 @@ def __init__(self, hass, zigpy_device, zha_gateway): 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 @@ def gateway(self): 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 @@ def device_info(self): 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 391b12189cf2f..4fbf96a22b672 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -18,15 +18,18 @@ 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 @@ 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 @@ def device_registry(self): 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 @@ async def async_device_initialized(self, device, is_new_join): # 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 @@ async def _async_process_endpoint( 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 @@ async def _async_process_endpoint( )) -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 f8d24ce903c81..0000000000000 --- 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 e8b765a07a693..5632c849d593b 100644 --- a/homeassistant/components/zha/device_entity.py +++ b/homeassistant/components/zha/device_entity.py @@ -11,7 +11,7 @@ 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 @@ 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 @@ def __init__(self, zha_device, listeners, keepalive_interval=7200, 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 @@ def __init__(self, zha_device, listeners, keepalive_interval=7200, '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 @@ def device_state_attributes(self): 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 @@ async def async_update(self): 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 @@ def async_set_available(self, available): 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 @@ async def _async_init_battery_values(self): 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 d914a76c4ce1d..2f5aed4ca293a 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 @@ def __init__(self, unique_id, zha_device, listeners, 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 @@ async def async_added_to_hass(self): ) 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 @@ async def async_will_remove_from_hass(self) -> None: 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 @@ async def async_accept_signal(self, listener, signal, func, 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 dfe3c8cdd232a..761dfaede1e84 100644 --- a/homeassistant/components/zha/fan.py +++ b/homeassistant/components/zha/fan.py @@ -11,7 +11,7 @@ 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 @@ def device_state_attributes(self): 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 @@ async def async_turn_off(self, **kwargs) -> None: 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 09f1812cd7602..efa6f679ae8fa 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -10,8 +10,8 @@ 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 @@ 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: + 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 @@ async def async_turn_on(self, **kwargs): 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 @@ async def async_turn_on(self, **kwargs): 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 @@ async def async_turn_on(self, **kwargs): 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 @@ async def async_turn_on(self, **kwargs): 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 @@ async def async_turn_off(self, **kwargs): 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 9c00d8124bb33..6dcdbb845dc9d 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -12,7 +12,7 @@ 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 @@ def pressure_formatter(value): 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 @@ def __init__(self, unique_id, zha_device, listeners, **kwargs): 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 @@ def state(self) -> str: 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 4eee3d5da35b5..bdbdd7a6a7680 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -9,7 +9,7 @@ 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 @@ def is_on(self) -> bool: 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 @@ 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) + 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 c806b1a2217cd..bd594941da13c 100644 --- a/tests/components/zha/conftest.py +++ b/tests/components/zha/conftest.py @@ -7,8 +7,8 @@ ) 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 4a7864d3f7f3c..4464992e5fa58 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -339,8 +339,10 @@ def on_subscribed(data): 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 | 123 +++++++++++++++++++++++------------------- 2 files changed, 73 insertions(+), 55 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 5cd23e9f9a2d4..e7f654f518486 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -744,7 +744,10 @@ def from_dict(cls, json_dict: Dict) -> Any: 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 3cb5b87b4bb57..4acb1de667702 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -460,61 +460,76 @@ def coroutine_listener(event): assert len(coroutine_calls) == 1 -class TestState(unittest.TestCase): - """Test State methods.""" - - 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) - - def test_domain(self): - """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_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_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_state_init(): + """Test state.init.""" + with pytest.raises(InvalidEntityFormatError): + ha.State('invalid_entity_format', 'test_state') - def test_repr(self): - """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))) + with pytest.raises(InvalidStateError): + ha.State('domain.long_state', 't' * 256) + + +def test_state_domain(): + """Test domain.""" + state = ha.State('some_domain.hello', 'world') + assert 'some_domain' == state.domain + + +def test_state_object_id(): + """Test object ID.""" + state = ha.State('domain.hello', 'world') + assert 'hello' == state.object_id + + +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_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 + + +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 1924d145529d4..9dbb06e8adf1a 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 4fbf96a22b672..cd549afc819f6 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 bc015ae47f027..a29b23d340b01 100644 --- a/homeassistant/components/zha/core/channels/general.py +++ b/homeassistant/components/zha/core/channels/general.py @@ -199,4 +199,4 @@ async def async_read_state(self, 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) + 'battery_voltage', from_cache=from_cache) diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 3a012ed789509..12bb397fbc3ec 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -5,6 +5,7 @@ https://home-assistant.io/components/zha/ """ import asyncio +from enum import Enum import logging from homeassistant.helpers.dispatcher import ( @@ -23,6 +24,13 @@ _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 @@ def __init__(self, hass, zigpy_device, zha_gateway): self._zigpy_device.__class__.__name__ ) self.power_source = None + self.status = DeviceStatus.CREATED @property def name(self): @@ -186,6 +195,7 @@ async def async_initialize(self, from_cache=False): 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 cd549afc819f6..a50bfeae1beb9 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -23,7 +23,7 @@ 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 @@ 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) + # 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,20 +325,21 @@ async def _handle_single_cluster_matches(hass, endpoint, zha_device, cluster_match_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( - hass, + # 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, - device_key, - zha_const.SINGLE_INPUT_CLUSTER_DEVICE_CLASS, is_new_join, )) - if cluster.cluster_id in NO_SENSOR_CLUSTERS: - cluster_match_tasks.append(_handle_channel_only_cluster_match( + 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, ))