From 88161cd5c9e15b7c7e7c85f366a2550396172d5e Mon Sep 17 00:00:00 2001 From: Jack Fan Date: Thu, 11 Jan 2018 23:14:37 -0500 Subject: [PATCH 001/150] Avoid returning empty media_image_url string (#11557) This relates to issue https://github.com/home-assistant/home-assistant/issues/11556 --- homeassistant/components/media_player/cast.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index 4cf8f72f074..2aaff646885 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -186,7 +186,7 @@ class CastDevice(MediaPlayerDevice): images = self.media_status.images - return images[0].url if images else None + return images[0].url if images and images[0].url else None @property def media_title(self): From 6918993c75f86f0cf10582d3ea7e80d224f223fd Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Fri, 12 Jan 2018 01:06:09 -0500 Subject: [PATCH 002/150] Fix state for trigger with forced updates (#11595) --- homeassistant/components/automation/state.py | 2 +- tests/components/automation/test_state.py | 34 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index e4d096d35fd..9243f960850 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -55,7 +55,7 @@ def async_trigger(hass, config, action): # Ignore changes to state attributes if from/to is in use if (not match_all and from_s is not None and to_s is not None and - from_s.last_changed == to_s.last_changed): + from_s.state == to_s.state): return if not time_delta: diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index b1ee0841e2d..bf54d24492a 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -409,6 +409,40 @@ class TestAutomationState(unittest.TestCase): self.hass.block_till_done() self.assertEqual(1, len(self.calls)) + def test_if_fires_on_entity_change_with_for_multiple_force_update(self): + """Test for firing on entity change with for and force update.""" + assert setup_component(self.hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.force_entity', + 'to': 'world', + 'for': { + 'seconds': 5 + }, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + utcnow = dt_util.utcnow() + with patch('homeassistant.core.dt_util.utcnow') as mock_utcnow: + mock_utcnow.return_value = utcnow + self.hass.states.set('test.force_entity', 'world', None, True) + self.hass.block_till_done() + for _ in range(0, 4): + mock_utcnow.return_value += timedelta(seconds=1) + fire_time_changed(self.hass, mock_utcnow.return_value) + self.hass.states.set('test.force_entity', 'world', None, True) + self.hass.block_till_done() + self.assertEqual(0, len(self.calls)) + mock_utcnow.return_value += timedelta(seconds=4) + fire_time_changed(self.hass, mock_utcnow.return_value) + self.hass.block_till_done() + self.assertEqual(1, len(self.calls)) + def test_if_fires_on_entity_change_with_for(self): """Test for firing on entity change with for.""" assert setup_component(self.hass, automation.DOMAIN, { From 8a301c6c59c2c21aaaa465342e4f0c112421bb23 Mon Sep 17 00:00:00 2001 From: Bob Anderson Date: Thu, 11 Jan 2018 23:45:01 -0800 Subject: [PATCH 003/150] Concord232 alarm arm away fix (#11597) * fix arming away cmd for concord232 client * bump required version of concord232 to 0.15 --- homeassistant/components/alarm_control_panel/concord232.py | 4 ++-- homeassistant/components/binary_sensor/concord232.py | 2 +- requirements_all.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/concord232.py b/homeassistant/components/alarm_control_panel/concord232.py index 291d4bc80b5..af91bc78e67 100644 --- a/homeassistant/components/alarm_control_panel/concord232.py +++ b/homeassistant/components/alarm_control_panel/concord232.py @@ -18,7 +18,7 @@ from homeassistant.const import ( STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['concord232==0.14'] +REQUIREMENTS = ['concord232==0.15'] _LOGGER = logging.getLogger(__name__) @@ -121,4 +121,4 @@ class Concord232Alarm(alarm.AlarmControlPanel): def alarm_arm_away(self, code=None): """Send arm away command.""" - self._alarm.arm('auto') + self._alarm.arm('away') diff --git a/homeassistant/components/binary_sensor/concord232.py b/homeassistant/components/binary_sensor/concord232.py index 73cf77f2b93..c8442491b29 100644 --- a/homeassistant/components/binary_sensor/concord232.py +++ b/homeassistant/components/binary_sensor/concord232.py @@ -15,7 +15,7 @@ from homeassistant.components.binary_sensor import ( from homeassistant.const import (CONF_HOST, CONF_PORT) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['concord232==0.14'] +REQUIREMENTS = ['concord232==0.15'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 813e4e4dcf1..31da31d56a9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -177,7 +177,7 @@ colorlog==3.0.1 # homeassistant.components.alarm_control_panel.concord232 # homeassistant.components.binary_sensor.concord232 -concord232==0.14 +concord232==0.15 # homeassistant.scripts.credstash # credstash==1.14.0 From dacd7cd8a481ca062f6b3355203feeb31d806ab5 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Fri, 12 Jan 2018 15:29:58 +0100 Subject: [PATCH 004/150] Core support for hass.io calls & Bugfix check_config (#11571) * Initial overwrites * Add check_config function. * Update hassio.py * Address comments * add hassio support * add more tests * revert core changes * Address check_config * Address comment with api_bool * Bugfix check_config * Update core.py * Update test_core.py * Update config.py * Update hassio.py * Update config.py * Update test_config.py --- homeassistant/components/hassio.py | 86 ++++++++++++++++++++++++++--- homeassistant/components/updater.py | 7 +++ homeassistant/config.py | 18 ++++-- tests/components/test_hassio.py | 58 ++++++++++++++++++- tests/components/test_updater.py | 23 +++++++- tests/test_config.py | 4 +- 6 files changed, 179 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/hassio.py b/homeassistant/components/hassio.py index 8bd1b11cf0d..cc6db5fbab3 100644 --- a/homeassistant/components/hassio.py +++ b/homeassistant/components/hassio.py @@ -17,13 +17,16 @@ from aiohttp.hdrs import CONTENT_TYPE import async_timeout import voluptuous as vol -import homeassistant.helpers.config_validation as cv +from homeassistant.core import callback, DOMAIN as HASS_DOMAIN from homeassistant.const import ( - CONTENT_TYPE_TEXT_PLAIN, SERVER_PORT, CONF_TIME_ZONE) + CONTENT_TYPE_TEXT_PLAIN, SERVER_PORT, CONF_TIME_ZONE, + SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART) +from homeassistant.components import SERVICE_CHECK_CONFIG from homeassistant.components.http import ( HomeAssistantView, KEY_AUTHENTICATED, CONF_API_PASSWORD, CONF_SERVER_PORT, CONF_SERVER_HOST, CONF_SSL_CERTIFICATE) from homeassistant.loader import bind_hass +import homeassistant.helpers.config_validation as cv from homeassistant.util.dt import utcnow _LOGGER = logging.getLogger(__name__) @@ -34,7 +37,7 @@ DEPENDENCIES = ['http'] X_HASSIO = 'X-HASSIO-KEY' DATA_HOMEASSISTANT_VERSION = 'hassio_hass_version' -HASSIO_UPDATE_INTERVAL = timedelta(hours=1) +HASSIO_UPDATE_INTERVAL = timedelta(minutes=55) SERVICE_ADDON_START = 'addon_start' SERVICE_ADDON_STOP = 'addon_stop' @@ -120,12 +123,40 @@ MAP_SERVICE_API = { } +@callback @bind_hass def get_homeassistant_version(hass): - """Return last available HomeAssistant version.""" + """Return latest available HomeAssistant version. + + Async friendly. + """ return hass.data.get(DATA_HOMEASSISTANT_VERSION) +@callback +@bind_hass +def is_hassio(hass): + """Return True if hass.io is loaded. + + Async friendly. + """ + return DOMAIN in hass.config.components + + +@bind_hass +@asyncio.coroutine +def async_check_config(hass): + """Check config over Hass.io API.""" + result = yield from hass.data[DOMAIN].send_command( + '/homeassistant/check', timeout=300) + + if not result: + return "Hass.io config check API error" + elif result['result'] == "error": + return result['message'] + return None + + @asyncio.coroutine def async_setup(hass, config): """Set up the HASSio component.""" @@ -136,7 +167,7 @@ def async_setup(hass, config): return False websession = hass.helpers.aiohttp_client.async_get_clientsession() - hassio = HassIO(hass.loop, websession, host) + hass.data[DOMAIN] = hassio = HassIO(hass.loop, websession, host) if not (yield from hassio.is_connected()): _LOGGER.error("Not connected with HassIO!") @@ -170,11 +201,14 @@ def async_setup(hass, config): payload = data # Call API - yield from hassio.send_command( + ret = yield from hassio.send_command( api_command.format(addon=addon, snapshot=snapshot), payload=payload, timeout=MAP_SERVICE_API[service.service][2] ) + if not ret or ret['result'] != "ok": + _LOGGER.error("Error on Hass.io API: %s", ret['message']) + for service, settings in MAP_SERVICE_API.items(): hass.services.async_register( DOMAIN, service, async_service_handler, schema=settings[1]) @@ -193,9 +227,44 @@ def async_setup(hass, config): # Fetch last version yield from update_homeassistant_version(None) + @asyncio.coroutine + def async_handle_core_service(call): + """Service handler for handling core services.""" + if call.service == SERVICE_HOMEASSISTANT_STOP: + yield from hassio.send_command('/homeassistant/stop') + return + + error = yield from async_check_config(hass) + if error: + _LOGGER.error(error) + hass.components.persistent_notification.async_create( + "Config error. See dev-info panel for details.", + "Config validating", "{0}.check_config".format(HASS_DOMAIN)) + return + + if call.service == SERVICE_HOMEASSISTANT_RESTART: + yield from hassio.send_command('/homeassistant/restart') + + # Mock core services + for service in (SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART, + SERVICE_CHECK_CONFIG): + hass.services.async_register( + HASS_DOMAIN, service, async_handle_core_service) + return True +def _api_bool(funct): + """API wrapper to return Boolean.""" + @asyncio.coroutine + def _wrapper(*argv, **kwargs): + """Wrapper function.""" + data = yield from funct(*argv, **kwargs) + return data and data['result'] == "ok" + + return _wrapper + + class HassIO(object): """Small API wrapper for HassIO.""" @@ -205,6 +274,7 @@ class HassIO(object): self.websession = websession self._ip = ip + @_api_bool def is_connected(self): """Return True if it connected to HassIO supervisor. @@ -219,6 +289,7 @@ class HassIO(object): """ return self.send_command("/homeassistant/info", method="get") + @_api_bool def update_hass_api(self, http_config): """Update Home-Assistant API data on HassIO. @@ -238,6 +309,7 @@ class HassIO(object): return self.send_command("/homeassistant/options", payload=options) + @_api_bool def update_hass_timezone(self, core_config): """Update Home-Assistant timezone data on HassIO. @@ -261,7 +333,7 @@ class HassIO(object): X_HASSIO: os.environ.get('HASSIO_TOKEN') }) - if request.status != 200: + if request.status not in (200, 400): _LOGGER.error( "%s return code %d.", command, request.status) return None diff --git a/homeassistant/components/updater.py b/homeassistant/components/updater.py index f1f5b7dd1fd..f7bf9774e42 100644 --- a/homeassistant/components/updater.py +++ b/homeassistant/components/updater.py @@ -97,9 +97,15 @@ def async_setup(hass, config): newest, releasenotes = result + # Skip on dev if newest is None or 'dev' in current_version: return + # Load data from supervisor on hass.io + if hass.components.hassio.is_hassio(): + newest = hass.components.hassio.get_homeassistant_version() + + # Validate version if StrictVersion(newest) > StrictVersion(current_version): _LOGGER.info("The latest available version is %s", newest) hass.states.async_set( @@ -131,6 +137,7 @@ def get_system_info(hass, include_components): 'timezone': dt_util.DEFAULT_TIME_ZONE.zone, 'version': current_version, 'virtualenv': os.environ.get('VIRTUAL_ENV') is not None, + 'hassio': hass.components.hassio.is_hassio(), } if include_components: diff --git a/homeassistant/config.py b/homeassistant/config.py index fee7572a2c2..3f4c4c174d7 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -33,6 +33,8 @@ from homeassistant.helpers import config_per_platform, extract_domain_configs _LOGGER = logging.getLogger(__name__) DATA_PERSISTENT_ERRORS = 'bootstrap_persistent_errors' +RE_YAML_ERROR = re.compile(r"homeassistant\.util\.yaml") +RE_ASCII = re.compile(r"\033\[[^m]*m") HA_COMPONENT_URL = '[{}](https://home-assistant.io/components/{}/)' YAML_CONFIG_FILE = 'configuration.yaml' VERSION_FILE = '.HA_VERSION' @@ -655,15 +657,19 @@ def async_check_ha_config_file(hass): proc = yield from asyncio.create_subprocess_exec( sys.executable, '-m', 'homeassistant', '--script', 'check_config', '--config', hass.config.config_dir, - stdout=asyncio.subprocess.PIPE, loop=hass.loop) + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.STDOUT, loop=hass.loop) + # Wait for the subprocess exit - stdout_data, dummy = yield from proc.communicate() - result = yield from proc.wait() + log, _ = yield from proc.communicate() + exit_code = yield from proc.wait() - if not result: - return None + # Convert to ASCII + log = RE_ASCII.sub('', log.decode()) - return re.sub(r'\033\[[^m]*m', '', str(stdout_data, 'utf-8')) + if exit_code != 0 or RE_YAML_ERROR.search(log): + return log + return None @callback diff --git a/tests/components/test_hassio.py b/tests/components/test_hassio.py index b6be6f5a6a1..48443658fc4 100644 --- a/tests/components/test_hassio.py +++ b/tests/components/test_hassio.py @@ -7,6 +7,7 @@ import pytest from homeassistant.const import HTTP_HEADER_HA_AUTH from homeassistant.setup import async_setup_component +from homeassistant.components.hassio import async_check_config from tests.common import mock_coro @@ -60,6 +61,8 @@ def test_fail_setup_cannot_connect(hass): result = yield from async_setup_component(hass, 'hassio', {}) assert not result + assert not hass.components.hassio.is_hassio() + @asyncio.coroutine def test_setup_api_ping(hass, aioclient_mock): @@ -75,7 +78,8 @@ def test_setup_api_ping(hass, aioclient_mock): assert result assert aioclient_mock.call_count == 2 - assert hass.data['hassio_hass_version'] == "10.0" + assert hass.components.hassio.get_homeassistant_version() == "10.0" + assert hass.components.hassio.is_hassio() @asyncio.coroutine @@ -215,6 +219,7 @@ def test_service_register(hassio_env, hass): assert hass.services.has_service('hassio', 'addon_stdin') assert hass.services.has_service('hassio', 'host_shutdown') assert hass.services.has_service('hassio', 'host_reboot') + assert hass.services.has_service('hassio', 'host_reboot') assert hass.services.has_service('hassio', 'snapshot_full') assert hass.services.has_service('hassio', 'snapshot_partial') assert hass.services.has_service('hassio', 'restore_full') @@ -294,6 +299,57 @@ def test_service_calls(hassio_env, hass, aioclient_mock): 'addons': ['test'], 'folders': ['ssl'], 'homeassistant': False} +@asyncio.coroutine +def test_service_calls_core(hassio_env, hass, aioclient_mock): + """Call core service and check the API calls behind that.""" + assert (yield from async_setup_component(hass, 'hassio', {})) + + aioclient_mock.post( + "http://127.0.0.1/homeassistant/restart", json={'result': 'ok'}) + aioclient_mock.post( + "http://127.0.0.1/homeassistant/stop", json={'result': 'ok'}) + aioclient_mock.post( + "http://127.0.0.1/homeassistant/check", json={'result': 'ok'}) + + yield from hass.services.async_call('homeassistant', 'stop') + yield from hass.async_block_till_done() + + assert aioclient_mock.call_count == 1 + + yield from hass.services.async_call('homeassistant', 'check_config') + yield from hass.async_block_till_done() + + assert aioclient_mock.call_count == 2 + + yield from hass.services.async_call('homeassistant', 'restart') + yield from hass.async_block_till_done() + + assert aioclient_mock.call_count == 4 + + +@asyncio.coroutine +def test_check_config_ok(hassio_env, hass, aioclient_mock): + """Check Config that is okay.""" + assert (yield from async_setup_component(hass, 'hassio', {})) + + aioclient_mock.post( + "http://127.0.0.1/homeassistant/check", json={'result': 'ok'}) + + assert (yield from async_check_config(hass)) is None + + +@asyncio.coroutine +def test_check_config_fail(hassio_env, hass, aioclient_mock): + """Check Config that is wrong.""" + assert (yield from async_setup_component(hass, 'hassio', {})) + + aioclient_mock.post( + "http://127.0.0.1/homeassistant/check", json={ + 'result': 'error', 'message': "Error"}) + + assert (yield from async_check_config(hass)) == "Error" + + @asyncio.coroutine def test_forward_request(hassio_client): """Test fetching normal path.""" diff --git a/tests/components/test_updater.py b/tests/components/test_updater.py index 6d68add93a5..28ffcac2b13 100644 --- a/tests/components/test_updater.py +++ b/tests/components/test_updater.py @@ -8,7 +8,7 @@ import pytest from homeassistant.setup import async_setup_component from homeassistant.components import updater import homeassistant.util.dt as dt_util -from tests.common import async_fire_time_changed, mock_coro +from tests.common import async_fire_time_changed, mock_coro, mock_component NEW_VERSION = '10000.0' MOCK_VERSION = '10.0' @@ -174,3 +174,24 @@ def test_error_fetching_new_version_invalid_response(hass, aioclient_mock): Mock(return_value=mock_coro({'fake': 'bla'}))): res = yield from updater.get_newest_version(hass, MOCK_HUUID, False) assert res is None + + +@asyncio.coroutine +def test_new_version_shows_entity_after_hour_hassio( + hass, mock_get_uuid, mock_get_newest_version): + """Test if new entity is created if new version is available / hass.io.""" + mock_get_uuid.return_value = MOCK_HUUID + mock_get_newest_version.return_value = mock_coro((NEW_VERSION, '')) + mock_component(hass, 'hassio') + hass.data['hassio_hass_version'] = "999.0" + + res = yield from async_setup_component( + hass, updater.DOMAIN, {updater.DOMAIN: {}}) + assert res, 'Updater failed to setup' + + with patch('homeassistant.components.updater.current_version', + MOCK_VERSION): + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(hours=1)) + yield from hass.async_block_till_done() + + assert hass.states.is_state(updater.ENTITY_ID, "999.0") diff --git a/tests/test_config.py b/tests/test_config.py index 2c8edc32f82..377c650e91f 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -531,7 +531,7 @@ class TestConfig(unittest.TestCase): """Check that restart propagates to stop.""" process_mock = mock.MagicMock() attrs = { - 'communicate.return_value': mock_coro(('output', 'error')), + 'communicate.return_value': mock_coro((b'output', None)), 'wait.return_value': mock_coro(0)} process_mock.configure_mock(**attrs) mock_create.return_value = mock_coro(process_mock) @@ -546,7 +546,7 @@ class TestConfig(unittest.TestCase): process_mock = mock.MagicMock() attrs = { 'communicate.return_value': - mock_coro(('\033[34mhello'.encode('utf-8'), 'error')), + mock_coro(('\033[34mhello'.encode('utf-8'), None)), 'wait.return_value': mock_coro(1)} process_mock.configure_mock(**attrs) mock_create.return_value = mock_coro(process_mock) From cc236529c4d19fac33e65aff33b015251668885b Mon Sep 17 00:00:00 2001 From: Thijs de Jong Date: Fri, 12 Jan 2018 16:04:44 +0100 Subject: [PATCH 005/150] Fix Tahoma stop command for 2 types of shutters (#11588) * add working stop command This fixes the stop command for 2 types of roller shutters * fix line too long * fix indentation * fix indentation --- homeassistant/components/cover/tahoma.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/cover/tahoma.py b/homeassistant/components/cover/tahoma.py index d492ad50866..7ec09c781d2 100644 --- a/homeassistant/components/cover/tahoma.py +++ b/homeassistant/components/cover/tahoma.py @@ -15,6 +15,11 @@ DEPENDENCIES = ['tahoma'] _LOGGER = logging.getLogger(__name__) +TAHOMA_STOP_COMMAND = { + 'io:RollerShutterWithLowSpeedManagementIOComponent': 'my', + 'io:RollerShutterVeluxIOComponent': 'my', +} + SCAN_INTERVAL = timedelta(seconds=60) @@ -73,7 +78,8 @@ class TahomaCover(TahomaDevice, CoverDevice): def stop_cover(self, **kwargs): """Stop the cover.""" - self.apply_action('stopIdentify') + self.apply_action(TAHOMA_STOP_COMMAND.get(self.tahoma_device.type, + 'stopIdentify')) def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" From b8e4c2ff690a207442096127d1be48209d37491e Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Fri, 12 Jan 2018 13:19:43 -0500 Subject: [PATCH 006/150] Snips add say and say_actions services (new) (#11596) * Added snips.say and snips.say_action services * Added snips.say and snips.say_action services * Merged services.yaml changes I missed * added tests for new service configs * Woof * Woof Woof * Changed attribute names to follow hass standards. * updated test_snips with new attribute names --- homeassistant/components/services.yaml | 32 ++++++++++++ homeassistant/components/snips.py | 59 ++++++++++++++++++++++ tests/components/test_snips.py | 68 +++++++++++++++++++++++++- 3 files changed, 158 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/services.yaml b/homeassistant/components/services.yaml index 03c1d24184a..522939a213a 100644 --- a/homeassistant/components/services.yaml +++ b/homeassistant/components/services.yaml @@ -364,6 +364,38 @@ abode: 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 + input_boolean: toggle: description: Toggles an input boolean. diff --git a/homeassistant/components/snips.py b/homeassistant/components/snips.py index ae387f7ab4c..d221c8512c6 100644 --- a/homeassistant/components/snips.py +++ b/homeassistant/components/snips.py @@ -8,17 +8,29 @@ import asyncio import json import logging from datetime import timedelta + import voluptuous as vol + from homeassistant.helpers import intent, config_validation as cv import homeassistant.components.mqtt as mqtt DOMAIN = 'snips' DEPENDENCIES = ['mqtt'] + CONF_INTENTS = 'intents' CONF_ACTION = 'action' +SERVICE_SAY = 'say' +SERVICE_SAY_ACTION = 'say_action' + INTENT_TOPIC = 'hermes/intent/#' +ATTR_TEXT = 'text' +ATTR_SITE_ID = 'site_id' +ATTR_CUSTOM_DATA = 'custom_data' +ATTR_CAN_BE_ENQUEUED = 'can_be_enqueued' +ATTR_INTENT_FILTER = 'intent_filter' + _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema({ @@ -40,6 +52,20 @@ INTENT_SCHEMA = vol.Schema({ }] }, extra=vol.ALLOW_EXTRA) +SERVICE_SCHEMA_SAY = vol.Schema({ + vol.Required(ATTR_TEXT): str, + vol.Optional(ATTR_SITE_ID, default='default'): str, + vol.Optional(ATTR_CUSTOM_DATA, default=''): str +}) + +SERVICE_SCHEMA_SAY_ACTION = vol.Schema({ + vol.Required(ATTR_TEXT): str, + vol.Optional(ATTR_SITE_ID, default='default'): str, + vol.Optional(ATTR_CUSTOM_DATA, default=''): str, + vol.Optional(ATTR_CAN_BE_ENQUEUED, default=True): cv.boolean, + vol.Optional(ATTR_INTENT_FILTER): vol.All(cv.ensure_list), +}) + @asyncio.coroutine def async_setup(hass, config): @@ -93,6 +119,39 @@ def async_setup(hass, config): yield from hass.components.mqtt.async_subscribe( INTENT_TOPIC, message_received) + @asyncio.coroutine + def snips_say(call): + """Send a Snips notification message.""" + notification = {'siteId': call.data.get(ATTR_SITE_ID, 'default'), + 'customData': call.data.get(ATTR_CUSTOM_DATA, ''), + 'init': {'type': 'notification', + 'text': call.data.get(ATTR_TEXT)}} + mqtt.async_publish(hass, 'hermes/dialogueManager/startSession', + json.dumps(notification)) + return + + @asyncio.coroutine + def snips_say_action(call): + """Send a Snips action message.""" + notification = {'siteId': call.data.get(ATTR_SITE_ID, 'default'), + 'customData': call.data.get(ATTR_CUSTOM_DATA, ''), + 'init': {'type': 'action', + 'text': call.data.get(ATTR_TEXT), + 'canBeEnqueued': call.data.get( + ATTR_CAN_BE_ENQUEUED, True), + 'intentFilter': + call.data.get(ATTR_INTENT_FILTER, [])}} + mqtt.async_publish(hass, 'hermes/dialogueManager/startSession', + json.dumps(notification)) + return + + hass.services.async_register( + DOMAIN, SERVICE_SAY, snips_say, + schema=SERVICE_SCHEMA_SAY) + hass.services.async_register( + DOMAIN, SERVICE_SAY_ACTION, snips_say_action, + schema=SERVICE_SCHEMA_SAY_ACTION) + return True diff --git a/tests/components/test_snips.py b/tests/components/test_snips.py index 9ee500bb4c7..711d13dc341 100644 --- a/tests/components/test_snips.py +++ b/tests/components/test_snips.py @@ -4,7 +4,10 @@ import json from homeassistant.core import callback from homeassistant.bootstrap import async_setup_component -from tests.common import async_fire_mqtt_message, async_mock_intent +from tests.common import (async_fire_mqtt_message, async_mock_intent, + async_mock_service) +from homeassistant.components.snips import (SERVICE_SCHEMA_SAY, + SERVICE_SCHEMA_SAY_ACTION) @asyncio.coroutine @@ -238,3 +241,66 @@ def test_snips_intent_username(hass, mqtt_mock): intent = intents[0] assert intent.platform == 'snips' assert intent.intent_type == 'Lights' + + +@asyncio.coroutine +def test_snips_say(hass, caplog): + """Test snips say with invalid config.""" + calls = async_mock_service(hass, 'snips', 'say', + SERVICE_SCHEMA_SAY) + + data = {'text': 'Hello'} + yield from hass.services.async_call('snips', 'say', data) + yield from hass.async_block_till_done() + + assert len(calls) == 1 + assert calls[0].domain == 'snips' + assert calls[0].service == 'say' + assert calls[0].data['text'] == 'Hello' + + +@asyncio.coroutine +def test_snips_say_action(hass, caplog): + """Test snips say_action with invalid config.""" + calls = async_mock_service(hass, 'snips', 'say_action', + SERVICE_SCHEMA_SAY_ACTION) + + data = {'text': 'Hello', 'intent_filter': ['myIntent']} + yield from hass.services.async_call('snips', 'say_action', data) + yield from hass.async_block_till_done() + + assert len(calls) == 1 + assert calls[0].domain == 'snips' + assert calls[0].service == 'say_action' + assert calls[0].data['text'] == 'Hello' + assert calls[0].data['intent_filter'] == ['myIntent'] + + +@asyncio.coroutine +def test_snips_say_invalid_config(hass, caplog): + """Test snips say with invalid config.""" + calls = async_mock_service(hass, 'snips', 'say', + SERVICE_SCHEMA_SAY) + + data = {'text': 'Hello', 'badKey': 'boo'} + yield from hass.services.async_call('snips', 'say', data) + yield from hass.async_block_till_done() + + assert len(calls) == 0 + assert 'ERROR' in caplog.text + assert 'Invalid service data' in caplog.text + + +@asyncio.coroutine +def test_snips_say_action_invalid_config(hass, caplog): + """Test snips say_action with invalid config.""" + calls = async_mock_service(hass, 'snips', 'say_action', + SERVICE_SCHEMA_SAY_ACTION) + + data = {'text': 'Hello', 'can_be_enqueued': 'notabool'} + yield from hass.services.async_call('snips', 'say_action', data) + yield from hass.async_block_till_done() + + assert len(calls) == 0 + assert 'ERROR' in caplog.text + assert 'Invalid service data' in caplog.text From c036141b374951342a5a77641713b573d5bb00df Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Fri, 12 Jan 2018 14:06:42 -0500 Subject: [PATCH 007/150] Pushbullet email support (fix) (#11590) * Simplified push calls * Cleaned up and added unittests * Fixed email parameter * Fixed email parameter --- homeassistant/components/notify/pushbullet.py | 29 ++++++------- tests/components/notify/test_pushbullet.py | 42 +++++++++++++++++++ 2 files changed, 57 insertions(+), 14 deletions(-) create mode 100644 tests/components/notify/test_pushbullet.py diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/notify/pushbullet.py index 0e846ebaf84..359810bb6bc 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -22,6 +22,7 @@ _LOGGER = logging.getLogger(__name__) ATTR_URL = 'url' ATTR_FILE = 'file' ATTR_FILE_URL = 'file_url' +ATTR_LIST = 'list' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_API_KEY): cv.string, @@ -99,7 +100,7 @@ class PushBulletNotificationService(BaseNotificationService): continue # Target is email, send directly, don't use a target object. - # This also seems works to send to all devices in own account. + # This also seems to work to send to all devices in own account. if ttype == 'email': self._push_data(message, title, data, self.pushbullet, tname) _LOGGER.info("Sent notification to email %s", tname) @@ -127,20 +128,18 @@ class PushBulletNotificationService(BaseNotificationService): _LOGGER.error("No such target: %s/%s", ttype, tname) continue - def _push_data(self, message, title, data, pusher, tname=None): + def _push_data(self, message, title, data, pusher, email=None): """Helper for creating the message content.""" from pushbullet import PushError if data is None: data = {} + data_list = data.get(ATTR_LIST) url = data.get(ATTR_URL) filepath = data.get(ATTR_FILE) file_url = data.get(ATTR_FILE_URL) try: if url: - if tname: - pusher.push_link(title, url, body=message, email=tname) - else: - pusher.push_link(title, url, body=message) + pusher.push_link(title, url, body=message, email=email) elif filepath: if not self.hass.config.is_allowed_path(filepath): _LOGGER.error("Filepath is not valid or allowed") @@ -150,18 +149,20 @@ class PushBulletNotificationService(BaseNotificationService): if filedata.get('file_type') == 'application/x-empty': _LOGGER.error("Can not send an empty file") return - pusher.push_file(title=title, body=message, **filedata) + + pusher.push_file(title=title, body=message, + email=email, **filedata) elif file_url: if not file_url.startswith('http'): _LOGGER.error("URL should start with http or https") return - pusher.push_file(title=title, body=message, file_name=file_url, - file_url=file_url, - file_type=mimetypes.guess_type(file_url)[0]) + pusher.push_file(title=title, body=message, email=email, + file_name=file_url, file_url=file_url, + file_type=(mimetypes + .guess_type(file_url)[0])) + elif data_list: + pusher.push_note(title, data_list, email=email) else: - if tname: - pusher.push_note(title, message, email=tname) - else: - pusher.push_note(title, message) + pusher.push_note(title, message, email=email) except PushError as err: _LOGGER.error("Notify failed: %s", err) diff --git a/tests/components/notify/test_pushbullet.py b/tests/components/notify/test_pushbullet.py new file mode 100644 index 00000000000..ba3046e8fd7 --- /dev/null +++ b/tests/components/notify/test_pushbullet.py @@ -0,0 +1,42 @@ +"""The tests for the pushbullet notification platform.""" + +import unittest + +from homeassistant.setup import setup_component +import homeassistant.components.notify as notify +from tests.common import assert_setup_component, get_test_home_assistant + + +class TestPushbullet(unittest.TestCase): + """Test the pushbullet notifications.""" + + def setUp(self): # pylint: disable=invalid-name + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + + def tearDown(self): # pylint: disable=invalid-name + """Stop down everything that was started.""" + self.hass.stop() + + def test_setup(self): + """Test setup.""" + with assert_setup_component(1) as handle_config: + assert setup_component(self.hass, 'notify', { + 'notify': { + 'name': 'test', + 'platform': 'pushbullet', + 'api_key': 'MYFAKEKEY', } + }) + assert handle_config[notify.DOMAIN] + + def test_bad_config(self): + """Test set up the platform with bad/missing configuration.""" + config = { + notify.DOMAIN: { + 'name': 'test', + 'platform': 'pushbullet', + } + } + with assert_setup_component(0) as handle_config: + assert setup_component(self.hass, notify.DOMAIN, config) + assert not handle_config[notify.DOMAIN] From 0409192e648ee60703f972567acc367560948251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Fri, 12 Jan 2018 20:52:53 +0100 Subject: [PATCH 008/150] Bugfix and cleanup for Rfxtrx (#11600) * rfxtrx clean up * rfxtrx clean up * rfxtrx clean up --- .../components/binary_sensor/rfxtrx.py | 76 +++++---- homeassistant/components/cover/rfxtrx.py | 21 ++- homeassistant/components/light/rfxtrx.py | 23 ++- homeassistant/components/rfxtrx.py | 156 +++++------------- homeassistant/components/sensor/rfxtrx.py | 25 ++- homeassistant/components/switch/rfxtrx.py | 21 ++- requirements_all.txt | 2 +- 7 files changed, 156 insertions(+), 168 deletions(-) diff --git a/homeassistant/components/binary_sensor/rfxtrx.py b/homeassistant/components/binary_sensor/rfxtrx.py index edaee574232..4073cb9eac1 100644 --- a/homeassistant/components/binary_sensor/rfxtrx.py +++ b/homeassistant/components/binary_sensor/rfxtrx.py @@ -7,30 +7,40 @@ tested. Other types may need some work. """ import logging + import voluptuous as vol + +from homeassistant.const import ( + CONF_DEVICE_CLASS, CONF_COMMAND_ON, CONF_COMMAND_OFF, CONF_NAME) from homeassistant.components import rfxtrx +from homeassistant.helpers import event as evt +from homeassistant.helpers import config_validation as cv +from homeassistant.components.binary_sensor import ( + BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.rfxtrx import ( + ATTR_NAME, ATTR_DATA_BITS, ATTR_OFF_DELAY, ATTR_FIRE_EVENT, + CONF_AUTOMATIC_ADD, CONF_FIRE_EVENT, + CONF_DATA_BITS, CONF_DEVICES) from homeassistant.util import slugify from homeassistant.util import dt as dt_util -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers import event as evt -from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.rfxtrx import ( - ATTR_AUTOMATIC_ADD, ATTR_NAME, ATTR_OFF_DELAY, ATTR_FIREEVENT, - ATTR_DATA_BITS, CONF_DEVICES -) -from homeassistant.const import ( - CONF_DEVICE_CLASS, CONF_COMMAND_ON, CONF_COMMAND_OFF -) + DEPENDENCIES = ["rfxtrx"] _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = vol.Schema({ - vol.Required("platform"): rfxtrx.DOMAIN, - vol.Optional(CONF_DEVICES, default={}): vol.All( - dict, rfxtrx.valid_binary_sensor), - vol.Optional(ATTR_AUTOMATIC_ADD, default=False): cv.boolean, +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_DEVICES, default={}): { + cv.string: vol.Schema({ + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_DEVICE_CLASS): cv.string, + vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean, + 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_AUTOMATIC_ADD, default=False): cv.boolean, }, extra=vol.ALLOW_EXTRA) @@ -46,17 +56,17 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): if device_id in rfxtrx.RFX_DEVICES: continue - if entity[ATTR_DATA_BITS] is not None: - _LOGGER.info("Masked device id: %s", - rfxtrx.get_pt2262_deviceid(device_id, - entity[ATTR_DATA_BITS])) + if entity[CONF_DATA_BITS] is not None: + _LOGGER.debug("Masked device id: %s", + rfxtrx.get_pt2262_deviceid(device_id, + entity[ATTR_DATA_BITS])) - _LOGGER.info("Add %s rfxtrx.binary_sensor (class %s)", - entity[ATTR_NAME], entity[CONF_DEVICE_CLASS]) + _LOGGER.debug("Add %s rfxtrx.binary_sensor (class %s)", + entity[ATTR_NAME], entity[CONF_DEVICE_CLASS]) device = RfxtrxBinarySensor(event, entity[ATTR_NAME], entity[CONF_DEVICE_CLASS], - entity[ATTR_FIREEVENT], + entity[ATTR_FIRE_EVENT], entity[ATTR_OFF_DELAY], entity[ATTR_DATA_BITS], entity[CONF_COMMAND_ON], @@ -82,15 +92,15 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): if sensor is None: # Add the entity if not exists and automatic_add is True - if not config[ATTR_AUTOMATIC_ADD]: + if not config[CONF_AUTOMATIC_ADD]: return if event.device.packettype == 0x13: poss_dev = rfxtrx.find_possible_pt2262_device(device_id) if poss_dev is not None: poss_id = slugify(poss_dev.event.device.id_string.lower()) - _LOGGER.info("Found possible matching deviceid %s.", - poss_id) + _LOGGER.debug("Found possible matching deviceid %s.", + poss_id) pkt_id = "".join("{0:02x}".format(x) for x in event.data) sensor = RfxtrxBinarySensor(event, pkt_id) @@ -107,11 +117,11 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): elif not isinstance(sensor, RfxtrxBinarySensor): return else: - _LOGGER.info("Binary sensor update " - "(Device_id: %s Class: %s Sub: %s)", - slugify(event.device.id_string.lower()), - event.device.__class__.__name__, - event.device.subtype) + _LOGGER.debug("Binary sensor update " + "(Device_id: %s Class: %s Sub: %s)", + slugify(event.device.id_string.lower()), + event.device.__class__.__name__, + event.device.subtype) if sensor.is_lighting4: if sensor.data_bits is not None: @@ -163,10 +173,8 @@ class RfxtrxBinarySensor(BinarySensorDevice): self._masked_id = rfxtrx.get_pt2262_deviceid( event.device.id_string.lower(), data_bits) - - def __str__(self): - """Return the name of the sensor.""" - return self._name + else: + self._masked_id = None @property def name(self): diff --git a/homeassistant/components/cover/rfxtrx.py b/homeassistant/components/cover/rfxtrx.py index 0e28d3ef701..66f2fde52f4 100644 --- a/homeassistant/components/cover/rfxtrx.py +++ b/homeassistant/components/cover/rfxtrx.py @@ -4,12 +4,29 @@ Support for RFXtrx cover components. For more details about this platform, please refer to the documentation https://home-assistant.io/components/cover.rfxtrx/ """ +import voluptuous as vol + import homeassistant.components.rfxtrx as rfxtrx -from homeassistant.components.cover import CoverDevice +from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA +from homeassistant.const import CONF_NAME +from homeassistant.components.rfxtrx import ( + CONF_AUTOMATIC_ADD, CONF_FIRE_EVENT, DEFAULT_SIGNAL_REPETITIONS, + CONF_SIGNAL_REPETITIONS, CONF_DEVICES) +from homeassistant.helpers import config_validation as cv DEPENDENCIES = ['rfxtrx'] -PLATFORM_SCHEMA = rfxtrx.DEFAULT_SCHEMA +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_DEVICES, default={}): { + cv.string: vol.Schema({ + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean + }) + }, + vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean, + vol.Optional(CONF_SIGNAL_REPETITIONS, default=DEFAULT_SIGNAL_REPETITIONS): + vol.Coerce(int), +}) def setup_platform(hass, config, add_devices_callback, discovery_info=None): diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/light/rfxtrx.py index 9248b0131f1..cdfe2fe5671 100644 --- a/homeassistant/components/light/rfxtrx.py +++ b/homeassistant/components/light/rfxtrx.py @@ -6,15 +6,32 @@ https://home-assistant.io/components/light.rfxtrx/ """ import logging +import voluptuous as vol + import homeassistant.components.rfxtrx as rfxtrx -from homeassistant.components.light import (ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, Light) +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light, PLATFORM_SCHEMA) +from homeassistant.const import CONF_NAME +from homeassistant.components.rfxtrx import ( + CONF_AUTOMATIC_ADD, CONF_FIRE_EVENT, DEFAULT_SIGNAL_REPETITIONS, + CONF_SIGNAL_REPETITIONS, CONF_DEVICES) +from homeassistant.helpers import config_validation as cv DEPENDENCIES = ['rfxtrx'] _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = rfxtrx.DEFAULT_SCHEMA +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_DEVICES, default={}): { + cv.string: vol.Schema({ + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean + }) + }, + vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean, + vol.Optional(CONF_SIGNAL_REPETITIONS, default=DEFAULT_SIGNAL_REPETITIONS): + vol.Coerce(int), +}) SUPPORT_RFXTRX = SUPPORT_BRIGHTNESS diff --git a/homeassistant/components/rfxtrx.py b/homeassistant/components/rfxtrx.py index 8b730bf97f2..f28a9aafb19 100644 --- a/homeassistant/components/rfxtrx.py +++ b/homeassistant/components/rfxtrx.py @@ -16,11 +16,11 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, ATTR_ENTITY_ID, TEMP_CELSIUS, - CONF_DEVICE_CLASS, CONF_COMMAND_ON, CONF_COMMAND_OFF + CONF_DEVICES ) from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['pyRFXtrx==0.20.1'] +REQUIREMENTS = ['pyRFXtrx==0.21.1'] DOMAIN = 'rfxtrx' @@ -31,13 +31,19 @@ ATTR_DEVICE = 'device' ATTR_DEBUG = 'debug' ATTR_STATE = 'state' ATTR_NAME = 'name' -ATTR_FIREEVENT = 'fire_event' +ATTR_FIRE_EVENT = 'fire_event' ATTR_DATA_TYPE = 'data_type' ATTR_DATA_BITS = 'data_bits' ATTR_DUMMY = 'dummy' ATTR_OFF_DELAY = 'off_delay' +CONF_AUTOMATIC_ADD = 'automatic_add' +CONF_DATA_TYPE = 'data_type' CONF_SIGNAL_REPETITIONS = 'signal_repetitions' -CONF_DEVICES = 'devices' +CONF_FIRE_EVENT = 'fire_event' +CONF_DATA_BITS = 'data_bits' +CONF_DUMMY = 'dummy' +CONF_DEVICE = 'device' +CONF_DEBUG = 'debug' EVENT_BUTTON_PRESSED = 'button_pressed' DATA_TYPES = OrderedDict([ @@ -57,93 +63,13 @@ DATA_TYPES = OrderedDict([ RECEIVED_EVT_SUBSCRIBERS = [] RFX_DEVICES = {} _LOGGER = logging.getLogger(__name__) -RFXOBJECT = 'rfxobject' - - -def _valid_device(value, device_type): - """Validate a dictionary of devices definitions.""" - config = OrderedDict() - for key, device in value.items(): - - # Still accept old configuration - if 'packetid' in device.keys(): - msg = 'You are using an outdated configuration of the rfxtrx ' +\ - 'device, {}.'.format(key) +\ - ' Your new config should be:\n {}: \n name: {}'\ - .format(device.get('packetid'), - device.get(ATTR_NAME, 'deivce_name')) - _LOGGER.warning(msg) - key = device.get('packetid') - device.pop('packetid') - - key = str(key) - if not len(key) % 2 == 0: - key = '0' + key - - if device_type == 'sensor': - config[key] = DEVICE_SCHEMA_SENSOR(device) - elif device_type == 'binary_sensor': - config[key] = DEVICE_SCHEMA_BINARYSENSOR(device) - elif device_type == 'light_switch': - config[key] = DEVICE_SCHEMA(device) - else: - raise vol.Invalid('Rfxtrx device is invalid') - - if not config[key][ATTR_NAME]: - config[key][ATTR_NAME] = key - return config - - -def valid_sensor(value): - """Validate sensor configuration.""" - return _valid_device(value, "sensor") - - -def valid_binary_sensor(value): - """Validate binary sensor configuration.""" - return _valid_device(value, "binary_sensor") - - -def _valid_light_switch(value): - return _valid_device(value, "light_switch") - - -DEVICE_SCHEMA = vol.Schema({ - vol.Required(ATTR_NAME): cv.string, - vol.Optional(ATTR_FIREEVENT, default=False): cv.boolean, -}) - -DEVICE_SCHEMA_SENSOR = vol.Schema({ - vol.Optional(ATTR_NAME, default=None): cv.string, - vol.Optional(ATTR_FIREEVENT, default=False): cv.boolean, - vol.Optional(ATTR_DATA_TYPE, default=[]): - vol.All(cv.ensure_list, [vol.In(DATA_TYPES.keys())]), -}) - -DEVICE_SCHEMA_BINARYSENSOR = vol.Schema({ - vol.Optional(ATTR_NAME, default=None): cv.string, - vol.Optional(CONF_DEVICE_CLASS, default=None): cv.string, - vol.Optional(ATTR_FIREEVENT, default=False): cv.boolean, - vol.Optional(ATTR_OFF_DELAY, default=None): - vol.Any(cv.time_period, cv.positive_timedelta), - vol.Optional(ATTR_DATA_BITS, default=None): cv.positive_int, - vol.Optional(CONF_COMMAND_ON, default=None): cv.byte, - vol.Optional(CONF_COMMAND_OFF, default=None): cv.byte -}) - -DEFAULT_SCHEMA = vol.Schema({ - vol.Required("platform"): DOMAIN, - vol.Optional(CONF_DEVICES, default={}): vol.All(dict, _valid_light_switch), - vol.Optional(ATTR_AUTOMATIC_ADD, default=False): cv.boolean, - vol.Optional(CONF_SIGNAL_REPETITIONS, default=DEFAULT_SIGNAL_REPETITIONS): - vol.Coerce(int), -}) +DATA_RFXOBJECT = 'rfxobject' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ - vol.Required(ATTR_DEVICE): cv.string, - vol.Optional(ATTR_DEBUG, default=False): cv.boolean, - vol.Optional(ATTR_DUMMY, default=False): cv.boolean, + vol.Required(CONF_DEVICE): cv.string, + vol.Optional(CONF_DEBUG, default=False): cv.boolean, + vol.Optional(CONF_DUMMY, default=False): cv.boolean, }), }, extra=vol.ALLOW_EXTRA) @@ -152,7 +78,7 @@ def setup(hass, config): """Set up the RFXtrx component.""" # Declare the Handle event def handle_receive(event): - """Handle revieved messgaes from RFXtrx gateway.""" + """Handle revieved messages from RFXtrx gateway.""" # Log RFXCOM event if not event.device.id_string: return @@ -175,21 +101,22 @@ def setup(hass, config): dummy_connection = config[DOMAIN][ATTR_DUMMY] if dummy_connection: - hass.data[RFXOBJECT] =\ - rfxtrxmod.Connect(device, None, debug=debug, - transport_protocol=rfxtrxmod.DummyTransport2) + rfx_object = rfxtrxmod.Connect( + device, None, debug=debug, + transport_protocol=rfxtrxmod.DummyTransport2) else: - hass.data[RFXOBJECT] = rfxtrxmod.Connect(device, None, debug=debug) + rfx_object = rfxtrxmod.Connect(device, None, debug=debug) def _start_rfxtrx(event): - hass.data[RFXOBJECT].event_callback = handle_receive + rfx_object.event_callback = handle_receive hass.bus.listen_once(EVENT_HOMEASSISTANT_START, _start_rfxtrx) def _shutdown_rfxtrx(event): """Close connection with RFXtrx.""" - hass.data[RFXOBJECT].close_connection() + rfx_object.close_connection() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown_rfxtrx) + hass.data[DATA_RFXOBJECT] = rfx_object return True @@ -248,9 +175,9 @@ def get_pt2262_device(device_id): if (hasattr(device, 'is_lighting4') and device.masked_id == get_pt2262_deviceid(device_id, device.data_bits)): - _LOGGER.info("rfxtrx: found matching device %s for %s", - device_id, - device.masked_id) + _LOGGER.debug("rfxtrx: found matching device %s for %s", + device_id, + device.masked_id) return device return None @@ -295,11 +222,11 @@ def get_devices_from_config(config, device): device_id = slugify(event.device.id_string.lower()) if device_id in RFX_DEVICES: continue - _LOGGER.info("Add %s rfxtrx", entity_info[ATTR_NAME]) + _LOGGER.debug("Add %s rfxtrx", entity_info[ATTR_NAME]) # Check if i must fire event - fire_event = entity_info[ATTR_FIREEVENT] - datas = {ATTR_STATE: False, ATTR_FIREEVENT: fire_event} + fire_event = entity_info[ATTR_FIRE_EVENT] + datas = {ATTR_STATE: False, ATTR_FIRE_EVENT: fire_event} new_device = device(entity_info[ATTR_NAME], event, datas, signal_repetitions) @@ -318,14 +245,14 @@ def get_new_device(event, config, device): return pkt_id = "".join("{0:02x}".format(x) for x in event.data) - _LOGGER.info( + _LOGGER.debug( "Automatic add %s rfxtrx device (Class: %s Sub: %s Packet_id: %s)", device_id, event.device.__class__.__name__, event.device.subtype, pkt_id ) - datas = {ATTR_STATE: False, ATTR_FIREEVENT: False} + datas = {ATTR_STATE: False, ATTR_FIRE_EVENT: False} signal_repetitions = config[CONF_SIGNAL_REPETITIONS] new_device = device(pkt_id, event, datas, signal_repetitions) @@ -370,7 +297,7 @@ def apply_received_command(event): ATTR_STATE: event.values['Command'].lower() } ) - _LOGGER.info( + _LOGGER.debug( "Rfxtrx fired event: (event_type: %s, %s: %s, %s: %s)", EVENT_BUTTON_PRESSED, ATTR_ENTITY_ID, @@ -392,7 +319,7 @@ class RfxtrxDevice(Entity): self._name = name self._event = event self._state = datas[ATTR_STATE] - self._should_fire_event = datas[ATTR_FIREEVENT] + self._should_fire_event = datas[ATTR_FIRE_EVENT] self._brightness = 0 self.added_to_hass = False @@ -440,40 +367,35 @@ class RfxtrxDevice(Entity): def _send_command(self, command, brightness=0): if not self._event: return + rfx_object = self.hass.data[DATA_RFXOBJECT] if command == "turn_on": for _ in range(self.signal_repetitions): - self._event.device.send_on(self.hass.data[RFXOBJECT] - .transport) + self._event.device.send_on(rfx_object.transport) self._state = True elif command == "dim": for _ in range(self.signal_repetitions): - self._event.device.send_dim(self.hass.data[RFXOBJECT] - .transport, brightness) + self._event.device.send_dim(rfx_object.transport, brightness) self._state = True elif command == 'turn_off': for _ in range(self.signal_repetitions): - self._event.device.send_off(self.hass.data[RFXOBJECT] - .transport) + self._event.device.send_off(rfx_object.transport) self._state = False self._brightness = 0 elif command == "roll_up": for _ in range(self.signal_repetitions): - self._event.device.send_open(self.hass.data[RFXOBJECT] - .transport) + self._event.device.send_open(rfx_object.transport) elif command == "roll_down": for _ in range(self.signal_repetitions): - self._event.device.send_close(self.hass.data[RFXOBJECT] - .transport) + self._event.device.send_close(rfx_object.transport) elif command == "stop_roll": for _ in range(self.signal_repetitions): - self._event.device.send_stop(self.hass.data[RFXOBJECT] - .transport) + self._event.device.send_stop(rfx_object.transport) if self.added_to_hass: self.schedule_update_ha_state() diff --git a/homeassistant/components/sensor/rfxtrx.py b/homeassistant/components/sensor/rfxtrx.py index e01dbc83422..1c09bc01909 100644 --- a/homeassistant/components/sensor/rfxtrx.py +++ b/homeassistant/components/sensor/rfxtrx.py @@ -10,21 +10,28 @@ import voluptuous as vol import homeassistant.components.rfxtrx as rfxtrx import homeassistant.helpers.config_validation as cv -from homeassistant.const import CONF_PLATFORM +from homeassistant.const import ATTR_ENTITY_ID, CONF_NAME from homeassistant.helpers.entity import Entity from homeassistant.util import slugify +from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.components.rfxtrx import ( - ATTR_AUTOMATIC_ADD, ATTR_NAME, ATTR_FIREEVENT, CONF_DEVICES, DATA_TYPES, - ATTR_DATA_TYPE, ATTR_ENTITY_ID) + ATTR_NAME, ATTR_FIRE_EVENT, ATTR_DATA_TYPE, CONF_AUTOMATIC_ADD, + CONF_FIRE_EVENT, CONF_DEVICES, DATA_TYPES, CONF_DATA_TYPE) DEPENDENCIES = ['rfxtrx'] _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): rfxtrx.DOMAIN, - vol.Optional(CONF_DEVICES, default={}): vol.All(dict, rfxtrx.valid_sensor), - vol.Optional(ATTR_AUTOMATIC_ADD, default=False): cv.boolean, +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_DEVICES, default={}): { + cv.string: vol.Schema({ + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean, + vol.Optional(CONF_DATA_TYPE, default=[]): + vol.All(cv.ensure_list, [vol.In(DATA_TYPES.keys())]), + }) + }, + vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean, }, extra=vol.ALLOW_EXTRA) @@ -49,7 +56,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): break for _data_type in data_types: new_sensor = RfxtrxSensor(None, entity_info[ATTR_NAME], - _data_type, entity_info[ATTR_FIREEVENT]) + _data_type, entity_info[ATTR_FIRE_EVENT]) sensors.append(new_sensor) sub_sensors[_data_type] = new_sensor rfxtrx.RFX_DEVICES[device_id] = sub_sensors @@ -78,7 +85,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): return # Add entity if not exist and the automatic_add is True - if not config[ATTR_AUTOMATIC_ADD]: + if not config[CONF_AUTOMATIC_ADD]: return pkt_id = "".join("{0:02x}".format(x) for x in event.data) diff --git a/homeassistant/components/switch/rfxtrx.py b/homeassistant/components/switch/rfxtrx.py index 1361d22de18..7dd1d25ad94 100644 --- a/homeassistant/components/switch/rfxtrx.py +++ b/homeassistant/components/switch/rfxtrx.py @@ -6,14 +6,31 @@ https://home-assistant.io/components/switch.rfxtrx/ """ import logging +import voluptuous as vol + import homeassistant.components.rfxtrx as rfxtrx -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.const import CONF_NAME +from homeassistant.components.rfxtrx import ( + CONF_AUTOMATIC_ADD, CONF_FIRE_EVENT, DEFAULT_SIGNAL_REPETITIONS, + CONF_SIGNAL_REPETITIONS, CONF_DEVICES) +from homeassistant.helpers import config_validation as cv DEPENDENCIES = ['rfxtrx'] _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = rfxtrx.DEFAULT_SCHEMA +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_DEVICES, default={}): { + cv.string: vol.Schema({ + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean + }) + }, + vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean, + vol.Optional(CONF_SIGNAL_REPETITIONS, default=DEFAULT_SIGNAL_REPETITIONS): + vol.Coerce(int), +}) def setup_platform(hass, config, add_devices_callback, discovery_info=None): diff --git a/requirements_all.txt b/requirements_all.txt index 31da31d56a9..b17c22a5f4f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -622,7 +622,7 @@ pyCEC==0.4.13 pyHS100==0.3.0 # homeassistant.components.rfxtrx -pyRFXtrx==0.20.1 +pyRFXtrx==0.21.1 # homeassistant.components.sensor.tibber pyTibber==0.2.1 From be31a860d14936dd4f771c54fd470ee9c82f9292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Fri, 12 Jan 2018 22:35:32 +0100 Subject: [PATCH 009/150] Xiaomi lib upgrade (#11603) * upgrade xiaomi lib * xiaomi lib --- homeassistant/components/xiaomi_aqara.py | 6 +++--- requirements_all.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/xiaomi_aqara.py b/homeassistant/components/xiaomi_aqara.py index 678ead981c1..e059d3d8772 100644 --- a/homeassistant/components/xiaomi_aqara.py +++ b/homeassistant/components/xiaomi_aqara.py @@ -9,7 +9,7 @@ from homeassistant.components.discovery import SERVICE_XIAOMI_GW from homeassistant.const import (ATTR_BATTERY_LEVEL, EVENT_HOMEASSISTANT_STOP, CONF_MAC, CONF_HOST, CONF_PORT) -REQUIREMENTS = ['PyXiaomiGateway==0.6.0'] +REQUIREMENTS = ['PyXiaomiGateway==0.7.0'] ATTR_GW_MAC = 'gw_mac' ATTR_RINGTONE_ID = 'ringtone_id' @@ -105,8 +105,8 @@ def setup(hass, config): discovery.listen(hass, SERVICE_XIAOMI_GW, xiaomi_gw_discovered) - from PyXiaomiGateway import PyXiaomiGateway - xiaomi = hass.data[PY_XIAOMI_GATEWAY] = PyXiaomiGateway( + from xiaomi_gateway import XiaomiGatewayDiscovery + xiaomi = hass.data[PY_XIAOMI_GATEWAY] = XiaomiGatewayDiscovery( hass.add_job, gateways, interface) _LOGGER.debug("Expecting %s gateways", len(gateways)) diff --git a/requirements_all.txt b/requirements_all.txt index b17c22a5f4f..d06c1b4a539 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -35,7 +35,7 @@ PyMVGLive==1.1.4 PyMata==2.14 # homeassistant.components.xiaomi_aqara -PyXiaomiGateway==0.6.0 +PyXiaomiGateway==0.7.0 # homeassistant.components.rpi_gpio # RPi.GPIO==0.6.1 From b854cdb95ba6dea31c44c76afa4d533e632b65f2 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 13 Jan 2018 09:01:05 +0100 Subject: [PATCH 010/150] Upgrade yarl to 0.18.0 (#11609) --- 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 8ec1c648c35..243c6d418df 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -6,7 +6,7 @@ jinja2>=2.9.6 voluptuous==0.10.5 typing>=3,<4 aiohttp==2.3.7 -yarl==0.17.0 +yarl==0.18.0 async_timeout==2.0.0 chardet==3.0.4 astral==1.4 diff --git a/requirements_all.txt b/requirements_all.txt index d06c1b4a539..c949847ec86 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -7,7 +7,7 @@ jinja2>=2.9.6 voluptuous==0.10.5 typing>=3,<4 aiohttp==2.3.7 -yarl==0.17.0 +yarl==0.18.0 async_timeout==2.0.0 chardet==3.0.4 astral==1.4 diff --git a/setup.py b/setup.py index 0d7c746d564..4b19e47fb2c 100755 --- a/setup.py +++ b/setup.py @@ -54,7 +54,7 @@ REQUIRES = [ 'voluptuous==0.10.5', 'typing>=3,<4', 'aiohttp==2.3.7', # If updated, check if yarl also needs an update! - 'yarl==0.17.0', + 'yarl==0.18.0', 'async_timeout==2.0.0', 'chardet==3.0.4', 'astral==1.4', From 5656b0eb2c270455e483138926322f102d84e03b Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 13 Jan 2018 09:02:25 +0100 Subject: [PATCH 011/150] Upgrade keyring to 10.6.0 (#11608) --- homeassistant/scripts/keyring.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/scripts/keyring.py b/homeassistant/scripts/keyring.py index 922bd9c7fe1..b46d135c107 100644 --- a/homeassistant/scripts/keyring.py +++ b/homeassistant/scripts/keyring.py @@ -5,7 +5,7 @@ import os from homeassistant.util.yaml import _SECRET_NAMESPACE -REQUIREMENTS = ['keyring==10.3.2', 'keyrings.alt==2.3'] +REQUIREMENTS = ['keyring==10.6.0', 'keyrings.alt==2.3'] def run(args): diff --git a/requirements_all.txt b/requirements_all.txt index c949847ec86..c8fee7c658f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -419,7 +419,7 @@ jsonrpc-async==0.6 jsonrpc-websocket==0.5 # homeassistant.scripts.keyring -keyring==10.3.2 +keyring==10.6.0 # homeassistant.scripts.keyring keyrings.alt==2.3 From 37427d052eaa2cdda7fdef2e3aad94a875a53ff7 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 13 Jan 2018 09:02:51 +0100 Subject: [PATCH 012/150] Upgrad youtube_dl to 2017.12.31 (#11610) --- homeassistant/components/media_extractor.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_extractor.py b/homeassistant/components/media_extractor.py index 645a418cf8c..70c28d85585 100644 --- a/homeassistant/components/media_extractor.py +++ b/homeassistant/components/media_extractor.py @@ -14,7 +14,7 @@ from homeassistant.components.media_player import ( SERVICE_PLAY_MEDIA) from homeassistant.helpers import config_validation as cv -REQUIREMENTS = ['youtube_dl==2017.12.28'] +REQUIREMENTS = ['youtube_dl==2017.12.31'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index c8fee7c658f..dbe8d7e058f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1225,7 +1225,7 @@ yeelight==0.3.3 yeelightsunflower==0.0.8 # homeassistant.components.media_extractor -youtube_dl==2017.12.28 +youtube_dl==2017.12.31 # homeassistant.components.light.zengge zengge==0.2 From 5e81736f8804dd0c3752689ad44ead351dc5e0a5 Mon Sep 17 00:00:00 2001 From: Thijs de Jong Date: Sat, 13 Jan 2018 09:06:37 +0100 Subject: [PATCH 013/150] patch stop command (#11612) --- homeassistant/components/cover/tahoma.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/cover/tahoma.py b/homeassistant/components/cover/tahoma.py index 7ec09c781d2..9968e3d6503 100644 --- a/homeassistant/components/cover/tahoma.py +++ b/homeassistant/components/cover/tahoma.py @@ -15,11 +15,6 @@ DEPENDENCIES = ['tahoma'] _LOGGER = logging.getLogger(__name__) -TAHOMA_STOP_COMMAND = { - 'io:RollerShutterWithLowSpeedManagementIOComponent': 'my', - 'io:RollerShutterVeluxIOComponent': 'my', -} - SCAN_INTERVAL = timedelta(seconds=60) @@ -78,8 +73,11 @@ class TahomaCover(TahomaDevice, CoverDevice): def stop_cover(self, **kwargs): """Stop the cover.""" - self.apply_action(TAHOMA_STOP_COMMAND.get(self.tahoma_device.type, - 'stopIdentify')) + if self.tahoma_device.type == \ + 'io:RollerShutterWithLowSpeedManagementIOComponent': + self.apply_action('setPosition', 'secured') + else: + self.apply_action('stopIdentify') def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" From b2b836d4c1a323b80ab85bbe70ca369f5bbfda8f Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Sat, 13 Jan 2018 09:59:50 +0100 Subject: [PATCH 014/150] small sonos cleanup (#11607) --- .../components/media_player/sonos.py | 74 ++++++++----------- 1 file changed, 30 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py index 0c6d380e81e..947b86ce281 100644 --- a/homeassistant/components/media_player/sonos.py +++ b/homeassistant/components/media_player/sonos.py @@ -263,27 +263,14 @@ def _get_entity_from_soco(hass, soco): raise ValueError("No entity for SoCo device") -def soco_error(funct): - """Catch soco exceptions.""" - @ft.wraps(funct) - def wrapper(*args, **kwargs): - """Wrap for all soco exception.""" - from soco.exceptions import SoCoException - try: - return funct(*args, **kwargs) - except SoCoException as err: - _LOGGER.error("Error on %s with %s", funct.__name__, err) - return wrapper - - -def soco_filter_upnperror(errorcodes=None): - """Filter out specified UPnP errors from logs.""" +def soco_error(errorcodes=None): + """Filter out specified UPnP errors from logs and avoid exceptions.""" def decorator(funct): """Decorator function.""" @ft.wraps(funct) def wrapper(*args, **kwargs): """Wrap for all soco UPnP exception.""" - from soco.exceptions import SoCoUPnPException + from soco.exceptions import SoCoUPnPException, SoCoException # Temporarily disable SoCo logging because it will log the # UPnP exception otherwise @@ -295,7 +282,9 @@ def soco_filter_upnperror(errorcodes=None): if err.error_code in errorcodes: pass else: - raise + _LOGGER.error("Error on %s with %s", funct.__name__, err) + except SoCoException as err: + _LOGGER.error("Error on %s with %s", funct.__name__, err) finally: _SOCO_SERVICES_LOGGER.disabled = False @@ -901,32 +890,32 @@ class SonosDevice(MediaPlayerDevice): return supported - @soco_error + @soco_error() def volume_up(self): """Volume up media player.""" self._player.volume += self.volume_increment - @soco_error + @soco_error() def volume_down(self): """Volume down media player.""" self._player.volume -= self.volume_increment - @soco_error + @soco_error() def set_volume_level(self, volume): """Set volume level, range 0..1.""" self._player.volume = str(int(volume * 100)) - @soco_error + @soco_error() def set_shuffle(self, shuffle): """Enable/Disable shuffle mode.""" self._player.play_mode = 'SHUFFLE' if shuffle else 'NORMAL' - @soco_error + @soco_error() def mute_volume(self, mute): """Mute (true) or unmute (false) media player.""" self._player.mute = mute - @soco_error + @soco_error() @soco_coordinator def select_source(self, source): """Select input source.""" @@ -1008,64 +997,61 @@ class SonosDevice(MediaPlayerDevice): return self._source_name - @soco_error + @soco_error() def turn_off(self): """Turn off media player.""" if self._support_stop: self.media_stop() - @soco_error - @soco_filter_upnperror(UPNP_ERRORS_TO_IGNORE) + @soco_error(UPNP_ERRORS_TO_IGNORE) @soco_coordinator def media_play(self): """Send play command.""" self._player.play() - @soco_error - @soco_filter_upnperror(UPNP_ERRORS_TO_IGNORE) + @soco_error(UPNP_ERRORS_TO_IGNORE) @soco_coordinator def media_stop(self): """Send stop command.""" self._player.stop() - @soco_error - @soco_filter_upnperror(UPNP_ERRORS_TO_IGNORE) + @soco_error(UPNP_ERRORS_TO_IGNORE) @soco_coordinator def media_pause(self): """Send pause command.""" self._player.pause() - @soco_error + @soco_error() @soco_coordinator def media_next_track(self): """Send next track command.""" self._player.next() - @soco_error + @soco_error() @soco_coordinator def media_previous_track(self): """Send next track command.""" self._player.previous() - @soco_error + @soco_error() @soco_coordinator def media_seek(self, position): """Send seek command.""" self._player.seek(str(datetime.timedelta(seconds=int(position)))) - @soco_error + @soco_error() @soco_coordinator def clear_playlist(self): """Clear players playlist.""" self._player.clear_queue() - @soco_error + @soco_error() def turn_on(self): """Turn the media player on.""" if self.support_play: self.media_play() - @soco_error + @soco_error() @soco_coordinator def play_media(self, media_type, media_id, **kwargs): """ @@ -1084,7 +1070,7 @@ class SonosDevice(MediaPlayerDevice): else: self._player.play_uri(media_id) - @soco_error + @soco_error() def join(self, master): """Join the player to a group.""" coord = [device for device in self.hass.data[DATA_SONOS] @@ -1099,13 +1085,13 @@ class SonosDevice(MediaPlayerDevice): else: _LOGGER.error("Master not found %s", master) - @soco_error + @soco_error() def unjoin(self): """Unjoin the player from a group.""" self._player.unjoin() self._coordinator = None - @soco_error + @soco_error() def snapshot(self, with_group=True): """Snapshot the player.""" from soco.snapshot import Snapshot @@ -1120,7 +1106,7 @@ class SonosDevice(MediaPlayerDevice): else: self._snapshot_group = None - @soco_error + @soco_error() def restore(self, with_group=True): """Restore snapshot for the player.""" from soco.exceptions import SoCoException @@ -1170,19 +1156,19 @@ class SonosDevice(MediaPlayerDevice): if s_dev != old.coordinator: s_dev.join(old.coordinator) - @soco_error + @soco_error() @soco_coordinator def set_sleep_timer(self, sleep_time): """Set the timer on the player.""" self._player.set_sleep_timer(sleep_time) - @soco_error + @soco_error() @soco_coordinator def clear_sleep_timer(self): """Clear the timer on the player.""" self._player.set_sleep_timer(None) - @soco_error + @soco_error() @soco_coordinator def update_alarm(self, **data): """Set the alarm clock on the player.""" @@ -1206,7 +1192,7 @@ class SonosDevice(MediaPlayerDevice): a.include_linked_zones = data[ATTR_INCLUDE_LINKED_ZONES] a.save() - @soco_error + @soco_error() def update_option(self, **data): """Modify playback options.""" if ATTR_NIGHT_SOUND in data and self.night_sound is not None: From e476b9d2251a88549f73003409f5db0e7a2e038f Mon Sep 17 00:00:00 2001 From: Kees Schollaart Date: Sat, 13 Jan 2018 12:16:09 +0100 Subject: [PATCH 015/150] Update Xiaomi Miio compontent broken URI (#11621) --- homeassistant/components/light/xiaomi_miio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/light/xiaomi_miio.py b/homeassistant/components/light/xiaomi_miio.py index b35b5a3740e..9812933b7d6 100644 --- a/homeassistant/components/light/xiaomi_miio.py +++ b/homeassistant/components/light/xiaomi_miio.py @@ -2,7 +2,7 @@ Support for Xiaomi Philips Lights (LED Ball & Ceiling Lamp, Eyecare Lamp 2). For more details about this platform, please refer to the documentation -https://home-assistant.io/components/light.xiaomi_philipslight/ +https://home-assistant.io/components/light.xiaomi_miio/ """ import asyncio from functools import partial From 2e08766cb1ff4c488daa5f17ceb82bbf966a8ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jason=20K=C3=B6lker?= Date: Sat, 13 Jan 2018 17:58:21 +0000 Subject: [PATCH 016/150] light/mqqt_json: allow brightness scaling (#11613) --- homeassistant/components/light/mqtt_json.py | 18 ++++++-- tests/components/light/test_mqtt_json.py | 48 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/light/mqtt_json.py b/homeassistant/components/light/mqtt_json.py index 3646de977cf..19747b89ca0 100644 --- a/homeassistant/components/light/mqtt_json.py +++ b/homeassistant/components/light/mqtt_json.py @@ -17,6 +17,7 @@ from homeassistant.components.light import ( FLASH_LONG, FLASH_SHORT, Light, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, SUPPORT_XY_COLOR) +from homeassistant.components.light.mqtt import CONF_BRIGHTNESS_SCALE from homeassistant.const import ( CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_EFFECT, CONF_NAME, CONF_OPTIMISTIC, CONF_RGB, CONF_WHITE_VALUE, CONF_XY) @@ -42,6 +43,7 @@ DEFAULT_OPTIMISTIC = False DEFAULT_RGB = False DEFAULT_WHITE_VALUE = False DEFAULT_XY = False +DEFAULT_BRIGHTNESS_SCALE = 255 CONF_EFFECT_LIST = 'effect_list' @@ -51,6 +53,8 @@ CONF_FLASH_TIME_SHORT = 'flash_time_short' # Stealing some of these from the base MQTT configs. PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_BRIGHTNESS, default=DEFAULT_BRIGHTNESS): cv.boolean, + vol.Optional(CONF_BRIGHTNESS_SCALE, default=DEFAULT_BRIGHTNESS_SCALE): + vol.All(vol.Coerce(int), vol.Range(min=1)), vol.Optional(CONF_COLOR_TEMP, default=DEFAULT_COLOR_TEMP): cv.boolean, vol.Optional(CONF_EFFECT, default=DEFAULT_EFFECT): cv.boolean, vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]), @@ -102,7 +106,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): }, config.get(CONF_AVAILABILITY_TOPIC), config.get(CONF_PAYLOAD_AVAILABLE), - config.get(CONF_PAYLOAD_NOT_AVAILABLE) + config.get(CONF_PAYLOAD_NOT_AVAILABLE), + config.get(CONF_BRIGHTNESS_SCALE) )]) @@ -112,7 +117,7 @@ class MqttJson(MqttAvailability, Light): def __init__(self, name, effect_list, topic, qos, retain, optimistic, brightness, color_temp, effect, rgb, white_value, xy, flash_times, availability_topic, payload_available, - payload_not_available): + payload_not_available, brightness_scale): """Initialize MQTT JSON light.""" super().__init__(availability_topic, qos, payload_available, payload_not_available) @@ -154,6 +159,7 @@ class MqttJson(MqttAvailability, Light): self._xy = None self._flash_times = flash_times + self._brightness_scale = brightness_scale self._supported_features = (SUPPORT_TRANSITION | SUPPORT_FLASH) self._supported_features |= (rgb and SUPPORT_RGB_COLOR) @@ -192,7 +198,9 @@ class MqttJson(MqttAvailability, Light): if self._brightness is not None: try: - self._brightness = int(values['brightness']) + self._brightness = int(values['brightness'] / + float(self._brightness_scale) * + 255) except KeyError: pass except ValueError: @@ -333,7 +341,9 @@ class MqttJson(MqttAvailability, Light): message['transition'] = int(kwargs[ATTR_TRANSITION]) if ATTR_BRIGHTNESS in kwargs: - message['brightness'] = int(kwargs[ATTR_BRIGHTNESS]) + message['brightness'] = int(kwargs[ATTR_BRIGHTNESS] / + float(DEFAULT_BRIGHTNESS_SCALE) * + self._brightness_scale) if self._optimistic: self._brightness = kwargs[ATTR_BRIGHTNESS] diff --git a/tests/components/light/test_mqtt_json.py b/tests/components/light/test_mqtt_json.py index 6bf24f595ac..d0412df22f5 100644 --- a/tests/components/light/test_mqtt_json.py +++ b/tests/components/light/test_mqtt_json.py @@ -76,7 +76,18 @@ light: name: mqtt_json_light_1 state_topic: "home/rgb1" command_topic: "home/rgb1/set" + +Config with brightness and scale: + +light: + platform: mqtt_json + name: test + state_topic: "mqtt_json_light_1" + command_topic: "mqtt_json_light_1/set" + brightness: true + brightness_scale: 99 """ + import json import unittest @@ -403,6 +414,43 @@ class TestLightMQTTJSON(unittest.TestCase): self.assertEqual(10, message_json["transition"]) self.assertEqual("OFF", message_json["state"]) + def test_brightness_scale(self): + """Test for brightness scaling.""" + assert setup_component(self.hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_json', + 'name': 'test', + 'state_topic': 'test_light_bright_scale', + 'command_topic': 'test_light_bright_scale/set', + 'brightness': True, + 'brightness_scale': 99 + } + }) + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_OFF, state.state) + self.assertIsNone(state.attributes.get('brightness')) + self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + + # Turn on the light + fire_mqtt_message(self.hass, 'test_light_bright_scale', + '{"state":"ON"}') + self.hass.block_till_done() + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_ON, state.state) + self.assertEqual(255, state.attributes.get('brightness')) + + # Turn on the light with brightness + fire_mqtt_message(self.hass, 'test_light_bright_scale', + '{"state":"ON",' + '"brightness": 99}') + self.hass.block_till_done() + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_ON, state.state) + self.assertEqual(255, state.attributes.get('brightness')) + def test_invalid_color_brightness_and_white_values(self): \ # pylint: disable=invalid-name """Test that invalid color/brightness/white values are ignored.""" From 3e43f4e58e36db6b460b48382efd174279650e88 Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Sat, 13 Jan 2018 11:11:20 -0700 Subject: [PATCH 017/150] Adding support for Lutron covers (#11602) * Adding support for Lutron Radio RA2 shades as cover components. * Adding initial version of the Lutron shades component. * Fixed line-length violation detected by Hound. --- homeassistant/components/cover/lutron.py | 76 ++++++++++++++++++++++++ homeassistant/components/lutron.py | 9 ++- 2 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/cover/lutron.py diff --git a/homeassistant/components/cover/lutron.py b/homeassistant/components/cover/lutron.py new file mode 100644 index 00000000000..08a2ef8c5ad --- /dev/null +++ b/homeassistant/components/cover/lutron.py @@ -0,0 +1,76 @@ +""" +Support for Lutron shades. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/cover.lutron/ +""" +import logging + +from homeassistant.components.cover import ( + CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_SET_POSITION, + ATTR_POSITION) +from homeassistant.components.lutron import ( + LutronDevice, LUTRON_DEVICES, LUTRON_CONTROLLER) + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['lutron'] + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Lutron shades.""" + devs = [] + for (area_name, device) in hass.data[LUTRON_DEVICES]['cover']: + dev = LutronCover(area_name, device, hass.data[LUTRON_CONTROLLER]) + devs.append(dev) + + add_devices(devs, True) + return True + + +class LutronCover(LutronDevice, CoverDevice): + """Representation of a Lutron shade.""" + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION + + @property + def is_closed(self): + """Return if the cover is closed.""" + return self._lutron_device.last_level() < 1 + + @property + def current_cover_position(self): + """Return the current position of cover.""" + return self._lutron_device.last_level() + + def close_cover(self, **kwargs): + """Close the cover.""" + self._lutron_device.level = 0 + + def open_cover(self, **kwargs): + """Open the cover.""" + self._lutron_device.level = 100 + + def set_cover_position(self, **kwargs): + """Move the shade to a specific position.""" + if ATTR_POSITION in kwargs: + position = kwargs[ATTR_POSITION] + self._lutron_device.level = position + + def update(self): + """Call when forcing a refresh of the device.""" + # Reading the property (rather than last_level()) fetchs value + level = self._lutron_device.level + _LOGGER.debug("Lutron ID: %d updated to %f", + self._lutron_device.id, level) + + @property + def device_state_attributes(self): + """Return the state attributes.""" + attr = {} + attr['Lutron Integration ID'] = self._lutron_device.id + return attr diff --git a/homeassistant/components/lutron.py b/homeassistant/components/lutron.py index 819844325d1..bef821220b3 100644 --- a/homeassistant/components/lutron.py +++ b/homeassistant/components/lutron.py @@ -37,7 +37,7 @@ def setup(hass, base_config): from pylutron import Lutron hass.data[LUTRON_CONTROLLER] = None - hass.data[LUTRON_DEVICES] = {'light': []} + hass.data[LUTRON_DEVICES] = {'light': [], 'cover': []} config = base_config.get(DOMAIN) hass.data[LUTRON_CONTROLLER] = Lutron( @@ -50,9 +50,12 @@ def setup(hass, base_config): # Sort our devices into types for area in hass.data[LUTRON_CONTROLLER].areas: for output in area.outputs: - hass.data[LUTRON_DEVICES]['light'].append((area.name, output)) + if output.type == 'SYSTEM_SHADE': + hass.data[LUTRON_DEVICES]['cover'].append((area.name, output)) + else: + hass.data[LUTRON_DEVICES]['light'].append((area.name, output)) - for component in ('light',): + for component in ('light', 'cover'): discovery.load_platform(hass, component, DOMAIN, None, base_config) return True From ea62deda592b62fe57b5141853f58d646ab6946f Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sat, 13 Jan 2018 14:00:04 -0500 Subject: [PATCH 018/150] Update Pyarlo to 0.1.2 (#11626) --- homeassistant/components/arlo.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/arlo.py b/homeassistant/components/arlo.py index a78b334de0b..a928ed108c9 100644 --- a/homeassistant/components/arlo.py +++ b/homeassistant/components/arlo.py @@ -12,7 +12,7 @@ from requests.exceptions import HTTPError, ConnectTimeout from homeassistant.helpers import config_validation as cv from homeassistant.const import CONF_USERNAME, CONF_PASSWORD -REQUIREMENTS = ['pyarlo==0.1.0'] +REQUIREMENTS = ['pyarlo==0.1.2'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index dbe8d7e058f..1572695552a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -640,7 +640,7 @@ pyairvisual==1.0.0 pyalarmdotcom==0.3.0 # homeassistant.components.arlo -pyarlo==0.1.0 +pyarlo==0.1.2 # homeassistant.components.notify.xmpp pyasn1-modules==0.1.5 From 5def6ebc3b0edc712f1350c50284179015e32ad7 Mon Sep 17 00:00:00 2001 From: Jesse Hills Date: Sun, 14 Jan 2018 08:07:39 +1300 Subject: [PATCH 019/150] Use kelvin/mireds correctly for setting iglo white (#11622) * Use kelvin/mireds correctly for setting iglo white * Update requirements_all.txt * Fix line lengths --- homeassistant/components/light/iglo.py | 19 ++++++++++++------- requirements_all.txt | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/light/iglo.py b/homeassistant/components/light/iglo.py index eaf783b13ca..11366ffc45c 100644 --- a/homeassistant/components/light/iglo.py +++ b/homeassistant/components/light/iglo.py @@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.iglo/ """ import logging +import math import voluptuous as vol @@ -16,8 +17,9 @@ from homeassistant.components.light import ( ) import homeassistant.helpers.config_validation as cv +import homeassistant.util.color as color_util -REQUIREMENTS = ['iglo==1.0.0'] +REQUIREMENTS = ['iglo==1.1.3'] _LOGGER = logging.getLogger(__name__) @@ -65,17 +67,19 @@ class IGloLamp(Light): @property def color_temp(self): """Return the color temperature.""" - return self._color_temp + return color_util.color_temperature_kelvin_to_mired(self._color_temp) @property def min_mireds(self): """Return the coldest color_temp that this light supports.""" - return 1 + return math.ceil(color_util.color_temperature_kelvin_to_mired( + self._lamp.max_kelvin)) @property def max_mireds(self): """Return the warmest color_temp that this light supports.""" - return 255 + return math.ceil(color_util.color_temperature_kelvin_to_mired( + self._lamp.min_kelvin)) @property def rgb_color(self): @@ -107,8 +111,9 @@ class IGloLamp(Light): return if ATTR_COLOR_TEMP in kwargs: - color_temp = 255 - kwargs[ATTR_COLOR_TEMP] - self._lamp.white(color_temp) + kelvin = int(color_util.color_temperature_mired_to_kelvin( + kwargs[ATTR_COLOR_TEMP])) + self._lamp.white(kelvin) return def turn_off(self, **kwargs): @@ -121,4 +126,4 @@ class IGloLamp(Light): self._on = state['on'] self._brightness = state['brightness'] self._rgb = state['rgb'] - self._color_temp = 255 - state['white'] + self._color_temp = state['white'] diff --git a/requirements_all.txt b/requirements_all.txt index 1572695552a..78ca9b4568a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -396,7 +396,7 @@ https://github.com/wokar/pylgnetcast/archive/v0.2.0.zip#pylgnetcast==0.2.0 # i2csense==0.0.4 # homeassistant.components.light.iglo -iglo==1.0.0 +iglo==1.1.3 # homeassistant.components.influxdb # homeassistant.components.sensor.influxdb From cdbf2f9293bbaaa1fdff24aece194a52f215b3da Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 13 Jan 2018 21:06:34 +0100 Subject: [PATCH 020/150] Hyperion: Add brightness, HDMI and effect support (#11543) * Hyperion: Add brightness, HDMI and effect support - added brightness support to dim the hyperion light - changed the "OFF" command to set the color to [0,0,0] after clearing all priorities. This is neccesary to keep the light turned off when an HDMI grabber is used for ambilight with hyperion. - added HDMI ambilight mode recognition and control. by setting the "hdmi_priority" in your "configuration.yaml" file (defaults to 880), home assistant will now be able to recognize when the hyperion light is in HDMI ambilight mode and will change its icon to an HDMI symbol and set the status to ON. Switching the hyperion light to HDMI ambilight mode can be done through the effect option (clears all priorities such that the HDMI grabber remains). - added effect support for the default effects of hyperion, a custom list can be defined in the "configuration.yaml" file by using the "effect_list" option. * Hyperion: Add brightness, HDMI and effect support - added brightness support to dim the hyperion light - changed the "OFF" command to set the color to [0,0,0] after clearing all priorities. This is neccesary to keep the light turned off when an HDMI grabber is used for ambilight with hyperion. - added HDMI ambilight mode recognition and control. by setting the "hdmi_priority" in your "configuration.yaml" file (defaults to 880), home assistant will now be able to recognize when the hyperion light is in HDMI ambilight mode and will change its icon to an HDMI symbol and set the status to ON. Switching the hyperion light to HDMI ambilight mode can be done through the effect option (clears all priorities such that the HDMI grabber remains). - added effect support for the default effects of hyperion, a custom list can be defined in the "configuration.yaml" file by using the "effect_list" option. - fixed some style issues with too long lines * Hyperion: Add brightness, HDMI and effect support - fixed some more indentation style issues * Hyperion: Add brightness, HDMI and effect support - yet more fixed visuel indent issues * Hyperion: Add brightness, HDMI and effect support - more visuel indents * Hyperion: Add brightness, HDMI and effect support - fixed invalid variable "A" * Hyperion: Add brightness, HDMI and effect support - remove unnececary brackets - specify specific exceptions * correct changing state holding attributes during a service method Proccesed the comments of @MartinHjelmare: https://github.com/home-assistant/home-assistant/pull/11543#pullrequestreview-88328659 * indent correction corrected tab instead of 4 spaces * Hyperion: Add brightness, HDMI and effect support - changed 'none' to None - renamed "self._skip_check" to "self._skip_update" * Add brightness, HDMI and effect support changed checking if a list is empty from "list == []" to "not list" --- homeassistant/components/light/hyperion.py | 141 +++++++++++++++++++-- 1 file changed, 130 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/light/hyperion.py b/homeassistant/components/light/hyperion.py index 88bdc1a4c95..644c1e84ebe 100644 --- a/homeassistant/components/light/hyperion.py +++ b/homeassistant/components/light/hyperion.py @@ -11,7 +11,8 @@ import socket import voluptuous as vol from homeassistant.components.light import ( - ATTR_RGB_COLOR, SUPPORT_RGB_COLOR, Light, PLATFORM_SCHEMA) + ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_EFFECT, SUPPORT_BRIGHTNESS, + SUPPORT_RGB_COLOR, SUPPORT_EFFECT, Light, PLATFORM_SCHEMA) from homeassistant.const import (CONF_HOST, CONF_PORT, CONF_NAME) import homeassistant.helpers.config_validation as cv @@ -19,13 +20,27 @@ _LOGGER = logging.getLogger(__name__) CONF_DEFAULT_COLOR = 'default_color' CONF_PRIORITY = 'priority' +CONF_HDMI_PRIORITY = 'hdmi_priority' +CONF_EFFECT_LIST = 'effect_list' DEFAULT_COLOR = [255, 255, 255] DEFAULT_NAME = 'Hyperion' DEFAULT_PORT = 19444 DEFAULT_PRIORITY = 128 +DEFAULT_HDMI_PRIORITY = 880 +DEFAULT_EFFECT_LIST = ['HDMI', 'Cinema brighten lights', 'Cinema dim lights', + 'Knight rider', 'Blue mood blobs', 'Cold mood blobs', + 'Full color mood blobs', 'Green mood blobs', + 'Red mood blobs', 'Warm mood blobs', + 'Police Lights Single', 'Police Lights Solid', + 'Rainbow mood', 'Rainbow swirl fast', + 'Rainbow swirl', 'Random', 'Running dots', + 'System Shutdown', 'Snake', 'Sparks Color', 'Sparks', + 'Strobe blue', 'Strobe Raspbmc', 'Strobe white', + 'Color traces', 'UDP multicast listener', + 'UDP listener', 'X-Mas'] -SUPPORT_HYPERION = SUPPORT_RGB_COLOR +SUPPORT_HYPERION = (SUPPORT_RGB_COLOR | SUPPORT_BRIGHTNESS | SUPPORT_EFFECT) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, @@ -35,6 +50,11 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ [vol.All(vol.Coerce(int), vol.Range(min=0, max=255))]), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_PRIORITY, default=DEFAULT_PRIORITY): cv.positive_int, + vol.Optional(CONF_HDMI_PRIORITY, + default=DEFAULT_HDMI_PRIORITY): cv.positive_int, + vol.Optional(CONF_EFFECT_LIST, + default=DEFAULT_EFFECT_LIST): vol.All(cv.ensure_list, + [cv.string]), }) @@ -43,10 +63,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None): host = config.get(CONF_HOST) port = config.get(CONF_PORT) priority = config.get(CONF_PRIORITY) + hdmi_priority = config.get(CONF_HDMI_PRIORITY) default_color = config.get(CONF_DEFAULT_COLOR) + effect_list = config.get(CONF_EFFECT_LIST) device = Hyperion(config.get(CONF_NAME), host, port, priority, - default_color) + default_color, hdmi_priority, effect_list) if device.setup(): add_devices([device]) @@ -57,20 +79,33 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class Hyperion(Light): """Representation of a Hyperion remote.""" - def __init__(self, name, host, port, priority, default_color): + def __init__(self, name, host, port, priority, default_color, + hdmi_priority, effect_list): """Initialize the light.""" self._host = host self._port = port self._name = name self._priority = priority + self._hdmi_priority = hdmi_priority self._default_color = default_color self._rgb_color = [0, 0, 0] + self._rgb_mem = [0, 0, 0] + self._brightness = 255 + self._icon = 'mdi:lightbulb' + self._effect_list = effect_list + self._effect = None + self._skip_update = False @property def name(self): """Return the name of the light.""" return self._name + @property + def brightness(self): + """Return the brightness of this light between 0..255.""" + return self._brightness + @property def rgb_color(self): """Return last RGB color value set.""" @@ -81,6 +116,21 @@ class Hyperion(Light): """Return true if not black.""" return self._rgb_color != [0, 0, 0] + @property + def icon(self): + """Return state specific icon.""" + return self._icon + + @property + def effect(self): + """Return the current effect.""" + return self._effect + + @property + def effect_list(self): + """Return the list of supported effects.""" + return self._effect_list + @property def supported_features(self): """Flag supported features.""" @@ -89,35 +139,104 @@ class Hyperion(Light): def turn_on(self, **kwargs): """Turn the lights on.""" if ATTR_RGB_COLOR in kwargs: - self._rgb_color = kwargs[ATTR_RGB_COLOR] + rgb_color = kwargs[ATTR_RGB_COLOR] + elif self._rgb_mem == [0, 0, 0]: + rgb_color = self._default_color else: - self._rgb_color = self._default_color + rgb_color = self._rgb_mem + if ATTR_BRIGHTNESS in kwargs: + brightness = kwargs[ATTR_BRIGHTNESS] + + if ATTR_EFFECT in kwargs: + self._skip_update = True + self._effect = kwargs[ATTR_EFFECT] + if self._effect == 'HDMI': + self.json_request({'command': 'clearall'}) + self._icon = 'mdi:video-input-hdmi' + self._brightness = 255 + self._rgb_color = [125, 125, 125] + else: + self.json_request({ + 'command': 'effect', + 'priority': self._priority, + 'effect': {'name': self._effect} + }) + self._icon = 'mdi:lava-lamp' + self._rgb_color = [175, 0, 255] + return + + cal_color = [int(round(x*float(brightness)/255)) + for x in rgb_color] self.json_request({ 'command': 'color', 'priority': self._priority, - 'color': self._rgb_color + 'color': cal_color }) def turn_off(self, **kwargs): """Disconnect all remotes.""" self.json_request({'command': 'clearall'}) - self._rgb_color = [0, 0, 0] + self.json_request({ + 'command': 'color', + 'priority': self._priority, + 'color': [0, 0, 0] + }) def update(self): - """Get the remote's active color.""" + """Get the lights status.""" + # postpone the immediate state check for changes that take time + if self._skip_update: + self._skip_update = False + return response = self.json_request({'command': 'serverinfo'}) if response: # workaround for outdated Hyperion if 'activeLedColor' not in response['info']: self._rgb_color = self._default_color + self._rgb_mem = self._default_color + self._brightness = 255 + self._icon = 'mdi:lightbulb' + self._effect = None return + # Check if Hyperion is in ambilight mode trough an HDMI grabber + try: + active_priority = response['info']['priorities'][0]['priority'] + if active_priority == self._hdmi_priority: + self._brightness = 255 + self._rgb_color = [125, 125, 125] + self._icon = 'mdi:video-input-hdmi' + self._effect = 'HDMI' + return + except (KeyError, IndexError): + pass - if response['info']['activeLedColor'] == []: - self._rgb_color = [0, 0, 0] + if not response['info']['activeLedColor']: + # Get the active effect + if response['info']['activeEffects']: + self._rgb_color = [175, 0, 255] + self._icon = 'mdi:lava-lamp' + try: + s_name = response['info']['activeEffects'][0]["script"] + s_name = s_name.split('/')[-1][:-3].split("-")[0] + self._effect = [x for x in self._effect_list + if s_name.lower() in x.lower()][0] + except (KeyError, IndexError): + self._effect = None + # Bulb off state + else: + self._rgb_color = [0, 0, 0] + self._icon = 'mdi:lightbulb' + self._effect = None else: + # Get the RGB color self._rgb_color =\ response['info']['activeLedColor'][0]['RGB Value'] + self._brightness = max(self._rgb_color) + self._rgb_mem = [int(round(float(x)*255/self._brightness)) + for x in self._rgb_color] + self._icon = 'mdi:lightbulb' + self._effect = None def setup(self): """Get the hostname of the remote.""" From 9d67d229fabae488af956f152c65fc1f48b7c31f Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Sat, 13 Jan 2018 21:24:10 +0100 Subject: [PATCH 021/150] Fixes and enhancements for the Tahoma platform (#11547) * Strip off the RTS/IO ID from the entity ID * Ignore exception thrown when the device does not provide an active state * Send actions with a label for easier identification in the Tahoma log * Linting * Strip off the RTS/IO ID from the entity ID, take 2 As per suggestions, let HA do the standard initialization and assign an appropriate entity_id, instead of overriding it with the lengthy unique_id coming from the TaHoma devices. --- homeassistant/components/cover/tahoma.py | 23 +++++++++++------------ homeassistant/components/tahoma.py | 2 +- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/cover/tahoma.py b/homeassistant/components/cover/tahoma.py index 9968e3d6503..a4159721de7 100644 --- a/homeassistant/components/cover/tahoma.py +++ b/homeassistant/components/cover/tahoma.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/cover.tahoma/ import logging from datetime import timedelta -from homeassistant.components.cover import CoverDevice, ENTITY_ID_FORMAT +from homeassistant.components.cover import CoverDevice from homeassistant.components.tahoma import ( DOMAIN as TAHOMA_DOMAIN, TahomaDevice) @@ -30,11 +30,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class TahomaCover(TahomaDevice, CoverDevice): """Representation a Tahoma Cover.""" - def __init__(self, tahoma_device, controller): - """Initialize the Tahoma device.""" - super().__init__(tahoma_device, controller) - self.entity_id = ENTITY_ID_FORMAT.format(self.unique_id) - def update(self): """Update method.""" self.controller.get_states([self.tahoma_device]) @@ -46,12 +41,16 @@ class TahomaCover(TahomaDevice, CoverDevice): 0 is closed, 100 is fully open. """ - position = 100 - self.tahoma_device.active_states['core:ClosureState'] - if position <= 5: - return 0 - if position >= 95: - return 100 - return position + try: + position = 100 - \ + self.tahoma_device.active_states['core:ClosureState'] + if position <= 5: + return 0 + if position >= 95: + return 100 + return position + except KeyError: + return None def set_cover_position(self, position, **kwargs): """Move the cover to a specific position.""" diff --git a/homeassistant/components/tahoma.py b/homeassistant/components/tahoma.py index aebe1e0d88e..040f499c115 100644 --- a/homeassistant/components/tahoma.py +++ b/homeassistant/components/tahoma.py @@ -124,4 +124,4 @@ class TahomaDevice(Entity): from tahoma_api import Action action = Action(self.tahoma_device.url) action.add_command(cmd_name, *args) - self.controller.apply_actions('', [action]) + self.controller.apply_actions('HomeAssistant', [action]) From c33b171043af0a20411b836894a377f8dc0be8fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Sat, 13 Jan 2018 21:34:56 +0100 Subject: [PATCH 022/150] upgrade xiaomi lib (#11629) --- homeassistant/components/xiaomi_aqara.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xiaomi_aqara.py b/homeassistant/components/xiaomi_aqara.py index e059d3d8772..6a76ed48fd8 100644 --- a/homeassistant/components/xiaomi_aqara.py +++ b/homeassistant/components/xiaomi_aqara.py @@ -9,7 +9,7 @@ from homeassistant.components.discovery import SERVICE_XIAOMI_GW from homeassistant.const import (ATTR_BATTERY_LEVEL, EVENT_HOMEASSISTANT_STOP, CONF_MAC, CONF_HOST, CONF_PORT) -REQUIREMENTS = ['PyXiaomiGateway==0.7.0'] +REQUIREMENTS = ['PyXiaomiGateway==0.7.1'] ATTR_GW_MAC = 'gw_mac' ATTR_RINGTONE_ID = 'ringtone_id' diff --git a/requirements_all.txt b/requirements_all.txt index 78ca9b4568a..9d4f953ace7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -35,7 +35,7 @@ PyMVGLive==1.1.4 PyMata==2.14 # homeassistant.components.xiaomi_aqara -PyXiaomiGateway==0.7.0 +PyXiaomiGateway==0.7.1 # homeassistant.components.rpi_gpio # RPi.GPIO==0.6.1 From 0550baaf4f006a6e74fc073c260c1c2b9c4b44dd Mon Sep 17 00:00:00 2001 From: Lukas Barth Date: Sun, 14 Jan 2018 12:55:19 +0100 Subject: [PATCH 023/150] Add templates to MQTT climate (#11623) * Add templates * Make pylint happy --- homeassistant/components/climate/mqtt.py | 103 +++++++++++++++++++++-- tests/components/climate/test_mqtt.py | 81 ++++++++++++++++++ 2 files changed, 177 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/climate/mqtt.py b/homeassistant/components/climate/mqtt.py index ae71e5a48dc..3656bf7b475 100644 --- a/homeassistant/components/climate/mqtt.py +++ b/homeassistant/components/climate/mqtt.py @@ -19,7 +19,7 @@ from homeassistant.components.climate import ( SUPPORT_SWING_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_AUX_HEAT) from homeassistant.const import ( - STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME) + STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, CONF_VALUE_TEMPLATE) from homeassistant.components.mqtt import ( CONF_AVAILABILITY_TOPIC, CONF_QOS, CONF_RETAIN, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, MQTT_BASE_PLATFORM_SCHEMA, MqttAvailability) @@ -35,21 +35,30 @@ DEFAULT_NAME = 'MQTT HVAC' CONF_POWER_COMMAND_TOPIC = 'power_command_topic' CONF_POWER_STATE_TOPIC = 'power_state_topic' +CONF_POWER_STATE_TEMPLATE = 'power_state_template' CONF_MODE_COMMAND_TOPIC = 'mode_command_topic' CONF_MODE_STATE_TOPIC = 'mode_state_topic' +CONF_MODE_STATE_TEMPLATE = 'mode_state_template' CONF_TEMPERATURE_COMMAND_TOPIC = 'temperature_command_topic' CONF_TEMPERATURE_STATE_TOPIC = 'temperature_state_topic' +CONF_TEMPERATURE_STATE_TEMPLATE = 'temperature_state_template' CONF_FAN_MODE_COMMAND_TOPIC = 'fan_mode_command_topic' CONF_FAN_MODE_STATE_TOPIC = 'fan_mode_state_topic' +CONF_FAN_MODE_STATE_TEMPLATE = 'fan_mode_state_template' CONF_SWING_MODE_COMMAND_TOPIC = 'swing_mode_command_topic' CONF_SWING_MODE_STATE_TOPIC = 'swing_mode_state_topic' +CONF_SWING_MODE_STATE_TEMPLATE = 'swing_mode_state_template' CONF_AWAY_MODE_COMMAND_TOPIC = 'away_mode_command_topic' CONF_AWAY_MODE_STATE_TOPIC = 'away_mode_state_topic' +CONF_AWAY_MODE_STATE_TEMPLATE = 'away_mode_state_template' CONF_HOLD_COMMAND_TOPIC = 'hold_command_topic' CONF_HOLD_STATE_TOPIC = 'hold_state_topic' +CONF_HOLD_STATE_TEMPLATE = 'hold_state_template' CONF_AUX_COMMAND_TOPIC = 'aux_command_topic' CONF_AUX_STATE_TOPIC = 'aux_state_topic' +CONF_AUX_STATE_TEMPLATE = 'aux_state_template' +CONF_CURRENT_TEMPERATURE_TEMPLATE = 'current_temperature_template' CONF_CURRENT_TEMPERATURE_TOPIC = 'current_temperature_topic' CONF_PAYLOAD_ON = 'payload_on' @@ -71,6 +80,7 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({ vol.Optional(CONF_AWAY_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_HOLD_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_AUX_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_POWER_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_TEMPERATURE_STATE_TOPIC): mqtt.valid_subscribe_topic, @@ -79,6 +89,18 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({ vol.Optional(CONF_AWAY_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_AUX_STATE_TOPIC): mqtt.valid_subscribe_topic, + + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_POWER_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_TEMPERATURE_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_FAN_MODE_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_SWING_MODE_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_AWAY_MODE_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_HOLD_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_AUX_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_CURRENT_TEMPERATURE_TEMPLATE): cv.template, + vol.Optional(CONF_CURRENT_TEMPERATURE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_FAN_MODE_LIST, @@ -100,6 +122,26 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the MQTT climate devices.""" + template_keys = ( + CONF_POWER_STATE_TEMPLATE, + CONF_MODE_STATE_TEMPLATE, + CONF_TEMPERATURE_STATE_TEMPLATE, + CONF_FAN_MODE_STATE_TEMPLATE, + CONF_SWING_MODE_STATE_TEMPLATE, + CONF_AWAY_MODE_STATE_TEMPLATE, + CONF_HOLD_STATE_TEMPLATE, + CONF_AUX_STATE_TEMPLATE, + CONF_CURRENT_TEMPERATURE_TEMPLATE + ) + value_templates = {} + if CONF_VALUE_TEMPLATE in config: + value_template = config.get(CONF_VALUE_TEMPLATE) + value_template.hass = hass + value_templates = {key: value_template for key in template_keys} + for key in template_keys & config.keys(): + value_templates[key] = config.get(key) + value_templates[key].hass = hass + async_add_devices([ MqttClimate( hass, @@ -125,6 +167,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): CONF_CURRENT_TEMPERATURE_TOPIC ) }, + value_templates, config.get(CONF_QOS), config.get(CONF_RETAIN), config.get(CONF_MODE_LIST), @@ -145,18 +188,19 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): class MqttClimate(MqttAvailability, ClimateDevice): """Representation of a demo climate device.""" - def __init__(self, hass, name, topic, qos, retain, mode_list, - fan_mode_list, swing_mode_list, target_temperature, away, - hold, current_fan_mode, current_swing_mode, - current_operation, aux, send_if_off, payload_on, - payload_off, availability_topic, payload_available, - payload_not_available): + def __init__(self, hass, name, topic, value_templates, qos, retain, + mode_list, fan_mode_list, swing_mode_list, + target_temperature, away, hold, current_fan_mode, + current_swing_mode, current_operation, aux, send_if_off, + payload_on, payload_off, availability_topic, + payload_available, payload_not_available): """Initialize the climate device.""" super().__init__(availability_topic, qos, payload_available, payload_not_available) self.hass = hass self._name = name self._topic = topic + self._value_templates = value_templates self._qos = qos self._retain = retain self._target_temperature = target_temperature @@ -184,6 +228,11 @@ class MqttClimate(MqttAvailability, ClimateDevice): @callback def handle_current_temp_received(topic, payload, qos): """Handle current temperature coming via MQTT.""" + if CONF_CURRENT_TEMPERATURE_TEMPLATE in self._value_templates: + payload =\ + self._value_templates[CONF_CURRENT_TEMPERATURE_TEMPLATE].\ + async_render_with_possible_json_value(payload) + try: self._current_temperature = float(payload) self.async_schedule_update_ha_state() @@ -198,6 +247,10 @@ class MqttClimate(MqttAvailability, ClimateDevice): @callback def handle_mode_received(topic, payload, qos): """Handle receiving mode via MQTT.""" + if CONF_MODE_STATE_TEMPLATE in self._value_templates: + payload = self._value_templates[CONF_MODE_STATE_TEMPLATE].\ + async_render_with_possible_json_value(payload) + if payload not in self._operation_list: _LOGGER.error("Invalid mode: %s", payload) else: @@ -212,6 +265,11 @@ class MqttClimate(MqttAvailability, ClimateDevice): @callback def handle_temperature_received(topic, payload, qos): """Handle target temperature coming via MQTT.""" + if CONF_TEMPERATURE_STATE_TEMPLATE in self._value_templates: + payload = \ + self._value_templates[CONF_TEMPERATURE_STATE_TEMPLATE].\ + async_render_with_possible_json_value(payload) + try: self._target_temperature = float(payload) self.async_schedule_update_ha_state() @@ -226,6 +284,11 @@ class MqttClimate(MqttAvailability, ClimateDevice): @callback def handle_fan_mode_received(topic, payload, qos): """Handle receiving fan mode via MQTT.""" + if CONF_FAN_MODE_STATE_TEMPLATE in self._value_templates: + payload = \ + self._value_templates[CONF_FAN_MODE_STATE_TEMPLATE].\ + async_render_with_possible_json_value(payload) + if payload not in self._fan_list: _LOGGER.error("Invalid fan mode: %s", payload) else: @@ -240,6 +303,11 @@ class MqttClimate(MqttAvailability, ClimateDevice): @callback def handle_swing_mode_received(topic, payload, qos): """Handle receiving swing mode via MQTT.""" + if CONF_SWING_MODE_STATE_TEMPLATE in self._value_templates: + payload = \ + self._value_templates[CONF_SWING_MODE_STATE_TEMPLATE].\ + async_render_with_possible_json_value(payload) + if payload not in self._swing_list: _LOGGER.error("Invalid swing mode: %s", payload) else: @@ -254,6 +322,15 @@ class MqttClimate(MqttAvailability, ClimateDevice): @callback def handle_away_mode_received(topic, payload, qos): """Handle receiving away mode via MQTT.""" + if CONF_AWAY_MODE_STATE_TEMPLATE in self._value_templates: + payload = \ + self._value_templates[CONF_AWAY_MODE_STATE_TEMPLATE].\ + async_render_with_possible_json_value(payload) + if payload == "True": + payload = self._payload_on + elif payload == "False": + payload = self._payload_off + if payload == self._payload_on: self._away = True elif payload == self._payload_off: @@ -271,6 +348,14 @@ class MqttClimate(MqttAvailability, ClimateDevice): @callback def handle_aux_mode_received(topic, payload, qos): """Handle receiving aux mode via MQTT.""" + if CONF_AUX_STATE_TEMPLATE in self._value_templates: + payload = self._value_templates[CONF_AUX_STATE_TEMPLATE].\ + async_render_with_possible_json_value(payload) + if payload == "True": + payload = self._payload_on + elif payload == "False": + payload = self._payload_off + if payload == self._payload_on: self._aux = True elif payload == self._payload_off: @@ -288,6 +373,10 @@ class MqttClimate(MqttAvailability, ClimateDevice): @callback def handle_hold_mode_received(topic, payload, qos): """Handle receiving hold mode via MQTT.""" + if CONF_HOLD_STATE_TEMPLATE in self._value_templates: + payload = self._value_templates[CONF_HOLD_STATE_TEMPLATE].\ + async_render_with_possible_json_value(payload) + self._hold = payload self.async_schedule_update_ha_state() diff --git a/tests/components/climate/test_mqtt.py b/tests/components/climate/test_mqtt.py index 4c179fa8042..c4fa2b304df 100644 --- a/tests/components/climate/test_mqtt.py +++ b/tests/components/climate/test_mqtt.py @@ -456,3 +456,84 @@ class TestMQTTClimate(unittest.TestCase): state = self.hass.states.get('climate.test') self.assertEqual(STATE_UNAVAILABLE, state.state) + + def test_set_with_templates(self): + """Test setting of new fan mode in pessimistic mode.""" + config = copy.deepcopy(DEFAULT_CONFIG) + # By default, just unquote the JSON-strings + config['climate']['value_template'] = '{{ value_json }}' + # Something more complicated for hold mode + config['climate']['hold_state_template'] = \ + '{{ value_json.attribute }}' + # Rendering to a bool for aux heat + config['climate']['aux_state_template'] = \ + "{{ value == 'switchmeon' }}" + + config['climate']['mode_state_topic'] = 'mode-state' + config['climate']['fan_mode_state_topic'] = 'fan-state' + config['climate']['swing_mode_state_topic'] = 'swing-state' + config['climate']['temperature_state_topic'] = 'temperature-state' + config['climate']['away_mode_state_topic'] = 'away-state' + config['climate']['hold_state_topic'] = 'hold-state' + config['climate']['aux_state_topic'] = 'aux-state' + config['climate']['current_temperature_topic'] = 'current-temperature' + + assert setup_component(self.hass, climate.DOMAIN, config) + + # Operation Mode + state = self.hass.states.get(ENTITY_CLIMATE) + self.assertEqual("off", state.attributes.get('operation_mode')) + fire_mqtt_message(self.hass, 'mode-state', '"cool"') + self.hass.block_till_done() + state = self.hass.states.get(ENTITY_CLIMATE) + self.assertEqual("cool", state.attributes.get('operation_mode')) + + # Fan Mode + self.assertEqual("low", state.attributes.get('fan_mode')) + fire_mqtt_message(self.hass, 'fan-state', '"high"') + self.hass.block_till_done() + state = self.hass.states.get(ENTITY_CLIMATE) + self.assertEqual('high', state.attributes.get('fan_mode')) + + # Swing Mode + self.assertEqual("off", state.attributes.get('swing_mode')) + fire_mqtt_message(self.hass, 'swing-state', '"on"') + self.hass.block_till_done() + state = self.hass.states.get(ENTITY_CLIMATE) + self.assertEqual("on", state.attributes.get('swing_mode')) + + # Temperature + self.assertEqual(21, state.attributes.get('temperature')) + fire_mqtt_message(self.hass, 'temperature-state', '"1031"') + self.hass.block_till_done() + state = self.hass.states.get(ENTITY_CLIMATE) + self.assertEqual(1031, state.attributes.get('temperature')) + + # Away Mode + self.assertEqual('off', state.attributes.get('away_mode')) + fire_mqtt_message(self.hass, 'away-state', '"ON"') + self.hass.block_till_done() + state = self.hass.states.get(ENTITY_CLIMATE) + self.assertEqual('on', state.attributes.get('away_mode')) + + # Hold Mode + self.assertEqual(None, state.attributes.get('hold_mode')) + fire_mqtt_message(self.hass, 'hold-state', """ + { "attribute": "somemode" } + """) + self.hass.block_till_done() + state = self.hass.states.get(ENTITY_CLIMATE) + self.assertEqual('somemode', state.attributes.get('hold_mode')) + + # Aux mode + self.assertEqual('off', state.attributes.get('aux_heat')) + fire_mqtt_message(self.hass, 'aux-state', 'switchmeon') + self.hass.block_till_done() + state = self.hass.states.get(ENTITY_CLIMATE) + self.assertEqual('on', state.attributes.get('aux_heat')) + + # Current temperature + fire_mqtt_message(self.hass, 'current-temperature', '"74656"') + self.hass.block_till_done() + state = self.hass.states.get(ENTITY_CLIMATE) + self.assertEqual(74656, state.attributes.get('current_temperature')) From 028597f7745305e8db8d6cfc7bdfcf669caeb3ca Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Sun, 14 Jan 2018 15:03:39 +0100 Subject: [PATCH 024/150] Update CODEOWNERS --- CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS index 99c103b1298..9ec7ce0742c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -32,12 +32,15 @@ homeassistant/components/zone.py @home-assistant/core # To monitor non-pypi additions requirements_all.txt @andrey-git +# HomeAssistant developer Teams Dockerfile @home-assistant/docker virtualization/Docker/* @home-assistant/docker homeassistant/components/zwave/* @home-assistant/z-wave homeassistant/components/*/zwave.py @home-assistant/z-wave +homeassistant/components/hassio.py @home-assistant/hassio + # Indiviudal components homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt homeassistant/components/camera/yi.py @bachya From 688d70644949a658e0607e52b3896a2c4c8a85e7 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 14 Jan 2018 22:31:47 +0100 Subject: [PATCH 025/150] Upgrade coinmarketcap to 4.1.2 (#11634) --- homeassistant/components/sensor/coinmarketcap.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/coinmarketcap.py b/homeassistant/components/sensor/coinmarketcap.py index dc5048a8176..9d69583f673 100644 --- a/homeassistant/components/sensor/coinmarketcap.py +++ b/homeassistant/components/sensor/coinmarketcap.py @@ -16,7 +16,7 @@ from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_CURRENCY, CONF_DISPLAY_CURRENCY) from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['coinmarketcap==4.1.1'] +REQUIREMENTS = ['coinmarketcap==4.1.2'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 9d4f953ace7..baff8528a84 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -170,7 +170,7 @@ ciscosparkapi==0.4.2 coinbase==2.0.6 # homeassistant.components.sensor.coinmarketcap -coinmarketcap==4.1.1 +coinmarketcap==4.1.2 # homeassistant.scripts.check_config colorlog==3.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 300f5496a16..cbd84af48a5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -38,7 +38,7 @@ apns2==0.3.0 caldav==0.5.0 # homeassistant.components.sensor.coinmarketcap -coinmarketcap==4.1.1 +coinmarketcap==4.1.2 # homeassistant.components.device_tracker.upc_connect defusedxml==0.5.0 From 3979387c808ef5469840b1b2fb41a2090112ebf9 Mon Sep 17 00:00:00 2001 From: Kane610 Date: Mon, 15 Jan 2018 10:47:55 +0100 Subject: [PATCH 026/150] Add Deconz support for Zigbee green power devices like Hue Tap (#11455) * Add support for Zigbee green power devices such as the Hue Tap * Clarify that imported SWITCH is a DECONZ_REMOTE --- homeassistant/components/deconz/__init__.py | 2 +- homeassistant/components/sensor/deconz.py | 5 ++--- requirements_all.txt | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 021febdc07c..06b08a9b9a8 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -17,7 +17,7 @@ from homeassistant.helpers import discovery from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util.json import load_json, save_json -REQUIREMENTS = ['pydeconz==23'] +REQUIREMENTS = ['pydeconz==24'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/deconz.py b/homeassistant/components/sensor/deconz.py index c01483169cb..2f1ff373581 100644 --- a/homeassistant/components/sensor/deconz.py +++ b/homeassistant/components/sensor/deconz.py @@ -17,7 +17,6 @@ from homeassistant.util import slugify DEPENDENCIES = ['deconz'] ATTR_EVENT_ID = 'event_id' -ATTR_ZHASWITCH = 'ZHASwitch' @asyncio.coroutine @@ -26,13 +25,13 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): if discovery_info is None: return - from pydeconz.sensor import DECONZ_SENSOR + from pydeconz.sensor import DECONZ_SENSOR, SWITCH as DECONZ_REMOTE sensors = hass.data[DECONZ_DATA].sensors entities = [] for sensor in sensors.values(): if sensor.type in DECONZ_SENSOR: - if sensor.type == ATTR_ZHASWITCH: + if sensor.type in DECONZ_REMOTE: DeconzEvent(hass, sensor) if sensor.battery: entities.append(DeconzBattery(sensor)) diff --git a/requirements_all.txt b/requirements_all.txt index baff8528a84..e33ad03c34d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -678,7 +678,7 @@ pycsspeechtts==1.0.2 pydaikin==0.4 # homeassistant.components.deconz -pydeconz==23 +pydeconz==24 # homeassistant.components.zwave pydispatcher==2.0.5 From 1ed0c7d85dd54762ca444bd1458ee77b447bcaf7 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 15 Jan 2018 11:42:31 +0100 Subject: [PATCH 027/150] Bump dev to 0.62.0.dev0 (#11652) --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index be085bd75f1..560c99bb653 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 = 61 +MINOR_VERSION = 62 PATCH_VERSION = '0.dev0' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) From 079d4039a10b819a0378580f7cb749959fe8ed43 Mon Sep 17 00:00:00 2001 From: heydonms <23112759+heydonms@users.noreply.github.com> Date: Tue, 16 Jan 2018 01:38:58 +0800 Subject: [PATCH 028/150] Add ability to specify a sender in the clicksend notification (#11046) * Add ability to specify a sender in the clicksend notification * Style fixes * Fix remaining issue * Add sender validation --- homeassistant/components/notify/clicksend.py | 49 ++++++++++++++------ 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/notify/clicksend.py b/homeassistant/components/notify/clicksend.py index 543ce434a8d..2b2cb4e7f22 100644 --- a/homeassistant/components/notify/clicksend.py +++ b/homeassistant/components/notify/clicksend.py @@ -14,7 +14,8 @@ import voluptuous as vol from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import ( - CONF_API_KEY, CONF_USERNAME, CONF_RECIPIENT, CONTENT_TYPE_JSON) + CONF_API_KEY, CONF_RECIPIENT, CONF_SENDER, CONF_USERNAME, + CONTENT_TYPE_JSON) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -23,15 +24,27 @@ BASE_API_URL = 'https://rest.clicksend.com/v3' HEADERS = {CONTENT_TYPE: CONTENT_TYPE_JSON} -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_RECIPIENT): cv.string, -}) + +def validate_sender(config): + """Set the optional sender name if sender name is not provided.""" + if CONF_SENDER in config: + return config + config[CONF_SENDER] = config[CONF_RECIPIENT] + return config + + +PLATFORM_SCHEMA = vol.Schema( + vol.All(PLATFORM_SCHEMA.extend({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_RECIPIENT): cv.string, + vol.Optional(CONF_SENDER): cv.string, + }), validate_sender)) def get_service(hass, config, discovery_info=None): """Get the ClickSend notification service.""" + print("#### ", config) if _authenticate(config) is False: _LOGGER.exception("You are not authorized to access ClickSend") return None @@ -47,16 +60,26 @@ class ClicksendNotificationService(BaseNotificationService): self.username = config.get(CONF_USERNAME) self.api_key = config.get(CONF_API_KEY) self.recipient = config.get(CONF_RECIPIENT) + self.sender = config.get(CONF_SENDER, CONF_RECIPIENT) def send_message(self, message="", **kwargs): """Send a message to a user.""" - data = ({'messages': [{'source': 'hass.notify', 'from': self.recipient, - 'to': self.recipient, 'body': message}]}) + data = ({ + 'messages': [ + { + 'source': 'hass.notify', + 'from': self.sender, + 'to': self.recipient, + 'body': message, + } + ] + }) api_url = "{}/sms/send".format(BASE_API_URL) - resp = requests.post(api_url, data=json.dumps(data), headers=HEADERS, - auth=(self.username, self.api_key), timeout=5) + resp = requests.post( + api_url, data=json.dumps(data), headers=HEADERS, + auth=(self.username, self.api_key), timeout=5) obj = json.loads(resp.text) response_msg = obj['response_msg'] @@ -70,9 +93,9 @@ class ClicksendNotificationService(BaseNotificationService): def _authenticate(config): """Authenticate with ClickSend.""" api_url = '{}/account'.format(BASE_API_URL) - resp = requests.get(api_url, headers=HEADERS, - auth=(config.get(CONF_USERNAME), - config.get(CONF_API_KEY)), timeout=5) + resp = requests.get( + api_url, headers=HEADERS, auth=(config.get(CONF_USERNAME), + config.get(CONF_API_KEY)), timeout=5) if resp.status_code != 200: return False From 5546ecd6377d278e39dab7c6cebfff08514367a5 Mon Sep 17 00:00:00 2001 From: covrig Date: Mon, 15 Jan 2018 20:40:12 +0100 Subject: [PATCH 029/150] Round values to one decimal (#11673) Temperature detection range: -20 - 60 Deg.C ( + / - 0.3 Deg.C ) Humidity detection range: 0 - 100pct RH ( + / - 0.3pct ) Atmospheric pressure detection range: 30 - 110KPa ( + / - 120Pa ) --- homeassistant/components/sensor/xiaomi_aqara.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/xiaomi_aqara.py b/homeassistant/components/sensor/xiaomi_aqara.py index 7b771e8c785..6dc574d75b0 100644 --- a/homeassistant/components/sensor/xiaomi_aqara.py +++ b/homeassistant/components/sensor/xiaomi_aqara.py @@ -77,5 +77,5 @@ class XiaomiSensor(XiaomiDevice): return False elif self._data_key == 'pressure' and value == 0: return False - self._state = round(value, 2) + self._state = round(value, 1) return True From 6b261540776210ae301b770bf9dd3b9113622240 Mon Sep 17 00:00:00 2001 From: Sean Dague Date: Mon, 15 Jan 2018 15:50:56 -0500 Subject: [PATCH 030/150] Add basic mychevy component (#11409) * mychevy: added basic mychevy component This implements a component using the mychevy library (which utilizes selenium to webscrape the mychevy website for onstar for your car). The architecture works by having a background thread doing periodic polling of the website, and updating the sensors when new data is found. This requires rather more setup than most platforms, as you need working selenium. Instructions will be provided on the component list. All the sensors are spawned and coordinated from a single "hub" as they are really just attributes of the same web scraping session. * mychevy: only poll every 30 minutes * mychevy: update sensors * mychevy: better error handling * mychevy: tweaking for refactor * mychevy: bump version to handle odometer > 1000 * mychevy: great sensor reorg * mychevy: add binary sensors * mychevy: bump mychevy requirement * mychevy: use dispatcher Instead of directly modifying the sensors, this lets us use a dispatcher to have sensors pull information from the car object when there is a relevant update for them. * mychevy: remove from coverage * mychevy: dedicated constants for dispatch signals This makes the dispatch signals dedicated topics, and fixes updating the state on the sensors so that they are correctly updated. * mychevy: updated with comments from martinhjelmare * mychevy: set battery icon based with helper function * Address additional review feedback * Address additional review comments --- .coveragerc | 3 + .../components/binary_sensor/mychevy.py | 85 +++++++++ homeassistant/components/mychevy.py | 132 ++++++++++++++ homeassistant/components/sensor/mychevy.py | 165 ++++++++++++++++++ requirements_all.txt | 3 + 5 files changed, 388 insertions(+) create mode 100644 homeassistant/components/binary_sensor/mychevy.py create mode 100644 homeassistant/components/mychevy.py create mode 100644 homeassistant/components/sensor/mychevy.py diff --git a/.coveragerc b/.coveragerc index a264bde79a2..b51829b00bc 100644 --- a/.coveragerc +++ b/.coveragerc @@ -145,6 +145,9 @@ omit = homeassistant/components/modbus.py homeassistant/components/*/modbus.py + homeassistant/components/mychevy.py + homeassistant/components/*/mychevy.py + homeassistant/components/mysensors.py homeassistant/components/*/mysensors.py diff --git a/homeassistant/components/binary_sensor/mychevy.py b/homeassistant/components/binary_sensor/mychevy.py new file mode 100644 index 00000000000..a89395ed86f --- /dev/null +++ b/homeassistant/components/binary_sensor/mychevy.py @@ -0,0 +1,85 @@ +"""Support for MyChevy sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.mychevy/ +""" + +import asyncio +import logging + +from homeassistant.components.mychevy import ( + EVBinarySensorConfig, DOMAIN as MYCHEVY_DOMAIN, UPDATE_TOPIC +) +from homeassistant.components.binary_sensor import ( + ENTITY_ID_FORMAT, BinarySensorDevice) +from homeassistant.core import callback +from homeassistant.util import slugify + +_LOGGER = logging.getLogger(__name__) + +SENSORS = [ + EVBinarySensorConfig("Plugged In", "plugged_in", "plug") +] + + +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up the MyChevy sensors.""" + if discovery_info is None: + return + + sensors = [] + hub = hass.data[MYCHEVY_DOMAIN] + for sconfig in SENSORS: + sensors.append(EVBinarySensor(hub, sconfig)) + + async_add_devices(sensors) + + +class EVBinarySensor(BinarySensorDevice): + """Base EVSensor class. + + 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): + """Initialize sensor with car connection.""" + self._conn = connection + self._name = config.name + self._attr = config.attr + self._type = config.device_class + self._is_on = None + + self.entity_id = ENTITY_ID_FORMAT.format( + '{}_{}'.format(MYCHEVY_DOMAIN, slugify(self._name))) + + @property + def name(self): + """Return the name.""" + return self._name + + @property + def is_on(self): + """Return if on.""" + return self._is_on + + @asyncio.coroutine + def async_added_to_hass(self): + """Register callbacks.""" + self.hass.helpers.dispatcher.async_dispatcher_connect( + UPDATE_TOPIC, self.async_update_callback) + + @callback + def async_update_callback(self): + """Update state.""" + if self._conn.car is not None: + self._is_on = getattr(self._conn.car, self._attr, None) + self.async_schedule_update_ha_state() + + @property + def should_poll(self): + """Return the polling state.""" + return False diff --git a/homeassistant/components/mychevy.py b/homeassistant/components/mychevy.py new file mode 100644 index 00000000000..3a4894a2a66 --- /dev/null +++ b/homeassistant/components/mychevy.py @@ -0,0 +1,132 @@ +""" +MyChevy Component. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/mychevy/ +""" + +from datetime import timedelta +import logging +import time +import threading + +import voluptuous as vol + +from homeassistant.const import ( + CONF_USERNAME, CONF_PASSWORD) +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import discovery +from homeassistant.util import Throttle + +REQUIREMENTS = ["mychevy==0.1.1"] + +DOMAIN = 'mychevy' +UPDATE_TOPIC = DOMAIN +ERROR_TOPIC = DOMAIN + "_error" + +MYCHEVY_SUCCESS = "success" +MYCHEVY_ERROR = "error" + +NOTIFICATION_ID = 'mychevy_website_notification' +NOTIFICATION_TITLE = 'MyChevy website status' + +_LOGGER = logging.getLogger(__name__) + +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=30) +ERROR_SLEEP_TIME = timedelta(minutes=30) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string + }), +}, extra=vol.ALLOW_EXTRA) + + +class EVSensorConfig(object): + """EV Sensor Config.""" + + def __init__(self, name, attr, unit_of_measurement=None, icon=None): + """Create new Sensor Config.""" + self.name = name + self.attr = attr + self.unit_of_measurement = unit_of_measurement + self.icon = icon + + +class EVBinarySensorConfig(object): + """EV Binary Sensor Config.""" + + def __init__(self, name, attr, device_class=None): + """Create new Binary Sensor Config.""" + self.name = name + self.attr = attr + self.device_class = device_class + + +def setup(hass, base_config): + """Setup mychevy platform.""" + import mychevy.mychevy as mc + + config = base_config.get(DOMAIN) + + email = config.get(CONF_USERNAME) + password = config.get(CONF_PASSWORD) + hass.data[DOMAIN] = MyChevyHub(mc.MyChevy(email, password), hass) + hass.data[DOMAIN].start() + + discovery.load_platform(hass, 'sensor', DOMAIN, {}, config) + discovery.load_platform(hass, 'binary_sensor', DOMAIN, {}, config) + + return True + + +class MyChevyHub(threading.Thread): + """MyChevy Hub. + + Connecting to the mychevy website is done through a selenium + webscraping process. That can only run synchronously. In order to + prevent blocking of other parts of Home Assistant the architecture + launches a polling loop in a thread. + + When new data is received, sensors are updated, and hass is + signaled that there are updates. Sensors are not created until the + first update, which will be 60 - 120 seconds after the platform + starts. + """ + + def __init__(self, client, hass): + """Initialize MyChevy Hub.""" + super().__init__() + self._client = client + self.hass = hass + self.car = None + self.status = None + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Update sensors from mychevy website. + + This is a synchronous polling call that takes a very long time + (like 2 to 3 minutes long time) + + """ + self.car = self._client.data() + + def run(self): + """Thread run loop.""" + # We add the status device first outside of the loop + + # And then busy wait on threads + while True: + try: + _LOGGER.info("Starting mychevy loop") + self.update() + self.hass.helpers.dispatcher.dispatcher_send(UPDATE_TOPIC) + time.sleep(MIN_TIME_BETWEEN_UPDATES.seconds) + except Exception: # pylint: disable=broad-except + _LOGGER.exception( + "Error updating mychevy data. " + "This probably means the OnStar link is down again") + self.hass.helpers.dispatcher.dispatcher_send(ERROR_TOPIC) + time.sleep(ERROR_SLEEP_TIME.seconds) diff --git a/homeassistant/components/sensor/mychevy.py b/homeassistant/components/sensor/mychevy.py new file mode 100644 index 00000000000..bdbffc46ca8 --- /dev/null +++ b/homeassistant/components/sensor/mychevy.py @@ -0,0 +1,165 @@ +"""Support for MyChevy sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.mychevy/ +""" + +import asyncio +import logging + +from homeassistant.components.mychevy import ( + EVSensorConfig, DOMAIN as MYCHEVY_DOMAIN, MYCHEVY_ERROR, MYCHEVY_SUCCESS, + NOTIFICATION_ID, NOTIFICATION_TITLE, UPDATE_TOPIC, ERROR_TOPIC +) +from homeassistant.components.sensor import ENTITY_ID_FORMAT +from homeassistant.core import callback +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.icon import icon_for_battery_level +from homeassistant.util import slugify + +BATTERY_SENSOR = "percent" + +SENSORS = [ + EVSensorConfig("Mileage", "mileage", "miles", "mdi:speedometer"), + EVSensorConfig("Range", "range", "miles", "mdi:speedometer"), + EVSensorConfig("Charging", "charging"), + EVSensorConfig("Charge Mode", "charge_mode"), + EVSensorConfig("EVCharge", BATTERY_SENSOR, "%", "mdi:battery") +] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the MyChevy sensors.""" + if discovery_info is None: + return + + hub = hass.data[MYCHEVY_DOMAIN] + sensors = [MyChevyStatus()] + for sconfig in SENSORS: + sensors.append(EVSensor(hub, sconfig)) + + add_devices(sensors) + + +class MyChevyStatus(Entity): + """A string representing the charge mode.""" + + _name = "MyChevy Status" + _icon = "mdi:car-connected" + + def __init__(self): + """Initialize sensor with car connection.""" + self._state = None + + @asyncio.coroutine + def async_added_to_hass(self): + """Register callbacks.""" + self.hass.helpers.dispatcher.async_dispatcher_connect( + UPDATE_TOPIC, self.success) + + self.hass.helpers.dispatcher.async_dispatcher_connect( + ERROR_TOPIC, self.error) + + @callback + def success(self): + """Update state, trigger updates.""" + if self._state != MYCHEVY_SUCCESS: + _LOGGER.debug("Successfully connected to mychevy website") + self._state = MYCHEVY_SUCCESS + self.async_schedule_update_ha_state() + + @callback + def error(self): + """Update state, trigger updates.""" + if self._state != MYCHEVY_ERROR: + self.hass.components.persistent_notification.create( + "Error:
Connection to mychevy website failed. " + "This probably means the mychevy to OnStar link is down.", + title=NOTIFICATION_TITLE, + notification_id=NOTIFICATION_ID) + self._state = MYCHEVY_ERROR + self.async_schedule_update_ha_state() + + @property + def icon(self): + """Return the icon.""" + return self._icon + + @property + def name(self): + """Return the name.""" + return self._name + + @property + def state(self): + """Return the state.""" + return self._state + + @property + def should_poll(self): + """Return the polling state.""" + return False + + +class EVSensor(Entity): + """Base EVSensor class. + + 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): + """Initialize sensor with car connection.""" + self._conn = connection + self._name = config.name + self._attr = config.attr + self._unit_of_measurement = config.unit_of_measurement + self._icon = config.icon + self._state = None + + self.entity_id = ENTITY_ID_FORMAT.format( + '{}_{}'.format(MYCHEVY_DOMAIN, slugify(self._name))) + + @asyncio.coroutine + def async_added_to_hass(self): + """Register callbacks.""" + self.hass.helpers.dispatcher.async_dispatcher_connect( + UPDATE_TOPIC, self.async_update_callback) + + @property + def icon(self): + """Return the icon.""" + if self._attr == BATTERY_SENSOR: + return icon_for_battery_level(self.state) + return self._icon + + @property + def name(self): + """Return the name.""" + return self._name + + @callback + def async_update_callback(self): + """Update state.""" + if self._conn.car is not None: + self._state = getattr(self._conn.car, self._attr, None) + self.async_schedule_update_ha_state() + + @property + def state(self): + """Return the state.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement the state is expressed in.""" + return self._unit_of_measurement + + @property + def should_poll(self): + """Return the polling state.""" + return False diff --git a/requirements_all.txt b/requirements_all.txt index e33ad03c34d..1e9efd05723 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -490,6 +490,9 @@ motorparts==1.0.2 # homeassistant.components.tts mutagen==1.39 +# homeassistant.components.mychevy +mychevy==0.1.1 + # homeassistant.components.mycroft mycroftapi==2.0 From bf3329e9a90278dd633e92a9fdf2ab6b1b4b4a53 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 15 Jan 2018 22:41:49 +0100 Subject: [PATCH 031/150] update mypy commandline arguments (#11638) --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 70612658715..fafc149f624 100644 --- a/tox.ini +++ b/tox.ini @@ -41,4 +41,4 @@ basepython = {env:PYTHON3_PATH:python3} deps = -r{toxinidir}/requirements_test.txt commands = - mypy --silent-imports homeassistant + mypy --ignore-missing-imports --follow-imports=skip homeassistant From fdcf332a8aa809762c993bb41afbb0ecd867928f Mon Sep 17 00:00:00 2001 From: Heiko Thiery Date: Mon, 15 Jan 2018 22:49:17 +0100 Subject: [PATCH 032/150] Add support for configuring jeelink RF parameters (#11620) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add support for configuring jeelink ŔF parameters Signed-off-by: Heiko Thiery * fix houndci-bot error Signed-off-by: Heiko Thiery --- homeassistant/components/sensor/lacrosse.py | 25 ++++++++++++++++++++- requirements_all.txt | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/lacrosse.py b/homeassistant/components/sensor/lacrosse.py index 28cba7da0b4..d284e3012dc 100644 --- a/homeassistant/components/sensor/lacrosse.py +++ b/homeassistant/components/sensor/lacrosse.py @@ -19,12 +19,17 @@ from homeassistant.helpers.entity import Entity, async_generate_entity_id from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util import dt as dt_util -REQUIREMENTS = ['pylacrosse==0.2.7'] +REQUIREMENTS = ['pylacrosse==0.3.1'] _LOGGER = logging.getLogger(__name__) CONF_BAUD = 'baud' +CONF_DATARATE = 'datarate' CONF_EXPIRE_AFTER = 'expire_after' +CONF_FREQUENCY = 'frequency' +CONF_JEELINK_LED = 'led' +CONF_TOGGLE_INTERVAL = 'toggle_interval' +CONF_TOGGLE_MASK = 'toggle_mask' DEFAULT_DEVICE = '/dev/ttyUSB0' DEFAULT_BAUD = '57600' @@ -42,7 +47,12 @@ SENSOR_SCHEMA = vol.Schema({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}), vol.Optional(CONF_BAUD, default=DEFAULT_BAUD): cv.string, + vol.Optional(CONF_DATARATE): cv.positive_int, vol.Optional(CONF_DEVICE, default=DEFAULT_DEVICE): cv.string, + vol.Optional(CONF_FREQUENCY): cv.positive_int, + vol.Optional(CONF_JEELINK_LED): cv.boolean, + vol.Optional(CONF_TOGGLE_INTERVAL): cv.positive_int, + vol.Optional(CONF_TOGGLE_MASK): cv.positive_int, }) @@ -66,6 +76,19 @@ def setup_platform(hass, config, add_devices, discovery_info=None): hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lacrosse.close) + if CONF_JEELINK_LED in config: + lacrosse.led_mode_state(config.get(CONF_JEELINK_LED)) + if CONF_FREQUENCY in config: + lacrosse.set_frequency(config.get(CONF_FREQUENCY)) + if CONF_DATARATE in config: + lacrosse.set_datarate(config.get(CONF_DATARATE)) + if CONF_TOGGLE_INTERVAL in config: + lacrosse.set_toggle_interval(config.get(CONF_TOGGLE_INTERVAL)) + if CONF_TOGGLE_MASK in config: + lacrosse.set_toggle_mask(config.get(CONF_TOGGLE_MASK)) + + lacrosse.start_scan() + sensors = [] for device, device_config in config[CONF_SENSORS].items(): _LOGGER.debug("%s %s", device, device_config) diff --git a/requirements_all.txt b/requirements_all.txt index 1e9efd05723..ca444659e01 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -753,7 +753,7 @@ pykira==0.1.1 pykwb==0.0.8 # homeassistant.components.sensor.lacrosse -pylacrosse==0.2.7 +pylacrosse==0.3.1 # homeassistant.components.sensor.lastfm pylast==2.0.0 From 799e1f0469243a75e922b9c4616aeb3423841303 Mon Sep 17 00:00:00 2001 From: angel12 Date: Mon, 15 Jan 2018 15:08:48 -0700 Subject: [PATCH 033/150] Wemo Dimmer Support (#10882) * Wemo Dimmer Support Add support for the Wemo Dimmer Switch * Add newline at end of file Re: findings from hound * Syntax for the hound Sorry for the excess edits, new to python * Change order of Models Fixed order back to ABC order * Changes as requested I made the changes I was comfortable with at this point, the dimmer addition now very closely mirrors what is under the switch component, at least as far as the parts necessary for the dimmer. Any changes past these with regards to the subscription registry / callback info is probably going to be over my head, but I will try to look deeper at them if required. * Remove unnecessary lines Removed self.schedule_update_ha_state() from turn off / turn on * Remove update(self) Removed update method * Move subscription to async_added_to_hass * Move subscription. * Clean up. * Wait until the job in the executor is done * Run gen_requirements_all script * Only update instance attributes via callback --- homeassistant/components/light/wemo.py | 93 +++++++++++++++++++++++++- homeassistant/components/wemo.py | 7 +- 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/light/wemo.py b/homeassistant/components/light/wemo.py index 620271a1071..693e40c0292 100644 --- a/homeassistant/components/light/wemo.py +++ b/homeassistant/components/light/wemo.py @@ -4,6 +4,7 @@ Support for Belkin WeMo lights. For more details about this component, please refer to the documentation at https://home-assistant.io/components/light.wemo/ """ +import asyncio import logging from datetime import timedelta @@ -13,6 +14,7 @@ from homeassistant.components.light import ( Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ATTR_TRANSITION, ATTR_XY_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, SUPPORT_XY_COLOR) +from homeassistant.loader import get_component DEPENDENCIES = ['wemo'] @@ -26,7 +28,7 @@ SUPPORT_WEMO = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_RGB_COLOR | def setup_platform(hass, config, add_devices, discovery_info=None): - """Set up the WeMo bridges and register connected lights.""" + """Set up discovered WeMo switches.""" import pywemo.discovery as discovery if discovery_info is not None: @@ -34,7 +36,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): mac = discovery_info['mac_address'] device = discovery.device_from_description(location, mac) - if device: + if device.model_name == 'Dimmer': + add_devices([WemoDimmer(device)]) + else: setup_bridge(device, add_devices) @@ -140,3 +144,88 @@ class WemoLight(Light): def update(self): """Synchronize state with bridge.""" self.update_lights(no_throttle=True) + + +class WemoDimmer(Light): + """Representation of a WeMo dimmer.""" + + def __init__(self, device): + """Initialize the WeMo dimmer.""" + self.wemo = device + self._brightness = None + self._state = None + + @asyncio.coroutine + def async_added_to_hass(self): + """Register update callback.""" + wemo = get_component('wemo') + # The register method uses a threading condition, so call via executor. + # and yield from to wait until the task is done. + yield from self.hass.async_add_job( + wemo.SUBSCRIPTION_REGISTRY.register, self.wemo) + # The on method just appends to a defaultdict list. + wemo.SUBSCRIPTION_REGISTRY.on(self.wemo, None, self._update_callback) + + def _update_callback(self, _device, _type, _params): + """Update the state by the Wemo device.""" + _LOGGER.debug("Subscription update for %s", _device) + updated = self.wemo.subscription_update(_type, _params) + self._update(force_update=(not updated)) + self.schedule_update_ha_state() + + @property + def unique_id(self): + """Return the ID of this WeMo dimmer.""" + return "{}.{}".format(self.__class__, self.wemo.serialnumber) + + @property + def name(self): + """Return the name of the dimmer if any.""" + return self.wemo.name + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORT_BRIGHTNESS + + @property + def should_poll(self): + """No polling needed with subscriptions.""" + return False + + @property + def brightness(self): + """Return the brightness of this light between 1 and 100.""" + return self._brightness + + @property + def is_on(self): + """Return true if dimmer is on. Standby is on.""" + return self._state + + def _update(self, force_update=True): + """Update the device state.""" + try: + self._state = self.wemo.get_state(force_update) + wemobrightness = int(self.wemo.get_brightness(force_update)) + self._brightness = int((wemobrightness * 255) / 100) + except AttributeError as err: + _LOGGER.warning("Could not update status for %s (%s)", + self.name, err) + + def turn_on(self, **kwargs): + """Turn the dimmer on.""" + self.wemo.on() + + # Wemo dimmer switches use a range of [0, 100] to control + # brightness. Level 255 might mean to set it to previous value + if ATTR_BRIGHTNESS in kwargs: + brightness = kwargs[ATTR_BRIGHTNESS] + brightness = int((brightness / 255) * 100) + else: + brightness = 255 + self.wemo.set_brightness(brightness) + + def turn_off(self, **kwargs): + """Turn the dimmer off.""" + self.wemo.off() diff --git a/homeassistant/components/wemo.py b/homeassistant/components/wemo.py index aaeccaf6eba..9929b64be7d 100644 --- a/homeassistant/components/wemo.py +++ b/homeassistant/components/wemo.py @@ -21,12 +21,13 @@ DOMAIN = 'wemo' # Mapping from Wemo model_name to component. WEMO_MODEL_DISPATCH = { 'Bridge': 'light', + 'CoffeeMaker': 'switch', + 'Dimmer': 'light', 'Insight': 'switch', + 'LightSwitch': 'switch', 'Maker': 'switch', 'Sensor': 'binary_sensor', - 'Socket': 'switch', - 'LightSwitch': 'switch', - 'CoffeeMaker': 'switch' + 'Socket': 'switch' } SUBSCRIPTION_REGISTRY = None From c43eceb2cb5fc37ec6b4ea866af0c4ad9170aa64 Mon Sep 17 00:00:00 2001 From: Thom Troy Date: Mon, 15 Jan 2018 22:13:48 +0000 Subject: [PATCH 034/150] add temperature controls to eph-ember (#11400) * add temperature controls to eph-ember * fix linting --- homeassistant/components/climate/ephember.py | 45 +++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/climate/ephember.py b/homeassistant/components/climate/ephember.py index a1d11bce901..e1f1ab7d448 100644 --- a/homeassistant/components/climate/ephember.py +++ b/homeassistant/components/climate/ephember.py @@ -9,9 +9,10 @@ from datetime import timedelta import voluptuous as vol from homeassistant.components.climate import ( - ClimateDevice, PLATFORM_SCHEMA, STATE_HEAT, STATE_IDLE, SUPPORT_AUX_HEAT) + ClimateDevice, PLATFORM_SCHEMA, STATE_HEAT, STATE_IDLE, SUPPORT_AUX_HEAT, + SUPPORT_TARGET_TEMPERATURE) from homeassistant.const import ( - TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD) + TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD, ATTR_TEMPERATURE) import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['pyephember==0.1.1'] @@ -59,7 +60,10 @@ class EphEmberThermostat(ClimateDevice): @property def supported_features(self): """Return the list of supported features.""" - return SUPPORT_AUX_HEAT + if self._hot_water: + return SUPPORT_AUX_HEAT + + return SUPPORT_TARGET_TEMPERATURE | SUPPORT_AUX_HEAT @property def name(self): @@ -81,6 +85,14 @@ class EphEmberThermostat(ClimateDevice): """Return the temperature we try to reach.""" return self._zone['targetTemperature'] + @property + def target_temperature_step(self): + """Return the supported step of target temperature.""" + if self._hot_water: + return None + + return 1 + @property def current_operation(self): """Return current operation ie. heat, cool, idle.""" @@ -105,17 +117,38 @@ class EphEmberThermostat(ClimateDevice): def set_temperature(self, **kwargs): """Set new target temperature.""" - return + temperature = kwargs.get(ATTR_TEMPERATURE) + if temperature is None: + return + + if self._hot_water: + return + + if temperature == self.target_temperature: + return + + if temperature > self.max_temp or temperature < self.min_temp: + return + + self._ember.set_target_temperture_by_name(self._zone_name, + int(temperature)) @property def min_temp(self): """Return the minimum temperature.""" - return self._zone['targetTemperature'] + # Hot water temp doesn't support being changed + if self._hot_water: + return self._zone['targetTemperature'] + + return 5 @property def max_temp(self): """Return the maximum temperature.""" - return self._zone['targetTemperature'] + if self._hot_water: + return self._zone['targetTemperature'] + + return 35 def update(self): """Get the latest data.""" From 56a2c587adb3625b2cb7623463b4bb1448860a3f Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 15 Jan 2018 23:23:53 +0100 Subject: [PATCH 035/150] Upgrade youtube_dl to 2018.01.14 (#11661) --- homeassistant/components/media_extractor.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_extractor.py b/homeassistant/components/media_extractor.py index 70c28d85585..de56c5140e9 100644 --- a/homeassistant/components/media_extractor.py +++ b/homeassistant/components/media_extractor.py @@ -14,7 +14,7 @@ from homeassistant.components.media_player import ( SERVICE_PLAY_MEDIA) from homeassistant.helpers import config_validation as cv -REQUIREMENTS = ['youtube_dl==2017.12.31'] +REQUIREMENTS = ['youtube_dl==2018.01.14'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index ca444659e01..58b28b6a73d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1228,7 +1228,7 @@ yeelight==0.3.3 yeelightsunflower==0.0.8 # homeassistant.components.media_extractor -youtube_dl==2017.12.31 +youtube_dl==2018.01.14 # homeassistant.components.light.zengge zengge==0.2 From b0860ce5f08eca8c62702f679c98d3b1ebe6df6f Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 15 Jan 2018 23:24:12 +0100 Subject: [PATCH 036/150] Change line separator to LN (#11662) --- .../components/binary_sensor/hive.py | 126 +++---- homeassistant/components/climate/hive.py | 356 +++++++++--------- homeassistant/components/hive.py | 160 ++++---- homeassistant/components/light/hive.py | 282 +++++++------- homeassistant/components/map.py | 34 +- homeassistant/components/notify/prowl.py | 140 +++---- homeassistant/components/sensor/hive.py | 104 ++--- homeassistant/components/switch/hive.py | 138 +++---- 8 files changed, 670 insertions(+), 670 deletions(-) diff --git a/homeassistant/components/binary_sensor/hive.py b/homeassistant/components/binary_sensor/hive.py index b62c003c4fd..6223ebe50a1 100644 --- a/homeassistant/components/binary_sensor/hive.py +++ b/homeassistant/components/binary_sensor/hive.py @@ -1,63 +1,63 @@ -""" -Support for the Hive devices. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.hive/ -""" -from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.hive import DATA_HIVE - -DEPENDENCIES = ['hive'] - -DEVICETYPE_DEVICE_CLASS = {'motionsensor': 'motion', - 'contactsensor': 'opening'} - - -def setup_platform(hass, config, add_devices, discovery_info=None): - """Set up Hive sensor devices.""" - if discovery_info is None: - return - session = hass.data.get(DATA_HIVE) - - add_devices([HiveBinarySensorEntity(session, discovery_info)]) - - -class HiveBinarySensorEntity(BinarySensorDevice): - """Representation of a Hive binary sensor.""" - - def __init__(self, hivesession, hivedevice): - """Initialize the hive sensor.""" - self.node_id = hivedevice["Hive_NodeID"] - self.node_name = hivedevice["Hive_NodeName"] - self.device_type = hivedevice["HA_DeviceType"] - self.node_device_type = hivedevice["Hive_DeviceType"] - self.session = hivesession - self.data_updatesource = '{}.{}'.format(self.device_type, - self.node_id) - - self.session.entities.append(self) - - def handle_update(self, updatesource): - """Handle the new update request.""" - if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: - self.schedule_update_ha_state() - - @property - def device_class(self): - """Return the class of this sensor.""" - return DEVICETYPE_DEVICE_CLASS.get(self.node_device_type) - - @property - def name(self): - """Return the name of the binary sensor.""" - return self.node_name - - @property - def is_on(self): - """Return true if the binary sensor is on.""" - return self.session.sensor.get_state(self.node_id, - self.node_device_type) - - def update(self): - """Update all Node data frome Hive.""" - self.session.core.update_data(self.node_id) +""" +Support for the Hive devices. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.hive/ +""" +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.hive import DATA_HIVE + +DEPENDENCIES = ['hive'] + +DEVICETYPE_DEVICE_CLASS = {'motionsensor': 'motion', + 'contactsensor': 'opening'} + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up Hive sensor devices.""" + if discovery_info is None: + return + session = hass.data.get(DATA_HIVE) + + add_devices([HiveBinarySensorEntity(session, discovery_info)]) + + +class HiveBinarySensorEntity(BinarySensorDevice): + """Representation of a Hive binary sensor.""" + + def __init__(self, hivesession, hivedevice): + """Initialize the hive sensor.""" + self.node_id = hivedevice["Hive_NodeID"] + self.node_name = hivedevice["Hive_NodeName"] + self.device_type = hivedevice["HA_DeviceType"] + self.node_device_type = hivedevice["Hive_DeviceType"] + self.session = hivesession + self.data_updatesource = '{}.{}'.format(self.device_type, + self.node_id) + + self.session.entities.append(self) + + def handle_update(self, updatesource): + """Handle the new update request.""" + if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: + self.schedule_update_ha_state() + + @property + def device_class(self): + """Return the class of this sensor.""" + return DEVICETYPE_DEVICE_CLASS.get(self.node_device_type) + + @property + def name(self): + """Return the name of the binary sensor.""" + return self.node_name + + @property + def is_on(self): + """Return true if the binary sensor is on.""" + return self.session.sensor.get_state(self.node_id, + self.node_device_type) + + def update(self): + """Update all Node data frome Hive.""" + self.session.core.update_data(self.node_id) diff --git a/homeassistant/components/climate/hive.py b/homeassistant/components/climate/hive.py index 8305e772869..b8ac66d91b3 100644 --- a/homeassistant/components/climate/hive.py +++ b/homeassistant/components/climate/hive.py @@ -1,178 +1,178 @@ -""" -Support for the Hive devices. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/climate.hive/ -""" -from homeassistant.components.climate import ( - ClimateDevice, STATE_AUTO, STATE_HEAT, STATE_OFF, STATE_ON, - SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE) -from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS -from homeassistant.components.hive import DATA_HIVE - -DEPENDENCIES = ['hive'] -HIVE_TO_HASS_STATE = {'SCHEDULE': STATE_AUTO, 'MANUAL': STATE_HEAT, - 'ON': STATE_ON, 'OFF': STATE_OFF} -HASS_TO_HIVE_STATE = {STATE_AUTO: 'SCHEDULE', STATE_HEAT: 'MANUAL', - STATE_ON: 'ON', STATE_OFF: 'OFF'} - -SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | - SUPPORT_OPERATION_MODE | - SUPPORT_AUX_HEAT) - - -def setup_platform(hass, config, add_devices, discovery_info=None): - """Set up Hive climate devices.""" - if discovery_info is None: - return - session = hass.data.get(DATA_HIVE) - - add_devices([HiveClimateEntity(session, discovery_info)]) - - -class HiveClimateEntity(ClimateDevice): - """Hive Climate Device.""" - - def __init__(self, hivesession, hivedevice): - """Initialize the Climate device.""" - self.node_id = hivedevice["Hive_NodeID"] - self.node_name = hivedevice["Hive_NodeName"] - self.device_type = hivedevice["HA_DeviceType"] - self.session = hivesession - self.data_updatesource = '{}.{}'.format(self.device_type, - self.node_id) - - if self.device_type == "Heating": - self.modes = [STATE_AUTO, STATE_HEAT, STATE_OFF] - elif self.device_type == "HotWater": - self.modes = [STATE_AUTO, STATE_ON, STATE_OFF] - - self.session.entities.append(self) - - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS - - def handle_update(self, updatesource): - """Handle the new update request.""" - if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: - self.schedule_update_ha_state() - - @property - def name(self): - """Return the name of the Climate device.""" - friendly_name = "Climate Device" - if self.device_type == "Heating": - friendly_name = "Heating" - if self.node_name is not None: - friendly_name = '{} {}'.format(self.node_name, friendly_name) - elif self.device_type == "HotWater": - friendly_name = "Hot Water" - return friendly_name - - @property - def temperature_unit(self): - """Return the unit of measurement.""" - return TEMP_CELSIUS - - @property - def current_temperature(self): - """Return the current temperature.""" - if self.device_type == "Heating": - return self.session.heating.current_temperature(self.node_id) - - @property - def target_temperature(self): - """Return the target temperature.""" - if self.device_type == "Heating": - return self.session.heating.get_target_temperature(self.node_id) - - @property - def min_temp(self): - """Return minimum temperature.""" - if self.device_type == "Heating": - return self.session.heating.min_temperature(self.node_id) - - @property - def max_temp(self): - """Return the maximum temperature.""" - if self.device_type == "Heating": - return self.session.heating.max_temperature(self.node_id) - - @property - def operation_list(self): - """List of the operation modes.""" - return self.modes - - @property - def current_operation(self): - """Return current mode.""" - if self.device_type == "Heating": - currentmode = self.session.heating.get_mode(self.node_id) - elif self.device_type == "HotWater": - currentmode = self.session.hotwater.get_mode(self.node_id) - return HIVE_TO_HASS_STATE.get(currentmode) - - def set_operation_mode(self, operation_mode): - """Set new Heating mode.""" - new_mode = HASS_TO_HIVE_STATE.get(operation_mode) - if self.device_type == "Heating": - self.session.heating.set_mode(self.node_id, new_mode) - elif self.device_type == "HotWater": - self.session.hotwater.set_mode(self.node_id, new_mode) - - for entity in self.session.entities: - entity.handle_update(self.data_updatesource) - - def set_temperature(self, **kwargs): - """Set new target temperature.""" - new_temperature = kwargs.get(ATTR_TEMPERATURE) - if new_temperature is not None: - if self.device_type == "Heating": - self.session.heating.set_target_temperature(self.node_id, - new_temperature) - - for entity in self.session.entities: - entity.handle_update(self.data_updatesource) - - @property - def is_aux_heat_on(self): - """Return true if auxiliary heater is on.""" - boost_status = None - if self.device_type == "Heating": - boost_status = self.session.heating.get_boost(self.node_id) - elif self.device_type == "HotWater": - boost_status = self.session.hotwater.get_boost(self.node_id) - return boost_status == "ON" - - def turn_aux_heat_on(self): - """Turn auxiliary heater on.""" - target_boost_time = 30 - if self.device_type == "Heating": - curtemp = self.session.heating.current_temperature(self.node_id) - curtemp = round(curtemp * 2) / 2 - target_boost_temperature = curtemp + 0.5 - self.session.heating.turn_boost_on(self.node_id, - target_boost_time, - target_boost_temperature) - elif self.device_type == "HotWater": - self.session.hotwater.turn_boost_on(self.node_id, - target_boost_time) - - for entity in self.session.entities: - entity.handle_update(self.data_updatesource) - - def turn_aux_heat_off(self): - """Turn auxiliary heater off.""" - if self.device_type == "Heating": - self.session.heating.turn_boost_off(self.node_id) - elif self.device_type == "HotWater": - self.session.hotwater.turn_boost_off(self.node_id) - - for entity in self.session.entities: - entity.handle_update(self.data_updatesource) - - def update(self): - """Update all Node data frome Hive.""" - self.session.core.update_data(self.node_id) +""" +Support for the Hive devices. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/climate.hive/ +""" +from homeassistant.components.climate import ( + ClimateDevice, STATE_AUTO, STATE_HEAT, STATE_OFF, STATE_ON, + SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE) +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS +from homeassistant.components.hive import DATA_HIVE + +DEPENDENCIES = ['hive'] +HIVE_TO_HASS_STATE = {'SCHEDULE': STATE_AUTO, 'MANUAL': STATE_HEAT, + 'ON': STATE_ON, 'OFF': STATE_OFF} +HASS_TO_HIVE_STATE = {STATE_AUTO: 'SCHEDULE', STATE_HEAT: 'MANUAL', + STATE_ON: 'ON', STATE_OFF: 'OFF'} + +SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | + SUPPORT_OPERATION_MODE | + SUPPORT_AUX_HEAT) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up Hive climate devices.""" + if discovery_info is None: + return + session = hass.data.get(DATA_HIVE) + + add_devices([HiveClimateEntity(session, discovery_info)]) + + +class HiveClimateEntity(ClimateDevice): + """Hive Climate Device.""" + + def __init__(self, hivesession, hivedevice): + """Initialize the Climate device.""" + self.node_id = hivedevice["Hive_NodeID"] + self.node_name = hivedevice["Hive_NodeName"] + self.device_type = hivedevice["HA_DeviceType"] + self.session = hivesession + self.data_updatesource = '{}.{}'.format(self.device_type, + self.node_id) + + if self.device_type == "Heating": + self.modes = [STATE_AUTO, STATE_HEAT, STATE_OFF] + elif self.device_type == "HotWater": + self.modes = [STATE_AUTO, STATE_ON, STATE_OFF] + + self.session.entities.append(self) + + @property + def supported_features(self): + """Return the list of supported features.""" + return SUPPORT_FLAGS + + def handle_update(self, updatesource): + """Handle the new update request.""" + if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: + self.schedule_update_ha_state() + + @property + def name(self): + """Return the name of the Climate device.""" + friendly_name = "Climate Device" + if self.device_type == "Heating": + friendly_name = "Heating" + if self.node_name is not None: + friendly_name = '{} {}'.format(self.node_name, friendly_name) + elif self.device_type == "HotWater": + friendly_name = "Hot Water" + return friendly_name + + @property + def temperature_unit(self): + """Return the unit of measurement.""" + return TEMP_CELSIUS + + @property + def current_temperature(self): + """Return the current temperature.""" + if self.device_type == "Heating": + return self.session.heating.current_temperature(self.node_id) + + @property + def target_temperature(self): + """Return the target temperature.""" + if self.device_type == "Heating": + return self.session.heating.get_target_temperature(self.node_id) + + @property + def min_temp(self): + """Return minimum temperature.""" + if self.device_type == "Heating": + return self.session.heating.min_temperature(self.node_id) + + @property + def max_temp(self): + """Return the maximum temperature.""" + if self.device_type == "Heating": + return self.session.heating.max_temperature(self.node_id) + + @property + def operation_list(self): + """List of the operation modes.""" + return self.modes + + @property + def current_operation(self): + """Return current mode.""" + if self.device_type == "Heating": + currentmode = self.session.heating.get_mode(self.node_id) + elif self.device_type == "HotWater": + currentmode = self.session.hotwater.get_mode(self.node_id) + return HIVE_TO_HASS_STATE.get(currentmode) + + def set_operation_mode(self, operation_mode): + """Set new Heating mode.""" + new_mode = HASS_TO_HIVE_STATE.get(operation_mode) + if self.device_type == "Heating": + self.session.heating.set_mode(self.node_id, new_mode) + elif self.device_type == "HotWater": + self.session.hotwater.set_mode(self.node_id, new_mode) + + for entity in self.session.entities: + entity.handle_update(self.data_updatesource) + + def set_temperature(self, **kwargs): + """Set new target temperature.""" + new_temperature = kwargs.get(ATTR_TEMPERATURE) + if new_temperature is not None: + if self.device_type == "Heating": + self.session.heating.set_target_temperature(self.node_id, + new_temperature) + + for entity in self.session.entities: + entity.handle_update(self.data_updatesource) + + @property + def is_aux_heat_on(self): + """Return true if auxiliary heater is on.""" + boost_status = None + if self.device_type == "Heating": + boost_status = self.session.heating.get_boost(self.node_id) + elif self.device_type == "HotWater": + boost_status = self.session.hotwater.get_boost(self.node_id) + return boost_status == "ON" + + def turn_aux_heat_on(self): + """Turn auxiliary heater on.""" + target_boost_time = 30 + if self.device_type == "Heating": + curtemp = self.session.heating.current_temperature(self.node_id) + curtemp = round(curtemp * 2) / 2 + target_boost_temperature = curtemp + 0.5 + self.session.heating.turn_boost_on(self.node_id, + target_boost_time, + target_boost_temperature) + elif self.device_type == "HotWater": + self.session.hotwater.turn_boost_on(self.node_id, + target_boost_time) + + for entity in self.session.entities: + entity.handle_update(self.data_updatesource) + + def turn_aux_heat_off(self): + """Turn auxiliary heater off.""" + if self.device_type == "Heating": + self.session.heating.turn_boost_off(self.node_id) + elif self.device_type == "HotWater": + self.session.hotwater.turn_boost_off(self.node_id) + + for entity in self.session.entities: + entity.handle_update(self.data_updatesource) + + def update(self): + """Update all Node data frome Hive.""" + self.session.core.update_data(self.node_id) diff --git a/homeassistant/components/hive.py b/homeassistant/components/hive.py index bf5196d6582..666854bdd8e 100644 --- a/homeassistant/components/hive.py +++ b/homeassistant/components/hive.py @@ -1,80 +1,80 @@ -""" -Support for the Hive devices. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/hive/ -""" -import logging -import voluptuous as vol - -from homeassistant.const import (CONF_PASSWORD, CONF_SCAN_INTERVAL, - CONF_USERNAME) -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.discovery import load_platform - -REQUIREMENTS = ['pyhiveapi==0.2.10'] - -_LOGGER = logging.getLogger(__name__) -DOMAIN = 'hive' -DATA_HIVE = 'data_hive' -DEVICETYPES = { - 'binary_sensor': 'device_list_binary_sensor', - 'climate': 'device_list_climate', - 'light': 'device_list_light', - 'switch': 'device_list_plug', - 'sensor': 'device_list_sensor', - } - -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_SCAN_INTERVAL, default=2): cv.positive_int, - }) -}, extra=vol.ALLOW_EXTRA) - - -class HiveSession: - """Initiate Hive Session Class.""" - - entities = [] - core = None - heating = None - hotwater = None - light = None - sensor = None - switch = None - - -def setup(hass, config): - """Set up the Hive Component.""" - from pyhiveapi import Pyhiveapi - - session = HiveSession() - session.core = Pyhiveapi() - - username = config[DOMAIN][CONF_USERNAME] - password = config[DOMAIN][CONF_PASSWORD] - update_interval = config[DOMAIN][CONF_SCAN_INTERVAL] - - devicelist = session.core.initialise_api(username, - password, - update_interval) - - if devicelist is None: - _LOGGER.error("Hive API initialization failed") - return False - - session.sensor = Pyhiveapi.Sensor() - session.heating = Pyhiveapi.Heating() - session.hotwater = Pyhiveapi.Hotwater() - session.light = Pyhiveapi.Light() - session.switch = Pyhiveapi.Switch() - hass.data[DATA_HIVE] = session - - for ha_type, hive_type in DEVICETYPES.items(): - for key, devices in devicelist.items(): - if key == hive_type: - for hivedevice in devices: - load_platform(hass, ha_type, DOMAIN, hivedevice, config) - return True +""" +Support for the Hive devices. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/hive/ +""" +import logging +import voluptuous as vol + +from homeassistant.const import (CONF_PASSWORD, CONF_SCAN_INTERVAL, + CONF_USERNAME) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import load_platform + +REQUIREMENTS = ['pyhiveapi==0.2.10'] + +_LOGGER = logging.getLogger(__name__) +DOMAIN = 'hive' +DATA_HIVE = 'data_hive' +DEVICETYPES = { + 'binary_sensor': 'device_list_binary_sensor', + 'climate': 'device_list_climate', + 'light': 'device_list_light', + 'switch': 'device_list_plug', + 'sensor': 'device_list_sensor', + } + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_SCAN_INTERVAL, default=2): cv.positive_int, + }) +}, extra=vol.ALLOW_EXTRA) + + +class HiveSession: + """Initiate Hive Session Class.""" + + entities = [] + core = None + heating = None + hotwater = None + light = None + sensor = None + switch = None + + +def setup(hass, config): + """Set up the Hive Component.""" + from pyhiveapi import Pyhiveapi + + session = HiveSession() + session.core = Pyhiveapi() + + username = config[DOMAIN][CONF_USERNAME] + password = config[DOMAIN][CONF_PASSWORD] + update_interval = config[DOMAIN][CONF_SCAN_INTERVAL] + + devicelist = session.core.initialise_api(username, + password, + update_interval) + + if devicelist is None: + _LOGGER.error("Hive API initialization failed") + return False + + session.sensor = Pyhiveapi.Sensor() + session.heating = Pyhiveapi.Heating() + session.hotwater = Pyhiveapi.Hotwater() + session.light = Pyhiveapi.Light() + session.switch = Pyhiveapi.Switch() + hass.data[DATA_HIVE] = session + + for ha_type, hive_type in DEVICETYPES.items(): + for key, devices in devicelist.items(): + if key == hive_type: + for hivedevice in devices: + load_platform(hass, ha_type, DOMAIN, hivedevice, config) + return True diff --git a/homeassistant/components/light/hive.py b/homeassistant/components/light/hive.py index 3356d637be8..8fafb88a7db 100644 --- a/homeassistant/components/light/hive.py +++ b/homeassistant/components/light/hive.py @@ -1,141 +1,141 @@ -""" -Support for the Hive devices. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/light.hive/ -""" -import colorsys -from homeassistant.components.hive import DATA_HIVE -from homeassistant.components.light import (ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, - ATTR_RGB_COLOR, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR_TEMP, - SUPPORT_RGB_COLOR, Light) - -DEPENDENCIES = ['hive'] - - -def setup_platform(hass, config, add_devices, discovery_info=None): - """Set up Hive light devices.""" - if discovery_info is None: - return - session = hass.data.get(DATA_HIVE) - - add_devices([HiveDeviceLight(session, discovery_info)]) - - -class HiveDeviceLight(Light): - """Hive Active Light Device.""" - - def __init__(self, hivesession, hivedevice): - """Initialize the Light device.""" - self.node_id = hivedevice["Hive_NodeID"] - self.node_name = hivedevice["Hive_NodeName"] - self.device_type = hivedevice["HA_DeviceType"] - self.light_device_type = hivedevice["Hive_Light_DeviceType"] - self.session = hivesession - self.data_updatesource = '{}.{}'.format(self.device_type, - self.node_id) - self.session.entities.append(self) - - def handle_update(self, updatesource): - """Handle the new update request.""" - if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: - self.schedule_update_ha_state() - - @property - def name(self): - """Return the display name of this light.""" - return self.node_name - - @property - def brightness(self): - """Brightness of the light (an integer in the range 1-255).""" - return self.session.light.get_brightness(self.node_id) - - @property - def min_mireds(self): - """Return the coldest color_temp that this light supports.""" - if self.light_device_type == "tuneablelight" \ - or self.light_device_type == "colourtuneablelight": - return self.session.light.get_min_color_temp(self.node_id) - - @property - def max_mireds(self): - """Return the warmest color_temp that this light supports.""" - if self.light_device_type == "tuneablelight" \ - or self.light_device_type == "colourtuneablelight": - return self.session.light.get_max_color_temp(self.node_id) - - @property - def color_temp(self): - """Return the CT color value in mireds.""" - if self.light_device_type == "tuneablelight" \ - or self.light_device_type == "colourtuneablelight": - return self.session.light.get_color_temp(self.node_id) - - @property - def rgb_color(self) -> tuple: - """Return the RBG color value.""" - if self.light_device_type == "colourtuneablelight": - return self.session.light.get_color(self.node_id) - - @property - def is_on(self): - """Return true if light is on.""" - return self.session.light.get_state(self.node_id) - - def turn_on(self, **kwargs): - """Instruct the light to turn on.""" - new_brightness = None - new_color_temp = None - new_color = None - if ATTR_BRIGHTNESS in kwargs: - tmp_new_brightness = kwargs.get(ATTR_BRIGHTNESS) - percentage_brightness = ((tmp_new_brightness / 255) * 100) - new_brightness = int(round(percentage_brightness / 5.0) * 5.0) - if new_brightness == 0: - new_brightness = 5 - if ATTR_COLOR_TEMP in kwargs: - tmp_new_color_temp = kwargs.get(ATTR_COLOR_TEMP) - new_color_temp = round(1000000 / tmp_new_color_temp) - if ATTR_RGB_COLOR in kwargs: - get_new_color = kwargs.get(ATTR_RGB_COLOR) - tmp_new_color = colorsys.rgb_to_hsv(get_new_color[0], - get_new_color[1], - get_new_color[2]) - hue = int(round(tmp_new_color[0] * 360)) - saturation = int(round(tmp_new_color[1] * 100)) - value = int(round((tmp_new_color[2] / 255) * 100)) - new_color = (hue, saturation, value) - - self.session.light.turn_on(self.node_id, self.light_device_type, - new_brightness, new_color_temp, - new_color) - - for entity in self.session.entities: - entity.handle_update(self.data_updatesource) - - def turn_off(self): - """Instruct the light to turn off.""" - self.session.light.turn_off(self.node_id) - for entity in self.session.entities: - entity.handle_update(self.data_updatesource) - - @property - def supported_features(self): - """Flag supported features.""" - supported_features = None - if self.light_device_type == "warmwhitelight": - supported_features = SUPPORT_BRIGHTNESS - elif self.light_device_type == "tuneablelight": - supported_features = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP) - elif self.light_device_type == "colourtuneablelight": - supported_features = ( - SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_RGB_COLOR) - - return supported_features - - def update(self): - """Update all Node data frome Hive.""" - self.session.core.update_data(self.node_id) +""" +Support for the Hive devices. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/light.hive/ +""" +import colorsys +from homeassistant.components.hive import DATA_HIVE +from homeassistant.components.light import (ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, + ATTR_RGB_COLOR, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR_TEMP, + SUPPORT_RGB_COLOR, Light) + +DEPENDENCIES = ['hive'] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up Hive light devices.""" + if discovery_info is None: + return + session = hass.data.get(DATA_HIVE) + + add_devices([HiveDeviceLight(session, discovery_info)]) + + +class HiveDeviceLight(Light): + """Hive Active Light Device.""" + + def __init__(self, hivesession, hivedevice): + """Initialize the Light device.""" + self.node_id = hivedevice["Hive_NodeID"] + self.node_name = hivedevice["Hive_NodeName"] + self.device_type = hivedevice["HA_DeviceType"] + self.light_device_type = hivedevice["Hive_Light_DeviceType"] + self.session = hivesession + self.data_updatesource = '{}.{}'.format(self.device_type, + self.node_id) + self.session.entities.append(self) + + def handle_update(self, updatesource): + """Handle the new update request.""" + if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: + self.schedule_update_ha_state() + + @property + def name(self): + """Return the display name of this light.""" + return self.node_name + + @property + def brightness(self): + """Brightness of the light (an integer in the range 1-255).""" + return self.session.light.get_brightness(self.node_id) + + @property + def min_mireds(self): + """Return the coldest color_temp that this light supports.""" + if self.light_device_type == "tuneablelight" \ + or self.light_device_type == "colourtuneablelight": + return self.session.light.get_min_color_temp(self.node_id) + + @property + def max_mireds(self): + """Return the warmest color_temp that this light supports.""" + if self.light_device_type == "tuneablelight" \ + or self.light_device_type == "colourtuneablelight": + return self.session.light.get_max_color_temp(self.node_id) + + @property + def color_temp(self): + """Return the CT color value in mireds.""" + if self.light_device_type == "tuneablelight" \ + or self.light_device_type == "colourtuneablelight": + return self.session.light.get_color_temp(self.node_id) + + @property + def rgb_color(self) -> tuple: + """Return the RBG color value.""" + if self.light_device_type == "colourtuneablelight": + return self.session.light.get_color(self.node_id) + + @property + def is_on(self): + """Return true if light is on.""" + return self.session.light.get_state(self.node_id) + + def turn_on(self, **kwargs): + """Instruct the light to turn on.""" + new_brightness = None + new_color_temp = None + new_color = None + if ATTR_BRIGHTNESS in kwargs: + tmp_new_brightness = kwargs.get(ATTR_BRIGHTNESS) + percentage_brightness = ((tmp_new_brightness / 255) * 100) + new_brightness = int(round(percentage_brightness / 5.0) * 5.0) + if new_brightness == 0: + new_brightness = 5 + if ATTR_COLOR_TEMP in kwargs: + tmp_new_color_temp = kwargs.get(ATTR_COLOR_TEMP) + new_color_temp = round(1000000 / tmp_new_color_temp) + if ATTR_RGB_COLOR in kwargs: + get_new_color = kwargs.get(ATTR_RGB_COLOR) + tmp_new_color = colorsys.rgb_to_hsv(get_new_color[0], + get_new_color[1], + get_new_color[2]) + hue = int(round(tmp_new_color[0] * 360)) + saturation = int(round(tmp_new_color[1] * 100)) + value = int(round((tmp_new_color[2] / 255) * 100)) + new_color = (hue, saturation, value) + + self.session.light.turn_on(self.node_id, self.light_device_type, + new_brightness, new_color_temp, + new_color) + + for entity in self.session.entities: + entity.handle_update(self.data_updatesource) + + def turn_off(self): + """Instruct the light to turn off.""" + self.session.light.turn_off(self.node_id) + for entity in self.session.entities: + entity.handle_update(self.data_updatesource) + + @property + def supported_features(self): + """Flag supported features.""" + supported_features = None + if self.light_device_type == "warmwhitelight": + supported_features = SUPPORT_BRIGHTNESS + elif self.light_device_type == "tuneablelight": + supported_features = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP) + elif self.light_device_type == "colourtuneablelight": + supported_features = ( + SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_RGB_COLOR) + + return supported_features + + def update(self): + """Update all Node data frome Hive.""" + self.session.core.update_data(self.node_id) diff --git a/homeassistant/components/map.py b/homeassistant/components/map.py index 2b204e584c3..b8293f64fc0 100644 --- a/homeassistant/components/map.py +++ b/homeassistant/components/map.py @@ -1,17 +1,17 @@ -""" -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/ -""" -import asyncio - -DOMAIN = 'map' - - -@asyncio.coroutine -def async_setup(hass, config): - """Register the built-in map panel.""" - yield from hass.components.frontend.async_register_built_in_panel( - 'map', 'map', 'mdi:account-location') - return True +""" +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/ +""" +import asyncio + +DOMAIN = 'map' + + +@asyncio.coroutine +def async_setup(hass, config): + """Register the built-in map panel.""" + yield from hass.components.frontend.async_register_built_in_panel( + 'map', 'map', 'mdi:account-location') + return True diff --git a/homeassistant/components/notify/prowl.py b/homeassistant/components/notify/prowl.py index 1298657a69a..3928fa81167 100644 --- a/homeassistant/components/notify/prowl.py +++ b/homeassistant/components/notify/prowl.py @@ -1,70 +1,70 @@ -""" -Prowl notification service. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/notify.prowl/ -""" -import logging -import asyncio - -import async_timeout -import voluptuous as vol - -from homeassistant.components.notify import ( - ATTR_TITLE, ATTR_TITLE_DEFAULT, ATTR_DATA, PLATFORM_SCHEMA, - BaseNotificationService) -from homeassistant.const import CONF_API_KEY -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.aiohttp_client import async_get_clientsession - -_LOGGER = logging.getLogger(__name__) -_RESOURCE = 'https://api.prowlapp.com/publicapi/' - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_API_KEY): cv.string, -}) - - -@asyncio.coroutine -def async_get_service(hass, config, discovery_info=None): - """Get the Prowl notification service.""" - return ProwlNotificationService(hass, config[CONF_API_KEY]) - - -class ProwlNotificationService(BaseNotificationService): - """Implement the notification service for Prowl.""" - - def __init__(self, hass, api_key): - """Initialize the service.""" - self._hass = hass - self._api_key = api_key - - @asyncio.coroutine - def async_send_message(self, message, **kwargs): - """Send the message to the user.""" - response = None - session = None - url = '{}{}'.format(_RESOURCE, 'add') - data = kwargs.get(ATTR_DATA) - payload = { - 'apikey': self._api_key, - 'application': 'Home-Assistant', - 'event': kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT), - 'description': message, - 'priority': data['priority'] if data and 'priority' in data else 0 - } - - _LOGGER.debug("Attempting call Prowl service at %s", url) - session = async_get_clientsession(self._hass) - - try: - with async_timeout.timeout(10, loop=self._hass.loop): - response = yield from session.post(url, data=payload) - result = yield from response.text() - - if response.status != 200 or 'error' in result: - _LOGGER.error("Prowl service returned http " - "status %d, response %s", - response.status, result) - except asyncio.TimeoutError: - _LOGGER.error("Timeout accessing Prowl at %s", url) +""" +Prowl notification service. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/notify.prowl/ +""" +import logging +import asyncio + +import async_timeout +import voluptuous as vol + +from homeassistant.components.notify import ( + ATTR_TITLE, ATTR_TITLE_DEFAULT, ATTR_DATA, PLATFORM_SCHEMA, + BaseNotificationService) +from homeassistant.const import CONF_API_KEY +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +_LOGGER = logging.getLogger(__name__) +_RESOURCE = 'https://api.prowlapp.com/publicapi/' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_API_KEY): cv.string, +}) + + +@asyncio.coroutine +def async_get_service(hass, config, discovery_info=None): + """Get the Prowl notification service.""" + return ProwlNotificationService(hass, config[CONF_API_KEY]) + + +class ProwlNotificationService(BaseNotificationService): + """Implement the notification service for Prowl.""" + + def __init__(self, hass, api_key): + """Initialize the service.""" + self._hass = hass + self._api_key = api_key + + @asyncio.coroutine + def async_send_message(self, message, **kwargs): + """Send the message to the user.""" + response = None + session = None + url = '{}{}'.format(_RESOURCE, 'add') + data = kwargs.get(ATTR_DATA) + payload = { + 'apikey': self._api_key, + 'application': 'Home-Assistant', + 'event': kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT), + 'description': message, + 'priority': data['priority'] if data and 'priority' in data else 0 + } + + _LOGGER.debug("Attempting call Prowl service at %s", url) + session = async_get_clientsession(self._hass) + + try: + with async_timeout.timeout(10, loop=self._hass.loop): + response = yield from session.post(url, data=payload) + result = yield from response.text() + + if response.status != 200 or 'error' in result: + _LOGGER.error("Prowl service returned http " + "status %d, response %s", + response.status, result) + except asyncio.TimeoutError: + _LOGGER.error("Timeout accessing Prowl at %s", url) diff --git a/homeassistant/components/sensor/hive.py b/homeassistant/components/sensor/hive.py index ce07dfdda5a..af31c14789a 100644 --- a/homeassistant/components/sensor/hive.py +++ b/homeassistant/components/sensor/hive.py @@ -1,52 +1,52 @@ -""" -Support for the Hive devices. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.hive/ -""" -from homeassistant.components.hive import DATA_HIVE -from homeassistant.helpers.entity import Entity - -DEPENDENCIES = ['hive'] - - -def setup_platform(hass, config, add_devices, discovery_info=None): - """Set up Hive sensor devices.""" - if discovery_info is None: - return - session = hass.data.get(DATA_HIVE) - - if discovery_info["HA_DeviceType"] == "Hub_OnlineStatus": - add_devices([HiveSensorEntity(session, discovery_info)]) - - -class HiveSensorEntity(Entity): - """Hive Sensor Entity.""" - - def __init__(self, hivesession, hivedevice): - """Initialize the sensor.""" - self.node_id = hivedevice["Hive_NodeID"] - self.device_type = hivedevice["HA_DeviceType"] - self.session = hivesession - self.data_updatesource = '{}.{}'.format(self.device_type, - self.node_id) - self.session.entities.append(self) - - def handle_update(self, updatesource): - """Handle the new update request.""" - if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: - self.schedule_update_ha_state() - - @property - def name(self): - """Return the name of the sensor.""" - return "Hive hub status" - - @property - def state(self): - """Return the state of the sensor.""" - return self.session.sensor.hub_online_status(self.node_id) - - def update(self): - """Update all Node data frome Hive.""" - self.session.core.update_data(self.node_id) +""" +Support for the Hive devices. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.hive/ +""" +from homeassistant.components.hive import DATA_HIVE +from homeassistant.helpers.entity import Entity + +DEPENDENCIES = ['hive'] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up Hive sensor devices.""" + if discovery_info is None: + return + session = hass.data.get(DATA_HIVE) + + if discovery_info["HA_DeviceType"] == "Hub_OnlineStatus": + add_devices([HiveSensorEntity(session, discovery_info)]) + + +class HiveSensorEntity(Entity): + """Hive Sensor Entity.""" + + def __init__(self, hivesession, hivedevice): + """Initialize the sensor.""" + self.node_id = hivedevice["Hive_NodeID"] + self.device_type = hivedevice["HA_DeviceType"] + self.session = hivesession + self.data_updatesource = '{}.{}'.format(self.device_type, + self.node_id) + self.session.entities.append(self) + + def handle_update(self, updatesource): + """Handle the new update request.""" + if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: + self.schedule_update_ha_state() + + @property + def name(self): + """Return the name of the sensor.""" + return "Hive hub status" + + @property + def state(self): + """Return the state of the sensor.""" + return self.session.sensor.hub_online_status(self.node_id) + + def update(self): + """Update all Node data frome Hive.""" + self.session.core.update_data(self.node_id) diff --git a/homeassistant/components/switch/hive.py b/homeassistant/components/switch/hive.py index d77247a5c04..97d4320280d 100644 --- a/homeassistant/components/switch/hive.py +++ b/homeassistant/components/switch/hive.py @@ -1,69 +1,69 @@ -""" -Support for the Hive devices. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/switch.hive/ -""" -from homeassistant.components.switch import SwitchDevice -from homeassistant.components.hive import DATA_HIVE - -DEPENDENCIES = ['hive'] - - -def setup_platform(hass, config, add_devices, discovery_info=None): - """Set up Hive switches.""" - if discovery_info is None: - return - session = hass.data.get(DATA_HIVE) - - add_devices([HiveDevicePlug(session, discovery_info)]) - - -class HiveDevicePlug(SwitchDevice): - """Hive Active Plug.""" - - def __init__(self, hivesession, hivedevice): - """Initialize the Switch device.""" - self.node_id = hivedevice["Hive_NodeID"] - self.node_name = hivedevice["Hive_NodeName"] - self.device_type = hivedevice["HA_DeviceType"] - self.session = hivesession - self.data_updatesource = '{}.{}'.format(self.device_type, - self.node_id) - self.session.entities.append(self) - - def handle_update(self, updatesource): - """Handle the new update request.""" - if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: - self.schedule_update_ha_state() - - @property - def name(self): - """Return the name of this Switch device if any.""" - return self.node_name - - @property - def current_power_w(self): - """Return the current power usage in W.""" - return self.session.switch.get_power_usage(self.node_id) - - @property - def is_on(self): - """Return true if switch is on.""" - return self.session.switch.get_state(self.node_id) - - def turn_on(self, **kwargs): - """Turn the switch on.""" - self.session.switch.turn_on(self.node_id) - for entity in self.session.entities: - entity.handle_update(self.data_updatesource) - - def turn_off(self, **kwargs): - """Turn the device off.""" - self.session.switch.turn_off(self.node_id) - for entity in self.session.entities: - entity.handle_update(self.data_updatesource) - - def update(self): - """Update all Node data frome Hive.""" - self.session.core.update_data(self.node_id) +""" +Support for the Hive devices. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.hive/ +""" +from homeassistant.components.switch import SwitchDevice +from homeassistant.components.hive import DATA_HIVE + +DEPENDENCIES = ['hive'] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up Hive switches.""" + if discovery_info is None: + return + session = hass.data.get(DATA_HIVE) + + add_devices([HiveDevicePlug(session, discovery_info)]) + + +class HiveDevicePlug(SwitchDevice): + """Hive Active Plug.""" + + def __init__(self, hivesession, hivedevice): + """Initialize the Switch device.""" + self.node_id = hivedevice["Hive_NodeID"] + self.node_name = hivedevice["Hive_NodeName"] + self.device_type = hivedevice["HA_DeviceType"] + self.session = hivesession + self.data_updatesource = '{}.{}'.format(self.device_type, + self.node_id) + self.session.entities.append(self) + + def handle_update(self, updatesource): + """Handle the new update request.""" + if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: + self.schedule_update_ha_state() + + @property + def name(self): + """Return the name of this Switch device if any.""" + return self.node_name + + @property + def current_power_w(self): + """Return the current power usage in W.""" + return self.session.switch.get_power_usage(self.node_id) + + @property + def is_on(self): + """Return true if switch is on.""" + return self.session.switch.get_state(self.node_id) + + def turn_on(self, **kwargs): + """Turn the switch on.""" + self.session.switch.turn_on(self.node_id) + for entity in self.session.entities: + entity.handle_update(self.data_updatesource) + + def turn_off(self, **kwargs): + """Turn the device off.""" + self.session.switch.turn_off(self.node_id) + for entity in self.session.entities: + entity.handle_update(self.data_updatesource) + + def update(self): + """Update all Node data frome Hive.""" + self.session.core.update_data(self.node_id) From d219f244d2c4bba97b3161fb9e156a5f22da2f03 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 15 Jan 2018 23:24:34 +0100 Subject: [PATCH 037/150] Upgrade sqlalchemy to 1.2.1 (#11666) --- homeassistant/components/recorder/__init__.py | 40 +++++++++---------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 51da2d470ea..1c9524223e5 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -8,33 +8,33 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/recorder/ """ import asyncio +from collections import namedtuple import concurrent.futures +from datetime import datetime, timedelta import logging import queue import threading import time -from collections import namedtuple -from datetime import datetime, timedelta -from typing import Optional, Dict + +from typing import Dict, Optional import voluptuous as vol -from homeassistant.core import ( - HomeAssistant, callback, CoreState) from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_ENTITIES, CONF_EXCLUDE, CONF_DOMAINS, - CONF_INCLUDE, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START, - EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL) + ATTR_ENTITY_ID, CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_INCLUDE, + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, + EVENT_TIME_CHANGED, MATCH_ALL) +from homeassistant.core import CoreState, HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import generate_filter from homeassistant.helpers.typing import ConfigType import homeassistant.util.dt as dt_util -from . import purge, migration +from . import migration, purge from .const import DATA_INSTANCE from .util import session_scope -REQUIREMENTS = ['sqlalchemy==1.2.0'] +REQUIREMENTS = ['sqlalchemy==1.2.1'] _LOGGER = logging.getLogger(__name__) @@ -140,9 +140,9 @@ def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Handle calls to the purge service.""" instance.do_adhoc_purge(service.data[ATTR_KEEP_DAYS]) - hass.services.async_register(DOMAIN, SERVICE_PURGE, - async_handle_purge_service, - schema=SERVICE_PURGE_SCHEMA) + hass.services.async_register( + DOMAIN, SERVICE_PURGE, async_handle_purge_service, + schema=SERVICE_PURGE_SCHEMA) return (yield from instance.async_db_ready) @@ -169,10 +169,9 @@ class Recorder(threading.Thread): self.engine = None # type: Any self.run_info = None # type: Any - self.entity_filter = generate_filter(include.get(CONF_DOMAINS, []), - include.get(CONF_ENTITIES, []), - exclude.get(CONF_DOMAINS, []), - exclude.get(CONF_ENTITIES, [])) + self.entity_filter = generate_filter( + include.get(CONF_DOMAINS, []), include.get(CONF_ENTITIES, []), + exclude.get(CONF_DOMAINS, []), exclude.get(CONF_ENTITIES, [])) self.exclude_t = exclude.get(CONF_EVENT_TYPES, []) self.get_session = None @@ -238,8 +237,7 @@ class Recorder(threading.Thread): self.queue.put(None) self.join() - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, - shutdown) + self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown) if self.hass.state == CoreState.running: hass_started.set_result(None) @@ -249,8 +247,8 @@ class Recorder(threading.Thread): """Notify that hass has started.""" hass_started.set_result(None) - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, - notify_hass_started) + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_START, notify_hass_started) if self.keep_days and self.purge_interval: @callback diff --git a/requirements_all.txt b/requirements_all.txt index 58b28b6a73d..ee6de0be56c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1098,7 +1098,7 @@ speedtest-cli==1.0.7 # homeassistant.components.recorder # homeassistant.scripts.db_migrator -sqlalchemy==1.2.0 +sqlalchemy==1.2.1 # homeassistant.components.statsd statsd==3.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cbd84af48a5..a7501f3e618 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -165,7 +165,7 @@ somecomfort==0.5.0 # homeassistant.components.recorder # homeassistant.scripts.db_migrator -sqlalchemy==1.2.0 +sqlalchemy==1.2.1 # homeassistant.components.statsd statsd==3.2.1 From fb69620e494e805a1876651e3bc1b39e5274f9c7 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 15 Jan 2018 23:25:00 +0100 Subject: [PATCH 038/150] Upgrade pylast to 2.1.0 (#11668) --- homeassistant/components/sensor/lastfm.py | 4 ++-- requirements_all.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/lastfm.py b/homeassistant/components/sensor/lastfm.py index 8f3ac8ebb70..9d305973ecf 100644 --- a/homeassistant/components/sensor/lastfm.py +++ b/homeassistant/components/sensor/lastfm.py @@ -9,11 +9,11 @@ import re import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.helpers.entity import Entity from homeassistant.const import CONF_API_KEY import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['pylast==2.0.0'] +REQUIREMENTS = ['pylast==2.1.0'] ATTR_LAST_PLAYED = 'last_played' ATTR_PLAY_COUNT = 'play_count' diff --git a/requirements_all.txt b/requirements_all.txt index ee6de0be56c..c055ddbba15 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -756,7 +756,7 @@ pykwb==0.0.8 pylacrosse==0.3.1 # homeassistant.components.sensor.lastfm -pylast==2.0.0 +pylast==2.1.0 # homeassistant.components.media_player.webostv # homeassistant.components.notify.webostv From ff32f90a2968170f36870c54e1700c01ad6939b9 Mon Sep 17 00:00:00 2001 From: Russell Cloran Date: Mon, 15 Jan 2018 14:25:59 -0800 Subject: [PATCH 039/150] Fix zha color probe (#11670) --- homeassistant/components/light/zha.py | 59 ++++++++---------------- homeassistant/components/zha/__init__.py | 18 ++++++++ 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/light/zha.py b/homeassistant/components/light/zha.py index 2aad486a894..c468d50ce6d 100644 --- a/homeassistant/components/light/zha.py +++ b/homeassistant/components/light/zha.py @@ -31,42 +31,23 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): return endpoint = discovery_info['endpoint'] - try: - discovery_info['color_capabilities'] \ - = yield from endpoint.light_color['color_capabilities'] - except (AttributeError, KeyError): - pass - - if discovery_info.get('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 = yield from safe_read( - endpoint.light_color, ['color_temperature']) - if result.get('color_temperature') is not UNSUPPORTED_ATTRIBUTE: - discovery_info['color_capabilities'] |= CAPABILITIES_COLOR_TEMP + if hasattr(endpoint, 'light_color'): + caps = yield from zha.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 = yield from zha.safe_read( + endpoint.light_color, ['color_temperature']) + if result.get('color_temperature') is not UNSUPPORTED_ATTRIBUTE: + discovery_info['color_capabilities'] |= CAPABILITIES_COLOR_TEMP async_add_devices([Light(**discovery_info)], update_before_add=True) -@asyncio.coroutine -def safe_read(cluster, attributes): - """Swallow all exceptions from network read. - - If we throw during initialization, setup fails. Rather have an - entity that exists, but is in a maybe wrong state, than no entity. - """ - try: - result, _ = yield from cluster.read_attributes( - attributes, - allow_cache=False, - ) - return result - except Exception: # pylint: disable=broad-except - return {} - - class Light(zha.Entity, light.Light): """Representation of a ZHA or ZLL light.""" @@ -174,23 +155,23 @@ class Light(zha.Entity, light.Light): @asyncio.coroutine def async_update(self): """Retrieve latest state.""" - result = yield from safe_read(self._endpoint.on_off, ['on_off']) + result = yield from zha.safe_read(self._endpoint.on_off, ['on_off']) self._state = result.get('on_off', self._state) if self._supported_features & light.SUPPORT_BRIGHTNESS: - result = yield from safe_read(self._endpoint.level, - ['current_level']) + result = yield from zha.safe_read(self._endpoint.level, + ['current_level']) self._brightness = result.get('current_level', self._brightness) if self._supported_features & light.SUPPORT_COLOR_TEMP: - result = yield from safe_read(self._endpoint.light_color, - ['color_temperature']) + result = yield from zha.safe_read(self._endpoint.light_color, + ['color_temperature']) self._color_temp = result.get('color_temperature', self._color_temp) if self._supported_features & light.SUPPORT_XY_COLOR: - result = yield from safe_read(self._endpoint.light_color, - ['current_x', 'current_y']) + result = yield from zha.safe_read(self._endpoint.light_color, + ['current_x', 'current_y']) if 'current_x' in result and 'current_y' in result: self._xy_color = (result['current_x'], result['current_y']) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index a361fca9832..f87537a1938 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -315,3 +315,21 @@ def get_discovery_info(hass, discovery_info): all_discovery_info = hass.data.get(DISCOVERY_KEY, {}) discovery_info = all_discovery_info.get(discovery_key, None) return discovery_info + + +@asyncio.coroutine +def safe_read(cluster, attributes): + """Swallow all exceptions from network read. + + If we throw during initialization, setup fails. Rather have an entity that + exists, but is in a maybe wrong state, than no entity. This method should + probably only be used during initialization. + """ + try: + result, _ = yield from cluster.read_attributes( + attributes, + allow_cache=False, + ) + return result + except Exception: # pylint: disable=broad-except + return {} From a35817453666bd595bcc5dec012d8ee243ca92b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Mon, 15 Jan 2018 23:26:27 +0100 Subject: [PATCH 040/150] Rfxtrx fix (#11678) * fix voluptuous bug in rfxtrx * fix voluptuous bug in rfxtrx * fix voluptuous bug in rfxtrx --- .../components/binary_sensor/rfxtrx.py | 21 ++++++++++--------- homeassistant/components/rfxtrx.py | 7 ++++--- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/binary_sensor/rfxtrx.py b/homeassistant/components/binary_sensor/rfxtrx.py index 4073cb9eac1..1c283ad214a 100644 --- a/homeassistant/components/binary_sensor/rfxtrx.py +++ b/homeassistant/components/binary_sensor/rfxtrx.py @@ -18,9 +18,8 @@ from homeassistant.helpers import config_validation as cv from homeassistant.components.binary_sensor import ( BinarySensorDevice, PLATFORM_SCHEMA) from homeassistant.components.rfxtrx import ( - ATTR_NAME, ATTR_DATA_BITS, ATTR_OFF_DELAY, ATTR_FIRE_EVENT, - CONF_AUTOMATIC_ADD, CONF_FIRE_EVENT, - CONF_DATA_BITS, CONF_DEVICES) + ATTR_NAME, CONF_AUTOMATIC_ADD, CONF_FIRE_EVENT, + CONF_OFF_DELAY, CONF_DATA_BITS, CONF_DEVICES) from homeassistant.util import slugify from homeassistant.util import dt as dt_util @@ -35,9 +34,11 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_DEVICE_CLASS): cv.string, vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean, - 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_OFF_DELAY, default=None): + vol.Any(cv.time_period, cv.positive_timedelta), + vol.Optional(CONF_DATA_BITS, default=None): cv.positive_int, + vol.Optional(CONF_COMMAND_ON, default=None): cv.byte, + vol.Optional(CONF_COMMAND_OFF, default=None): cv.byte }) }, vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean, @@ -59,16 +60,16 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): if entity[CONF_DATA_BITS] is not None: _LOGGER.debug("Masked device id: %s", rfxtrx.get_pt2262_deviceid(device_id, - entity[ATTR_DATA_BITS])) + entity[CONF_DATA_BITS])) _LOGGER.debug("Add %s rfxtrx.binary_sensor (class %s)", entity[ATTR_NAME], entity[CONF_DEVICE_CLASS]) device = RfxtrxBinarySensor(event, entity[ATTR_NAME], entity[CONF_DEVICE_CLASS], - entity[ATTR_FIRE_EVENT], - entity[ATTR_OFF_DELAY], - entity[ATTR_DATA_BITS], + entity[CONF_FIRE_EVENT], + entity[CONF_OFF_DELAY], + entity[CONF_DATA_BITS], entity[CONF_COMMAND_ON], entity[CONF_COMMAND_OFF]) device.hass = hass diff --git a/homeassistant/components/rfxtrx.py b/homeassistant/components/rfxtrx.py index f28a9aafb19..4994e333eda 100644 --- a/homeassistant/components/rfxtrx.py +++ b/homeassistant/components/rfxtrx.py @@ -33,9 +33,8 @@ ATTR_STATE = 'state' ATTR_NAME = 'name' ATTR_FIRE_EVENT = 'fire_event' ATTR_DATA_TYPE = 'data_type' -ATTR_DATA_BITS = 'data_bits' ATTR_DUMMY = 'dummy' -ATTR_OFF_DELAY = 'off_delay' +CONF_DATA_BITS = 'data_bits' CONF_AUTOMATIC_ADD = 'automatic_add' CONF_DATA_TYPE = 'data_type' CONF_SIGNAL_REPETITIONS = 'signal_repetitions' @@ -44,6 +43,7 @@ CONF_DATA_BITS = 'data_bits' CONF_DUMMY = 'dummy' CONF_DEVICE = 'device' CONF_DEBUG = 'debug' +CONF_OFF_DELAY = 'off_delay' EVENT_BUTTON_PRESSED = 'button_pressed' DATA_TYPES = OrderedDict([ @@ -143,12 +143,13 @@ def get_rfx_object(packetid): def get_pt2262_deviceid(device_id, nb_data_bits): """Extract and return the address bits from a Lighting4/PT2262 packet.""" + if nb_data_bits is None: + return import binascii try: data = bytearray.fromhex(device_id) except ValueError: return None - mask = 0xFF & ~((1 << nb_data_bits) - 1) data[len(data)-1] &= mask From 87534692d04d85ef9b9462a89f6030061db9d32a Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Mon, 15 Jan 2018 23:53:56 +0100 Subject: [PATCH 041/150] Move several local services to their right domain (#11677) * Move several local services to their right domain * Fix lint --- homeassistant/components/calendar/todoist.py | 5 ++--- homeassistant/components/media_player/snapcast.py | 8 ++++---- homeassistant/components/media_player/soundtouch.py | 3 +-- homeassistant/components/sensor/mopar.py | 5 ++--- homeassistant/components/switch/broadlink.py | 8 ++++---- homeassistant/components/switch/scsgate.py | 2 -- 6 files changed, 13 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/calendar/todoist.py b/homeassistant/components/calendar/todoist.py index ceab1e98dd4..ecf8bfb7cf7 100644 --- a/homeassistant/components/calendar/todoist.py +++ b/homeassistant/components/calendar/todoist.py @@ -13,7 +13,7 @@ import logging import voluptuous as vol from homeassistant.components.calendar import ( - CalendarEventDevice, PLATFORM_SCHEMA) + CalendarEventDevice, DOMAIN, PLATFORM_SCHEMA) from homeassistant.components.google import ( CONF_DEVICE_ID) from homeassistant.const import ( @@ -26,7 +26,6 @@ from homeassistant.util import Throttle REQUIREMENTS = ['todoist-python==7.0.17'] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'todoist' # Calendar Platform: Does this calendar event last all day? ALL_DAY = 'all_day' @@ -78,7 +77,7 @@ SUMMARY = 'summary' # Todoist API: Fetch all Tasks TASKS = 'items' -SERVICE_NEW_TASK = 'new_task' +SERVICE_NEW_TASK = 'todoist_new_task' NEW_TASK_SERVICE_SCHEMA = vol.Schema({ vol.Required(CONTENT): cv.string, vol.Optional(PROJECT_NAME, default='inbox'): vol.All(cv.string, vol.Lower), diff --git a/homeassistant/components/media_player/snapcast.py b/homeassistant/components/media_player/snapcast.py index 220f1691c52..2413de136ab 100644 --- a/homeassistant/components/media_player/snapcast.py +++ b/homeassistant/components/media_player/snapcast.py @@ -12,7 +12,7 @@ import voluptuous as vol from homeassistant.components.media_player import ( SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_SELECT_SOURCE, - PLATFORM_SCHEMA, MediaPlayerDevice) + DOMAIN, PLATFORM_SCHEMA, MediaPlayerDevice) from homeassistant.const import ( STATE_ON, STATE_OFF, STATE_IDLE, STATE_PLAYING, STATE_UNKNOWN, CONF_HOST, CONF_PORT, ATTR_ENTITY_ID) @@ -22,7 +22,7 @@ REQUIREMENTS = ['snapcast==2.0.8'] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'snapcast' +DATA_KEY = 'snapcast' SERVICE_SNAPSHOT = 'snapcast_snapshot' SERVICE_RESTORE = 'snapcast_restore' @@ -59,7 +59,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): def _handle_service(service): """Handle services.""" entity_ids = service.data.get(ATTR_ENTITY_ID) - devices = [device for device in hass.data[DOMAIN] + devices = [device for device in hass.data[DATA_KEY] if device.entity_id in entity_ids] for device in devices: if service.service == SERVICE_SNAPSHOT: @@ -84,7 +84,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): groups = [SnapcastGroupDevice(group) for group in server.groups] clients = [SnapcastClientDevice(client) for client in server.clients] devices = groups + clients - hass.data[DOMAIN] = devices + hass.data[DATA_KEY] = devices async_add_devices(devices) return True diff --git a/homeassistant/components/media_player/soundtouch.py b/homeassistant/components/media_player/soundtouch.py index 790ad8b8e29..e4c3fa623c9 100644 --- a/homeassistant/components/media_player/soundtouch.py +++ b/homeassistant/components/media_player/soundtouch.py @@ -14,7 +14,7 @@ from homeassistant.components.media_player import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, SUPPORT_VOLUME_SET, SUPPORT_TURN_ON, SUPPORT_PLAY, MediaPlayerDevice, - PLATFORM_SCHEMA) + DOMAIN, PLATFORM_SCHEMA) from homeassistant.const import (CONF_HOST, CONF_NAME, STATE_OFF, CONF_PORT, STATE_PAUSED, STATE_PLAYING, STATE_UNAVAILABLE) @@ -23,7 +23,6 @@ REQUIREMENTS = ['libsoundtouch==0.7.2'] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'media_player' SERVICE_PLAY_EVERYWHERE = 'soundtouch_play_everywhere' SERVICE_CREATE_ZONE = 'soundtouch_create_zone' SERVICE_ADD_ZONE_SLAVE = 'soundtouch_add_zone_slave' diff --git a/homeassistant/components/sensor/mopar.py b/homeassistant/components/sensor/mopar.py index 66eea20ec70..fdf6c9132d5 100644 --- a/homeassistant/components/sensor/mopar.py +++ b/homeassistant/components/sensor/mopar.py @@ -9,7 +9,7 @@ from datetime import timedelta import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import DOMAIN, PLATFORM_SCHEMA from homeassistant.helpers.entity import Entity from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, CONF_PIN, ATTR_ATTRIBUTION, ATTR_COMMAND, @@ -23,9 +23,8 @@ REQUIREMENTS = ['motorparts==1.0.2'] _LOGGER = logging.getLogger(__name__) MIN_TIME_BETWEEN_UPDATES = timedelta(days=7) -DOMAIN = 'mopar' ATTR_VEHICLE_INDEX = 'vehicle_index' -SERVICE_REMOTE_COMMAND = 'remote_command' +SERVICE_REMOTE_COMMAND = 'mopar_remote_command' COOKIE_FILE = 'mopar_cookies.pickle' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ diff --git a/homeassistant/components/switch/broadlink.py b/homeassistant/components/switch/broadlink.py index 8abdba31b67..bd2ebc1704f 100644 --- a/homeassistant/components/switch/broadlink.py +++ b/homeassistant/components/switch/broadlink.py @@ -15,7 +15,8 @@ import voluptuous as vol from homeassistant.util.dt import utcnow from homeassistant.util import Throttle -from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) +from homeassistant.components.switch import ( + SwitchDevice, DOMAIN, PLATFORM_SCHEMA) from homeassistant.const import ( CONF_FRIENDLY_NAME, CONF_SWITCHES, CONF_COMMAND_OFF, CONF_COMMAND_ON, @@ -28,12 +29,11 @@ _LOGGER = logging.getLogger(__name__) TIME_BETWEEN_UPDATES = timedelta(seconds=5) -DOMAIN = 'broadlink' DEFAULT_NAME = 'Broadlink switch' DEFAULT_TIMEOUT = 10 DEFAULT_RETRY = 3 -SERVICE_LEARN = 'learn_command' -SERVICE_SEND = 'send_packet' +SERVICE_LEARN = 'broadlink_learn_command' +SERVICE_SEND = 'broadlink_send_packet' CONF_SLOTS = 'slots' RM_TYPES = ['rm', 'rm2', 'rm_mini', 'rm_pro_phicomm', 'rm2_home_plus', diff --git a/homeassistant/components/switch/scsgate.py b/homeassistant/components/switch/scsgate.py index 7c22e0d5a88..dfcf1816b7b 100644 --- a/homeassistant/components/switch/scsgate.py +++ b/homeassistant/components/switch/scsgate.py @@ -23,8 +23,6 @@ CONF_SCENARIO = 'scenario' CONF_SCS_ID = 'scs_id' -DOMAIN = 'scsgate' - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_DEVICES): vol.Schema({cv.slug: scsgate.SCSGATE_SCHEMA}), }) From 98692523bfd51374c9de6ddaf343e4269c29cc21 Mon Sep 17 00:00:00 2001 From: karlkar Date: Tue, 16 Jan 2018 09:23:48 +0100 Subject: [PATCH 042/150] Added extra arguments to onvif platform config (#11680) --- homeassistant/components/camera/onvif.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/camera/onvif.py b/homeassistant/components/camera/onvif.py index 8f30d9c8b8f..65f291bf41d 100644 --- a/homeassistant/components/camera/onvif.py +++ b/homeassistant/components/camera/onvif.py @@ -14,7 +14,7 @@ from homeassistant.const import ( CONF_NAME, CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT) from homeassistant.components.camera import Camera, PLATFORM_SCHEMA from homeassistant.components.ffmpeg import ( - DATA_FFMPEG) + DATA_FFMPEG, CONF_EXTRA_ARGUMENTS) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.aiohttp_client import ( async_aiohttp_proxy_stream) @@ -31,6 +31,7 @@ DEFAULT_NAME = 'ONVIF Camera' DEFAULT_PORT = 5000 DEFAULT_USERNAME = 'admin' DEFAULT_PASSWORD = '888888' +DEFAULT_ARGUMENTS = '-q:v 2' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, @@ -38,6 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_EXTRA_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string, }) @@ -59,7 +61,7 @@ class ONVIFCamera(Camera): super().__init__() self._name = config.get(CONF_NAME) - self._ffmpeg_arguments = '-q:v 2' + self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS) media = ONVIFService( 'http://{}:{}/onvif/device_service'.format( config.get(CONF_HOST), config.get(CONF_PORT)), From 632525f4d0d87678ac5d8dd8a7be991d6eb73edf Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 16 Jan 2018 09:25:13 +0100 Subject: [PATCH 043/150] Enable probot move (#11690) --- .github/move.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/move.yml diff --git a/.github/move.yml b/.github/move.yml new file mode 100644 index 00000000000..e041083c9ae --- /dev/null +++ b/.github/move.yml @@ -0,0 +1,13 @@ +# Configuration for move-issues - https://github.com/dessant/move-issues + +# Delete the command comment. Ignored when the comment also contains other content +deleteCommand: true +# Close the source issue after moving +closeSourceIssue: true +# Lock the source issue after moving +lockSourceIssue: false +# Set custom aliases for targets +# aliases: +# r: repo +# or: owner/repo + From dce079e7113ad26a69cdd848f29868af5deab3c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85skar=20Andersson?= Date: Tue, 16 Jan 2018 10:59:20 +0100 Subject: [PATCH 044/150] fixed not to include spaces or dots in attribute names (#11694) mold_indicator should be fixed not to include spaces or dots in attribute names https://community.home-assistant.io/t/how-to-show-sensor-attribute-est-crit-temp-in-customize-template-and-extra-badge/39451 --- homeassistant/components/sensor/mold_indicator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/mold_indicator.py b/homeassistant/components/sensor/mold_indicator.py index ccebd93dbec..2d233ec8c70 100644 --- a/homeassistant/components/sensor/mold_indicator.py +++ b/homeassistant/components/sensor/mold_indicator.py @@ -19,7 +19,7 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -ATTR_CRITICAL_TEMP = 'Est. Crit. Temp' +ATTR_CRITICAL_TEMP = 'EstimatedCriticalTemp' ATTR_DEWPOINT = 'Dewpoint' CONF_CALIBRATION_FACTOR = 'calibration_factor' From d5f63ebac42cf61f40218b69cebe02f678a39f9c Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 16 Jan 2018 12:32:08 +0100 Subject: [PATCH 045/150] Add attributes (#11698) --- homeassistant/components/sensor/random.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sensor/random.py b/homeassistant/components/sensor/random.py index b9083245218..e57bbcc3955 100644 --- a/homeassistant/components/sensor/random.py +++ b/homeassistant/components/sensor/random.py @@ -9,14 +9,17 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_MINIMUM, CONF_MAXIMUM, CONF_UNIT_OF_MEASUREMENT) + CONF_MAXIMUM, CONF_MINIMUM, CONF_NAME, CONF_UNIT_OF_MEASUREMENT) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) +ATTR_MAXIMUM = 'maximum' +ATTR_MINIMUM = 'minimum' + DEFAULT_NAME = 'Random Sensor' DEFAULT_MIN = 0 DEFAULT_MAX = 20 @@ -40,14 +43,13 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): unit = config.get(CONF_UNIT_OF_MEASUREMENT) async_add_devices([RandomSensor(name, minimum, maximum, unit)], True) - return True class RandomSensor(Entity): """Representation of a Random number sensor.""" def __init__(self, name, minimum, maximum, unit_of_measurement): - """Initialize the sensor.""" + """Initialize the Random sensor.""" self._name = name self._minimum = minimum self._maximum = maximum @@ -74,6 +76,14 @@ class RandomSensor(Entity): """Return the unit this state is expressed in.""" return self._unit_of_measurement + @property + def device_state_attributes(self): + """Return the attributes of the sensor.""" + return { + ATTR_MAXIMUM: self._maximum, + ATTR_MINIMUM: self._minimum, + } + @asyncio.coroutine def async_update(self): """Get a new number and updates the states.""" From 4a82606ffba5a5bb91418fea748367f12bce7f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Bastian=20P=C3=B6ttner?= Date: Tue, 16 Jan 2018 15:55:22 +0100 Subject: [PATCH 046/150] Feature/fritzdect errorhandling (#11490) * Update to fritzhome 1.0.4 * Improved error handling, added autoreconnect on connection failure * Move from STATE_UNKNOWN to None --- homeassistant/components/switch/fritzdect.py | 112 ++++++++++++++----- requirements_all.txt | 2 +- 2 files changed, 82 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/switch/fritzdect.py b/homeassistant/components/switch/fritzdect.py index 962a56e4bb7..8ddfca05fb6 100644 --- a/homeassistant/components/switch/fritzdect.py +++ b/homeassistant/components/switch/fritzdect.py @@ -6,15 +6,17 @@ https://home-assistant.io/components/switch.fritzdect/ """ import logging +from requests.exceptions import RequestException, HTTPError + import voluptuous as vol from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_USERNAME) import homeassistant.helpers.config_validation as cv -from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE, STATE_UNKNOWN +from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE -REQUIREMENTS = ['fritzhome==1.0.3'] +REQUIREMENTS = ['fritzhome==1.0.4'] _LOGGER = logging.getLogger(__name__) @@ -46,15 +48,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) - # Hack: fritzhome only throws Exception. To prevent pylint from - # complaining, we disable the warning here: - # pylint: disable=W0703 - # Log into Fritz Box fritz = FritzBox(host, username, password) try: fritz.login() - except Exception: + except Exception: # pylint: disable=W0703 _LOGGER.error("Login to Fritz!Box failed") return @@ -63,6 +61,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # Only add devices that support switching if actor.has_switch: data = FritzDectSwitchData(fritz, actor.actor_id) + data.is_online = True add_devices([FritzDectSwitch(hass, data, actor.name)], True) @@ -86,8 +85,8 @@ class FritzDectSwitch(SwitchDevice): attrs = {} if self.data.has_powermeter and \ - self.data.current_consumption != STATE_UNKNOWN and \ - self.data.total_consumption != STATE_UNKNOWN: + self.data.current_consumption is not None and \ + self.data.total_consumption is not None: attrs[ATTR_CURRENT_CONSUMPTION] = "{:.1f}".format( self.data.current_consumption) attrs[ATTR_CURRENT_CONSUMPTION_UNIT] = "{}".format( @@ -98,7 +97,7 @@ class FritzDectSwitch(SwitchDevice): ATTR_TOTAL_CONSUMPTION_UNIT_VALUE) if self.data.has_temperature and \ - self.data.temperature != STATE_UNKNOWN: + self.data.temperature is not None: attrs[ATTR_TEMPERATURE] = "{}".format( self.units.temperature(self.data.temperature, TEMP_CELSIUS)) attrs[ATTR_TEMPERATURE_UNIT] = "{}".format( @@ -120,17 +119,48 @@ class FritzDectSwitch(SwitchDevice): def turn_on(self, **kwargs): """Turn the switch on.""" - actor = self.data.fritz.get_actor_by_ain(self.data.ain) - actor.switch_on() + if not self.data.is_online: + _LOGGER.error("turn_on: Not online skipping request") + return + + try: + actor = self.data.fritz.get_actor_by_ain(self.data.ain) + actor.switch_on() + except (RequestException, HTTPError): + _LOGGER.error("Fritz!Box query failed, triggering relogin") + self.data.is_online = False def turn_off(self): """Turn the switch off.""" - actor = self.data.fritz.get_actor_by_ain(self.data.ain) - actor.switch_off() + if not self.data.is_online: + _LOGGER.error("turn_off: Not online skipping request") + return + + try: + actor = self.data.fritz.get_actor_by_ain(self.data.ain) + actor.switch_off() + except (RequestException, HTTPError): + _LOGGER.error("Fritz!Box query failed, triggering relogin") + self.data.is_online = False def update(self): """Get the latest data from the fritz box and updates the states.""" - self.data.update() + if not self.data.is_online: + _LOGGER.error("update: Not online, logging back in") + + try: + self.data.fritz.login() + except Exception: # pylint: disable=broad-except + _LOGGER.error("Login to Fritz!Box failed") + return + + self.data.is_online = True + + try: + self.data.update() + except Exception: # pylint: disable=broad-except + _LOGGER.error("Fritz!Box query failed, triggering relogin") + self.data.is_online = False class FritzDectSwitchData(object): @@ -140,32 +170,52 @@ class FritzDectSwitchData(object): """Initialize the data object.""" self.fritz = fritz self.ain = ain - self.state = STATE_UNKNOWN - self.temperature = STATE_UNKNOWN - self.current_consumption = STATE_UNKNOWN - self.total_consumption = STATE_UNKNOWN - self.has_switch = STATE_UNKNOWN - self.has_temperature = STATE_UNKNOWN - self.has_powermeter = STATE_UNKNOWN + self.state = None + self.temperature = None + self.current_consumption = None + self.total_consumption = None + self.has_switch = False + self.has_temperature = False + self.has_powermeter = False + self.is_online = False def update(self): """Get the latest data from the fritz box.""" - from requests.exceptions import RequestException + if not self.is_online: + _LOGGER.error("Not online skipping request") + return try: actor = self.fritz.get_actor_by_ain(self.ain) + except (RequestException, HTTPError): + _LOGGER.error("Request to actor registry failed") + self.state = None + self.temperature = None + self.current_consumption = None + self.total_consumption = None + raise Exception('Request to actor registry failed') + + if actor is None: + _LOGGER.error("Actor could not be found") + self.state = None + self.temperature = None + self.current_consumption = None + self.total_consumption = None + raise Exception('Actor could not be found') + + try: self.state = actor.get_state() - except RequestException: + self.current_consumption = (actor.get_power() or 0.0) / 1000 + self.total_consumption = (actor.get_energy() or 0.0) / 100000 + except (RequestException, HTTPError): _LOGGER.error("Request to actor failed") - self.state = STATE_UNKNOWN - self.temperature = STATE_UNKNOWN - self.current_consumption = STATE_UNKNOWN - self.total_consumption = STATE_UNKNOWN - return + self.state = None + self.temperature = None + self.current_consumption = None + self.total_consumption = None + raise Exception('Request to actor failed') self.temperature = actor.temperature - self.current_consumption = (actor.get_power() or 0.0) / 1000 - self.total_consumption = (actor.get_energy() or 0.0) / 100000 self.has_switch = actor.has_switch self.has_temperature = actor.has_temperature self.has_powermeter = actor.has_powermeter diff --git a/requirements_all.txt b/requirements_all.txt index c055ddbba15..8ff20e0cb58 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -286,7 +286,7 @@ freesms==0.1.2 # fritzconnection==0.6.5 # homeassistant.components.switch.fritzdect -fritzhome==1.0.3 +fritzhome==1.0.4 # homeassistant.components.media_player.frontier_silicon fsapi==0.0.7 From 3a00077305ca5a9526913fad8d0958b2ff2ac230 Mon Sep 17 00:00:00 2001 From: Sergey Isachenko Date: Tue, 16 Jan 2018 19:34:41 +0300 Subject: [PATCH 047/150] Tesla bug fix #11598 (#11707) --- homeassistant/components/tesla.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/tesla.py b/homeassistant/components/tesla.py index 86dc9c86792..64ffe40d428 100644 --- a/homeassistant/components/tesla.py +++ b/homeassistant/components/tesla.py @@ -15,7 +15,7 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import slugify -REQUIREMENTS = ['teslajsonpy==0.0.18'] +REQUIREMENTS = ['teslajsonpy==0.0.19'] DOMAIN = 'tesla' diff --git a/requirements_all.txt b/requirements_all.txt index 8ff20e0cb58..efe35f11049 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1131,7 +1131,7 @@ tellduslive==0.10.4 temperusb==1.5.3 # homeassistant.components.tesla -teslajsonpy==0.0.18 +teslajsonpy==0.0.19 # homeassistant.components.thingspeak thingspeak==0.4.1 From 94950cccc8eb4c650ed37a11b0b6fcf099d18c76 Mon Sep 17 00:00:00 2001 From: Bob Anderson Date: Tue, 16 Jan 2018 11:48:10 -0800 Subject: [PATCH 048/150] History order bugfix and opt-in option (#11686) * make history view re-ordering optional and opt-in, also fix type bug * use python false for default value * whitespace cleanup --- homeassistant/components/history.py | 38 ++++++++++++++++++----------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/history.py b/homeassistant/components/history.py index 8f96d95521d..8f58f5f7e17 100644 --- a/homeassistant/components/history.py +++ b/homeassistant/components/history.py @@ -20,14 +20,19 @@ from homeassistant.components import recorder, script from homeassistant.components.http import HomeAssistantView from homeassistant.const import ATTR_HIDDEN from homeassistant.components.recorder.util import session_scope, execute +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) DOMAIN = 'history' DEPENDENCIES = ['recorder', 'http'] +CONF_ORDER = 'use_include_order' + CONFIG_SCHEMA = vol.Schema({ - DOMAIN: recorder.FILTER_SCHEMA, + DOMAIN: recorder.FILTER_SCHEMA.extend({ + vol.Optional(CONF_ORDER, default=False): cv.boolean, + }) }, extra=vol.ALLOW_EXTRA) SIGNIFICANT_DOMAINS = ('thermostat', 'climate') @@ -242,8 +247,9 @@ def async_setup(hass, config): if include: filters.included_entities = include[CONF_ENTITIES] filters.included_domains = include[CONF_DOMAINS] + use_include_order = config[DOMAIN].get(CONF_ORDER) - hass.http.register_view(HistoryPeriodView(filters)) + hass.http.register_view(HistoryPeriodView(filters, use_include_order)) yield from hass.components.frontend.async_register_built_in_panel( 'history', 'history', 'mdi:poll-box') @@ -257,9 +263,10 @@ class HistoryPeriodView(HomeAssistantView): name = 'api:history:view-period' extra_urls = ['/api/history/period/{datetime}'] - def __init__(self, filters): + def __init__(self, filters, use_include_order): """Initialize the history period view.""" self.filters = filters + self.use_include_order = use_include_order @asyncio.coroutine def get(self, request, datetime=None): @@ -305,19 +312,22 @@ class HistoryPeriodView(HomeAssistantView): _LOGGER.debug( 'Extracted %d states in %fs', sum(map(len, result)), elapsed) - # Reorder the result to respect the ordering given by any - # entities explicitly included in the configuration. + # Optionally reorder the result to respect the ordering given + # by any entities explicitly included in the configuration. - sorted_result = [] - for order_entity in self.filters.included_entities: - for state_list in result: - if state_list[0].entity_id == order_entity: - sorted_result.append(state_list) - result.remove(state_list) - break - sorted_result.extend(result) + if self.use_include_order: + result = list(result) + sorted_result = [] + for order_entity in self.filters.included_entities: + for state_list in result: + if state_list[0].entity_id == order_entity: + sorted_result.append(state_list) + result.remove(state_list) + break + sorted_result.extend(result) + result = sorted_result - return self.json(sorted_result) + return self.json(result) class Filters(object): From 5a26d4c039e8339d13d0b446dfa1a813657ec61f Mon Sep 17 00:00:00 2001 From: Lukas Barth Date: Tue, 16 Jan 2018 22:35:23 +0100 Subject: [PATCH 049/150] Fix purge with MariaDB / MySQL (#11713) --- homeassistant/components/recorder/purge.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index 328bbe68dcb..4ff8e239352 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -22,11 +22,10 @@ def purge_old_data(instance, purge_days): # updated in a long time protected_states = session.query(States.state_id, States.event_id, func.max(States.last_updated)) \ - .group_by(States.entity_id).subquery() + .group_by(States.entity_id).all() - protected_state_ids = session.query(States.state_id).join( - protected_states, States.state_id == protected_states.c.state_id)\ - .subquery() + protected_state_ids = tuple((state[0] for state in protected_states)) + protected_event_ids = tuple((state[1] for state in protected_states)) deleted_rows = session.query(States) \ .filter((States.last_updated < purge_before)) \ @@ -39,11 +38,6 @@ def purge_old_data(instance, purge_days): # Otherwise, if the SQL server has "ON DELETE CASCADE" as default, it # will delete the protected state when deleting its associated # event. Also, we would be producing NULLed foreign keys otherwise. - - protected_event_ids = session.query(States.event_id).join( - protected_states, States.state_id == protected_states.c.state_id)\ - .filter(~States.event_id is not None).subquery() - deleted_rows = session.query(Events) \ .filter((Events.time_fired < purge_before)) \ .filter(~Events.event_id.in_( From 1f118c4b843465c70bea58b05a6d4d4f1eedcbe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85skar=20Andersson?= Date: Wed, 17 Jan 2018 09:12:50 +0100 Subject: [PATCH 050/150] Update mold_indicator.py (#11715) --- homeassistant/components/sensor/mold_indicator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/mold_indicator.py b/homeassistant/components/sensor/mold_indicator.py index 2d233ec8c70..b47367cafc8 100644 --- a/homeassistant/components/sensor/mold_indicator.py +++ b/homeassistant/components/sensor/mold_indicator.py @@ -19,8 +19,8 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -ATTR_CRITICAL_TEMP = 'EstimatedCriticalTemp' -ATTR_DEWPOINT = 'Dewpoint' +ATTR_CRITICAL_TEMP = 'estimated_critical_temp' +ATTR_DEWPOINT = 'dewpoint' CONF_CALIBRATION_FACTOR = 'calibration_factor' CONF_INDOOR_HUMIDITY = 'indoor_humidity_sensor' From 020593d50923511da979cbcd318ab201ab39eac9 Mon Sep 17 00:00:00 2001 From: Dan Nixon Date: Wed, 17 Jan 2018 08:52:32 +0000 Subject: [PATCH 051/150] Override default name for TP-Link devices (#11710) Fixes #11706 Corrects a bug introduced that prevents the names of TP-LInk devices from being pulled from the devices themselves. --- homeassistant/components/light/tplink.py | 14 ++++++++++---- homeassistant/components/switch/tplink.py | 4 +++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/light/tplink.py b/homeassistant/components/light/tplink.py index 87004f45ea0..30ad3a4d268 100644 --- a/homeassistant/components/light/tplink.py +++ b/homeassistant/components/light/tplink.py @@ -78,9 +78,7 @@ class TPLinkSmartBulb(Light): def __init__(self, smartbulb: 'SmartBulb', name): """Initialize the bulb.""" self.smartbulb = smartbulb - self._name = None - if name is not None: - self._name = name + self._name = name self._state = None self._available = True self._color_temp = None @@ -149,22 +147,30 @@ class TPLinkSmartBulb(Light): from pyHS100 import SmartDeviceException try: self._available = True + if self._supported_features == 0: self.get_features() + self._state = ( self.smartbulb.state == self.smartbulb.BULB_STATE_ON) - if self._name is None: + + # Pull the name from the device if a name was not specified + if self._name == DEFAULT_NAME: self._name = self.smartbulb.alias + if self._supported_features & SUPPORT_BRIGHTNESS: self._brightness = brightness_from_percentage( self.smartbulb.brightness) + if self._supported_features & SUPPORT_COLOR_TEMP: if (self.smartbulb.color_temp is not None and self.smartbulb.color_temp != 0): self._color_temp = kelvin_to_mired( self.smartbulb.color_temp) + if self._supported_features & SUPPORT_RGB_COLOR: self._rgb = hsv_to_rgb(self.smartbulb.hsv) + if self.smartbulb.has_emeter: self._emeter_params[ATTR_CURRENT_POWER_W] = '{:.1f}'.format( self.smartbulb.current_consumption()) diff --git a/homeassistant/components/switch/tplink.py b/homeassistant/components/switch/tplink.py index aa2e70e0020..f67aaec9796 100644 --- a/homeassistant/components/switch/tplink.py +++ b/homeassistant/components/switch/tplink.py @@ -90,10 +90,12 @@ class SmartPlugSwitch(SwitchDevice): from pyHS100 import SmartDeviceException try: self._available = True + self._state = self.smartplug.state == \ self.smartplug.SWITCH_STATE_ON - if self._name is None: + # Pull the name from the device if a name was not specified + if self._name == DEFAULT_NAME: self._name = self.smartplug.alias if self.smartplug.has_emeter: From 4ee2c311a759d15a85080ede4eea05bf9389726c Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 17 Jan 2018 15:20:26 +0100 Subject: [PATCH 052/150] Don't use None inside header (#11725) --- homeassistant/components/hassio.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/hassio.py b/homeassistant/components/hassio.py index cc6db5fbab3..4e4e6e7e64c 100644 --- a/homeassistant/components/hassio.py +++ b/homeassistant/components/hassio.py @@ -330,7 +330,7 @@ class HassIO(object): request = yield from self.websession.request( method, "http://{}{}".format(self._ip, command), json=payload, headers={ - X_HASSIO: os.environ.get('HASSIO_TOKEN') + X_HASSIO: os.environ.get('HASSIO_TOKEN', "") }) if request.status not in (200, 400): @@ -359,7 +359,7 @@ class HassIO(object): try: data = None - headers = {X_HASSIO: os.environ.get('HASSIO_TOKEN')} + headers = {X_HASSIO: os.environ.get('HASSIO_TOKEN', "")} with async_timeout.timeout(10, loop=self.loop): data = yield from request.read() if data: From 8703124c760df303d29571aa975a72cc4f868f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20St=C3=A5hl?= Date: Wed, 17 Jan 2018 19:34:21 +0100 Subject: [PATCH 053/150] Add enable_output service to Yamaha platform (#11103) * Add enable_output service to Yamaha platform * Fix lint issues * Fix review comment * Check entity_ids instead of device --- .../components/media_player/services.yaml | 14 + .../components/media_player/yamaha.py | 118 +- tests/components/media_player/test_yamaha.py | 178 +- .../media_player/yamaha_samples/desc.xml | 3441 ----------------- 4 files changed, 160 insertions(+), 3591 deletions(-) delete mode 100644 tests/components/media_player/yamaha_samples/desc.xml diff --git a/homeassistant/components/media_player/services.yaml b/homeassistant/components/media_player/services.yaml index fe8280fb2ab..3e5ee57cb2f 100644 --- a/homeassistant/components/media_player/services.yaml +++ b/homeassistant/components/media_player/services.yaml @@ -320,3 +320,17 @@ squeezebox_call_method: parameters: description: Optional array of parameters to be appended to the command. See 'Command Line Interface' official help page from Logitech for details. example: '["loadtracks", "track.titlesearch=highway to hell"]' + +yamaha_enable_output: + description: Enable or disable an output port + + fields: + entity_id: + description: Name(s) of entites to enable/disable port on. + example: 'media_player.yamaha' + port: + description: Name of port to enable/disable. + example: 'hdmi1' + enabled: + description: Boolean indicating if port should be enabled or not. + example: true diff --git a/homeassistant/components/media_player/yamaha.py b/homeassistant/components/media_player/yamaha.py index 10f7adccae0..577988bc58c 100644 --- a/homeassistant/components/media_player/yamaha.py +++ b/homeassistant/components/media_player/yamaha.py @@ -12,10 +12,10 @@ from homeassistant.components.media_player import ( SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_SELECT_SOURCE, SUPPORT_PLAY_MEDIA, SUPPORT_PAUSE, SUPPORT_STOP, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK, SUPPORT_PLAY, - MEDIA_TYPE_MUSIC, + MEDIA_TYPE_MUSIC, MEDIA_PLAYER_SCHEMA, DOMAIN, MediaPlayerDevice, PLATFORM_SCHEMA) from homeassistant.const import (CONF_NAME, CONF_HOST, STATE_OFF, STATE_ON, - STATE_PLAYING, STATE_IDLE) + STATE_PLAYING, STATE_IDLE, ATTR_ENTITY_ID) import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['rxv==0.5.1'] @@ -31,7 +31,7 @@ CONF_ZONE_NAMES = 'zone_names' CONF_ZONE_IGNORE = 'zone_ignore' DEFAULT_NAME = 'Yamaha Receiver' -KNOWN = 'yamaha_known_receivers' +DATA_YAMAHA = 'yamaha_known_receivers' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, @@ -44,15 +44,26 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_ZONE_NAMES, default={}): {cv.string: cv.string}, }) +SERVICE_ENABLE_OUTPUT = 'yamaha_enable_output' + +ATTR_PORT = 'port' +ATTR_ENABLED = 'enabled' + +ENABLE_OUTPUT_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({ + vol.Required(ATTR_PORT): cv.string, + vol.Required(ATTR_ENABLED): cv.boolean +}) + def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Yamaha platform.""" import rxv - # keep track of configured receivers so that we don't end up + # Keep track of configured receivers so that we don't end up # discovering a receiver dynamically that we have static config - # for. - if hass.data.get(KNOWN, None) is None: - hass.data[KNOWN] = set() + # for. Map each device from its unique_id to an instance since + # YamahaDevice is not hashable (thus not possible to add to a set). + if hass.data.get(DATA_YAMAHA) is None: + hass.data[DATA_YAMAHA] = {} name = config.get(CONF_NAME) host = config.get(CONF_HOST) @@ -66,9 +77,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): model = discovery_info.get('model_name') ctrl_url = discovery_info.get('control_url') desc_url = discovery_info.get('description_url') - if ctrl_url in hass.data[KNOWN]: - _LOGGER.info("%s already manually configured", ctrl_url) - return receivers = rxv.RXV( ctrl_url, model_name=model, friendly_name=name, unit_desc_url=desc_url).zone_controllers() @@ -83,13 +91,40 @@ def setup_platform(hass, config, add_devices, discovery_info=None): ctrl_url = "http://{}:80/YamahaRemoteControl/ctrl".format(host) receivers = rxv.RXV(ctrl_url, name).zone_controllers() + devices = [] for receiver in receivers: - if receiver.zone not in zone_ignore: - hass.data[KNOWN].add(receiver.ctrl_url) - add_devices([ - YamahaDevice(name, receiver, source_ignore, - source_names, zone_names) - ], True) + if receiver.zone in zone_ignore: + continue + + device = YamahaDevice(name, receiver, source_ignore, + source_names, zone_names) + + # Only add device if it's not already added + if device.unique_id not in hass.data[DATA_YAMAHA]: + hass.data[DATA_YAMAHA][device.unique_id] = device + devices.append(device) + else: + _LOGGER.debug('Ignoring duplicate receiver %s', name) + + def service_handler(service): + """Handle for services.""" + entity_ids = service.data.get(ATTR_ENTITY_ID) + + devices = [device for device in hass.data[DATA_YAMAHA].values() + if not entity_ids or device.entity_id in entity_ids] + + for device in devices: + port = service.data[ATTR_PORT] + enabled = service.data[ATTR_ENABLED] + + device.enable_output(port, enabled) + device.schedule_update_ha_state(True) + + hass.services.register( + DOMAIN, SERVICE_ENABLE_OUTPUT, service_handler, + schema=ENABLE_OUTPUT_SCHEMA) + + add_devices(devices) class YamahaDevice(MediaPlayerDevice): @@ -98,7 +133,7 @@ class YamahaDevice(MediaPlayerDevice): def __init__(self, name, receiver, source_ignore, source_names, zone_names): """Initialize the Yamaha Receiver.""" - self._receiver = receiver + self.receiver = receiver self._muted = False self._volume = 0 self._pwstate = STATE_OFF @@ -114,10 +149,15 @@ class YamahaDevice(MediaPlayerDevice): self._name = name self._zone = receiver.zone + @property + def unique_id(self): + """Return an unique ID.""" + return '{0}:{1}'.format(self.receiver.ctrl_url, self._zone) + def update(self): """Get the latest details from the device.""" - self._play_status = self._receiver.play_status() - if self._receiver.on: + self._play_status = self.receiver.play_status() + if self.receiver.on: if self._play_status is None: self._pwstate = STATE_ON elif self._play_status.playing: @@ -127,17 +167,17 @@ class YamahaDevice(MediaPlayerDevice): else: self._pwstate = STATE_OFF - self._muted = self._receiver.mute - self._volume = (self._receiver.volume / 100) + 1 + self._muted = self.receiver.mute + self._volume = (self.receiver.volume / 100) + 1 if self.source_list is None: self.build_source_list() - current_source = self._receiver.input + current_source = self.receiver.input self._current_source = self._source_names.get( current_source, current_source) - self._playback_support = self._receiver.get_playback_support() - self._is_playback_supported = self._receiver.is_playback_supported( + self._playback_support = self.receiver.get_playback_support() + self._is_playback_supported = self.receiver.is_playback_supported( self._current_source) def build_source_list(self): @@ -147,7 +187,7 @@ class YamahaDevice(MediaPlayerDevice): self._source_list = sorted( self._source_names.get(source, source) for source in - self._receiver.inputs() + self.receiver.inputs() if source not in self._source_ignore) @property @@ -203,42 +243,42 @@ class YamahaDevice(MediaPlayerDevice): def turn_off(self): """Turn off media player.""" - self._receiver.on = False + self.receiver.on = False def set_volume_level(self, volume): """Set volume level, range 0..1.""" receiver_vol = 100 - (volume * 100) negative_receiver_vol = -receiver_vol - self._receiver.volume = negative_receiver_vol + self.receiver.volume = negative_receiver_vol def mute_volume(self, mute): """Mute (true) or unmute (false) media player.""" - self._receiver.mute = mute + self.receiver.mute = mute def turn_on(self): """Turn the media player on.""" - self._receiver.on = True - self._volume = (self._receiver.volume / 100) + 1 + self.receiver.on = True + self._volume = (self.receiver.volume / 100) + 1 def media_play(self): """Send play command.""" - self._call_playback_function(self._receiver.play, "play") + self._call_playback_function(self.receiver.play, "play") def media_pause(self): """Send pause command.""" - self._call_playback_function(self._receiver.pause, "pause") + self._call_playback_function(self.receiver.pause, "pause") def media_stop(self): """Send stop command.""" - self._call_playback_function(self._receiver.stop, "stop") + self._call_playback_function(self.receiver.stop, "stop") def media_previous_track(self): """Send previous track command.""" - self._call_playback_function(self._receiver.previous, "previous track") + self._call_playback_function(self.receiver.previous, "previous track") def media_next_track(self): """Send next track command.""" - self._call_playback_function(self._receiver.next, "next track") + self._call_playback_function(self.receiver.next, "next track") def _call_playback_function(self, function, function_text): import rxv @@ -250,7 +290,7 @@ class YamahaDevice(MediaPlayerDevice): def select_source(self, source): """Select input source.""" - self._receiver.input = self._reverse_mapping.get(source, source) + self.receiver.input = self._reverse_mapping.get(source, source) def play_media(self, media_type, media_id, **kwargs): """Play media from an ID. @@ -275,7 +315,11 @@ class YamahaDevice(MediaPlayerDevice): """ if media_type == "NET RADIO": - self._receiver.net_radio(media_id) + self.receiver.net_radio(media_id) + + def enable_output(self, port, enabled): + """Enable or disable an output port..""" + self.receiver.enable_output(port, enabled) @property def media_artist(self): diff --git a/tests/components/media_player/test_yamaha.py b/tests/components/media_player/test_yamaha.py index 176cf7c5bf2..3322f6021e7 100644 --- a/tests/components/media_player/test_yamaha.py +++ b/tests/components/media_player/test_yamaha.py @@ -1,129 +1,81 @@ """The tests for the Yamaha Media player platform.""" import unittest -import xml.etree.ElementTree as ET +from unittest.mock import patch, MagicMock -import rxv - -import homeassistant.components.media_player.yamaha as yamaha - -TEST_CONFIG = { - 'name': "Test Receiver", - 'source_ignore': ['HDMI5'], - 'source_names': {'HDMI1': 'Laserdisc'}, - 'zone_names': {'Main_Zone': "Laser Dome"} -} +from homeassistant.setup import setup_component +import homeassistant.components.media_player as mp +from homeassistant.components.media_player import yamaha +from tests.common import get_test_home_assistant -def sample_content(name): - """Read content into a string from a file.""" - with open('tests/components/media_player/yamaha_samples/%s' % name, - encoding='utf-8') as content: - return content.read() +def _create_zone_mock(name, url): + zone = MagicMock() + zone.ctrl_url = url + zone.zone = name + return zone -def yamaha_player(receiver): - """Create a YamahaDevice from a given receiver, presumably a Mock.""" - zone_controller = receiver.zone_controllers()[0] - player = yamaha.YamahaDevice(receiver=zone_controller, **TEST_CONFIG) - player.build_source_list() - return player +class FakeYamahaDevice(object): + """A fake Yamaha device.""" + + def __init__(self, ctrl_url, name, zones=None): + """Initialize the fake Yamaha device.""" + self.ctrl_url = ctrl_url + self.name = name + self.zones = zones or [] + + def zone_controllers(self): + """Return controllers for all available zones.""" + return self.zones -class FakeYamaha(rxv.rxv.RXV): - """Fake Yamaha receiver. - - This inherits from RXV but overrides methods for testing that - would normally have hit the network. This makes it easier to - ensure that usage of the rxv library by HomeAssistant is as we'd - expect. - """ - - _fake_input = 'HDMI1' - - def _discover_features(self): - """Fake the discovery feature.""" - self._desc_xml = ET.fromstring(sample_content('desc.xml')) - - @property - def input(self): - """A fake input for the receiver.""" - return self._fake_input - - @input.setter - def input(self, input_name): - """Set the input for the fake receiver.""" - assert input_name in self.inputs() - self._fake_input = input_name - - def inputs(self): - """All inputs of the fake receiver.""" - return {'AUDIO1': None, - 'AUDIO2': None, - 'AV1': None, - 'AV2': None, - 'AV3': None, - 'AV4': None, - 'AV5': None, - 'AV6': None, - 'AirPlay': 'AirPlay', - 'HDMI1': None, - 'HDMI2': None, - 'HDMI3': None, - 'HDMI4': None, - 'HDMI5': None, - 'NET RADIO': 'NET_RADIO', - 'Pandora': 'Pandora', - 'Rhapsody': 'Rhapsody', - 'SERVER': 'SERVER', - 'SiriusXM': 'SiriusXM', - 'Spotify': 'Spotify', - 'TUNER': 'Tuner', - 'USB': 'USB', - 'V-AUX': None, - 'iPod (USB)': 'iPod_USB'} - - -# pylint: disable=no-member, invalid-name -class TestYamaha(unittest.TestCase): - """Test the media_player yamaha module.""" +class TestYamahaMediaPlayer(unittest.TestCase): + """Test the Yamaha media player.""" def setUp(self): """Setup things to be run when tests are started.""" - super(TestYamaha, self).setUp() - self.rec = FakeYamaha("http://10.0.0.0:80/YamahaRemoteControl/ctrl") - self.player = yamaha_player(self.rec) + self.hass = get_test_home_assistant() + self.main_zone = _create_zone_mock('Main zone', 'http://main') + self.device = FakeYamahaDevice( + 'http://receiver', 'Receiver', zones=[self.main_zone]) - def test_get_playback_support(self): - """Test the playback.""" - rec = self.rec - support = rec.get_playback_support() - self.assertFalse(support.play) - self.assertFalse(support.pause) - self.assertFalse(support.stop) - self.assertFalse(support.skip_f) - self.assertFalse(support.skip_r) + def tearDown(self): + """Stop everything that was started.""" + self.hass.stop() - rec.input = 'NET RADIO' - support = rec.get_playback_support() - self.assertTrue(support.play) - self.assertFalse(support.pause) - self.assertTrue(support.stop) - self.assertFalse(support.skip_f) - self.assertFalse(support.skip_r) + def enable_output(self, port, enabled): + """Enable ouput on a specific port.""" + data = { + 'entity_id': 'media_player.yamaha_receiver_main_zone', + 'port': port, + 'enabled': enabled + } - def test_configuration_options(self): - """Test configuration options.""" - rec_name = TEST_CONFIG['name'] - src_zone = 'Main_Zone' - src_zone_alt = src_zone.replace('_', ' ') - renamed_zone = TEST_CONFIG['zone_names'][src_zone] - ignored_src = TEST_CONFIG['source_ignore'][0] - renamed_src = 'HDMI1' - new_src = TEST_CONFIG['source_names'][renamed_src] - self.assertFalse(self.player.name == rec_name + ' ' + src_zone) - self.assertFalse(self.player.name == rec_name + ' ' + src_zone_alt) - self.assertTrue(self.player.name == rec_name + ' ' + renamed_zone) + self.hass.services.call(yamaha.DOMAIN, + yamaha.SERVICE_ENABLE_OUTPUT, + data, + True) - self.assertFalse(ignored_src in self.player.source_list) - self.assertFalse(renamed_src in self.player.source_list) - self.assertTrue(new_src in self.player.source_list) + def create_receiver(self, mock_rxv): + """Create a mocked receiver.""" + mock_rxv.return_value = self.device + + config = { + 'media_player': { + 'platform': 'yamaha', + 'host': '127.0.0.1' + } + } + + self.assertTrue(setup_component(self.hass, mp.DOMAIN, config)) + + @patch('rxv.RXV') + def test_enable_output(self, mock_rxv): + """Test enabling and disabling outputs.""" + self.create_receiver(mock_rxv) + + self.enable_output('hdmi1', True) + self.main_zone.enable_output.assert_called_with('hdmi1', True) + + self.enable_output('hdmi2', False) + self.main_zone.enable_output.assert_called_with('hdmi2', False) diff --git a/tests/components/media_player/yamaha_samples/desc.xml b/tests/components/media_player/yamaha_samples/desc.xml deleted file mode 100644 index d403fade5f7..00000000000 --- a/tests/components/media_player/yamaha_samples/desc.xml +++ /dev/null @@ -1,3441 +0,0 @@ - - - Title_1 - - - On - Off - - Param_1 - - On - Off - - - - - - - Param_1 - - Available - Unavailable - - - - - - - Param_1 - - 1,15,UTF-8 - - - - Param_1 - - 1,15,UTF-8 - - - - - - On - Standby - - - On - Off - - Param_1 - - On - Off - - - - - - Disable - Enable - - Param_1 - - Disable - Enable - - - - - System,Misc,Event,Notice - System,Power_Control,Power - System,Misc,Network,Network_Name - System,Misc,Network,Network_Standby - System,Misc,Network,DMC_Control - System,Misc,Event,Notice - System,Misc,Network,Network_Name - System,Misc,Network,Network_Standby - System,Misc,Update,Yamaha_Network_Site,Status - System,Misc,Network,DMC_Control - - - - - - Param_1 - - 1,9,Latin-1 - - - - Name,Zone=Param_1 - - 1,9,Latin-1 - - - - - - - Param_1 - - - - - - Input,Input_Sel=Param_1 - - - - - - - - Param_1 - - - - - - Input,Input_Sel=Param_1 - - - - - - - - - - Val=Param_1:Exp=Param_2:Unit=Param_3 - - -805,165,5 - - - 1 - - - dB - - - - Volume,Lvl,Val=Param_1:Volume,Lvl,Exp=Param_2:Volume,Lvl,Unit=Param_3 - - -805,165,5 - - - 1 - - - dB - - - - - On - Off - - Volume,Mute=Param_1 - - On - Off - - - - - - - On - Standby - - Power_Control,Power=Param_1 - - On - Standby - - - - - Last - 120 min - 90 min - 60 min - 30 min - Off - - Power_Control,Sleep=Param_1 - - 120 min - 90 min - 60 min - 30 min - Off - - - - - - - Play - Pause - Stop - - - Skip Fwd - Skip Rev - - - - - Up - Down - Left - Right - Return - Sel - Return to Home - On Screen - Top Menu - Menu - Option - Display - - - - - - - Param_1 - - Hall in Munich - Hall in Vienna - Chamber - Cellar Club - The Roxy Theatre - The Bottom Line - Sports - Action Game - Roleplaying Game - Music Video - Standard - Spectacle - Sci-Fi - Adventure - Drama - Mono Movie - Surround Decoder - 2ch Stereo - 7ch Stereo - - - - Surround,Program_Sel,Current,Sound_Program=Param_1 - - Hall in Munich - Hall in Vienna - Chamber - Cellar Club - The Roxy Theatre - The Bottom Line - Sports - Action Game - Roleplaying Game - Music Video - Standard - Spectacle - Sci-Fi - Adventure - Drama - Mono Movie - Surround Decoder - 2ch Stereo - 7ch Stereo - - - - - On - Off - - Surround,Program_Sel,Current,Straight=Param_1 - - On - Off - - - - - On - Off - - Surround,Program_Sel,Current,Enhancer=Param_1 - - On - Off - - - - - - - - Val=Param_1:Exp=Param_2:Unit=Param_3 - - -60,60,5 - - - 1 - - - dB - - - - Sound_Video,Tone,Bass,Val=Param_1:Sound_Video,Tone,Bass,Exp=Param_2:Sound_Video,Tone,Bass,Unit=Param_3 - - -60,60,5 - - - 1 - - - dB - - - - - - Val=Param_1:Exp=Param_2:Unit=Param_3 - - -60,60,5 - - - 1 - - - dB - - - - Sound_Video,Tone,Treble,Val=Param_1:Sound_Video,Tone,Treble,Exp=Param_2:Sound_Video,Tone,Treble,Unit=Param_3 - - -60,60,5 - - - 1 - - - dB - - - - - - - Val=Param_1:Exp=Param_2:Unit=Param_3 - - -60,60,5 - - - 1 - - - dB - - - - Volume,Subwoofer_Trim,Val=Param_1:Volume,Subwoofer_Trim,Exp=Param_2:Volume,Subwoofer_Trim,Unit=Param_3 - - -60,60,5 - - - 1 - - - dB - - - - - Auto - Off - - Sound_Video,Adaptive_DRC=Param_1 - - Auto - Off - - - - - Auto - Off - - Surround,_3D_Cinema_DSP=Param_1 - - Auto - Off - - - - - - Param_1 - - 0,5,1 - - - - Sound_Video,Dialogue_Adjust,Dialogue_Lift=Param_1 - - 0,5,1 - - - - - - Param_1 - - 0,3,1 - - - - Sound_Video,Dialogue_Adjust,Dialogue_Lvl=Param_1 - - 0,3,1 - - - - - On - Off - - Sound_Video,Pure_Direct,Mode=Param_1 - - On - Off - - - - - - Sound_Video,HDMI,Standby_Through_Info=Param_1 - - On - Off - - - - - - Main_Zone,Power_Control,Power - Main_Zone,Volume,Lvl - Main_Zone,Volume,Mute - Main_Zone,Input,Input_Sel - Main_Zone,Config,Name,Zone - Main_Zone,Scene,Scene_Sel - Main_Zone,Sound_Video,Tone,Bass - Main_Zone,Sound_Video,Tone,Treble - Main_Zone,Surround,Program_Sel,Current,Sound_Program - Main_Zone,Surround,Program_Sel,Current,Straight - Main_Zone,Surround,Program_Sel,Current,Enhancer - Main_Zone,Sound_Video,Adaptive_DRC - Main_Zone,Surround,_3D_Cinema_DSP - Main_Zone,Sound_Video,Dialogue_Adjust,Dialogue_Lift - System,Sound_Video,HDMI,Video,Preset_Sel,Current - Main_Zone,Sound_Video,Pure_Direct,Mode - Main_Zone,Cursor_Control,Cursor - Main_Zone,Cursor_Control,Menu_Control - Main_Zone,Surround,Enhancer_Type - Main_Zone,Sound_Video,Dialogue_Adjust,Dialogue_Lvl - Main_Zone,Volume,Subwoofer_Trim - Main_Zone,Power_Control,Sleep - Main_Zone,Play_Control,Playback - Main_Zone,Basic_Status - Main_Zone,Input,Input_Sel_Item - Main_Zone,Config - Main_Zone,Scene,Scene_Sel_Item - - - - - - Param_1 - - 1,9,Latin-1 - - - - Name,Zone=Param_1 - - 1,9,Latin-1 - - - - - - Param_1 - - - - - - Input,Input_Sel=Param_1 - - - - - - - - - Val=Param_1:Exp=Param_2:Unit=Param_3 - - -805,165,5 - - - 1 - - - dB - - - - Volume,Lvl,Val=Param_1:Volume,Lvl,Exp=Param_2:Volume,Lvl,Unit=Param_3 - - -805,165,5 - - - 1 - - - dB - - - - - On - Off - - Volume,Mute=Param_1 - - On - Off - - - - - - Volume,Output_Info=Param_1 - - Fixed - Variable - - - - - - - On - Standby - - Power_Control,Power=Param_1 - - On - Standby - - - - - Last - 120 min - 90 min - 60 min - 30 min - Off - - Power_Control,Sleep=Param_1 - - 120 min - 90 min - 60 min - 30 min - Off - - - - - - - Play - Pause - Stop - - - Skip Fwd - Skip Rev - - - - Zone_2,Power_Control,Power - Zone_2,Volume,Lvl - Zone_2,Volume,Mute - Zone_2,Input,Input_Sel - Zone_2,Config,Name,Zone - Zone_2,Scene,Scene_Sel - Zone_2,Sound_Video,Tone,Bass - Zone_2,Sound_Video,Tone,Treble - Zone_2,Cursor_Control,Cursor - Zone_2,Cursor_Control,Menu_Control - Zone_2,Volume,Output - Zone_2,Power_Control,Sleep - Zone_2,Play_Control,Playback - Zone_2,Basic_Status - Zone_2,Input,Input_Sel_Item - Zone_2,Config - Zone_2,Scene,Scene_Sel_Item - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - - Auto Up - Auto Down - Cancel - - Tuning,Freq,AM,Val=Param_1:Tuning,Freq,AM,Exp=Param_2:Tuning,Freq,AM,Unit=Param_3 - - 530,1710,10 - Auto Up - Auto Down - - - 0 - - - - - kHz - - - - - - - Auto Up - Auto Down - Cancel - - Tuning,Freq,FM,Val=Param_1:Tuning,Freq,FM,Exp=Param_2:Tuning,Freq,FM,Unit=Param_3 - - 8750,10790,20 - Auto Up - Auto Down - - - 2 - - - - - MHz - - - - - - - Up - Down - - Preset,Preset_Sel=Param_1 - - - - - - - - AM - FM - - Tuning,Band=Param_1 - - AM - FM - - - - - - - Val=Param_1:Exp=Param_2:Unit=Param_3 - - 530,1710,10 - - - 0 - - - kHz - - - - Tuning,Freq,AM,Val=Param_1:Tuning,Freq,AM,Exp=Param_2:Tuning,Freq,AM,Unit=Param_3 - - 530,1710,10 - Auto Up - Auto Down - - - 0 - - - - - kHz - - - - - - - - Val=Param_1:Exp=Param_2:Unit=Param_3 - - 8750,10790,20 - - - 2 - - - MHz - - - - Tuning,Freq,FM,Val=Param_1:Tuning,Freq,FM,Exp=Param_2:Tuning,Freq,FM,Unit=Param_3 - - 8750,10790,20 - Auto Up - Auto Down - - - 2 - - - - - MHz - - - - - - - - - Param_1 - - - - - - Preset,Preset_Sel=Param_1 - - - - - - - - - - Tuning,Band=Param_1 - - AM - FM - - - - - - Tuning,Freq,Current,Val=Param_1:Tuning,Freq,Current,Exp=Param_2:Tuning,Freq,Current,Unit=Param_3 - - 530,1710,10 - 8750,10790,20 - Auto Up - Auto Down - - - 0 - 2 - - - - - kHz - MHz - - - - - - - - - Signal_Info,Tuned=Param_1 - - Negate - Assert - - - - - - Signal_Info,Stereo=Param_1 - - Negate - Assert - - - - - - - Tuner,Play_Control,Search_Mode - Tuner,Play_Control,Preset,Preset_Sel - Tuner,Play_Control,Tuning,Band - Tuner,Play_Control,Tuning,Freq,FM - Tuner,Play_Control,Tuning,Freq,AM - Tuner,Play_Control,Tuning,Freq,FM,Val - Tuner,Play_Control,Tuning,Freq,AM,Val - Tuner,Play_Info - Tuner,Config - Tuner,Play_Control,Preset,Preset_Sel_Item - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Play - Pause - - Playback_Info=Param_1 - - Play - Stop - - - - - Skip Fwd - Skip Rev - - - - - - Meta_Info,Artist=Param_1 - - 0,128,UTF-8 - - - - - - Meta_Info,Album=Param_1 - - 0,128,UTF-8 - - - - - - Meta_Info,Song=Param_1 - - 0,128,UTF-8 - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Playback_Info=Param_1 - - Play - Stop - - - - - - Album_ART,URL=Param_1 - - 0,128,UTF-8 - - - - - - Album_ART,ID=Param_1 - - 0,255,1 - - - - - - Album_ART,Format=Param_1 - - BMP - YMF - - - - - - Input_Logo,URL_S=Param_1 - - 0,128,UTF-8 - - - - - - AirPlay,Play_Control,Playback - AirPlay,Play_Info - AirPlay,Config - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Play - Pause - - Playback_Info=Param_1 - - Play - Pause - Stop - - - - - Skip Fwd - Skip Rev - - - - - - Meta_Info,Artist=Param_1 - - 0,128,UTF-8 - - - - - - Meta_Info,Album=Param_1 - - 0,128,UTF-8 - - - - - - Meta_Info,Track=Param_1 - - 0,128,UTF-8 - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Playback_Info=Param_1 - - Play - Pause - Stop - - - - - - Input_Logo,URL_S=Param_1 - - 0,128,UTF-8 - - - - - - Spotify,Play_Control,Playback - Spotify,Play_Control,Play_Mode,Repeat - Spotify,Play_Control,Play_Mode,Shuffle - Spotify,Play_Info - Spotify,Config - - - - - Extended - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Off - One - All - - Play_Mode,Repeat=Param_1 - - Off - One - All - - - - - Off - Songs - Albums - - Play_Mode,Shuffle=Param_1 - - Off - Songs - Albums - - - - - Play - Pause - Stop - - Playback_Info=Param_1 - - Play - Pause - Stop - - - - - Skip Fwd - Skip Rev - - - - - - Meta_Info,Artist=Param_1 - - 0,128,UTF-8 - - - - - - Meta_Info,Album=Param_1 - - 0,128,UTF-8 - - - - - - Meta_Info,Song=Param_1 - - 0,128,UTF-8 - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Playback_Info=Param_1 - - Play - Pause - Stop - - - - - - Play_Mode,Repeat=Param_1 - - Off - One - All - - - - - - Play_Mode,Shuffle=Param_1 - - Off - Songs - Albums - - - - - - Album_ART,URL=Param_1 - - 0,128,UTF-8 - - - - - - Album_ART,ID=Param_1 - - 0,255,1 - - - - - - Album_ART,Format=Param_1 - - BMP - YMF - - - - - - - - - Param_1 - - 1,8,1,Line_% - - - - - Up - Down - Return - Sel - Return to Home - - - - Param_1 - - 1,65536,1 - - - - - Up - Down - - - - - - Menu_Status=Param_1 - - Ready - Busy - - - - - - Menu_Layer=Param_1 - - 1,16,1 - - - - - - Menu_Name=Param_1 - - 0,128,UTF-8 - - - - - - Line_1 - - Current_List,Line_1,Txt=Param_1:Current_List,Line_1,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_2 - - Current_List,Line_2,Txt=Param_1:Current_List,Line_2,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_3 - - Current_List,Line_3,Txt=Param_1:Current_List,Line_3,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_4 - - Current_List,Line_4,Txt=Param_1:Current_List,Line_4,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_5 - - Current_List,Line_5,Txt=Param_1:Current_List,Line_5,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_6 - - Current_List,Line_6,Txt=Param_1:Current_List,Line_6,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_7 - - Current_List,Line_7,Txt=Param_1:Current_List,Line_7,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_8 - - Current_List,Line_8,Txt=Param_1:Current_List,Line_8,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - - - Cursor_Position,Current_Line=Param_1 - - 1,65536,1 - - - - - - Cursor_Position,Max_Line=Param_1 - - 0,65536,1 - - - - - - - iPod_USB,Play_Control,Playback - iPod_USB,List_Control,Direct_Sel - iPod_USB,List_Control,Jump_Line - iPod_USB,List_Control,Cursor - iPod_USB,List_Control,Page - iPod_USB,Play_Control,Play_Mode,Repeat - iPod_USB,Play_Control,Play_Mode,Shuffle - iPod_USB,Play_Control,iPod_Mode - iPod_USB,Play_Info - iPod_USB,List_Info - iPod_USB,Config - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Off - One - All - - Play_Mode,Repeat=Param_1 - - Off - One - All - - - - - Off - On - - Play_Mode,Shuffle=Param_1 - - Off - On - - - - - Play - Pause - Stop - - Playback_Info=Param_1 - - Play - Pause - Stop - - - - - Skip Fwd - Skip Rev - - - - - - Meta_Info,Artist=Param_1 - - 0,64,UTF-8 - - - - - - Meta_Info,Album=Param_1 - - 0,64,UTF-8 - - - - - - Meta_Info,Song=Param_1 - - 0,64,UTF-8 - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Playback_Info=Param_1 - - Play - Pause - Stop - - - - - - Play_Mode,Repeat=Param_1 - - Off - One - All - - - - - - Play_Mode,Shuffle=Param_1 - - Off - On - - - - - - Album_ART,URL=Param_1 - - 0,128,UTF-8 - - - - - - Album_ART,ID=Param_1 - - 0,255,1 - - - - - - Album_ART,Format=Param_1 - - BMP - YMF - - - - - - - - - Param_1 - - 1,8,1,Line_% - - - - - Up - Down - Return - Sel - Return to Home - - - - Param_1 - - 1,65536,1 - - - - - Up - Down - - - - - - Menu_Status=Param_1 - - Ready - Busy - - - - - - Menu_Layer=Param_1 - - 1,16,1 - - - - - - Menu_Name=Param_1 - - 0,128,UTF-8 - - - - - - Line_1 - - Current_List,Line_1,Txt=Param_1:Current_List,Line_1,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_2 - - Current_List,Line_2,Txt=Param_1:Current_List,Line_2,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_3 - - Current_List,Line_3,Txt=Param_1:Current_List,Line_3,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_4 - - Current_List,Line_4,Txt=Param_1:Current_List,Line_4,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_5 - - Current_List,Line_5,Txt=Param_1:Current_List,Line_5,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_6 - - Current_List,Line_6,Txt=Param_1:Current_List,Line_6,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_7 - - Current_List,Line_7,Txt=Param_1:Current_List,Line_7,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_8 - - Current_List,Line_8,Txt=Param_1:Current_List,Line_8,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - - - Cursor_Position,Current_Line=Param_1 - - 1,65536,1 - - - - - - Cursor_Position,Max_Line=Param_1 - - 0,65536,1 - - - - - - - USB,Play_Control,Play_Mode,Repeat - USB,Play_Control,Play_Mode,Shuffle - USB,Play_Control,Playback - USB,Play_Control,Preset,Preset_Sel - USB,List_Control,Direct_Sel - USB,List_Control,Jump_Line - USB,List_Control,Cursor - USB,List_Control,Page - USB,Play_Info - USB,List_Info - USB,Config - USB,Play_Control,Preset,Preset_Sel_Item - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Play - Stop - - Playback_Info=Param_1 - - Play - Stop - - - - - - - - Meta_Info,Station=Param_1 - - 0,128,UTF-8 - - - - - - Meta_Info,Album=Param_1 - - 0,128,UTF-8 - - - - - - Meta_Info,Song=Param_1 - - 0,128,UTF-8 - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Playback_Info=Param_1 - - Play - Stop - - - - - - Album_ART,URL=Param_1 - - 0,128,UTF-8 - - - - - - Album_ART,ID=Param_1 - - 0,255,1 - - - - - - Album_ART,Format=Param_1 - - BMP - YMF - - - - - - - - - Param_1 - - 1,8,1,Line_% - - - - - Up - Down - Return - Sel - Return to Home - - - - Param_1 - - 1,65536,1 - - - - - Up - Down - - - - - - Menu_Status=Param_1 - - Ready - Busy - - - - - - Menu_Layer=Param_1 - - 1,16,1 - - - - - - Menu_Name=Param_1 - - 0,128,UTF-8 - - - - - - Line_1 - - Current_List,Line_1,Txt=Param_1:Current_List,Line_1,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_2 - - Current_List,Line_2,Txt=Param_1:Current_List,Line_2,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_3 - - Current_List,Line_3,Txt=Param_1:Current_List,Line_3,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_4 - - Current_List,Line_4,Txt=Param_1:Current_List,Line_4,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_5 - - Current_List,Line_5,Txt=Param_1:Current_List,Line_5,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_6 - - Current_List,Line_6,Txt=Param_1:Current_List,Line_6,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_7 - - Current_List,Line_7,Txt=Param_1:Current_List,Line_7,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_8 - - Current_List,Line_8,Txt=Param_1:Current_List,Line_8,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - - - Cursor_Position,Current_Line=Param_1 - - 1,65536,1 - - - - - - Cursor_Position,Max_Line=Param_1 - - 0,65536,1 - - - - - - - NET_RADIO,Play_Control,Playback - NET_RADIO,List_Control,Direct_Sel - NET_RADIO,List_Control,Jump_Line - NET_RADIO,List_Control,Cursor - NET_RADIO,List_Control,Page - NET_RADIO,Play_Control,Preset,Preset_Sel - NET_RADIO,List_Control,Bookmark - NET_RADIO,Play_Info - NET_RADIO,List_Info - NET_RADIO,Config - NET_RADIO,Play_Control,Preset,Preset_Sel_Item - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Off - One - All - - Play_Mode,Repeat=Param_1 - - Off - One - All - - - - - Off - On - - Play_Mode,Shuffle=Param_1 - - Off - On - - - - - Play - Pause - Stop - - Playback_Info=Param_1 - - Play - Pause - Stop - - - - - Skip Fwd - Skip Rev - - - - - - Meta_Info,Artist=Param_1 - - 0,128,UTF-8 - - - - - - Meta_Info,Album=Param_1 - - 0,128,UTF-8 - - - - - - Meta_Info,Song=Param_1 - - 0,128,UTF-8 - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Playback_Info=Param_1 - - Play - Pause - Stop - - - - - - Play_Mode,Repeat=Param_1 - - Off - One - All - - - - - - Play_Mode,Shuffle=Param_1 - - Off - On - - - - - - Album_ART,URL=Param_1 - - 0,128,UTF-8 - - - - - - Album_ART,ID=Param_1 - - 0,255,1 - - - - - - Album_ART,Format=Param_1 - - BMP - YMF - - - - - - - - - Param_1 - - 1,8,1,Line_% - - - - - Up - Down - Return - Sel - Return to Home - - - - Param_1 - - 1,65536,1 - - - - - Up - Down - - - - - - Menu_Status=Param_1 - - Ready - Busy - - - - - - Menu_Layer=Param_1 - - 1,16,1 - - - - - - Menu_Name=Param_1 - - 0,128,UTF-8 - - - - - - Line_1 - - Current_List,Line_1,Txt=Param_1:Current_List,Line_1,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_2 - - Current_List,Line_2,Txt=Param_1:Current_List,Line_2,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_3 - - Current_List,Line_3,Txt=Param_1:Current_List,Line_3,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_4 - - Current_List,Line_4,Txt=Param_1:Current_List,Line_4,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_5 - - Current_List,Line_5,Txt=Param_1:Current_List,Line_5,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_6 - - Current_List,Line_6,Txt=Param_1:Current_List,Line_6,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_7 - - Current_List,Line_7,Txt=Param_1:Current_List,Line_7,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_8 - - Current_List,Line_8,Txt=Param_1:Current_List,Line_8,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - - - Cursor_Position,Current_Line=Param_1 - - 1,65536,1 - - - - - - Cursor_Position,Max_Line=Param_1 - - 0,65536,1 - - - - - - - SERVER,Play_Control,Play_Mode,Repeat - SERVER,Play_Control,Play_Mode,Shuffle - SERVER,Play_Control,Playback - SERVER,Play_Control,Preset,Preset_Sel - SERVER,List_Control,Direct_Sel - SERVER,List_Control,Jump_Line - SERVER,List_Control,Cursor - SERVER,List_Control,Page - SERVER,Play_Control,Play_URI - SERVER,Play_Info - SERVER,List_Info - SERVER,Config - SERVER,Play_Control,Preset,Preset_Sel_Item - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Off - One - All - - Play_Mode,Repeat=Param_1 - - Off - One - All - - - - - Off - On - - Play_Mode,Shuffle=Param_1 - - Off - On - - - - - Play - Pause - Stop - - Playback_Info=Param_1 - - Play - Pause - Stop - - - - - Skip Fwd - Skip Rev - - - - - - Meta_Info,Artist=Param_1 - - 0,128,UTF-8 - - - - - - Meta_Info,Album=Param_1 - - 0,128,UTF-8 - - - - - - Meta_Info,Song=Param_1 - - 0,128,UTF-8 - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Playback_Info=Param_1 - - Play - Pause - Stop - - - - - - Play_Mode,Repeat=Param_1 - - Off - One - All - - - - - - Play_Mode,Shuffle=Param_1 - - Off - On - - - - - - Album_ART,URL=Param_1 - - 0,128,UTF-8 - - - - - - Album_ART,ID=Param_1 - - 0,255,1 - - - - - - Album_ART,Format=Param_1 - - BMP - YMF - - - - - - Input_Logo,URL_S=Param_1 - - 0,128,UTF-8 - - - - - - - - - Param_1 - - 1,8,1,Line_% - - - - - - Line=Param_1:Keyword=Param_2 - - 1,8,1,Line_% - - - 0,30,Ascii - - - - - Up - Down - Return - Sel - Return to Home - - - - Param_1 - - 1,65536,1 - - - - - Up - Down - - - - - - Menu_Status=Param_1 - - Ready - Busy - - - - - - Menu_Layer=Param_1 - - 1,16,1 - - - - - - Menu_Name=Param_1 - - 0,128,UTF-8 - - - - - - Line_1 - - Current_List,Line_1,Txt=Param_1:Current_List,Line_1,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - Keyword - - - - - Line_2 - - Current_List,Line_2,Txt=Param_1:Current_List,Line_2,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - Keyword - - - - - Line_3 - - Current_List,Line_3,Txt=Param_1:Current_List,Line_3,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - Keyword - - - - - Line_4 - - Current_List,Line_4,Txt=Param_1:Current_List,Line_4,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - Keyword - - - - - Line_5 - - Current_List,Line_5,Txt=Param_1:Current_List,Line_5,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - Keyword - - - - - Line_6 - - Current_List,Line_6,Txt=Param_1:Current_List,Line_6,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - Keyword - - - - - Line_7 - - Current_List,Line_7,Txt=Param_1:Current_List,Line_7,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - Keyword - - - - - Line_8 - - Current_List,Line_8,Txt=Param_1:Current_List,Line_8,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - Keyword - - - - - - - Cursor_Position,Current_Line=Param_1 - - 1,65536,1 - - - - - - Cursor_Position,Max_Line=Param_1 - - 0,65536,1 - - - - - - - Rhapsody,Play_Control,Play_Mode,Repeat - Rhapsody,Play_Control,Play_Mode,Shuffle - Rhapsody,Play_Control,Playback - Rhapsody,Play_Control,Preset,Preset_Sel - Rhapsody,List_Control,Direct_Sel - Rhapsody,List_Control,Jump_Line - Rhapsody,List_Control,Cursor - Rhapsody,List_Control,Page - Rhapsody,List_Control,Direct_Sel_with_Keyword - Rhapsody,Play_Info - Rhapsody,List_Info - Rhapsody,Config - Rhapsody,Play_Control,Preset,Preset_Sel_Item - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Play - Stop - - Playback_Info=Param_1 - - Play - Stop - - - - - - - - Meta_Info,Ch_Name=Param_1 - - 0,128,UTF-8 - - - - - - Meta_Info,Artist=Param_1 - - 0,128,UTF-8 - - - - - - Meta_Info,Song=Param_1 - - 0,128,UTF-8 - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Playback_Info=Param_1 - - Play - Stop - - - - - - Album_ART,URL=Param_1 - - 0,128,UTF-8 - - - - - - Album_ART,ID=Param_1 - - 0,255,1 - - - - - - Album_ART,Format=Param_1 - - BMP - YMF - - - - - - Input_Logo,URL_S=Param_1 - - 0,128,UTF-8 - - - - - - - - - Param_1 - - 1,8,1,Line_% - - - - - Up - Down - Return - Sel - Return to Home - - - - Param_1 - - 1,65536,1 - - - - - Up - Down - - - - - - Menu_Status=Param_1 - - Ready - Busy - - - - - - Menu_Layer=Param_1 - - 1,16,1 - - - - - - Menu_Name=Param_1 - - 0,128,UTF-8 - - - - - - Line_1 - - Current_List,Line_1,Txt=Param_1:Current_List,Line_1,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_2 - - Current_List,Line_2,Txt=Param_1:Current_List,Line_2,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_3 - - Current_List,Line_3,Txt=Param_1:Current_List,Line_3,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_4 - - Current_List,Line_4,Txt=Param_1:Current_List,Line_4,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_5 - - Current_List,Line_5,Txt=Param_1:Current_List,Line_5,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_6 - - Current_List,Line_6,Txt=Param_1:Current_List,Line_6,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_7 - - Current_List,Line_7,Txt=Param_1:Current_List,Line_7,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_8 - - Current_List,Line_8,Txt=Param_1:Current_List,Line_8,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - - - Cursor_Position,Current_Line=Param_1 - - 1,65536,1 - - - - - - Cursor_Position,Max_Line=Param_1 - - 0,65536,1 - - - - - - - SiriusXM,Play_Control,Playback - SiriusXM,Play_Control,Preset,Preset_Sel - SiriusXM,List_Control,Direct_Sel - SiriusXM,List_Control,Jump_Line - SiriusXM,List_Control,Cursor - SiriusXM,List_Control,Page - SiriusXM,Play_Info - SiriusXM,List_Info - SiriusXM,Config - SiriusXM,Play_Control,Preset,Preset_Sel_Item - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Play - Pause - Stop - - Playback_Info=Param_1 - - Play - Pause - Stop - - - - - Skip Fwd - - - Thumb Up - Thumb Down - - Feedback=Param_1 - - --- - Thumb Up - Thumb Down - - - - - - - - Meta_Info,Station=Param_1 - - 0,128,UTF-8 - - - - - - Meta_Info,Album=Param_1 - - 0,128,UTF-8 - - - - - - Meta_Info,Track=Param_1 - - 0,128,UTF-8 - - - - - - Feedback=Param_1 - - --- - Thumb Up - Thumb Down - - - - - - Feature_Availability=Param_1 - - Ready - Not Ready - - - - - - Playback_Info=Param_1 - - Play - Pause - Stop - - - - - - Album_ART,URL=Param_1 - - 0,128,UTF-8 - - - - - - Album_ART,ID=Param_1 - - 0,255,1 - - - - - - Album_ART,Format=Param_1 - - BMP - YMF - - - - - - Input_Logo,URL_S=Param_1 - - 0,128,UTF-8 - - - - - - - - - Param_1 - - 1,8,1,Line_% - - - - - Up - Down - Return - Sel - Return to Home - - - - Param_1 - - 1,65536,1 - - - - - Up - Down - - - - - - Menu_Status=Param_1 - - Ready - Busy - - - - - - Menu_Layer=Param_1 - - 1,16,1 - - - - - - Menu_Name=Param_1 - - 0,128,UTF-8 - - - - - - Line_1 - - Current_List,Line_1,Txt=Param_1:Current_List,Line_1,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_2 - - Current_List,Line_2,Txt=Param_1:Current_List,Line_2,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_3 - - Current_List,Line_3,Txt=Param_1:Current_List,Line_3,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_4 - - Current_List,Line_4,Txt=Param_1:Current_List,Line_4,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_5 - - Current_List,Line_5,Txt=Param_1:Current_List,Line_5,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_6 - - Current_List,Line_6,Txt=Param_1:Current_List,Line_6,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_7 - - Current_List,Line_7,Txt=Param_1:Current_List,Line_7,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - Line_8 - - Current_List,Line_8,Txt=Param_1:Current_List,Line_8,Attribute=Param_2 - - 0,128,UTF-8 - - - Container - Unplayable Item - Item - Unselectable - - - - - - - Cursor_Position,Current_Line=Param_1 - - 1,65536,1 - - - - - - Cursor_Position,Max_Line=Param_1 - - 0,65536,1 - - - - - - - Pandora,Play_Control,Feedback - Pandora,Play_Control,Playback - Pandora,Play_Control,Preset,Preset_Sel - Pandora,List_Control,Direct_Sel - Pandora,List_Control,Jump_Line - Pandora,List_Control,Cursor - Pandora,List_Control,Page - Pandora,Play_Info - Pandora,List_Info - Pandora,Config - Pandora,Play_Control,Preset,Preset_Sel_Item - - - From 33fd9c7c8f6741c5beecc2430584ef90cdda6579 Mon Sep 17 00:00:00 2001 From: Martin Rowan Date: Wed, 17 Jan 2018 21:00:46 +0000 Subject: [PATCH 054/150] Fix effects not appearing in UI due to missing attribute (#11738) --- homeassistant/components/light/flux_led.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 homeassistant/components/light/flux_led.py diff --git a/homeassistant/components/light/flux_led.py b/homeassistant/components/light/flux_led.py old mode 100644 new mode 100755 index 95f13cad860..a97455bc513 --- a/homeassistant/components/light/flux_led.py +++ b/homeassistant/components/light/flux_led.py @@ -78,7 +78,7 @@ EFFECT_MAP = { FLUX_EFFECT_LIST = [ EFFECT_RANDOM, - ].extend(EFFECT_MAP.keys()) + ] + list(EFFECT_MAP) DEVICE_SCHEMA = vol.Schema({ vol.Optional(CONF_NAME): cv.string, From 7617b8af52ecfbe1a60f80b419bdb731588a6042 Mon Sep 17 00:00:00 2001 From: karlkar Date: Thu, 18 Jan 2018 06:10:15 +0100 Subject: [PATCH 055/150] Fix for None object access attempt (#11748) * Fix NoneType access error * Update lib dependencies * Fix for line length --- homeassistant/components/camera/arlo.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/camera/arlo.py b/homeassistant/components/camera/arlo.py index 4f597771726..4d461b0e0b5 100644 --- a/homeassistant/components/camera/arlo.py +++ b/homeassistant/components/camera/arlo.py @@ -75,7 +75,9 @@ class ArloCam(Camera): self._ffmpeg = hass.data[DATA_FFMPEG] self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS) self._last_refresh = None - self._camera.base_station.refresh_rate = SCAN_INTERVAL.total_seconds() + if self._camera.base_station: + self._camera.base_station.refresh_rate = \ + SCAN_INTERVAL.total_seconds() self.attrs = {} def camera_image(self): From e72fefa74d243cb12527b4228eae313913ed93ec Mon Sep 17 00:00:00 2001 From: iliketoprogram14 Date: Wed, 17 Jan 2018 21:19:34 -0800 Subject: [PATCH 056/150] Fixed universal media player templated source_select bug (issue #10981) and corrected typo in test_universal (#11746) --- homeassistant/components/media_player/universal.py | 3 ++- tests/components/media_player/test_universal.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/universal.py b/homeassistant/components/media_player/universal.py index a7173e35a48..27a0714527d 100644 --- a/homeassistant/components/media_player/universal.py +++ b/homeassistant/components/media_player/universal.py @@ -154,7 +154,8 @@ class UniversalMediaPlayer(MediaPlayerDevice): if allow_override and service_name in self._cmds: yield from async_call_from_config( self.hass, self._cmds[service_name], - variables=service_data, blocking=True) + variables=service_data, blocking=True, + validate_config=False) return active_child = self._child_state diff --git a/tests/components/media_player/test_universal.py b/tests/components/media_player/test_universal.py index ffd4008f385..b7f3165f11c 100644 --- a/tests/components/media_player/test_universal.py +++ b/tests/components/media_player/test_universal.py @@ -143,7 +143,7 @@ class MockMediaPlayer(media_player.MediaPlayerDevice): def select_source(self, source): """Set the input source.""" - self._state = source + self._source = source def clear_playlist(self): """Clear players playlist.""" From aac01cb096cce96e8a8550fad05bc464fb02b944 Mon Sep 17 00:00:00 2001 From: Thijs de Jong Date: Thu, 18 Jan 2018 06:20:23 +0100 Subject: [PATCH 057/150] Fix Tahoma device class (#11745) * fix device class * edit description --- homeassistant/components/cover/tahoma.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/cover/tahoma.py b/homeassistant/components/cover/tahoma.py index a4159721de7..fd2b5847292 100644 --- a/homeassistant/components/cover/tahoma.py +++ b/homeassistant/components/cover/tahoma.py @@ -62,6 +62,14 @@ class TahomaCover(TahomaDevice, CoverDevice): if self.current_cover_position is not None: return self.current_cover_position == 0 + @property + def device_class(self): + """Return the class of the device.""" + if self.tahoma_device.type == 'io:WindowOpenerVeluxIOComponent': + return 'window' + else: + return None + def open_cover(self, **kwargs): """Open the cover.""" self.apply_action('open') @@ -77,10 +85,3 @@ class TahomaCover(TahomaDevice, CoverDevice): self.apply_action('setPosition', 'secured') else: self.apply_action('stopIdentify') - - def device_class(self): - """Return the class of this device, from component DEVICE_CLASSES.""" - if self.tahoma_device.type == 'io:WindowOpenerVeluxIOComponent': - return 'window' - else: - return None From 8bcaf832ae452d8dc6b8f2acfef1ddeaecbffade Mon Sep 17 00:00:00 2001 From: Kane610 Date: Thu, 18 Jan 2018 06:22:42 +0100 Subject: [PATCH 058/150] Add deCONZ entities in a predicitive order (#11712) * Make sure that entities names are created in a predicitive order * Debug print for deconz config parameters * Bump requirement to v25 --- homeassistant/components/binary_sensor/deconz.py | 5 +++-- homeassistant/components/deconz/__init__.py | 3 ++- homeassistant/components/sensor/deconz.py | 5 +++-- requirements_all.txt | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/binary_sensor/deconz.py b/homeassistant/components/binary_sensor/deconz.py index 97f78ff21d0..07524a2d868 100644 --- a/homeassistant/components/binary_sensor/deconz.py +++ b/homeassistant/components/binary_sensor/deconz.py @@ -25,8 +25,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): sensors = hass.data[DECONZ_DATA].sensors entities = [] - for sensor in sensors.values(): - if sensor.type in DECONZ_BINARY_SENSOR: + for key in sorted(sensors.keys(), key=int): + sensor = sensors[key] + if sensor and sensor.type in DECONZ_BINARY_SENSOR: entities.append(DeconzBinarySensor(sensor)) async_add_devices(entities, True) diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 06b08a9b9a8..88eb893d897 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -17,7 +17,7 @@ from homeassistant.helpers import discovery from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util.json import load_json, save_json -REQUIREMENTS = ['pydeconz==24'] +REQUIREMENTS = ['pydeconz==25'] _LOGGER = logging.getLogger(__name__) @@ -90,6 +90,7 @@ def async_setup_deconz(hass, config, deconz_config): Load config, group, light and sensor data for server information. Start websocket for push notification of state changes from deCONZ. """ + _LOGGER.debug('deCONZ config %s', deconz_config) from pydeconz import DeconzSession websession = async_get_clientsession(hass) deconz = DeconzSession(hass.loop, websession, **deconz_config) diff --git a/homeassistant/components/sensor/deconz.py b/homeassistant/components/sensor/deconz.py index 2f1ff373581..3a3050eac79 100644 --- a/homeassistant/components/sensor/deconz.py +++ b/homeassistant/components/sensor/deconz.py @@ -29,8 +29,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): sensors = hass.data[DECONZ_DATA].sensors entities = [] - for sensor in sensors.values(): - if sensor.type in DECONZ_SENSOR: + for key in sorted(sensors.keys(), key=int): + sensor = sensors[key] + if sensor and sensor.type in DECONZ_SENSOR: if sensor.type in DECONZ_REMOTE: DeconzEvent(hass, sensor) if sensor.battery: diff --git a/requirements_all.txt b/requirements_all.txt index efe35f11049..24c5e0779b8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -681,7 +681,7 @@ pycsspeechtts==1.0.2 pydaikin==0.4 # homeassistant.components.deconz -pydeconz==24 +pydeconz==25 # homeassistant.components.zwave pydispatcher==2.0.5 From aad14b8b87e23f61300f31b96b8a92769527516c Mon Sep 17 00:00:00 2001 From: Rene Nulsch <33263735+ReneNulschDE@users.noreply.github.com> Date: Thu, 18 Jan 2018 06:26:23 +0100 Subject: [PATCH 059/150] Xbox sensor - Extend error handling (#11637) * Extend error handling fix #9016 * lint finding * Implement requested changes * Remove unneeded return statement --- homeassistant/components/sensor/xbox_live.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sensor/xbox_live.py b/homeassistant/components/sensor/xbox_live.py index c3c8cde0177..2fd1a66e790 100644 --- a/homeassistant/components/sensor/xbox_live.py +++ b/homeassistant/components/sensor/xbox_live.py @@ -34,15 +34,22 @@ def setup_platform(hass, config, add_devices, discovery_info=None): api = xbox_api.XboxApi(config.get(CONF_API_KEY)) devices = [] + # request personal profile to check api connection + profile = api.get_profile() + if profile.get('error_code') is not None: + _LOGGER.error("Can't setup XboxAPI connection. Check your account or " + " api key on xboxapi.com. Code: %s Description: %s ", + profile.get('error_code', STATE_UNKNOWN), + profile.get('error_message', STATE_UNKNOWN)) + return + for xuid in config.get(CONF_XUID): new_device = XboxSensor(hass, api, xuid) if new_device.success_init: devices.append(new_device) if devices: - add_devices(devices) - else: - return False + add_devices(devices, True) class XboxSensor(Entity): @@ -59,11 +66,16 @@ class XboxSensor(Entity): # get profile info profile = self._api.get_user_gamercard(self._xuid) - if profile.get('success', True) and profile.get('code', 0) != 28: + if profile.get('success', True) and profile.get('code') is None: self.success_init = True self._gamertag = profile.get('gamertag') self._picture = profile.get('gamerpicSmallSslImagePath') else: + _LOGGER.error("Can't get user profile %s. " + "Error Code: %s Description: %s", + self._xuid, + profile.get('code', STATE_UNKNOWN), + profile.get('description', STATE_UNKNOWN)) self.success_init = False @property From f5fba333d9e297fbf266db5fdc0903828d85c76f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 18 Jan 2018 03:51:01 -0800 Subject: [PATCH 060/150] Upgrade netdisco (#11752) --- homeassistant/components/discovery.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 0c3152db3d6..9ceb82d5c69 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -21,7 +21,7 @@ from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.discovery import async_load_platform, async_discover import homeassistant.util.dt as dt_util -REQUIREMENTS = ['netdisco==1.2.3'] +REQUIREMENTS = ['netdisco==1.2.4'] DOMAIN = 'discovery' diff --git a/requirements_all.txt b/requirements_all.txt index 24c5e0779b8..086c7d0e530 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -504,7 +504,7 @@ myusps==1.2.2 nad_receiver==0.0.6 # homeassistant.components.discovery -netdisco==1.2.3 +netdisco==1.2.4 # homeassistant.components.sensor.neurio_energy neurio==0.3.1 From 216075cc725ab77e08468600608de8b064e3edc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Thu, 18 Jan 2018 19:58:56 +0100 Subject: [PATCH 061/150] set default value for rfxtrx config (#11767) --- homeassistant/components/binary_sensor/rfxtrx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/binary_sensor/rfxtrx.py b/homeassistant/components/binary_sensor/rfxtrx.py index 1c283ad214a..9a23b419cfc 100644 --- a/homeassistant/components/binary_sensor/rfxtrx.py +++ b/homeassistant/components/binary_sensor/rfxtrx.py @@ -31,8 +31,8 @@ _LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_DEVICES, default={}): { cv.string: vol.Schema({ - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_DEVICE_CLASS): cv.string, + vol.Optional(CONF_NAME, default=None): cv.string, + vol.Optional(CONF_DEVICE_CLASS, default=None): cv.string, vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean, vol.Optional(CONF_OFF_DELAY, default=None): vol.Any(cv.time_period, cv.positive_timedelta), From 2df2f354232a10be3c091011903acb3ac830b515 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Thu, 18 Jan 2018 15:47:46 -0500 Subject: [PATCH 062/150] Round off exchange rate (#11765) --- homeassistant/components/sensor/alpha_vantage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/alpha_vantage.py b/homeassistant/components/sensor/alpha_vantage.py index 7987de7caf3..20899396052 100644 --- a/homeassistant/components/sensor/alpha_vantage.py +++ b/homeassistant/components/sensor/alpha_vantage.py @@ -191,7 +191,7 @@ class AlphaVantageForeignExchange(Entity): @property def state(self): """Return the state of the sensor.""" - return self.values['5. Exchange Rate'] + return round(float(self.values['5. Exchange Rate']), 4) @property def icon(self): From 5c474ec42d322c1dc915315f2d3ce7466d399b5c Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 18 Jan 2018 21:48:21 +0100 Subject: [PATCH 063/150] Update icon (fixes #11744) (#11758) --- homeassistant/components/sensor/fixer.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/sensor/fixer.py b/homeassistant/components/sensor/fixer.py index 6e994395b3b..3e909b7b21d 100644 --- a/homeassistant/components/sensor/fixer.py +++ b/homeassistant/components/sensor/fixer.py @@ -4,14 +4,14 @@ Currency exchange rate support that comes from fixer.io. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.fixer/ """ -import logging from datetime import timedelta +import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_NAME, ATTR_ATTRIBUTION, CONF_BASE) +from homeassistant.const import ATTR_ATTRIBUTION, CONF_BASE, CONF_NAME +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity REQUIREMENTS = ['fixerio==0.1.1'] @@ -28,7 +28,7 @@ CONF_TARGET = 'target' DEFAULT_BASE = 'USD' DEFAULT_NAME = 'Exchange rate' -ICON = 'mdi:currency' +ICON = 'mdi:currency-usd' SCAN_INTERVAL = timedelta(days=1) @@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Fixer.io sensor.""" - from fixerio import (Fixerio, exceptions) + from fixerio import Fixerio, exceptions name = config.get(CONF_NAME) base = config.get(CONF_BASE) From 46bbd78b23cd41e7082c753b2817609c74262cc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20D=C3=B6tsch?= Date: Thu, 18 Jan 2018 21:59:50 +0100 Subject: [PATCH 064/150] Use localized forecast for openweathermap (#11770) Use "detailed status" of forecast to get localized value --- homeassistant/components/sensor/openweathermap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/sensor/openweathermap.py index d7443039e57..49280efe718 100644 --- a/homeassistant/components/sensor/openweathermap.py +++ b/homeassistant/components/sensor/openweathermap.py @@ -176,7 +176,7 @@ class OpenWeatherMapSensor(Entity): elif self.type == 'forecast': if fc_data is None: return - self._state = fc_data.get_weathers()[0].get_status() + self._state = fc_data.get_weathers()[0].get_detailed_status() class WeatherData(object): From 0859e38bd52bdb36814ad4679b377397c73a8b41 Mon Sep 17 00:00:00 2001 From: Ioan Loosley Date: Thu, 18 Jan 2018 21:03:01 +0000 Subject: [PATCH 065/150] Unit should be mph as that is what the metoffices datapoint API returns (#11760) --- homeassistant/components/sensor/metoffice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/metoffice.py b/homeassistant/components/sensor/metoffice.py index 43290d21e11..772e59f266e 100644 --- a/homeassistant/components/sensor/metoffice.py +++ b/homeassistant/components/sensor/metoffice.py @@ -64,9 +64,9 @@ SENSOR_TYPES = { 'weather': ['Weather', None], 'temperature': ['Temperature', TEMP_CELSIUS], 'feels_like_temperature': ['Feels Like Temperature', TEMP_CELSIUS], - 'wind_speed': ['Wind Speed', 'm/s'], + 'wind_speed': ['Wind Speed', 'mph'], 'wind_direction': ['Wind Direction', None], - 'wind_gust': ['Wind Gust', 'm/s'], + 'wind_gust': ['Wind Gust', 'mph'], 'visibility': ['Visibility', None], 'visibility_distance': ['Visibility Distance', 'km'], 'uv': ['UV', None], From 536424b0c84a7684c0bb54a59a6b87d6a0639105 Mon Sep 17 00:00:00 2001 From: Conrad Juhl Andersen Date: Thu, 18 Jan 2018 23:00:20 +0100 Subject: [PATCH 066/150] Owntracks: Use bluetooth_le as source_type if beacon was used for location change. (#11615) * Use bluetooth_le source_type, if location was changed by beacon * No reason to do nested ifs * Added tests for source_type on owntracks * Fixed The Hound * Added test and fixed bug surfaced by test --- .../components/device_tracker/owntracks.py | 10 +++++- .../device_tracker/test_owntracks.py | 31 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py index 32d677a59db..1742a0aed95 100644 --- a/homeassistant/components/device_tracker/owntracks.py +++ b/homeassistant/components/device_tracker/owntracks.py @@ -15,7 +15,10 @@ import voluptuous as vol import homeassistant.components.mqtt as mqtt import homeassistant.helpers.config_validation as cv from homeassistant.components import zone as zone_comp -from homeassistant.components.device_tracker import PLATFORM_SCHEMA +from homeassistant.components.device_tracker import ( + PLATFORM_SCHEMA, ATTR_SOURCE_TYPE, SOURCE_TYPE_BLUETOOTH_LE, + SOURCE_TYPE_GPS +) from homeassistant.const import STATE_HOME from homeassistant.core import callback from homeassistant.util import slugify, decorator @@ -140,6 +143,11 @@ def _parse_see_args(message, subscribe_topic): kwargs['attributes']['tid'] = message['tid'] if 'addr' in message: kwargs['attributes']['address'] = message['addr'] + if 't' in message: + if message['t'] == 'c': + kwargs['attributes'][ATTR_SOURCE_TYPE] = SOURCE_TYPE_GPS + if message['t'] == 'b': + kwargs['attributes'][ATTR_SOURCE_TYPE] = SOURCE_TYPE_BLUETOOTH_LE return dev_id, kwargs diff --git a/tests/components/device_tracker/test_owntracks.py b/tests/components/device_tracker/test_owntracks.py index 5f1f29e7697..44c0e0c6295 100644 --- a/tests/components/device_tracker/test_owntracks.py +++ b/tests/components/device_tracker/test_owntracks.py @@ -316,6 +316,11 @@ class BaseMQTT(unittest.TestCase): state = self.hass.states.get(DEVICE_TRACKER_STATE) self.assertEqual(state.attributes.get('gps_accuracy'), accuracy) + def assert_location_source_type(self, source_type): + """Test the assertion of source_type.""" + state = self.hass.states.get(DEVICE_TRACKER_STATE) + self.assertEqual(state.attributes.get('source_type'), source_type) + class TestDeviceTrackerOwnTracks(BaseMQTT): """Test the OwnTrack sensor.""" @@ -666,6 +671,32 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): # Location update processed self.assert_location_state('outer') + def test_event_source_type_entry_exit(self): + """Test the entry and exit events of source type.""" + # Entering the owntrack circular region named "inner" + self.send_message(EVENT_TOPIC, REGION_GPS_ENTER_MESSAGE) + + # source_type should be gps when enterings using gps. + self.assert_location_source_type('gps') + + # owntracks shouldn't send beacon events with acc = 0 + self.send_message(EVENT_TOPIC, build_message( + {'acc': 1}, REGION_BEACON_ENTER_MESSAGE)) + + # We should be able to enter a beacon zone even inside a gps zone + self.assert_location_source_type('bluetooth_le') + + self.send_message(EVENT_TOPIC, REGION_GPS_LEAVE_MESSAGE) + + # source_type should be gps when leaving using gps. + self.assert_location_source_type('gps') + + # owntracks shouldn't send beacon events with acc = 0 + self.send_message(EVENT_TOPIC, build_message( + {'acc': 1}, REGION_BEACON_LEAVE_MESSAGE)) + + self.assert_location_source_type('bluetooth_le') + # Region Beacon based event entry / exit testing def test_event_region_entry_exit(self): From 526405c83bbf2f06fa1abb027744e34f60fca73b Mon Sep 17 00:00:00 2001 From: markferry Date: Thu, 18 Jan 2018 22:03:41 +0000 Subject: [PATCH 067/150] Add a 'last' mode and attribute to min_max sensor (#11037) * Add 'last' type to min/max sensor Now supports types: min, max, mean, last 'last' is the most recently received value from all tracked entities. * Min/max sensor 'last' type test * Fix min/max sensor 'last' test --- homeassistant/components/sensor/min_max.py | 6 ++++- tests/components/sensor/test_min_max.py | 30 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/min_max.py b/homeassistant/components/sensor/min_max.py index 59a89fa0e3e..912bf7b7500 100644 --- a/homeassistant/components/sensor/min_max.py +++ b/homeassistant/components/sensor/min_max.py @@ -23,12 +23,14 @@ ATTR_MIN_VALUE = 'min_value' ATTR_MAX_VALUE = 'max_value' ATTR_COUNT_SENSORS = 'count_sensors' ATTR_MEAN = 'mean' +ATTR_LAST = 'last' ATTR_TO_PROPERTY = [ ATTR_COUNT_SENSORS, ATTR_MAX_VALUE, ATTR_MEAN, ATTR_MIN_VALUE, + ATTR_LAST, ] CONF_ENTITY_IDS = 'entity_ids' @@ -40,6 +42,7 @@ SENSOR_TYPES = { ATTR_MIN_VALUE: 'min', ATTR_MAX_VALUE: 'max', ATTR_MEAN: 'mean', + ATTR_LAST: 'last', } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -116,7 +119,7 @@ class MinMaxSensor(Entity): if self._sensor_type == v)).capitalize() self._unit_of_measurement = None self._unit_of_measurement_mismatch = False - self.min_value = self.max_value = self.mean = STATE_UNKNOWN + self.min_value = self.max_value = self.mean = self.last = STATE_UNKNOWN self.count_sensors = len(self._entity_ids) self.states = {} @@ -142,6 +145,7 @@ class MinMaxSensor(Entity): try: self.states[entity] = float(new_state.state) + self.last = float(new_state.state) except ValueError: _LOGGER.warning("Unable to store state. " "Only numerical states are supported") diff --git a/tests/components/sensor/test_min_max.py b/tests/components/sensor/test_min_max.py index a6d6a5adc68..0376c780ee7 100644 --- a/tests/components/sensor/test_min_max.py +++ b/tests/components/sensor/test_min_max.py @@ -260,3 +260,33 @@ class TestMinMaxSensor(unittest.TestCase): self.assertEqual(STATE_UNKNOWN, state.state) self.assertEqual('ERR', state.attributes.get('unit_of_measurement')) + + def test_last_sensor(self): + """Test the last sensor.""" + config = { + 'sensor': { + 'platform': 'min_max', + 'name': 'test_last', + 'type': 'last', + 'entity_ids': [ + 'sensor.test_1', + 'sensor.test_2', + 'sensor.test_3', + ] + } + } + + assert setup_component(self.hass, 'sensor', config) + + entity_ids = config['sensor']['entity_ids'] + state = self.hass.states.get('sensor.test_last') + + for entity_id, value in dict(zip(entity_ids, self.values)).items(): + self.hass.states.set(entity_id, value) + self.hass.block_till_done() + state = self.hass.states.get('sensor.test_last') + self.assertEqual(str(float(value)), state.state) + + self.assertEqual(self.min, state.attributes.get('min_value')) + self.assertEqual(self.max, state.attributes.get('max_value')) + self.assertEqual(self.mean, state.attributes.get('mean')) From 23b2ca50e2dfa24531241fc4b09ce2a076705229 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 18 Jan 2018 23:04:18 +0100 Subject: [PATCH 068/150] Update header and make it less verbose (#11774) --- homeassistant/components/asterisk_mbox.py | 43 +++++++++++------------ 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/asterisk_mbox.py b/homeassistant/components/asterisk_mbox.py index c1dafb87a6d..0b5e7c1e1d7 100644 --- a/homeassistant/components/asterisk_mbox.py +++ b/homeassistant/components/asterisk_mbox.py @@ -1,34 +1,34 @@ -"""Support for Asterisk Voicemail interface.""" +""" +Support for Asterisk Voicemail interface. +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/asterisk_mbox/ +""" import logging import voluptuous as vol - -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers import discovery -from homeassistant.const import (CONF_HOST, - CONF_PORT, CONF_PASSWORD) - +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT from homeassistant.core import callback -from homeassistant.helpers.dispatcher import (async_dispatcher_connect, - async_dispatcher_send) +from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, async_dispatcher_send) REQUIREMENTS = ['asterisk_mbox==0.4.0'] -SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated' -SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request' +_LOGGER = logging.getLogger(__name__) DOMAIN = 'asterisk_mbox' -_LOGGER = logging.getLogger(__name__) - +SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request' +SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PORT): int, vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_PORT): int, }), }, extra=vol.ALLOW_EXTRA) @@ -43,7 +43,7 @@ def setup(hass, config): hass.data[DOMAIN] = AsteriskData(hass, host, port, password) - discovery.load_platform(hass, "mailbox", DOMAIN, {}, config) + discovery.load_platform(hass, 'mailbox', DOMAIN, {}, config) return True @@ -68,15 +68,14 @@ class AsteriskData(object): from asterisk_mbox.commands import CMD_MESSAGE_LIST if command == CMD_MESSAGE_LIST: - _LOGGER.info("AsteriskVM sent updated message list") - self.messages = sorted(msg, - key=lambda item: item['info']['origtime'], - reverse=True) - async_dispatcher_send(self.hass, SIGNAL_MESSAGE_UPDATE, - self.messages) + _LOGGER.debug("AsteriskVM sent updated message list") + self.messages = sorted( + msg, key=lambda item: item['info']['origtime'], reverse=True) + async_dispatcher_send( + self.hass, SIGNAL_MESSAGE_UPDATE, self.messages) @callback def _request_messages(self): """Handle changes to the mailbox.""" - _LOGGER.info("Requesting message list") + _LOGGER.debug("Requesting message list") self.client.messages() From 8ca45acb4e1a62848de49dad718971f3814d4675 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Jodoin Date: Thu, 18 Jan 2018 17:15:13 -0500 Subject: [PATCH 069/150] Added support for TekSavvy bandwidth sensor (#11186) * Added support for TekSavvy bandwidth sensor * Variable name fix * Units are Gigabytes and not Gigabits * Change REST calls to use asyncio * houndci violation fix * pylint fix * Fix for code review * Import order asyncxxx:wq * Tweak docstrings and log message --- .coveragerc | 1 + homeassistant/components/sensor/teksavvy.py | 159 ++++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 homeassistant/components/sensor/teksavvy.py diff --git a/.coveragerc b/.coveragerc index b51829b00bc..8608d30236d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -609,6 +609,7 @@ omit = homeassistant/components/sensor/sytadin.py homeassistant/components/sensor/tank_utility.py homeassistant/components/sensor/ted5000.py + homeassistant/components/sensor/teksavvy.py homeassistant/components/sensor/temper.py homeassistant/components/sensor/tibber.py homeassistant/components/sensor/time_date.py diff --git a/homeassistant/components/sensor/teksavvy.py b/homeassistant/components/sensor/teksavvy.py new file mode 100644 index 00000000000..cb78caae095 --- /dev/null +++ b/homeassistant/components/sensor/teksavvy.py @@ -0,0 +1,159 @@ +""" +Support for TekSavvy Bandwidth Monitor. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.teksavvy/ +""" +from datetime import timedelta +import logging + +import asyncio +import async_timeout +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_API_KEY, CONF_MONITORED_VARIABLES, CONF_NAME) +from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle +import voluptuous as vol + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_NAME = 'TekSavvy' +CONF_TOTAL_BANDWIDTH = 'total_bandwidth' + +GIGABYTES = 'GB' # type: str +PERCENT = '%' # type: str + +MIN_TIME_BETWEEN_UPDATES = timedelta(hours=1) +REQUEST_TIMEOUT = 5 # seconds + +SENSOR_TYPES = { + 'usage': ['Usage', PERCENT, 'mdi:percent'], + 'usage_gb': ['Usage', GIGABYTES, 'mdi:download'], + 'limit': ['Data limit', GIGABYTES, 'mdi:download'], + 'onpeak_download': ['On Peak Download', GIGABYTES, 'mdi:download'], + 'onpeak_upload': ['On Peak Upload ', GIGABYTES, 'mdi:upload'], + 'onpeak_total': ['On Peak Total', GIGABYTES, 'mdi:download'], + 'offpeak_download': ['Off Peak download', GIGABYTES, 'mdi:download'], + 'offpeak_upload': ['Off Peak Upload', GIGABYTES, 'mdi:upload'], + 'offpeak_total': ['Off Peak Total', GIGABYTES, 'mdi:download'], + 'onpeak_remaining': ['Remaining', GIGABYTES, 'mdi:download'] +} + +API_HA_MAP = ( + ('OnPeakDownload', 'onpeak_download'), + ('OnPeakUpload', 'onpeak_upload'), + ('OffPeakDownload', 'offpeak_download'), + ('OffPeakUpload', 'offpeak_upload')) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_MONITORED_VARIABLES): + vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_TOTAL_BANDWIDTH): cv.positive_int, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, +}) + + +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up the sensor platform.""" + websession = async_get_clientsession(hass) + apikey = config.get(CONF_API_KEY) + bandwidthcap = config.get(CONF_TOTAL_BANDWIDTH) + + ts_data = TekSavvyData(hass.loop, websession, apikey, bandwidthcap) + ret = yield from ts_data.async_update() + if ret is False: + _LOGGER.error("Invalid Teksavvy API key: %s", apikey) + return + + name = config.get(CONF_NAME) + sensors = [] + for variable in config[CONF_MONITORED_VARIABLES]: + sensors.append(TekSavvySensor(ts_data, variable, name)) + async_add_devices(sensors, True) + + +class TekSavvySensor(Entity): + """Representation of TekSavvy Bandwidth sensor.""" + + def __init__(self, teksavvydata, sensor_type, name): + """Initialize the sensor.""" + self.client_name = name + self.type = sensor_type + self._name = SENSOR_TYPES[sensor_type][0] + self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] + self._icon = SENSOR_TYPES[sensor_type][2] + self.teksavvydata = teksavvydata + self._state = None + + @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 unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return self._unit_of_measurement + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return self._icon + + @asyncio.coroutine + def async_update(self): + """Get the latest data from TekSavvy and update the state.""" + yield from self.teksavvydata.async_update() + if self.type in self.teksavvydata.data: + self._state = round(self.teksavvydata.data[self.type], 2) + + +class TekSavvyData(object): + """Get data from TekSavvy API.""" + + def __init__(self, loop, websession, api_key, bandwidth_cap): + """Initialize the data object.""" + self.loop = loop + self.websession = websession + self.api_key = api_key + self.bandwidth_cap = bandwidth_cap + self.data = {"limit": self.bandwidth_cap} + + @asyncio.coroutine + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def async_update(self): + """Get the TekSavvy bandwidth data from the web service.""" + headers = {"TekSavvy-APIKey": self.api_key} + _LOGGER.debug("Updating TekSavvy data") + url = "https://api.teksavvy.com/"\ + "web/Usage/UsageSummaryRecords?$filter=IsCurrent%20eq%20true" + with async_timeout.timeout(REQUEST_TIMEOUT, loop=self.loop): + req = yield from self.websession.get(url, headers=headers) + if req.status != 200: + _LOGGER.error("Request failed with status: %u", req.status) + return False + else: + data = yield from req.json() + for (api, ha_name) in API_HA_MAP: + self.data[ha_name] = float(data["value"][0][api]) + on_peak_download = self.data["onpeak_download"] + on_peak_upload = self.data["onpeak_upload"] + off_peak_download = self.data["offpeak_download"] + off_peak_upload = self.data["offpeak_upload"] + limit = self.data["limit"] + self.data["usage"] = 100*on_peak_download/self.bandwidth_cap + self.data["usage_gb"] = on_peak_download + self.data["onpeak_total"] = on_peak_download + on_peak_upload + self.data["offpeak_total"] = off_peak_download + off_peak_upload + self.data["onpeak_remaining"] = limit - on_peak_download + return True From cf6f916ed49d3a340c4f24e6029d2fc76b6e925c Mon Sep 17 00:00:00 2001 From: karlkar Date: Thu, 18 Jan 2018 23:37:24 +0100 Subject: [PATCH 070/150] Fix for Neato D3 and D5 (#11775) --- homeassistant/components/neato.py | 4 ++-- requirements_all.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/neato.py b/homeassistant/components/neato.py index bd680b5361e..7402bb18843 100644 --- a/homeassistant/components/neato.py +++ b/homeassistant/components/neato.py @@ -17,8 +17,8 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['https://github.com/jabesq/pybotvac/archive/v0.0.4.zip' - '#pybotvac==0.0.4'] +REQUIREMENTS = ['https://github.com/jabesq/pybotvac/archive/v0.0.5.zip' + '#pybotvac==0.0.5'] DOMAIN = 'neato' NEATO_ROBOTS = 'neato_robots' diff --git a/requirements_all.txt b/requirements_all.txt index 086c7d0e530..16a3693b5e5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -370,7 +370,7 @@ https://github.com/happyleavesaoc/spotipy/archive/544614f4b1d508201d363e84e871f8 https://github.com/jabesq/netatmo-api-python/archive/v0.9.2.1.zip#lnetatmo==0.9.2.1 # homeassistant.components.neato -https://github.com/jabesq/pybotvac/archive/v0.0.4.zip#pybotvac==0.0.4 +https://github.com/jabesq/pybotvac/archive/v0.0.5.zip#pybotvac==0.0.5 # homeassistant.components.sensor.sabnzbd https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1 From a9634199e6aefdc63acb1a5e454c42eecd985335 Mon Sep 17 00:00:00 2001 From: Kane610 Date: Thu, 18 Jan 2018 23:51:01 +0100 Subject: [PATCH 071/150] Axis discovery fails to save conf (#11769) * Signal callback isnt JSON serializable so it has to be removed before saving to conf * Remove filtered events list which is not a part of component configuration --- homeassistant/components/axis.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/axis.py b/homeassistant/components/axis.py index 23dbe052d1c..16bd544e96e 100644 --- a/homeassistant/components/axis.py +++ b/homeassistant/components/axis.py @@ -101,6 +101,8 @@ def request_configuration(hass, config, name, host, serialnumber): return False if setup_device(hass, config, device_config): + del device_config['events'] + del device_config['signal'] config_file = load_json(hass.config.path(CONFIG_FILE)) config_file[serialnumber] = dict(device_config) save_json(hass.config.path(CONFIG_FILE), config_file) From ce9673b06de431a75036832976ed3e5f591ae852 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Fri, 19 Jan 2018 06:59:03 +0100 Subject: [PATCH 072/150] Limit service description loading to a single thread (#11733) --- homeassistant/helpers/service.py | 35 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 83df8b48ab6..6c68c20991b 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -137,30 +137,29 @@ def async_get_all_descriptions(hass): component_path = path.dirname(get_component(domain).__file__) return path.join(component_path, 'services.yaml') - def load_services_file(yaml_file): - """Load and cache a services.yaml file.""" - try: - yaml_cache[yaml_file] = load_yaml(yaml_file) - except FileNotFoundError: - pass + def load_services_files(yaml_files): + """Load and parse services.yaml files.""" + loaded = {} + for yaml_file in yaml_files: + try: + loaded[yaml_file] = load_yaml(yaml_file) + except FileNotFoundError: + loaded[yaml_file] = {} + + return loaded services = hass.services.async_services() # Load missing files - yaml_cache = {} - loading_tasks = [] + missing = set() for domain in services: - yaml_file = domain_yaml_file(domain) - for service in services[domain]: if format_cache_key(domain, service) not in description_cache: - if yaml_file not in yaml_cache: - yaml_cache[yaml_file] = {} - task = hass.async_add_job(load_services_file, yaml_file) - loading_tasks.append(task) + missing.add(domain_yaml_file(domain)) + break - if loading_tasks: - yield from asyncio.wait(loading_tasks, loop=hass.loop) + if missing: + loaded = yield from hass.async_add_job(load_services_files, missing) # Build response catch_all_yaml_file = domain_yaml_file(ha.DOMAIN) @@ -176,9 +175,9 @@ def async_get_all_descriptions(hass): # Cache missing descriptions if description is None: if yaml_file == catch_all_yaml_file: - yaml_services = yaml_cache[yaml_file].get(domain, {}) + yaml_services = loaded[yaml_file].get(domain, {}) else: - yaml_services = yaml_cache[yaml_file] + yaml_services = loaded[yaml_file] yaml_description = yaml_services.get(service, {}) description = description_cache[cache_key] = { From 0e1cc0518903311b4b64b46907a6cda10b5dd39d Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Fri, 19 Jan 2018 01:01:04 -0500 Subject: [PATCH 073/150] Snips: (change) Removed unknown intent speech response (#11776) * Removed unknown intent speech response * Fixed tests, doh! * Woof --- homeassistant/components/snips.py | 1 - tests/components/test_snips.py | 32 +++++++++---------------------- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/snips.py b/homeassistant/components/snips.py index d221c8512c6..5a317b09e87 100644 --- a/homeassistant/components/snips.py +++ b/homeassistant/components/snips.py @@ -104,7 +104,6 @@ def async_setup(hass, config): except intent.UnknownIntent as err: _LOGGER.warning("Received unknown intent %s", request['intent']['intentName']) - snips_response = "Unknown Intent" except intent.IntentError: _LOGGER.exception("Error while handling intent: %s.", intent_type) snips_response = "Error while handling intent" diff --git a/tests/components/test_snips.py b/tests/components/test_snips.py index 711d13dc341..f37beef7960 100644 --- a/tests/components/test_snips.py +++ b/tests/components/test_snips.py @@ -1,6 +1,7 @@ """Test the Snips component.""" import asyncio import json +import logging from homeassistant.core import callback from homeassistant.bootstrap import async_setup_component @@ -151,42 +152,27 @@ def test_intent_speech_response(hass, mqtt_mock): @asyncio.coroutine -def test_snips_unknown_intent(hass, mqtt_mock): - """Test calling unknown Intent via Snips.""" - event = 'call_service' - events = [] - - @callback - def record_event(event): - """Add recorded event to set.""" - events.append(event) +def test_unknown_intent(hass, mqtt_mock, caplog): + """Test unknown intent.""" + caplog.set_level(logging.WARNING) result = yield from async_setup_component(hass, "snips", { "snips": {}, }) assert result payload = """ { - "input": "what to do", + "input": "I don't know what I am supposed to do", + "sessionId": "abcdef1234567890", "intent": { "intentName": "unknownIntent" }, "slots": [] } """ - intents = async_mock_intent(hass, 'knownIntent') - hass.bus.async_listen(event, record_event) - async_fire_mqtt_message(hass, 'hermes/intent/unknownIntent', - payload) + async_fire_mqtt_message(hass, + 'hermes/intent/unknownIntent', payload) yield from hass.async_block_till_done() - - assert not intents - assert len(events) == 1 - assert events[0].data['domain'] == 'mqtt' - assert events[0].data['service'] == 'publish' - payload = json.loads(events[0].data['service_data']['payload']) - topic = events[0].data['service_data']['topic'] - assert payload['text'] == 'Unknown Intent' - assert topic == 'hermes/dialogueManager/endSession' + assert 'Received unknown intent unknownIntent' in caplog.text @asyncio.coroutine From 48619c9d7c59827777bff25c6197c6fc72d31498 Mon Sep 17 00:00:00 2001 From: tschmidty69 Date: Fri, 19 Jan 2018 01:13:14 -0500 Subject: [PATCH 074/150] Implemented event_data_template (new) (#11057) * Implemented event_data_template * The hound does not like my indentation * Added passed variables to tests for event and svc template calls * Moved recursive function to template.py * Update template.py * Update template.py * Cleaned up service.py and fixed unit tests * Blank lines * Removed stray logger statement * Blank lines again --- homeassistant/helpers/config_validation.py | 1 + homeassistant/helpers/script.py | 19 +++++++--- homeassistant/helpers/service.py | 18 ++++------ homeassistant/helpers/template.py | 11 ++++++ tests/helpers/test_script.py | 41 +++++++++++++++++++--- tests/helpers/test_template.py | 30 ++++++++++++++++ 6 files changed, 101 insertions(+), 19 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index afb4483647d..e32b041ffa2 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -475,6 +475,7 @@ EVENT_SCHEMA = vol.Schema({ vol.Optional(CONF_ALIAS): string, vol.Required('event'): string, vol.Optional('event_data'): dict, + vol.Optional('event_data_template'): {match_all: template_complex} }) SERVICE_SCHEMA = vol.All(vol.Schema({ diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 7154e990563..b8b6b29df81 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -8,8 +8,10 @@ import voluptuous as vol from homeassistant.core import HomeAssistant, callback from homeassistant.const import CONF_CONDITION, CONF_TIMEOUT +from homeassistant.exceptions import TemplateError from homeassistant.helpers import ( - service, condition, template, config_validation as cv) + service, condition, template as template, + config_validation as cv) from homeassistant.helpers.event import ( async_track_point_in_utc_time, async_track_template) from homeassistant.helpers.typing import ConfigType @@ -25,6 +27,7 @@ CONF_SERVICE_DATA = 'data' CONF_SEQUENCE = 'sequence' CONF_EVENT = 'event' CONF_EVENT_DATA = 'event_data' +CONF_EVENT_DATA_TEMPLATE = 'event_data_template' CONF_DELAY = 'delay' CONF_WAIT_TEMPLATE = 'wait_template' @@ -145,7 +148,7 @@ class Script(): break elif CONF_EVENT in action: - self._async_fire_event(action) + self._async_fire_event(action, variables) else: yield from self._async_call_service(action, variables) @@ -180,12 +183,20 @@ class Script(): yield from service.async_call_from_config( self.hass, action, True, variables, validate_config=False) - def _async_fire_event(self, action): + def _async_fire_event(self, action, variables): """Fire an event.""" self.last_action = action.get(CONF_ALIAS, action[CONF_EVENT]) self._log("Executing step %s" % self.last_action) + event_data = dict(action.get(CONF_EVENT_DATA, {})) + if CONF_EVENT_DATA_TEMPLATE in action: + try: + event_data.update(template.render_complex( + action[CONF_EVENT_DATA_TEMPLATE], variables)) + except TemplateError as ex: + _LOGGER.error('Error rendering event data template: %s', ex) + self.hass.bus.async_fire(action[CONF_EVENT], - action.get(CONF_EVENT_DATA)) + event_data) def _async_check_condition(self, action, variables): """Test if condition is matching.""" diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 6c68c20991b..f5b626c8828 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -10,6 +10,7 @@ import voluptuous as vol from homeassistant.const import ATTR_ENTITY_ID import homeassistant.core as ha from homeassistant.exceptions import TemplateError +from homeassistant.helpers import template from homeassistant.loader import get_component, bind_hass from homeassistant.util.yaml import load_yaml import homeassistant.helpers.config_validation as cv @@ -67,17 +68,12 @@ def async_call_from_config(hass, config, blocking=False, variables=None, service_data = dict(config.get(CONF_SERVICE_DATA, {})) if CONF_SERVICE_DATA_TEMPLATE in config: - def _data_template_creator(value): - """Recursive template creator helper function.""" - if isinstance(value, list): - return [_data_template_creator(item) for item in value] - elif isinstance(value, dict): - return {key: _data_template_creator(item) - for key, item in value.items()} - value.hass = hass - return value.async_render(variables) - service_data.update(_data_template_creator( - config[CONF_SERVICE_DATA_TEMPLATE])) + try: + template.attach(hass, config[CONF_SERVICE_DATA_TEMPLATE]) + service_data.update(template.render_complex( + config[CONF_SERVICE_DATA_TEMPLATE], variables)) + except TemplateError as ex: + _LOGGER.error('Error rendering data template: %s', ex) if CONF_SERVICE_ENTITY_ID in config: service_data[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID] diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 1295d4961df..d56cef1dd83 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -44,6 +44,17 @@ def attach(hass, obj): obj.hass = hass +def render_complex(value, variables=None): + """Recursive template creator helper function.""" + if isinstance(value, list): + return [render_complex(item, variables) + for item in value] + elif isinstance(value, dict): + return {key: render_complex(item, variables) + for key, item in value.items()} + return value.async_render(variables) + + def extract_entities(template, variables=None): """Extract all entities for state_changed listener from template string.""" if template is None or _RE_NONE_ENTITIES.search(template): diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index b6e3ea17e1a..385b0a5df05 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -56,6 +56,39 @@ class TestScriptHelper(unittest.TestCase): assert calls[0].data.get('hello') == 'world' assert not script_obj.can_cancel + def test_firing_event_template(self): + """Test the firing of events.""" + event = 'test_event' + calls = [] + + @callback + def record_event(event): + """Add recorded event to set.""" + calls.append(event) + + self.hass.bus.listen(event, record_event) + + script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA({ + 'event': event, + 'event_data_template': { + 'hello': """ + {% if is_world == 'yes' %} + world + {% else %} + not world + {% endif %} + """ + } + })) + + script_obj.run({'is_world': 'yes'}) + + self.hass.block_till_done() + + assert len(calls) == 1 + assert calls[0].data.get('hello') == 'world' + assert not script_obj.can_cancel + def test_calling_service(self): """Test the calling of a service.""" calls = [] @@ -99,14 +132,14 @@ class TestScriptHelper(unittest.TestCase): {% endif %}""", 'data_template': { 'hello': """ - {% if True %} + {% if is_world == 'yes' %} world {% else %} - Not world + not world {% endif %} """ } - }) + }, {'is_world': 'yes'}) self.hass.block_till_done() @@ -147,7 +180,7 @@ class TestScriptHelper(unittest.TestCase): def test_delay_template(self): """Test the delay as a template.""" - event = 'test_evnt' + event = 'test_event' events = [] @callback diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 614d2f881a0..47e46bae3c7 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -279,6 +279,36 @@ class TestHelpersTemplate(unittest.TestCase): '127', template.Template('{{ hello }}', self.hass).render({'hello': 127})) + def test_passing_vars_as_list(self): + """Test passing variables as list.""" + self.assertEqual( + "['foo', 'bar']", + template.render_complex(template.Template('{{ hello }}', + self.hass), {'hello': ['foo', 'bar']})) + + def test_passing_vars_as_list_element(self): + """Test passing variables as list.""" + self.assertEqual( + 'bar', + template.render_complex(template.Template('{{ hello[1] }}', + self.hass), + {'hello': ['foo', 'bar']})) + + def test_passing_vars_as_dict_element(self): + """Test passing variables as list.""" + self.assertEqual( + 'bar', + template.render_complex(template.Template('{{ hello.foo }}', + self.hass), + {'hello': {'foo': 'bar'}})) + + def test_passing_vars_as_dict(self): + """Test passing variables as list.""" + self.assertEqual( + "{'foo': 'bar'}", + template.render_complex(template.Template('{{ hello }}', + self.hass), {'hello': {'foo': 'bar'}})) + def test_render_with_possible_json_value_with_valid_json(self): """Render with possible JSON value with valid JSON.""" tpl = template.Template('{{ value_json.hello }}', self.hass) From 7fe2dafa0479645aee7ba535130c9a48597401ad Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Fri, 19 Jan 2018 07:36:29 +0100 Subject: [PATCH 075/150] Fix PEP8 and PEP257 issues (#11780) --- homeassistant/components/binary_sensor/deconz.py | 7 +++---- homeassistant/components/deconz/__init__.py | 15 +++++++-------- homeassistant/components/light/deconz.py | 11 +++++------ homeassistant/components/scene/deconz.py | 3 +-- homeassistant/components/sensor/deconz.py | 13 ++++++------- 5 files changed, 22 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/binary_sensor/deconz.py b/homeassistant/components/binary_sensor/deconz.py index 07524a2d868..3c02dfb3508 100644 --- a/homeassistant/components/binary_sensor/deconz.py +++ b/homeassistant/components/binary_sensor/deconz.py @@ -4,7 +4,6 @@ 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/ """ - import asyncio from homeassistant.components.binary_sensor import BinarySensorDevice @@ -17,7 +16,7 @@ DEPENDENCIES = ['deconz'] @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup binary sensor for deCONZ component.""" + """Set up the deCONZ binary sensor.""" if discovery_info is None: return @@ -36,7 +35,7 @@ class DeconzBinarySensor(BinarySensorDevice): """Representation of a binary sensor.""" def __init__(self, sensor): - """Setup sensor and add update callback to get data from websocket.""" + """Set up sensor and add update callback to get data from websocket.""" self._sensor = sensor @asyncio.coroutine @@ -68,7 +67,7 @@ class DeconzBinarySensor(BinarySensorDevice): @property def device_class(self): - """Class of the sensor.""" + """Return the class of the sensor.""" return self._sensor.sensor_class @property diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 88eb893d897..72845ee671f 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -4,14 +4,14 @@ Support for deCONZ devices. For more details about this component, please refer to the documentation at https://home-assistant.io/components/deconz/ """ - import asyncio import logging + import voluptuous as vol +from homeassistant.components.discovery import SERVICE_DECONZ from homeassistant.const import ( CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP) -from homeassistant.components.discovery import SERVICE_DECONZ from homeassistant.helpers import config_validation as cv from homeassistant.helpers import discovery from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -27,8 +27,8 @@ CONFIG_FILE = 'deconz.conf' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ - vol.Optional(CONF_HOST): cv.string, vol.Optional(CONF_API_KEY): cv.string, + vol.Optional(CONF_HOST): cv.string, vol.Optional(CONF_PORT, default=80): cv.port, }) }, extra=vol.ALLOW_EXTRA) @@ -53,7 +53,7 @@ Unlock your deCONZ gateway to register with Home Assistant. @asyncio.coroutine def async_setup(hass, config): - """Setup services and configuration for deCONZ component.""" + """Set up services and configuration for deCONZ component.""" result = False config_file = yield from hass.async_add_job( load_json, hass.config.path(CONFIG_FILE)) @@ -85,7 +85,7 @@ def async_setup(hass, config): @asyncio.coroutine def async_setup_deconz(hass, config, deconz_config): - """Setup deCONZ session. + """Set up a deCONZ session. Load config, group, light and sensor data for server information. Start websocket for push notification of state changes from deCONZ. @@ -147,9 +147,8 @@ def async_request_configuration(hass, config, deconz_config): deconz_config[CONF_API_KEY] = api_key result = yield from async_setup_deconz(hass, config, deconz_config) if result: - yield from hass.async_add_job(save_json, - hass.config.path(CONFIG_FILE), - deconz_config) + yield from hass.async_add_job( + save_json, hass.config.path(CONFIG_FILE), deconz_config) configurator.async_request_done(request_id) return else: diff --git a/homeassistant/components/light/deconz.py b/homeassistant/components/light/deconz.py index a1c43ad4cbc..6b22190dce9 100644 --- a/homeassistant/components/light/deconz.py +++ b/homeassistant/components/light/deconz.py @@ -4,15 +4,14 @@ Support for deCONZ light. For more details about this component, please refer to the documentation at https://home-assistant.io/components/light.deconz/ """ - import asyncio from homeassistant.components.deconz import DOMAIN as DECONZ_DATA from homeassistant.components.light import ( - Light, ATTR_BRIGHTNESS, ATTR_FLASH, ATTR_COLOR_TEMP, ATTR_EFFECT, - ATTR_RGB_COLOR, ATTR_TRANSITION, EFFECT_COLORLOOP, FLASH_LONG, FLASH_SHORT, + ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_RGB_COLOR, + ATTR_TRANSITION, EFFECT_COLORLOOP, FLASH_LONG, FLASH_SHORT, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_FLASH, - SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, SUPPORT_XY_COLOR) + SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, SUPPORT_XY_COLOR, Light) from homeassistant.core import callback from homeassistant.util.color import color_RGB_to_xy @@ -23,7 +22,7 @@ ATTR_LIGHT_GROUP = 'LightGroup' @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup light for deCONZ component.""" + """Set up the deCONZ light.""" if discovery_info is None: return @@ -44,7 +43,7 @@ class DeconzLight(Light): """Representation of a deCONZ light.""" def __init__(self, light): - """Setup light and add update callback to get data from websocket.""" + """Set up light and add update callback to get data from websocket.""" self._light = light self._features = SUPPORT_BRIGHTNESS diff --git a/homeassistant/components/scene/deconz.py b/homeassistant/components/scene/deconz.py index f035ae3128e..067db1f93a3 100644 --- a/homeassistant/components/scene/deconz.py +++ b/homeassistant/components/scene/deconz.py @@ -4,7 +4,6 @@ Support for deCONZ scenes. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/scene.deconz/ """ - import asyncio from homeassistant.components.deconz import DOMAIN as DECONZ_DATA @@ -31,7 +30,7 @@ class DeconzScene(Scene): """Representation of a deCONZ scene.""" def __init__(self, scene): - """Setup scene.""" + """Set up a scene.""" self._scene = scene @asyncio.coroutine diff --git a/homeassistant/components/sensor/deconz.py b/homeassistant/components/sensor/deconz.py index 3a3050eac79..b3118d3906d 100644 --- a/homeassistant/components/sensor/deconz.py +++ b/homeassistant/components/sensor/deconz.py @@ -4,12 +4,11 @@ Support for deCONZ sensor. For more details about this component, please refer to the documentation at https://home-assistant.io/components/sensor.deconz/ """ - import asyncio from homeassistant.components.deconz import DOMAIN as DECONZ_DATA from homeassistant.const import ATTR_BATTERY_LEVEL, CONF_EVENT, CONF_ID -from homeassistant.core import callback, EventOrigin +from homeassistant.core import EventOrigin, callback from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.util import slugify @@ -21,7 +20,7 @@ ATTR_EVENT_ID = 'event_id' @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup sensor for deCONZ component.""" + """Set up the deCONZ sensors.""" if discovery_info is None: return @@ -45,7 +44,7 @@ class DeconzSensor(Entity): """Representation of a sensor.""" def __init__(self, sensor): - """Setup sensor and add update callback to get data from websocket.""" + """Set up sensor and add update callback to get data from websocket.""" self._sensor = sensor @asyncio.coroutine @@ -77,7 +76,7 @@ class DeconzSensor(Entity): @property def device_class(self): - """Class of the sensor.""" + """Return the class of the sensor.""" return self._sensor.sensor_class @property @@ -115,7 +114,7 @@ class DeconzBattery(Entity): def __init__(self, device): """Register dispatcher callback for update of battery state.""" self._device = device - self._name = self._device.name + ' Battery Level' + self._name = '{} {}'.format(self._device.name, 'Battery Level') self._device_class = 'battery' self._unit_of_measurement = "%" @@ -142,7 +141,7 @@ class DeconzBattery(Entity): @property def device_class(self): - """Class of the sensor.""" + """Return the class of the sensor.""" return self._device_class @property From b1fd9daf5fc91509d3e5b0f3a22d8a4c77eeffb3 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Fri, 19 Jan 2018 07:36:48 +0100 Subject: [PATCH 076/150] Fix typos (#11781) --- .../components/system_log/__init__.py | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/system_log/__init__.py b/homeassistant/components/system_log/__init__.py index d25f32eacc7..0d478ac9316 100644 --- a/homeassistant/components/system_log/__init__.py +++ b/homeassistant/components/system_log/__init__.py @@ -1,36 +1,35 @@ """ Support for system log. -For more details about this platform, please refer to the documentation at +For more details about this component, please refer to the documentation at https://home-assistant.io/components/system_log/ """ -import re import asyncio -import logging -import traceback -from io import StringIO from collections import deque +from io import StringIO +import logging +import re +import traceback import voluptuous as vol from homeassistant import __path__ as HOMEASSISTANT_PATH -import homeassistant.helpers.config_validation as cv from homeassistant.components.http import HomeAssistantView - -DOMAIN = 'system_log' -DEPENDENCIES = ['http'] -SERVICE_CLEAR = 'clear' +import homeassistant.helpers.config_validation as cv CONF_MAX_ENTRIES = 'max_entries' -DEFAULT_MAX_ENTRIES = 50 - DATA_SYSTEM_LOG = 'system_log' +DEFAULT_MAX_ENTRIES = 50 +DEPENDENCIES = ['http'] +DOMAIN = 'system_log' + +SERVICE_CLEAR = 'clear' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ - vol.Optional(CONF_MAX_ENTRIES, - default=DEFAULT_MAX_ENTRIES): cv.positive_int, + vol.Optional(CONF_MAX_ENTRIES, default=DEFAULT_MAX_ENTRIES): + cv.positive_int, }), }, extra=vol.ALLOW_EXTRA) @@ -48,9 +47,9 @@ class LogErrorHandler(logging.Handler): def emit(self, record): """Save error and warning logs. - Everyhing logged with error or warning is saved in local buffer. A + Everything logged with error or warning is saved in local buffer. A default upper limit is set to 50 (older entries are discarded) but can - be changed if neeeded. + be changed if needed. """ if record.levelno >= logging.WARN: stack = [] @@ -97,7 +96,7 @@ def _figure_out_source(record, call_stack, hass): paths.append(netdisco_path[0]) except ImportError: pass - # If a stack trace exists, extract filenames from the entire call stack. + # If a stack trace exists, extract file names from the entire call stack. # The other case is when a regular "log" is made (without an attached # exception). In that case, just use the file where the log was made from. if record.exc_info: @@ -115,10 +114,10 @@ def _figure_out_source(record, call_stack, hass): stack = call_stack[0:index+1] # Iterate through the stack call (in reverse) and find the last call from - # a file in HA. Try to figure out where error happened. + # a file in Home Assistant. Try to figure out where error happened. for pathname in reversed(stack): - # Try to match with a file within HA + # Try to match with a file within Home Assistant match = re.match(r'(?:{})/(.*)'.format('|'.join(paths)), pathname) if match: return match.group(1) From 314582ef0cdcd3f787379d0faed73e14cf42c152 Mon Sep 17 00:00:00 2001 From: William Scanlon Date: Fri, 19 Jan 2018 01:37:24 -0500 Subject: [PATCH 077/150] Support for performance mode on Rheem water heaters. (#11786) --- homeassistant/components/climate/econet.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/climate/econet.py b/homeassistant/components/climate/econet.py index 5620bcbfa11..8f2342782ed 100644 --- a/homeassistant/components/climate/econet.py +++ b/homeassistant/components/climate/econet.py @@ -16,6 +16,7 @@ from homeassistant.components.climate import ( STATE_HEAT_PUMP, STATE_HIGH_DEMAND, STATE_OFF, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, + STATE_PERFORMANCE, ClimateDevice) from homeassistant.const import (ATTR_ENTITY_ID, CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT, @@ -59,6 +60,7 @@ HA_STATE_TO_ECONET = { STATE_GAS: 'gas', STATE_HIGH_DEMAND: 'High Demand', STATE_OFF: 'Off', + STATE_PERFORMANCE: 'Performance' } ECONET_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_ECONET.items()} From b10fd172fd571e0a94fb009854e018c0b010b993 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Fri, 19 Jan 2018 07:52:30 +0100 Subject: [PATCH 078/150] Service for setting a fixed scene of Xiaomi MIIO lights (#10819) * Service for setting a fixed scene introduced. Fixes https://github.com/syssi/philipslight/issues/6. Fixes https://github.com/home-assistant/home-assistant/issues/10458. * Service description added. * Typo fixed. * Error message updated and naming improved. * Name ("scene") of the method parameter aligned. * Hound error fixed: Spaces removed. * async_setup_platform method simplified. * Lazy loading of service descriptions. * Unused import removed. --- homeassistant/components/light/services.yaml | 10 +++ homeassistant/components/light/xiaomi_miio.py | 65 +++++++++++++++++-- 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/light/services.yaml b/homeassistant/components/light/services.yaml index 23814f16598..0bcf6933e68 100644 --- a/homeassistant/components/light/services.yaml +++ b/homeassistant/components/light/services.yaml @@ -159,3 +159,13 @@ lifx_effect_stop: entity_id: description: Name(s) of entities to stop effects on. Leave out to stop effects everywhere. example: 'light.bedroom' + +xiaomi_miio_set_scene: + description: Set a fixed scene. + fields: + entity_id: + description: Name of the light entity. + example: 'light.xiaomi_miio' + scene: + description: Number of the fixed scene, between 1 and 4. + example: 1 diff --git a/homeassistant/components/light/xiaomi_miio.py b/homeassistant/components/light/xiaomi_miio.py index 9812933b7d6..cd3ea9f0f39 100644 --- a/homeassistant/components/light/xiaomi_miio.py +++ b/homeassistant/components/light/xiaomi_miio.py @@ -13,7 +13,7 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.light import ( PLATFORM_SCHEMA, ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, - ATTR_COLOR_TEMP, SUPPORT_COLOR_TEMP, Light, ) + ATTR_COLOR_TEMP, SUPPORT_COLOR_TEMP, Light, ATTR_ENTITY_ID, DOMAIN, ) from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_TOKEN, ) from homeassistant.exceptions import PlatformNotReady @@ -21,6 +21,7 @@ from homeassistant.exceptions import PlatformNotReady _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'Xiaomi Philips Light' +PLATFORM = 'xiaomi_miio' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, @@ -36,6 +37,24 @@ CCT_MAX = 100 SUCCESS = ['ok'] ATTR_MODEL = 'model' +ATTR_SCENE = 'scene' + +SERVICE_SET_SCENE = 'xiaomi_miio_set_scene' + +XIAOMI_MIIO_SERVICE_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, +}) + +SERVICE_SCHEMA_SCENE = XIAOMI_MIIO_SERVICE_SCHEMA.extend({ + vol.Required(ATTR_SCENE): + vol.All(vol.Coerce(int), vol.Clamp(min=1, max=4)) +}) + +SERVICE_TO_METHOD = { + SERVICE_SET_SCENE: { + 'method': 'async_set_scene', + 'schema': SERVICE_SCHEMA_SCENE} +} # pylint: disable=unused-argument @@ -43,6 +62,8 @@ ATTR_MODEL = 'model' def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the light from config.""" from miio import Device, DeviceException + if PLATFORM not in hass.data: + hass.data[PLATFORM] = {} host = config.get(CONF_HOST) name = config.get(CONF_NAME) @@ -50,7 +71,6 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): _LOGGER.info("Initializing with host %s (token %s...)", host, token[:5]) - devices = [] try: light = Device(host, token) device_info = light.info() @@ -63,27 +83,53 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): from miio import PhilipsEyecare light = PhilipsEyecare(host, token) device = XiaomiPhilipsEyecareLamp(name, light, device_info) - devices.append(device) elif device_info.model == 'philips.light.ceiling': from miio import Ceil light = Ceil(host, token) device = XiaomiPhilipsCeilingLamp(name, light, device_info) - devices.append(device) elif device_info.model == 'philips.light.bulb': from miio import PhilipsBulb light = PhilipsBulb(host, token) device = XiaomiPhilipsLightBall(name, light, device_info) - devices.append(device) else: _LOGGER.error( 'Unsupported device found! Please create an issue at ' 'https://github.com/rytilahti/python-miio/issues ' 'and provide the following data: %s', device_info.model) + return False except DeviceException: raise PlatformNotReady - async_add_devices(devices, update_before_add=True) + hass.data[PLATFORM][host] = device + async_add_devices([device], update_before_add=True) + + @asyncio.coroutine + def async_service_handler(service): + """Map services to methods on Xiaomi Philips Lights.""" + method = SERVICE_TO_METHOD.get(service.service) + params = {key: value for key, value in service.data.items() + if key != ATTR_ENTITY_ID} + entity_ids = service.data.get(ATTR_ENTITY_ID) + if entity_ids: + target_devices = [dev for dev in hass.data[PLATFORM].values() + if dev.entity_id in entity_ids] + else: + target_devices = hass.data[PLATFORM].values() + + update_tasks = [] + for target_device in target_devices: + yield from getattr(target_device, method['method'])(**params) + update_tasks.append(target_device.async_update_ha_state(True)) + + if update_tasks: + yield from asyncio.wait(update_tasks, loop=hass.loop) + + for xiaomi_miio_service in SERVICE_TO_METHOD: + schema = SERVICE_TO_METHOD[xiaomi_miio_service].get( + 'schema', XIAOMI_MIIO_SERVICE_SCHEMA) + hass.services.async_register( + DOMAIN, xiaomi_miio_service, async_service_handler, schema=schema) class XiaomiPhilipsGenericLight(Light): @@ -194,6 +240,13 @@ class XiaomiPhilipsGenericLight(Light): except DeviceException as ex: _LOGGER.error("Got exception while fetching the state: %s", ex) + @asyncio.coroutine + def async_set_scene(self, scene: int=1): + """Set the fixed scene.""" + yield from self._try_command( + "Setting a fixed scene failed.", + self._light.set_scene, scene) + @staticmethod def translate(value, left_min, left_max, right_min, right_max): """Map a value from left span to right span.""" From c48ef281ab513018bb20e79f496bafd3ad6ec15b Mon Sep 17 00:00:00 2001 From: Steaff Date: Fri, 19 Jan 2018 08:20:05 +0100 Subject: [PATCH 079/150] Homematic ip tilt covers (#11650) * add pyhomematic support for ip and tiltable covers * use cached data in current_cover_tilt_position * check for existance not for None * reformatting * check node for LEVEL_2 --- homeassistant/components/cover/homematic.py | 43 ++++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/cover/homematic.py b/homeassistant/components/cover/homematic.py index 9e3d675cabe..1fa215e5fb9 100644 --- a/homeassistant/components/cover/homematic.py +++ b/homeassistant/components/cover/homematic.py @@ -5,9 +5,11 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/cover.homematic/ """ import logging -from homeassistant.const import STATE_UNKNOWN -from homeassistant.components.cover import CoverDevice, ATTR_POSITION + +from homeassistant.components.cover import CoverDevice, ATTR_POSITION,\ + ATTR_TILT_POSITION from homeassistant.components.homematic import HMDevice, ATTR_DISCOVER_DEVICES +from homeassistant.const import STATE_UNKNOWN _LOGGER = logging.getLogger(__name__) @@ -69,3 +71,40 @@ class HMCover(HMDevice, CoverDevice): """Generate a data dictoinary (self._data) from metadata.""" self._state = "LEVEL" self._data.update({self._state: STATE_UNKNOWN}) + if "LEVEL_2" in self._hmdevice.WRITENODE: + self._data.update( + {'LEVEL_2': STATE_UNKNOWN}) + + @property + def current_cover_tilt_position(self): + """Return current position of cover tilt. + + None is unknown, 0 is closed, 100 is fully open. + """ + if 'LEVEL_2' not in self._data: + return None + + return int(self._data.get('LEVEL_2', 0) * 100) + + def set_cover_tilt_position(self, **kwargs): + """Move the cover tilt to a specific position.""" + if "LEVEL_2" in self._data and ATTR_TILT_POSITION in kwargs: + position = float(kwargs[ATTR_TILT_POSITION]) + position = min(100, max(0, position)) + level = position / 100.0 + self._hmdevice.set_cover_tilt_position(level, self._channel) + + def open_cover_tilt(self, **kwargs): + """Open the cover tilt.""" + if "LEVEL_2" in self._data: + self._hmdevice.open_slats() + + def close_cover_tilt(self, **kwargs): + """Close the cover tilt.""" + if "LEVEL_2" in self._data: + self._hmdevice.close_slats() + + def stop_cover_tilt(self, **kwargs): + """Stop cover tilt.""" + if "LEVEL_2" in self._data: + self.stop_cover(**kwargs) From b84e551aea52fb9898c67d051ae0905b275e2a5a Mon Sep 17 00:00:00 2001 From: ChristianKuehnel Date: Fri, 19 Jan 2018 08:46:12 +0100 Subject: [PATCH 080/150] plant - check history for min_brightness (#9534) * Added option to create a group for the plant and all of it's sensors so that they appear together in the UI * fixed warnings from the hound * added check for min_brightness over several days * fixed hound complaints * 1) added missing dependency on recorder 2) using group.Group instead of hass.states.async_set as requested by @pvizeli * fixed pylint error in docstring * changed the way the groups are created * fixed requirements issue * Changed the way the groups are implemented. This is proposal number 4... * Data read from recorder only on startup. We now only store one data point per day. If a recorder is configured, this data is initialized from the database. If not the list is empty on startup. * added missing documentation * fixed typo in comment * removed group fature * added group dependency since it's still needed * fixed bug: now "None" is no longer added to the DailyHistory * now also outputting unit of measurement if defined by the sensors * removed iconss * fixed line length * Implemented changes requested in code reviews. These changes affect the interface to the UI: * renamed attribute for units of measurement to "unit_of_measurement_dict" * renamed attribute for maximum in brightness history to "max_brightness" * only loading the history if a brightness sensor was configured * fixed testcase * fixed stupid bug in check of brightness history Also added test for this bug * added missing docstring * Fixed sporadic failure in test case. Sometimes the component was created before the data was stored in the history. This lead to an empty history being read. * removed unused import statement in testcase * reverted change to test case * Changed startup behavior of the component. No failed tests after 20 local test runs. * added missing docstring * fixed tests * added hass.start() to Setup * fixed call parameters in constructor * added time.sleep * removed sleep * fixed typo in variable name * disabled loading from database as it's not stable at the moment and nobody knows why :( * fixed flake8 * now using pytest.mark.skipif to skip test --- homeassistant/components/plant.py | 168 ++++++++++++++++++++++---- tests/components/test_plant.py | 189 +++++++++++++++++++++++++----- 2 files changed, 306 insertions(+), 51 deletions(-) diff --git a/homeassistant/components/plant.py b/homeassistant/components/plant.py index 523fa2d6859..7df990fa0e5 100644 --- a/homeassistant/components/plant.py +++ b/homeassistant/components/plant.py @@ -1,23 +1,24 @@ -""" -Component to monitor plants. +"""Component to monitor plants. For more details about this component, please refer to the documentation at https://home-assistant.io/components/plant/ """ import logging import asyncio - +from datetime import datetime, timedelta +from collections import deque import voluptuous as vol from homeassistant.const import ( STATE_OK, STATE_PROBLEM, STATE_UNKNOWN, TEMP_CELSIUS, ATTR_TEMPERATURE, - CONF_SENSORS, ATTR_UNIT_OF_MEASUREMENT, ATTR_ICON) + CONF_SENSORS, ATTR_UNIT_OF_MEASUREMENT) from homeassistant.components import group 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__) @@ -30,7 +31,13 @@ READING_CONDUCTIVITY = 'conductivity' READING_BRIGHTNESS = 'brightness' ATTR_PROBLEM = 'problem' +ATTR_SENSORS = 'sensors' PROBLEM_NONE = 'none' +ATTR_MAX_BRIGHTNESS_HISTORY = 'max_brightness' + +# we're not returning only one value, we're returning a dict here. So we need +# to have a separate literal for it to avoid confusion. +ATTR_DICT_OF_UNITS_OF_MEASUREMENT = 'unit_of_measurement_dict' CONF_MIN_BATTERY_LEVEL = 'min_' + READING_BATTERY CONF_MIN_TEMPERATURE = 'min_' + READING_TEMPERATURE @@ -41,6 +48,7 @@ CONF_MIN_CONDUCTIVITY = 'min_' + READING_CONDUCTIVITY CONF_MAX_CONDUCTIVITY = 'max_' + READING_CONDUCTIVITY CONF_MIN_BRIGHTNESS = 'min_' + READING_BRIGHTNESS CONF_MAX_BRIGHTNESS = 'max_' + READING_BRIGHTNESS +CONF_CHECK_DAYS = 'check_days' CONF_SENSOR_BATTERY_LEVEL = READING_BATTERY CONF_SENSOR_MOISTURE = READING_MOISTURE @@ -67,6 +75,7 @@ PLANT_SCHEMA = vol.Schema({ vol.Optional(CONF_MAX_CONDUCTIVITY): cv.positive_int, vol.Optional(CONF_MIN_BRIGHTNESS): cv.positive_int, vol.Optional(CONF_MAX_BRIGHTNESS): cv.positive_int, + vol.Optional(CONF_CHECK_DAYS): cv.positive_int, }) DOMAIN = 'plant' @@ -82,6 +91,11 @@ CONFIG_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) +# Flag for enabling/disabling the loading of the history from the database. +# This feature is turned off right now as it's tests are not 100% stable. +ENABLE_LOAD_HISTORY = False + + @asyncio.coroutine def async_setup(hass, config): """Set up the Plant component.""" @@ -98,7 +112,6 @@ def async_setup(hass, config): entities.append(entity) yield from component.async_add_entities(entities) - return True @@ -113,31 +126,26 @@ class Plant(Entity): READING_BATTERY: { ATTR_UNIT_OF_MEASUREMENT: '%', 'min': CONF_MIN_BATTERY_LEVEL, - 'icon': 'mdi:battery-outline' }, READING_TEMPERATURE: { ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, 'min': CONF_MIN_TEMPERATURE, 'max': CONF_MAX_TEMPERATURE, - 'icon': 'mdi:thermometer' }, READING_MOISTURE: { ATTR_UNIT_OF_MEASUREMENT: '%', 'min': CONF_MIN_MOISTURE, 'max': CONF_MAX_MOISTURE, - 'icon': 'mdi:water' }, READING_CONDUCTIVITY: { ATTR_UNIT_OF_MEASUREMENT: 'µS/cm', 'min': CONF_MIN_CONDUCTIVITY, 'max': CONF_MAX_CONDUCTIVITY, - 'icon': 'mdi:emoticon-poop' }, READING_BRIGHTNESS: { ATTR_UNIT_OF_MEASUREMENT: 'lux', 'min': CONF_MIN_BRIGHTNESS, 'max': CONF_MAX_BRIGHTNESS, - 'icon': 'mdi:white-balance-sunny' } } @@ -145,8 +153,11 @@ class Plant(Entity): """Initialize the Plant component.""" self._config = config self._sensormap = dict() + self._readingmap = dict() + self._unit_of_measurement = dict() for reading, entity_id in config['sensors'].items(): self._sensormap[entity_id] = reading + self._readingmap[reading] = entity_id self._state = STATE_UNKNOWN self._name = name self._battery = None @@ -154,9 +165,13 @@ class Plant(Entity): self._conductivity = None self._temperature = None self._brightness = None - self._icon = 'mdi:help-circle' self._problems = PROBLEM_NONE + self._conf_check_days = 3 # default check interval: 3 days + if CONF_CHECK_DAYS in self._config: + self._conf_check_days = self._config[CONF_CHECK_DAYS] + self._brightness_history = DailyHistory(self._conf_check_days) + @callback def state_changed(self, entity_id, _, new_state): """Update the sensor status. @@ -180,9 +195,14 @@ class Plant(Entity): self._conductivity = int(float(value)) elif reading == READING_BRIGHTNESS: self._brightness = int(float(value)) + self._brightness_history.add_measurement(self._brightness, + new_state.last_updated) else: raise _LOGGER.error("Unknown reading from sensor %s: %s", entity_id, value) + if ATTR_UNIT_OF_MEASUREMENT in new_state.attributes: + self._unit_of_measurement[reading] = \ + new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) self._update_state() def _update_state(self): @@ -192,28 +212,80 @@ class Plant(Entity): params = self.READINGS[sensor_name] value = getattr(self, '_{}'.format(sensor_name)) if value is not None: - if 'min' in params and params['min'] in self._config: - min_value = self._config[params['min']] - if value < min_value: - result.append('{} low'.format(sensor_name)) - self._icon = params['icon'] + if sensor_name == READING_BRIGHTNESS: + result.append(self._check_min( + sensor_name, self._brightness_history.max, params)) + else: + result.append(self._check_min(sensor_name, value, params)) + result.append(self._check_max(sensor_name, value, params)) - if 'max' in params and params['max'] in self._config: - max_value = self._config[params['max']] - if value > max_value: - result.append('{} high'.format(sensor_name)) - self._icon = params['icon'] + result = [r for r in result if r is not None] if result: self._state = STATE_PROBLEM - self._problems = ','.join(result) + self._problems = ', '.join(result) else: self._state = STATE_OK - self._icon = 'mdi:thumb-up' self._problems = PROBLEM_NONE _LOGGER.debug("New data processed") self.async_schedule_update_ha_state() + def _check_min(self, sensor_name, value, params): + """If configured, check the value against the defined minimum value.""" + if 'min' in params and params['min'] in self._config: + min_value = self._config[params['min']] + if value < min_value: + return '{} low'.format(sensor_name) + + def _check_max(self, sensor_name, value, params): + """If configured, check the value against the defined maximum value.""" + if 'max' in params and params['max'] in self._config: + max_value = self._config[params['max']] + if value > max_value: + return '{} high'.format(sensor_name) + return None + + @asyncio.coroutine + def async_added_to_hass(self): + """After being added to hass, load from history.""" + if ENABLE_LOAD_HISTORY and 'recorder' in self.hass.config.components: + # only use the database if it's configured + self.hass.async_add_job(self._load_history_from_db) + + @asyncio.coroutine + def _load_history_from_db(self): + """Load the history of the brightness values from the database. + + This only needs to be done once during startup. + """ + from homeassistant.components.recorder.models import States + 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.") + return + + _LOGGER.debug("initializing values for %s from the database", + self._name) + with session_scope(hass=self.hass) as session: + query = session.query(States).filter( + (States.entity_id == entity_id.lower()) and + (States.last_updated > start_date) + ).order_by(States.last_updated.asc()) + states = execute(query) + + for state in states: + # filter out all None, NaN and "unknown" states + # only keep real values + try: + self._brightness_history.add_measurement( + int(state.state), state.last_updated) + except ValueError: + pass + _LOGGER.debug("initializing from database completed") + self.async_schedule_update_ha_state() + @property def should_poll(self): """No polling needed.""" @@ -237,11 +309,59 @@ class Plant(Entity): sensor in the attributes of the device. """ attrib = { - ATTR_ICON: self._icon, ATTR_PROBLEM: self._problems, + ATTR_SENSORS: self._readingmap, + ATTR_DICT_OF_UNITS_OF_MEASUREMENT: self._unit_of_measurement, } for reading in self._sensormap.values(): attrib[reading] = getattr(self, '_{}'.format(reading)) + if self._brightness_history.max is not None: + attrib[ATTR_MAX_BRIGHTNESS_HISTORY] = self._brightness_history.max + return attrib + + +class DailyHistory(object): + """Stores one measurement per day for a maximum number of days. + + At the moment only the maximum value per day is kept. + """ + + def __init__(self, max_length): + """Create new DailyHistory with a maximum length of the history.""" + self.max_length = max_length + self._days = None + self._max_dict = dict() + self.max = None + + def add_measurement(self, value, timestamp=datetime.now()): + """Add a new measurement for a certain day.""" + day = timestamp.date() + if value is None: + return + if self._days is None: + self._days = deque() + self._add_day(day, value) + else: + current_day = self._days[-1] + if day == current_day: + self._max_dict[day] = max(value, self._max_dict[day]) + elif day > current_day: + self._add_day(day, value) + else: + _LOGGER.warning('received old measurement, not storing it!') + + self.max = max(self._max_dict.values()) + + def _add_day(self, day, value): + """Add a new day to the history. + + Deletes the oldest day, if the queue becomes too long. + """ + if len(self._days) == self.max_length: + oldest = self._days.popleft() + del self._max_dict[oldest] + self._days.append(day) + self._max_dict[day] = value diff --git a/tests/components/test_plant.py b/tests/components/test_plant.py index 70641afd6b4..f5a042ac8c1 100644 --- a/tests/components/test_plant.py +++ b/tests/components/test_plant.py @@ -1,7 +1,16 @@ """Unit tests for platform/plant.py.""" import asyncio +import unittest +import pytest +from datetime import datetime, timedelta +from homeassistant.const import (ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, + STATE_PROBLEM, STATE_OK) +from homeassistant.components import recorder import homeassistant.components.plant as plant +from homeassistant.setup import setup_component + +from tests.common import get_test_home_assistant, init_recorder_component GOOD_DATA = { @@ -12,19 +21,23 @@ GOOD_DATA = { 'brightness': 987, } +BRIGHTNESS_ENTITY = 'sensor.mqtt_plant_brightness' +MOISTURE_ENTITY = 'sensor.mqtt_plant_moisture' + GOOD_CONFIG = { 'sensors': { - 'moisture': 'sensor.mqtt_plant_moisture', + 'moisture': MOISTURE_ENTITY, 'battery': 'sensor.mqtt_plant_battery', 'temperature': 'sensor.mqtt_plant_temperature', 'conductivity': 'sensor.mqtt_plant_conductivity', - 'brightness': 'sensor.mqtt_plant_brightness', + 'brightness': BRIGHTNESS_ENTITY, }, 'min_moisture': 20, 'max_moisture': 60, 'min_battery': 17, 'min_conductivity': 500, 'min_temperature': 15, + 'min_brightness': 500, } @@ -34,30 +47,152 @@ class _MockState(object): self.state = state -@asyncio.coroutine -def test_valid_data(hass): - """Test processing valid data.""" - sensor = plant.Plant('my plant', GOOD_CONFIG) - sensor.hass = hass - for reading, value in GOOD_DATA.items(): - sensor.state_changed( - GOOD_CONFIG['sensors'][reading], None, - _MockState(value)) - assert sensor.state == 'ok' - attrib = sensor.state_attributes - for reading, value in GOOD_DATA.items(): - # battery level has a different name in - # the JSON format than in hass - assert attrib[reading] == value +class TestPlant(unittest.TestCase): + """Tests for component "plant".""" + + def setUp(self): + """Create test instance of home assistant.""" + self.hass = get_test_home_assistant() + self.hass.start() + + def tearDown(self): + """Stop everything that was started.""" + self.hass.stop() + + @asyncio.coroutine + def test_valid_data(self): + """Test processing valid data.""" + sensor = plant.Plant('my plant', GOOD_CONFIG) + sensor.hass = self.hass + for reading, value in GOOD_DATA.items(): + sensor.state_changed( + GOOD_CONFIG['sensors'][reading], None, + _MockState(value)) + assert sensor.state == 'ok' + attrib = sensor.state_attributes + for reading, value in GOOD_DATA.items(): + # battery level has a different name in + # the JSON format than in hass + assert attrib[reading] == value + + @asyncio.coroutine + def test_low_battery(self): + """Test processing with low battery data and limit set.""" + sensor = plant.Plant('other plant', GOOD_CONFIG) + sensor.hass = self.hass + assert sensor.state_attributes['problem'] == 'none' + sensor.state_changed('sensor.mqtt_plant_battery', + _MockState(45), _MockState(10)) + assert sensor.state == 'problem' + assert sensor.state_attributes['problem'] == 'battery low' + + def test_update_states(self): + """Test updating the state of a sensor. + + Make sure that plant processes this correctly. + """ + plant_name = 'some_plant' + assert setup_component(self.hass, plant.DOMAIN, { + plant.DOMAIN: { + plant_name: GOOD_CONFIG + } + }) + self.hass.states.set(MOISTURE_ENTITY, 5, + {ATTR_UNIT_OF_MEASUREMENT: 'us/cm'}) + self.hass.block_till_done() + state = self.hass.states.get('plant.'+plant_name) + self.assertEquals(STATE_PROBLEM, state.state) + self.assertEquals(5, state.attributes[plant.READING_MOISTURE]) + + @pytest.mark.skipif(plant.ENABLE_LOAD_HISTORY is False, + reason="tests for loading from DB are instable, thus" + "this feature is turned of until tests become" + "stable") + def test_load_from_db(self): + """Test bootstrapping the brightness history from the database. + + This test can should only be executed if the loading of the history + is enabled via plant.ENABLE_LOAD_HISTORY. + """ + init_recorder_component(self.hass) + plant_name = 'wise_plant' + for value in [20, 30, 10]: + + self.hass.states.set(BRIGHTNESS_ENTITY, value, + {ATTR_UNIT_OF_MEASUREMENT: 'Lux'}) + self.hass.block_till_done() + # wait for the recorder to really store the data + self.hass.data[recorder.DATA_INSTANCE].block_till_done() + + assert setup_component(self.hass, plant.DOMAIN, { + plant.DOMAIN: { + plant_name: GOOD_CONFIG + } + }) + self.hass.block_till_done() + + state = self.hass.states.get('plant.'+plant_name) + self.assertEquals(STATE_UNKNOWN, state.state) + max_brightness = state.attributes.get( + plant.ATTR_MAX_BRIGHTNESS_HISTORY) + self.assertEquals(30, max_brightness) + + def test_brightness_history(self): + """Test the min_brightness check.""" + plant_name = 'some_plant' + assert setup_component(self.hass, plant.DOMAIN, { + plant.DOMAIN: { + plant_name: GOOD_CONFIG + } + }) + self.hass.states.set(BRIGHTNESS_ENTITY, 100, + {ATTR_UNIT_OF_MEASUREMENT: 'lux'}) + self.hass.block_till_done() + state = self.hass.states.get('plant.'+plant_name) + self.assertEquals(STATE_PROBLEM, state.state) + + self.hass.states.set(BRIGHTNESS_ENTITY, 600, + {ATTR_UNIT_OF_MEASUREMENT: 'lux'}) + self.hass.block_till_done() + state = self.hass.states.get('plant.'+plant_name) + self.assertEquals(STATE_OK, state.state) + + self.hass.states.set(BRIGHTNESS_ENTITY, 100, + {ATTR_UNIT_OF_MEASUREMENT: 'lux'}) + self.hass.block_till_done() + state = self.hass.states.get('plant.'+plant_name) + self.assertEquals(STATE_OK, state.state) -@asyncio.coroutine -def test_low_battery(hass): - """Test processing with low battery data and limit set.""" - sensor = plant.Plant(hass, GOOD_CONFIG) - sensor.hass = hass - assert sensor.state_attributes['problem'] == 'none' - sensor.state_changed('sensor.mqtt_plant_battery', - _MockState(45), _MockState(10)) - assert sensor.state == 'problem' - assert sensor.state_attributes['problem'] == 'battery low' +class TestDailyHistory(unittest.TestCase): + """Test the DailyHistory helper class.""" + + def test_no_data(self): + """Test with empty history.""" + dh = plant.DailyHistory(3) + self.assertIsNone(dh.max) + + def test_one_day(self): + """Test storing data for the same day.""" + dh = plant.DailyHistory(3) + values = [-2, 10, 0, 5, 20] + for i in range(len(values)): + dh.add_measurement(values[i]) + max_value = max(values[0:i+1]) + self.assertEqual(1, len(dh._days)) + self.assertEqual(dh.max, max_value) + + def test_multiple_days(self): + """Test storing data for different days.""" + dh = plant.DailyHistory(3) + today = datetime.now() + today_minus_1 = today - timedelta(days=1) + today_minus_2 = today_minus_1 - timedelta(days=1) + today_minus_3 = today_minus_2 - timedelta(days=1) + days = [today_minus_3, today_minus_2, today_minus_1, today] + values = [10, 1, 7, 3] + max_values = [10, 10, 10, 7] + + for i in range(len(days)): + dh.add_measurement(values[i], days[i]) + self.assertEquals(max_values[i], dh.max) From 5de828d6e25f2c05cfda1a2ba1bcccdc1fcb54f5 Mon Sep 17 00:00:00 2001 From: Philip Kleimeyer Date: Fri, 19 Jan 2018 08:47:35 +0100 Subject: [PATCH 081/150] add generic rollershutter agian, was missing in last merge (#11788) --- homeassistant/components/tahoma.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/tahoma.py b/homeassistant/components/tahoma.py index 040f499c115..4eacee08ec1 100644 --- a/homeassistant/components/tahoma.py +++ b/homeassistant/components/tahoma.py @@ -41,6 +41,7 @@ TAHOMA_TYPES = { 'rts:CurtainRTSComponent': 'cover', 'io:RollerShutterWithLowSpeedManagementIOComponent': 'cover', 'io:RollerShutterVeluxIOComponent': 'cover', + 'io:RollerShutterGenericIOComponent': 'cover', 'io:WindowOpenerVeluxIOComponent': 'cover', 'io:LightIOSystemSensor': 'sensor', } From 03a5d4e131b4967860c8c22aa6467d9fdb5972d9 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Fri, 19 Jan 2018 08:50:56 +0100 Subject: [PATCH 082/150] Additional attributes and services of the Xiaomi Air Purifier introduced (#11249) * Attributes average_aqi and purify_volume introduced. Fixes https://github.com/syssi/xiaomi_airpurifier/issues/14. New service light.xiaomi_miio_set_child_lock_{on,off} added. Fixes https://github.com/syssi/xiaomi_airpurifier/issues/13. * Lazy loading of service descriptions. * Merge conflict resolved. --- homeassistant/components/fan/services.yaml | 62 +++++++++++++++++++ homeassistant/components/fan/xiaomi_miio.py | 50 +++++++++++---- .../components/fan/xiaomi_miio_services.yaml | 56 ----------------- 3 files changed, 99 insertions(+), 69 deletions(-) delete mode 100644 homeassistant/components/fan/xiaomi_miio_services.yaml diff --git a/homeassistant/components/fan/services.yaml b/homeassistant/components/fan/services.yaml index 2a8ad453ec8..a306cf7767c 100644 --- a/homeassistant/components/fan/services.yaml +++ b/homeassistant/components/fan/services.yaml @@ -63,3 +63,65 @@ dyson_set_night_mode: night_mode: description: Night mode status example: true + +xiaomi_miio_set_buzzer_on: + description: Turn the buzzer on. + fields: + entity_id: + description: Name of the air purifier entity. + example: 'fan.xiaomi_air_purifier' + +xiaomi_miio_set_buzzer_off: + description: Turn the buzzer off. + fields: + entity_id: + description: Name of the air purifier entity. + example: 'fan.xiaomi_air_purifier' + +xiaomi_miio_set_led_on: + description: Turn the led on. + fields: + entity_id: + description: Name of the air purifier entity. + example: 'fan.xiaomi_air_purifier' + +xiaomi_miio_set_led_off: + description: Turn the led off. + fields: + entity_id: + description: Name of the air purifier entity. + example: 'fan.xiaomi_air_purifier' + +xiaomi_miio_set_child_lock_on: + description: Turn the child lock on. + fields: + entity_id: + description: Name of the air purifier entity. + example: 'fan.xiaomi_air_purifier' + +xiaomi_miio_set_child_lock_off: + description: Turn the child lock off. + fields: + entity_id: + description: Name of the air purifier entity. + example: 'fan.xiaomi_air_purifier' + +xiaomi_miio_set_favorite_level: + description: Set the favorite level. + fields: + entity_id: + description: Name of the air purifier entity. + example: 'fan.xiaomi_air_purifier' + level: + description: Level, between 0 and 16. + example: 1 + +xiaomi_miio_set_led_brightness: + description: Set the led brightness. + fields: + entity_id: + description: Name of the air purifier entity. + example: 'fan.xiaomi_air_purifier' + brightness: + description: Brightness (0 = Bright, 1 = Dim, 2 = Off) + example: 1 diff --git a/homeassistant/components/fan/xiaomi_miio.py b/homeassistant/components/fan/xiaomi_miio.py index 9f21fda408d..694a68d7f8e 100644 --- a/homeassistant/components/fan/xiaomi_miio.py +++ b/homeassistant/components/fan/xiaomi_miio.py @@ -43,6 +43,8 @@ ATTR_CHILD_LOCK = 'child_lock' ATTR_LED = 'led' ATTR_LED_BRIGHTNESS = 'led_brightness' ATTR_MOTOR_SPEED = 'motor_speed' +ATTR_AVERAGE_AIR_QUALITY_INDEX = 'average_aqi' +ATTR_PURIFY_VOLUME = 'purify_volume' ATTR_BRIGHTNESS = 'brightness' ATTR_LEVEL = 'level' @@ -53,6 +55,8 @@ SERVICE_SET_BUZZER_ON = 'xiaomi_miio_set_buzzer_on' SERVICE_SET_BUZZER_OFF = 'xiaomi_miio_set_buzzer_off' SERVICE_SET_LED_ON = 'xiaomi_miio_set_led_on' SERVICE_SET_LED_OFF = 'xiaomi_miio_set_led_off' +SERVICE_SET_CHILD_LOCK_ON = 'xiaomi_miio_set_child_lock_on' +SERVICE_SET_CHILD_LOCK_OFF = 'xiaomi_miio_set_child_lock_off' SERVICE_SET_FAVORITE_LEVEL = 'xiaomi_miio_set_favorite_level' SERVICE_SET_LED_BRIGHTNESS = 'xiaomi_miio_set_led_brightness' @@ -75,6 +79,8 @@ SERVICE_TO_METHOD = { SERVICE_SET_BUZZER_OFF: {'method': 'async_set_buzzer_off'}, SERVICE_SET_LED_ON: {'method': 'async_set_led_on'}, SERVICE_SET_LED_OFF: {'method': 'async_set_led_off'}, + SERVICE_SET_CHILD_LOCK_ON: {'method': 'async_set_child_lock_on'}, + SERVICE_SET_CHILD_LOCK_OFF: {'method': 'async_set_child_lock_off'}, SERVICE_SET_FAVORITE_LEVEL: { 'method': 'async_set_favorite_level', 'schema': SERVICE_SCHEMA_FAVORITE_LEVEL}, @@ -116,15 +122,15 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): if key != ATTR_ENTITY_ID} entity_ids = service.data.get(ATTR_ENTITY_ID) if entity_ids: - target_air_purifiers = [air for air in hass.data[PLATFORM].values() - if air.entity_id in entity_ids] + devices = [device for device in hass.data[PLATFORM].values() if + device.entity_id in entity_ids] else: - target_air_purifiers = hass.data[PLATFORM].values() + devices = hass.data[PLATFORM].values() update_tasks = [] - for air_purifier in target_air_purifiers: - yield from getattr(air_purifier, method['method'])(**params) - update_tasks.append(air_purifier.async_update_ha_state(True)) + for device in devices: + yield from getattr(device, method['method'])(**params) + update_tasks.append(device.async_update_ha_state(True)) if update_tasks: yield from asyncio.wait(update_tasks, loop=hass.loop) @@ -157,7 +163,9 @@ class XiaomiAirPurifier(FanEntity): ATTR_CHILD_LOCK: None, ATTR_LED: None, ATTR_LED_BRIGHTNESS: None, - ATTR_MOTOR_SPEED: None + ATTR_MOTOR_SPEED: None, + ATTR_AVERAGE_AIR_QUALITY_INDEX: None, + ATTR_PURIFY_VOLUME: None, } @property @@ -244,7 +252,9 @@ class XiaomiAirPurifier(FanEntity): ATTR_BUZZER: state.buzzer, ATTR_CHILD_LOCK: state.child_lock, ATTR_LED: state.led, - ATTR_MOTOR_SPEED: state.motor_speed + ATTR_MOTOR_SPEED: state.motor_speed, + ATTR_AVERAGE_AIR_QUALITY_INDEX: state.average_aqi, + ATTR_PURIFY_VOLUME: state.purify_volume, } if state.led_brightness: @@ -284,30 +294,44 @@ class XiaomiAirPurifier(FanEntity): def async_set_buzzer_on(self): """Turn the buzzer on.""" yield from self._try_command( - "Turning the buzzer of air purifier on failed.", + "Turning the buzzer of the air purifier on failed.", self._air_purifier.set_buzzer, True) @asyncio.coroutine def async_set_buzzer_off(self): - """Turn the buzzer on.""" + """Turn the buzzer off.""" yield from self._try_command( - "Turning the buzzer of air purifier off failed.", + "Turning the buzzer of the air purifier off failed.", self._air_purifier.set_buzzer, False) @asyncio.coroutine def async_set_led_on(self): """Turn the led on.""" yield from self._try_command( - "Turning the led of air purifier off failed.", + "Turning the led of the air purifier off failed.", self._air_purifier.set_led, True) @asyncio.coroutine def async_set_led_off(self): """Turn the led off.""" yield from self._try_command( - "Turning the led of air purifier off failed.", + "Turning the led of the air purifier off failed.", self._air_purifier.set_led, False) + @asyncio.coroutine + def async_set_child_lock_on(self): + """Turn the child lock on.""" + yield from self._try_command( + "Turning the child lock of the air purifier on failed.", + self._air_purifier.set_child_lock, True) + + @asyncio.coroutine + def async_set_child_lock_off(self): + """Turn the child lock off.""" + yield from self._try_command( + "Turning the child lock of the air purifier off failed.", + self._air_purifier.set_child_lock, False) + @asyncio.coroutine def async_set_led_brightness(self, brightness: int=2): """Set the led brightness.""" diff --git a/homeassistant/components/fan/xiaomi_miio_services.yaml b/homeassistant/components/fan/xiaomi_miio_services.yaml deleted file mode 100644 index 93f6318e60b..00000000000 --- a/homeassistant/components/fan/xiaomi_miio_services.yaml +++ /dev/null @@ -1,56 +0,0 @@ - -xiaomi_miio_set_buzzer_on: - description: Turn the buzzer on. - - fields: - entity_id: - description: Name of the air purifier entity. - example: 'fan.xiaomi_air_purifier' - -xiaomi_miio_set_buzzer_off: - description: Turn the buzzer off. - - fields: - entity_id: - description: Name of the air purifier entity. - example: 'fan.xiaomi_air_purifier' - -xiaomi_miio_set_led_on: - description: Turn the led on. - - fields: - entity_id: - description: Name of the air purifier entity. - example: 'fan.xiaomi_air_purifier' - -xiaomi_miio_set_led_off: - description: Turn the led off. - - fields: - entity_id: - description: Name of the air purifier entity. - example: 'fan.xiaomi_air_purifier' - -xiaomi_miio_set_favorite_level: - description: Set the favorite level. - - fields: - entity_id: - description: Name of the air purifier entity. - example: 'fan.xiaomi_air_purifier' - - level: - description: Level, between 0 and 16. - example: '1' - -xiaomi_miio_set_led_brightness: - description: Set the led brightness. - - fields: - entity_id: - description: Name of the air purifier entity. - example: 'fan.xiaomi_air_purifier' - - brightness: - description: Brightness (0 = Bright, 1 = Dim, 2 = Off) - example: '1' From 4ceb13291fc04b25c02f526e44b3a99fa8bc7b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Fri, 19 Jan 2018 09:44:40 +0100 Subject: [PATCH 083/150] flux led version 0.20 (#11791) --- homeassistant/components/light/flux_led.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/light/flux_led.py b/homeassistant/components/light/flux_led.py index a97455bc513..c48de4deaf8 100755 --- a/homeassistant/components/light/flux_led.py +++ b/homeassistant/components/light/flux_led.py @@ -17,7 +17,7 @@ from homeassistant.components.light import ( SUPPORT_RGB_COLOR, Light, PLATFORM_SCHEMA) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['flux_led==0.20'] +REQUIREMENTS = ['flux_led==0.21'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 16a3693b5e5..28f7a7807f1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -275,7 +275,7 @@ fitbit==0.3.0 fixerio==0.1.1 # homeassistant.components.light.flux_led -flux_led==0.20 +flux_led==0.21 # homeassistant.components.notify.free_mobile freesms==0.1.2 From 2ca4bde06aa49febc671470a2cd0bbce9d173c70 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Fri, 19 Jan 2018 10:50:12 +0100 Subject: [PATCH 084/150] Hyperion: fix (#11793) --- homeassistant/components/light/hyperion.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/light/hyperion.py b/homeassistant/components/light/hyperion.py index 644c1e84ebe..4701866cd9a 100644 --- a/homeassistant/components/light/hyperion.py +++ b/homeassistant/components/light/hyperion.py @@ -147,6 +147,8 @@ class Hyperion(Light): if ATTR_BRIGHTNESS in kwargs: brightness = kwargs[ATTR_BRIGHTNESS] + else: + brightness = self._brightness if ATTR_EFFECT in kwargs: self._skip_update = True From 5ef7a8d55a53b197ed47bd73202cecc3f0adaffe Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 19 Jan 2018 14:04:54 +0200 Subject: [PATCH 085/150] Fix sensibo function names (#11797) --- homeassistant/components/climate/sensibo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/climate/sensibo.py b/homeassistant/components/climate/sensibo.py index 870e2db6b42..67113e7c48a 100644 --- a/homeassistant/components/climate/sensibo.py +++ b/homeassistant/components/climate/sensibo.py @@ -294,14 +294,14 @@ class SensiboClimate(ClimateDevice): self._id, 'swing', swing_mode, self._ac_states) @asyncio.coroutine - def async_on(self): + def async_turn_on(self): """Turn Sensibo unit on.""" with async_timeout.timeout(TIMEOUT): yield from self._client.async_set_ac_state_property( self._id, 'on', True, self._ac_states) @asyncio.coroutine - def async_off(self): + def async_turn_off(self): """Turn Sensibo unit on.""" with async_timeout.timeout(TIMEOUT): yield from self._client.async_set_ac_state_property( From 273db75248e2a286362bb593ccabd0b4c05699f3 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 19 Jan 2018 14:06:34 +0200 Subject: [PATCH 086/150] Always load yr picture from https (#11796) --- homeassistant/components/sensor/yr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/sensor/yr.py index 0b4c6919994..71a4e60e8a6 100644 --- a/homeassistant/components/sensor/yr.py +++ b/homeassistant/components/sensor/yr.py @@ -129,7 +129,7 @@ class YrSensor(Entity): """Weather symbol if type is symbol.""" if self.type != 'symbol': return None - return "//api.met.no/weatherapi/weathericon/1.1/" \ + return "https://api.met.no/weatherapi/weathericon/1.1/" \ "?symbol={0};content_type=image/png".format(self._state) @property From d5df1c070d7413b65268af1bbd2278ce82652652 Mon Sep 17 00:00:00 2001 From: Paul Rabahy Date: Fri, 19 Jan 2018 09:27:40 -0500 Subject: [PATCH 087/150] Make Google TTS secure (#11031) * Make Google TTS secure I noticed that my TTS queries were showing up in the log on my router, so I was curious if there was a way to make it secure. A quick search showed people using https instead of http (https://stackoverflow.com/questions/32053442/google-translate-tts-api-blocked), so I figure that should work. I am using hass.io, so I'm not actually sure how to test this, but its a pretty simple change. * Fix the tts test. --- homeassistant/components/tts/google.py | 2 +- tests/components/tts/test_google.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/tts/google.py b/homeassistant/components/tts/google.py index e405e5be531..85b223864e9 100644 --- a/homeassistant/components/tts/google.py +++ b/homeassistant/components/tts/google.py @@ -21,7 +21,7 @@ REQUIREMENTS = ['gTTS-token==1.1.1'] _LOGGER = logging.getLogger(__name__) -GOOGLE_SPEECH_URL = "http://translate.google.com/translate_tts" +GOOGLE_SPEECH_URL = "https://translate.google.com/translate_tts" MESSAGE_SIZE = 148 SUPPORT_LANGUAGES = [ diff --git a/tests/components/tts/test_google.py b/tests/components/tts/test_google.py index a68aeef80e3..6a2d2c65035 100644 --- a/tests/components/tts/test_google.py +++ b/tests/components/tts/test_google.py @@ -22,7 +22,7 @@ class TestTTSGooglePlatform(object): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.url = "http://translate.google.com/translate_tts" + self.url = "https://translate.google.com/translate_tts" self.url_param = { 'tl': 'en', 'q': From d697e8e6772c50b43d51baed319e0e01d7655d67 Mon Sep 17 00:00:00 2001 From: Martin Rowan Date: Fri, 19 Jan 2018 15:28:47 +0000 Subject: [PATCH 088/150] Correct inadvertent change to file permissions (#11755) --- homeassistant/components/light/flux_led.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 homeassistant/components/light/flux_led.py diff --git a/homeassistant/components/light/flux_led.py b/homeassistant/components/light/flux_led.py old mode 100755 new mode 100644 From 51dd9b6dde0ca09d6b8a297274554c268be7bea5 Mon Sep 17 00:00:00 2001 From: Lukas Barth Date: Fri, 19 Jan 2018 17:56:28 +0100 Subject: [PATCH 089/150] Fix recorder purge (#11802) * Do proper largest-of query * Fix error when event_id is NULL --- homeassistant/components/recorder/purge.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index 4ff8e239352..fad6a7de70d 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -12,7 +12,8 @@ _LOGGER = logging.getLogger(__name__) def purge_old_data(instance, purge_days): """Purge events and states older than purge_days ago.""" from .models import States, Events - from sqlalchemy import func + from sqlalchemy import orm + from sqlalchemy.sql import exists purge_before = dt_util.utcnow() - timedelta(days=purge_days) @@ -20,12 +21,18 @@ def purge_old_data(instance, purge_days): # For each entity, the most recent state is protected from deletion # s.t. we can properly restore state even if the entity has not been # updated in a long time - protected_states = session.query(States.state_id, States.event_id, - func.max(States.last_updated)) \ - .group_by(States.entity_id).all() + states_alias = orm.aliased(States, name='StatesAlias') + protected_states = session.query(States.state_id, States.event_id)\ + .filter(~exists() + .where(States.entity_id == + states_alias.entity_id) + .where(states_alias.last_updated > + States.last_updated))\ + .all() protected_state_ids = tuple((state[0] for state in protected_states)) - protected_event_ids = tuple((state[1] for state in protected_states)) + protected_event_ids = tuple((state[1] for state in protected_states + if state[1] is not None)) deleted_rows = session.query(States) \ .filter((States.last_updated < purge_before)) \ From 51c41ba4e3eb3c534eb7a898dc90ff1fe3e99432 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 19 Jan 2018 09:47:45 -0800 Subject: [PATCH 090/150] Disable installing Telldus in Docker (#11806) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 5081b4ba721..8082b452d60 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ FROM python:3.6 LABEL maintainer="Paulus Schoutsen " # Uncomment any of the following lines to disable the installation. -#ENV INSTALL_TELLSTICK no +ENV INSTALL_TELLSTICK no #ENV INSTALL_OPENALPR no #ENV INSTALL_FFMPEG no #ENV INSTALL_LIBCEC no From c1b0ab75e16d23859501738e8e09b5502a457653 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 19 Jan 2018 09:54:01 -0800 Subject: [PATCH 091/150] Update frontend to 20180119.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 7d19ed46cd9..002f680927e 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -23,7 +23,7 @@ from homeassistant.const import CONF_NAME, EVENT_THEMES_UPDATED from homeassistant.core import callback from homeassistant.loader import bind_hass -REQUIREMENTS = ['home-assistant-frontend==20180112.0', 'user-agents==1.1.0'] +REQUIREMENTS = ['home-assistant-frontend==20180119.0', 'user-agents==1.1.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log'] diff --git a/requirements_all.txt b/requirements_all.txt index 28f7a7807f1..29abb713b79 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -349,7 +349,7 @@ hipnotify==1.0.8 holidays==0.8.1 # homeassistant.components.frontend -home-assistant-frontend==20180112.0 +home-assistant-frontend==20180119.0 # homeassistant.components.camera.onvif http://github.com/tgaugry/suds-passworddigest-py3/archive/86fc50e39b4d2b8997481967d6a7fe1c57118999.zip#suds-passworddigest-py3==0.1.2a diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a7501f3e618..83f2b8df1e9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -75,7 +75,7 @@ hbmqtt==0.9.1 holidays==0.8.1 # homeassistant.components.frontend -home-assistant-frontend==20180112.0 +home-assistant-frontend==20180119.0 # homeassistant.components.influxdb # homeassistant.components.sensor.influxdb From 7cbe017932a8b40cb057b2296d82f7baae3e1968 Mon Sep 17 00:00:00 2001 From: robhuls Date: Sat, 20 Jan 2018 06:41:12 +0100 Subject: [PATCH 092/150] Nad (#11800) * Update nad_receiver to 0.0.9 * Update nad_receiver to 0.0.9 --- homeassistant/components/media_player/nad.py | 2 +- homeassistant/components/media_player/nadtcp.py | 2 +- requirements_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/media_player/nad.py b/homeassistant/components/media_player/nad.py index 43355782d29..2f0c49b2583 100644 --- a/homeassistant/components/media_player/nad.py +++ b/homeassistant/components/media_player/nad.py @@ -17,7 +17,7 @@ from homeassistant.const import ( CONF_NAME, STATE_OFF, STATE_ON) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['nad_receiver==0.0.6'] +REQUIREMENTS = ['nad_receiver==0.0.9'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/media_player/nadtcp.py b/homeassistant/components/media_player/nadtcp.py index a59a032f624..707a0c29eaf 100644 --- a/homeassistant/components/media_player/nadtcp.py +++ b/homeassistant/components/media_player/nadtcp.py @@ -15,7 +15,7 @@ from homeassistant.const import ( CONF_NAME, STATE_OFF, STATE_ON) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['nad_receiver==0.0.6'] +REQUIREMENTS = ['nad_receiver==0.0.9'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 29abb713b79..f7a76d1fe2d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -501,7 +501,7 @@ myusps==1.2.2 # homeassistant.components.media_player.nad # homeassistant.components.media_player.nadtcp -nad_receiver==0.0.6 +nad_receiver==0.0.9 # homeassistant.components.discovery netdisco==1.2.4 From f744467c5d1be1f219050922b70c0f8b8c1d1581 Mon Sep 17 00:00:00 2001 From: Spencer Oberstadt Date: Sat, 20 Jan 2018 00:56:56 -0500 Subject: [PATCH 093/150] bump roku version (#11816) --- homeassistant/components/media_player/roku.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/roku.py b/homeassistant/components/media_player/roku.py index 5917f1e3083..15b16eec11b 100644 --- a/homeassistant/components/media_player/roku.py +++ b/homeassistant/components/media_player/roku.py @@ -16,7 +16,7 @@ from homeassistant.const import ( CONF_HOST, STATE_IDLE, STATE_PLAYING, STATE_UNKNOWN, STATE_HOME) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-roku==3.1.3'] +REQUIREMENTS = ['python-roku==3.1.5'] KNOWN_HOSTS = [] DEFAULT_PORT = 8060 diff --git a/requirements_all.txt b/requirements_all.txt index f7a76d1fe2d..f7c7ce2d889 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -918,7 +918,7 @@ python-pushover==0.3 python-ripple-api==0.0.3 # homeassistant.components.media_player.roku -python-roku==3.1.3 +python-roku==3.1.5 # homeassistant.components.sensor.sochain python-sochain-api==0.0.2 From 323992e224be72b9bd4af33a659eb95edee519d5 Mon Sep 17 00:00:00 2001 From: ChristianKuehnel Date: Sat, 20 Jan 2018 08:50:25 +0100 Subject: [PATCH 094/150] MiFlora - use bluepy on linux systems (#11284) * Updated bluepy to version 1.1.4 as some issues with the native code were fixed there. * Miflora - Added support for bluepy backend. * miflora - now using bluepy backend on linux platforms * fixed pylint findings * miflora now using bluepy as default bluetooth backend and gatttool as fallback * fixed hound complaints * fixed pylint warning * updated requirements * Update miflora.py --- homeassistant/components/light/decora.py | 2 +- homeassistant/components/sensor/miflora.py | 13 ++++++++++--- requirements_all.txt | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/light/decora.py b/homeassistant/components/light/decora.py index 17cc741c593..6d502e15d6f 100644 --- a/homeassistant/components/light/decora.py +++ b/homeassistant/components/light/decora.py @@ -16,7 +16,7 @@ from homeassistant.components.light import ( PLATFORM_SCHEMA) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['decora==0.6', 'bluepy==1.1.1'] +REQUIREMENTS = ['decora==0.6', 'bluepy==1.1.4'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/miflora.py b/homeassistant/components/sensor/miflora.py index 77d77949ebd..56f8c3cfe47 100644 --- a/homeassistant/components/sensor/miflora.py +++ b/homeassistant/components/sensor/miflora.py @@ -15,6 +15,7 @@ from homeassistant.const import ( CONF_FORCE_UPDATE, CONF_MONITORED_CONDITIONS, CONF_NAME, CONF_MAC ) + REQUIREMENTS = ['miflora==0.2.0'] _LOGGER = logging.getLogger(__name__) @@ -60,12 +61,19 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the MiFlora sensor.""" from miflora import miflora_poller - from miflora.backends.gatttool import GatttoolBackend + try: + import bluepy.btle # noqa: F401 # pylint: disable=unused-variable + from miflora.backends.bluepy import BluepyBackend + backend = BluepyBackend + except ImportError: + from miflora.backends.gatttool import GatttoolBackend + backend = GatttoolBackend + _LOGGER.debug('Miflora is using %s backend.', backend.__name__) cache = config.get(CONF_CACHE) poller = miflora_poller.MiFloraPoller( config.get(CONF_MAC), cache_timeout=cache, - adapter=config.get(CONF_ADAPTER), backend=GatttoolBackend) + adapter=config.get(CONF_ADAPTER), backend=backend) force_update = config.get(CONF_FORCE_UPDATE) median = config.get(CONF_MEDIAN) poller.ble_timeout = config.get(CONF_TIMEOUT) @@ -135,7 +143,6 @@ class MiFloraSensor(Entity): data = self.poller.parameter_value(self.parameter) except IOError as ioerr: _LOGGER.info("Polling error %s", ioerr) - data = None return if data is not None: diff --git a/requirements_all.txt b/requirements_all.txt index f7c7ce2d889..ffa7c4e1cbd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -141,7 +141,7 @@ blinkstick==1.1.8 blockchain==1.4.0 # homeassistant.components.light.decora -# bluepy==1.1.1 +# bluepy==1.1.4 # homeassistant.components.notify.aws_lambda # homeassistant.components.notify.aws_sns From e02d5e7ff1a96094a7eda0a7d48117f0526a2148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20=C3=98stergaard=20Nielsen?= Date: Sat, 20 Jan 2018 16:29:50 +0100 Subject: [PATCH 095/150] Ihc component and platforms (#10916) * Added IHC platform * Updated requirements for IHC platform * Exclude IHC from test * Correcting flake8 issues * Fixing more flake8 issues * Fixed flake8 issues * Fixing pylint issues * Fixed flake8 issues * Changes from PR review. * STATE_UNKNOWN changed to None * Spelling mistake in comment * Added IHC platform * Updated requirements for IHC platform * Exclude IHC from test * Correcting flake8 issues * Fixing more flake8 issues * Fixed flake8 issues * Fixing pylint issues * Fixed flake8 issues * Changes from PR review. * STATE_UNKNOWN changed to None * Spelling mistake in comment * Updated requirements_all.txt with gen_requirements_app.py * Pylint fix: No space allowed around keyword argument assignment * PR review changes * Moved auto setup from platforms to ihc component * Do no auto setup if there are no IHC products found * Changes from PR review --- .coveragerc | 3 + homeassistant/components/binary_sensor/ihc.py | 95 ++++++++ homeassistant/components/ihc/__init__.py | 213 ++++++++++++++++++ homeassistant/components/ihc/const.py | 19 ++ .../components/ihc/ihc_auto_setup.yaml | 98 ++++++++ homeassistant/components/ihc/ihcdevice.py | 65 ++++++ homeassistant/components/ihc/services.yaml | 26 +++ homeassistant/components/light/ihc.py | 123 ++++++++++ homeassistant/components/sensor/ihc.py | 84 +++++++ homeassistant/components/switch/ihc.py | 77 +++++++ requirements_all.txt | 3 + 11 files changed, 806 insertions(+) create mode 100644 homeassistant/components/binary_sensor/ihc.py create mode 100644 homeassistant/components/ihc/__init__.py create mode 100644 homeassistant/components/ihc/const.py create mode 100644 homeassistant/components/ihc/ihc_auto_setup.yaml create mode 100644 homeassistant/components/ihc/ihcdevice.py create mode 100644 homeassistant/components/ihc/services.yaml create mode 100644 homeassistant/components/light/ihc.py create mode 100644 homeassistant/components/sensor/ihc.py create mode 100644 homeassistant/components/switch/ihc.py diff --git a/.coveragerc b/.coveragerc index 8608d30236d..e4d5bca95d3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -97,6 +97,9 @@ omit = homeassistant/components/homematic/__init__.py homeassistant/components/*/homematic.py + homeassistant/components/ihc/* + homeassistant/components/*/ihc.py + homeassistant/components/insteon_local.py homeassistant/components/*/insteon_local.py diff --git a/homeassistant/components/binary_sensor/ihc.py b/homeassistant/components/binary_sensor/ihc.py new file mode 100644 index 00000000000..14e45f88cf1 --- /dev/null +++ b/homeassistant/components/binary_sensor/ihc.py @@ -0,0 +1,95 @@ +"""IHC binary sensor platform. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.ihc/ +""" +from xml.etree.ElementTree import Element + +import voluptuous as vol + +from homeassistant.components.binary_sensor import ( + BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA) +from homeassistant.components.ihc import ( + validate_name, IHC_DATA, IHC_CONTROLLER, IHC_INFO) +from homeassistant.components.ihc.const import CONF_INVERTING +from homeassistant.components.ihc.ihcdevice import IHCDevice +from homeassistant.const import ( + CONF_NAME, CONF_TYPE, CONF_ID, CONF_BINARY_SENSORS) +import homeassistant.helpers.config_validation as cv + +DEPENDENCIES = ['ihc'] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_BINARY_SENSORS, default=[]): + vol.All(cv.ensure_list, [ + vol.All({ + vol.Required(CONF_ID): cv.positive_int, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_TYPE, default=None): DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_INVERTING, default=False): cv.boolean, + }, validate_name) + ]) +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the IHC binary sensor platform.""" + ihc_controller = hass.data[IHC_DATA][IHC_CONTROLLER] + info = hass.data[IHC_DATA][IHC_INFO] + devices = [] + if discovery_info: + for name, device in discovery_info.items(): + ihc_id = device['ihc_id'] + product_cfg = device['product_cfg'] + product = device['product'] + sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info, + product_cfg[CONF_TYPE], + product_cfg[CONF_INVERTING], + product) + devices.append(sensor) + else: + binary_sensors = config[CONF_BINARY_SENSORS] + for sensor_cfg in binary_sensors: + ihc_id = sensor_cfg[CONF_ID] + name = sensor_cfg[CONF_NAME] + sensor_type = sensor_cfg[CONF_TYPE] + inverting = sensor_cfg[CONF_INVERTING] + sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info, + sensor_type, inverting) + devices.append(sensor) + + add_devices(devices) + + +class IHCBinarySensor(IHCDevice, BinarySensorDevice): + """IHC Binary Sensor. + + The associated IHC resource can be any in or output from a IHC product + or function block, but it must be a boolean ON/OFF resources. + """ + + def __init__(self, ihc_controller, name, ihc_id: int, info: bool, + sensor_type: str, inverting: bool, product: Element=None): + """Initialize the IHC binary sensor.""" + super().__init__(ihc_controller, name, ihc_id, info, product) + self._state = None + self._sensor_type = sensor_type + self.inverting = inverting + + @property + def device_class(self): + """Return the class of this sensor.""" + return self._sensor_type + + @property + def is_on(self): + """Return true if the binary sensor is on/open.""" + return self._state + + def on_ihc_change(self, ihc_id, value): + """IHC resource has changed.""" + if self.inverting: + self._state = not value + else: + self._state = value + self.schedule_update_ha_state() diff --git a/homeassistant/components/ihc/__init__.py b/homeassistant/components/ihc/__init__.py new file mode 100644 index 00000000000..f3cd9d79046 --- /dev/null +++ b/homeassistant/components/ihc/__init__.py @@ -0,0 +1,213 @@ +"""IHC component. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/ihc/ +""" +import logging +import os.path +import xml.etree.ElementTree +import voluptuous as vol + +from homeassistant.components.ihc.const import ( + ATTR_IHC_ID, ATTR_VALUE, CONF_INFO, CONF_AUTOSETUP, + CONF_BINARY_SENSOR, CONF_LIGHT, CONF_SENSOR, CONF_SWITCH, + CONF_XPATH, CONF_NODE, CONF_DIMMABLE, CONF_INVERTING, + SERVICE_SET_RUNTIME_VALUE_BOOL, SERVICE_SET_RUNTIME_VALUE_INT, + SERVICE_SET_RUNTIME_VALUE_FLOAT) +from homeassistant.config import load_yaml_config_file +from homeassistant.const import ( + CONF_URL, CONF_USERNAME, CONF_PASSWORD, CONF_ID, CONF_NAME, + CONF_UNIT_OF_MEASUREMENT, CONF_TYPE, TEMP_CELSIUS) +from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import HomeAssistantType + +REQUIREMENTS = ['ihcsdk==2.1.1'] + +DOMAIN = 'ihc' +IHC_DATA = 'ihc' +IHC_CONTROLLER = 'controller' +IHC_INFO = 'info' +AUTO_SETUP_YAML = 'ihc_auto_setup.yaml' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_URL): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_AUTOSETUP, default=True): cv.boolean, + vol.Optional(CONF_INFO, default=True): cv.boolean + }), +}, extra=vol.ALLOW_EXTRA) + +AUTO_SETUP_SCHEMA = vol.Schema({ + vol.Optional(CONF_BINARY_SENSOR, default=[]): + vol.All(cv.ensure_list, [ + vol.All({ + vol.Required(CONF_XPATH): cv.string, + vol.Required(CONF_NODE): cv.string, + vol.Optional(CONF_TYPE, default=None): cv.string, + vol.Optional(CONF_INVERTING, default=False): cv.boolean, + }) + ]), + vol.Optional(CONF_LIGHT, default=[]): + vol.All(cv.ensure_list, [ + vol.All({ + vol.Required(CONF_XPATH): cv.string, + vol.Required(CONF_NODE): cv.string, + vol.Optional(CONF_DIMMABLE, default=False): cv.boolean, + }) + ]), + vol.Optional(CONF_SENSOR, default=[]): + vol.All(cv.ensure_list, [ + vol.All({ + vol.Required(CONF_XPATH): cv.string, + vol.Required(CONF_NODE): cv.string, + vol.Optional(CONF_UNIT_OF_MEASUREMENT, + default=TEMP_CELSIUS): cv.string, + }) + ]), + vol.Optional(CONF_SWITCH, default=[]): + vol.All(cv.ensure_list, [ + vol.All({ + vol.Required(CONF_XPATH): cv.string, + vol.Required(CONF_NODE): cv.string, + }) + ]), +}) + +SET_RUNTIME_VALUE_BOOL_SCHEMA = vol.Schema({ + vol.Required(ATTR_IHC_ID): cv.positive_int, + vol.Required(ATTR_VALUE): cv.boolean +}) + +SET_RUNTIME_VALUE_INT_SCHEMA = vol.Schema({ + vol.Required(ATTR_IHC_ID): cv.positive_int, + vol.Required(ATTR_VALUE): int +}) + +SET_RUNTIME_VALUE_FLOAT_SCHEMA = vol.Schema({ + vol.Required(ATTR_IHC_ID): cv.positive_int, + vol.Required(ATTR_VALUE): vol.Coerce(float) +}) + +_LOGGER = logging.getLogger(__name__) + +IHC_PLATFORMS = ('binary_sensor', 'light', 'sensor', 'switch') + + +def setup(hass, config): + """Setup the IHC component.""" + from ihcsdk.ihccontroller import IHCController + conf = config[DOMAIN] + url = conf[CONF_URL] + username = conf[CONF_USERNAME] + password = conf[CONF_PASSWORD] + ihc_controller = IHCController(url, username, password) + + if not ihc_controller.authenticate(): + _LOGGER.error("Unable to authenticate on ihc controller.") + return False + + if (conf[CONF_AUTOSETUP] and + not autosetup_ihc_products(hass, config, ihc_controller)): + return False + + hass.data[IHC_DATA] = { + IHC_CONTROLLER: ihc_controller, + IHC_INFO: conf[CONF_INFO]} + + setup_service_functions(hass, ihc_controller) + return True + + +def autosetup_ihc_products(hass: HomeAssistantType, config, ihc_controller): + """Auto setup of IHC products from the ihc project file.""" + project_xml = ihc_controller.get_project() + if not project_xml: + _LOGGER.error("Unable to read project from ihc controller.") + return False + project = xml.etree.ElementTree.fromstring(project_xml) + + # if an auto setup file exist in the configuration it will override + yaml_path = hass.config.path(AUTO_SETUP_YAML) + if not os.path.isfile(yaml_path): + yaml_path = os.path.join(os.path.dirname(__file__), AUTO_SETUP_YAML) + yaml = load_yaml_config_file(yaml_path) + try: + auto_setup_conf = AUTO_SETUP_SCHEMA(yaml) + except vol.Invalid as exception: + _LOGGER.error("Invalid IHC auto setup data: %s", exception) + return False + groups = project.findall('.//group') + for component in IHC_PLATFORMS: + component_setup = auto_setup_conf[component] + discovery_info = get_discovery_info(component_setup, groups) + if discovery_info: + discovery.load_platform(hass, component, DOMAIN, discovery_info, + config) + return True + + +def get_discovery_info(component_setup, groups): + """Get discovery info for specified component.""" + discovery_data = {} + for group in groups: + groupname = group.attrib['name'] + for product_cfg in component_setup: + products = group.findall(product_cfg[CONF_XPATH]) + for product in products: + nodes = product.findall(product_cfg[CONF_NODE]) + for node in nodes: + if ('setting' in node.attrib + and node.attrib['setting'] == 'yes'): + continue + ihc_id = int(node.attrib['id'].strip('_'), 0) + name = '{}_{}'.format(groupname, ihc_id) + device = { + 'ihc_id': ihc_id, + 'product': product, + 'product_cfg': product_cfg} + discovery_data[name] = device + return discovery_data + + +def setup_service_functions(hass: HomeAssistantType, ihc_controller): + """Setup the ihc service functions.""" + def set_runtime_value_bool(call): + """Set a IHC runtime bool value service function.""" + ihc_id = call.data[ATTR_IHC_ID] + value = call.data[ATTR_VALUE] + ihc_controller.set_runtime_value_bool(ihc_id, value) + + def set_runtime_value_int(call): + """Set a IHC runtime integer value service function.""" + ihc_id = call.data[ATTR_IHC_ID] + value = call.data[ATTR_VALUE] + ihc_controller.set_runtime_value_int(ihc_id, value) + + def set_runtime_value_float(call): + """Set a IHC runtime float value service function.""" + ihc_id = call.data[ATTR_IHC_ID] + value = call.data[ATTR_VALUE] + ihc_controller.set_runtime_value_float(ihc_id, value) + + hass.services.register(DOMAIN, SERVICE_SET_RUNTIME_VALUE_BOOL, + set_runtime_value_bool, + schema=SET_RUNTIME_VALUE_BOOL_SCHEMA) + hass.services.register(DOMAIN, SERVICE_SET_RUNTIME_VALUE_INT, + set_runtime_value_int, + schema=SET_RUNTIME_VALUE_INT_SCHEMA) + hass.services.register(DOMAIN, SERVICE_SET_RUNTIME_VALUE_FLOAT, + set_runtime_value_float, + schema=SET_RUNTIME_VALUE_FLOAT_SCHEMA) + + +def validate_name(config): + """Validate device name.""" + if CONF_NAME in config: + return config + ihcid = config[CONF_ID] + name = 'ihc_{}'.format(ihcid) + config[CONF_NAME] = name + return config diff --git a/homeassistant/components/ihc/const.py b/homeassistant/components/ihc/const.py new file mode 100644 index 00000000000..b06746c8e7a --- /dev/null +++ b/homeassistant/components/ihc/const.py @@ -0,0 +1,19 @@ +"""IHC component constants.""" + +CONF_AUTOSETUP = 'auto_setup' +CONF_INFO = 'info' +CONF_XPATH = 'xpath' +CONF_NODE = 'node' +CONF_INVERTING = 'inverting' +CONF_DIMMABLE = 'dimmable' +CONF_BINARY_SENSOR = 'binary_sensor' +CONF_LIGHT = 'light' +CONF_SENSOR = 'sensor' +CONF_SWITCH = 'switch' + +ATTR_IHC_ID = 'ihc_id' +ATTR_VALUE = 'value' + +SERVICE_SET_RUNTIME_VALUE_BOOL = "set_runtime_value_bool" +SERVICE_SET_RUNTIME_VALUE_INT = "set_runtime_value_int" +SERVICE_SET_RUNTIME_VALUE_FLOAT = "set_runtime_value_float" diff --git a/homeassistant/components/ihc/ihc_auto_setup.yaml b/homeassistant/components/ihc/ihc_auto_setup.yaml new file mode 100644 index 00000000000..81d5bf37977 --- /dev/null +++ b/homeassistant/components/ihc/ihc_auto_setup.yaml @@ -0,0 +1,98 @@ +# IHC auto setup configuration. +# To customize this, copy this file to the home assistant configuration +# folder and make your changes. + +binary_sensor: + # Magnet contact + - xpath: './/product_dataline[@product_identifier="_0x2109"]' + node: 'dataline_input' + type: 'opening' + inverting: True + # Pir sensors + - xpath: './/product_dataline[@product_identifier="_0x210e"]' + node: 'dataline_input[1]' + type: 'motion' + # Pir sensors twilight sensor + - xpath: './/product_dataline[@product_identifier="_0x0"]' + node: 'dataline_input[1]' + type: 'motion' + # Pir sensors alarm + - xpath: './/product_dataline[@product_identifier="_0x210f"]' + node: 'dataline_input' + type: 'motion' + # Smoke detector + - xpath: './/product_dataline[@product_identifier="_0x210a"]' + node: 'dataline_input' + type: 'smoke' + # leak detector + - xpath: './/product_dataline[@product_identifier="_0x210c"]' + node: 'dataline_input' + type: 'moisture' + # light detector + - xpath: './/product_dataline[@product_identifier="_0x2110"]' + node: 'dataline_input' + type: 'light' + +light: + # Wireless Combi dimmer 4 buttons + - xpath: './/product_airlink[@product_identifier="_0x4406"]' + node: 'airlink_dimming' + dimmable: True + # Wireless Lamp outlet dimmer + - xpath: './/product_airlink[@product_identifier="_0x4304"]' + node: 'airlink_dimming' + dimmable: True + # Wireless universal dimmer + - xpath: './/product_airlink[@product_identifier="_0x4306"]' + node: 'airlink_dimming' + dimmable: True + # Wireless Lamp outlet relay + - xpath: './/product_airlink[@product_identifier="_0x4202"]' + node: 'airlink_relay' + # Wireless Combi relay 4 buttons + - xpath: './/product_airlink[@product_identifier="_0x4404"]' + node: 'airlink_relay' + # Dataline Lamp outlet + - xpath: './/product_dataline[@product_identifier="_0x2202"]' + node: 'dataline_output' + # Mobile Wireless dimmer + - xpath: './/product_airlink[@product_identifier="_0x4303"]' + node: 'airlink_dimming' + dimmable: True + +sensor: + # Temperature sensor + - xpath: './/product_dataline[@product_identifier="_0x2124"]' + node: 'resource_temperature' + unit_of_measurement: '°C' + # Humidity/temperature + - xpath: './/product_dataline[@product_identifier="_0x2135"]' + node: 'resource_humidity_level' + unit_of_measurement: '%' + # Humidity/temperature + - xpath: './/product_dataline[@product_identifier="_0x2135"]' + node: 'resource_temperature' + unit_of_measurement: '°C' + # Lux/temperature + - xpath: './/product_dataline[@product_identifier="_0x2136"]' + node: 'resource_light' + unit_of_measurement: 'Lux' + # Lux/temperature + - xpath: './/product_dataline[@product_identifier="_0x2136"]' + node: 'resource_temperature' + unit_of_measurement: '°C' + +switch: + # Wireless Plug outlet + - xpath: './/product_airlink[@product_identifier="_0x4201"]' + node: 'airlink_relay' + # Dataline universal relay + - xpath: './/product_airlink[@product_identifier="_0x4203"]' + node: 'airlink_relay' + # Dataline plug outlet + - xpath: './/product_dataline[@product_identifier="_0x2201"]' + node: 'dataline_output' + # Wireless mobile relay + - xpath: './/product_airlink[@product_identifier="_0x4204"]' + node: 'airlink_relay' + diff --git a/homeassistant/components/ihc/ihcdevice.py b/homeassistant/components/ihc/ihcdevice.py new file mode 100644 index 00000000000..48827851f92 --- /dev/null +++ b/homeassistant/components/ihc/ihcdevice.py @@ -0,0 +1,65 @@ +"""Implements a base class for all IHC devices.""" +import asyncio +from xml.etree.ElementTree import Element + +from homeassistant.helpers.entity import Entity + + +class IHCDevice(Entity): + """Base class for all ihc devices. + + All IHC devices have an associated IHC resource. IHCDevice handled the + registration of the IHC controller callback when the IHC resource changes. + Derived classes must implement the on_ihc_change method + """ + + def __init__(self, ihc_controller, name, ihc_id: int, info: bool, + product: Element=None): + """Initialize IHC attributes.""" + self.ihc_controller = ihc_controller + self._name = name + self.ihc_id = ihc_id + self.info = info + if product: + self.ihc_name = product.attrib['name'] + self.ihc_note = product.attrib['note'] + self.ihc_position = product.attrib['position'] + else: + self.ihc_name = '' + self.ihc_note = '' + self.ihc_position = '' + + @asyncio.coroutine + def async_added_to_hass(self): + """Add callback for ihc changes.""" + self.ihc_controller.add_notify_event( + self.ihc_id, self.on_ihc_change, True) + + @property + def should_poll(self) -> bool: + """No polling needed for ihc devices.""" + return False + + @property + def name(self): + """Return the device name.""" + return self._name + + @property + def device_state_attributes(self): + """Return the state attributes.""" + if not self.info: + return {} + return { + 'ihc_id': self.ihc_id, + 'ihc_name': self.ihc_name, + 'ihc_note': self.ihc_note, + 'ihc_position': self.ihc_position + } + + def on_ihc_change(self, ihc_id, value): + """Callback when ihc resource changes. + + Derived classes must overwrite this to do device specific stuff. + """ + raise NotImplementedError diff --git a/homeassistant/components/ihc/services.yaml b/homeassistant/components/ihc/services.yaml new file mode 100644 index 00000000000..7b6053eff89 --- /dev/null +++ b/homeassistant/components/ihc/services.yaml @@ -0,0 +1,26 @@ +# Describes the format for available ihc services + +set_runtime_value_bool: + description: Set a boolean runtime value on the ihc controller + fields: + ihc_id: + description: The integer ihc resource id + value: + description: The boolean value to set + +set_runtime_value_int: + description: Set an integer runtime value on the ihc controller + fields: + ihc_id: + description: The integer ihc resource id + value: + description: The integer value to set + +set_runtime_value_float: + description: Set a float runtime value on the ihc controller + fields: + ihc_id: + description: The integer ihc resource id + value: + description: The float value to set + diff --git a/homeassistant/components/light/ihc.py b/homeassistant/components/light/ihc.py new file mode 100644 index 00000000000..f23ae77c8b2 --- /dev/null +++ b/homeassistant/components/light/ihc.py @@ -0,0 +1,123 @@ +"""IHC light platform. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/light.ihc/ +""" +from xml.etree.ElementTree import Element + +import voluptuous as vol + +from homeassistant.components.ihc import ( + validate_name, IHC_DATA, IHC_CONTROLLER, IHC_INFO) +from homeassistant.components.ihc.const import CONF_DIMMABLE +from homeassistant.components.ihc.ihcdevice import IHCDevice +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, PLATFORM_SCHEMA, Light) +from homeassistant.const import CONF_ID, CONF_NAME, CONF_LIGHTS +import homeassistant.helpers.config_validation as cv + +DEPENDENCIES = ['ihc'] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_LIGHTS, default=[]): + vol.All(cv.ensure_list, [ + vol.All({ + vol.Required(CONF_ID): cv.positive_int, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_DIMMABLE, default=False): cv.boolean, + }, validate_name) + ]) +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the ihc lights platform.""" + ihc_controller = hass.data[IHC_DATA][IHC_CONTROLLER] + info = hass.data[IHC_DATA][IHC_INFO] + devices = [] + if discovery_info: + for name, device in discovery_info.items(): + ihc_id = device['ihc_id'] + product_cfg = device['product_cfg'] + product = device['product'] + light = IhcLight(ihc_controller, name, ihc_id, info, + product_cfg[CONF_DIMMABLE], product) + devices.append(light) + else: + lights = config[CONF_LIGHTS] + for light in lights: + ihc_id = light[CONF_ID] + name = light[CONF_NAME] + dimmable = light[CONF_DIMMABLE] + device = IhcLight(ihc_controller, name, ihc_id, info, dimmable) + devices.append(device) + + add_devices(devices) + + +class IhcLight(IHCDevice, Light): + """Representation of a IHC light. + + For dimmable lights, the associated IHC resource should be a light + level (integer). For non dimmable light the IHC resource should be + an on/off (boolean) resource + """ + + def __init__(self, ihc_controller, name, ihc_id: int, info: bool, + dimmable=False, product: Element=None): + """Initialize the light.""" + super().__init__(ihc_controller, name, ihc_id, info, product) + self._brightness = 0 + self._dimmable = dimmable + self._state = None + + @property + def brightness(self) -> int: + """Return the brightness of this light between 0..255.""" + return self._brightness + + @property + def is_on(self) -> bool: + """Return true if light is on.""" + return self._state + + @property + def supported_features(self): + """Flag supported features.""" + if self._dimmable: + return SUPPORT_BRIGHTNESS + return 0 + + def turn_on(self, **kwargs) -> None: + """Turn the light on.""" + if ATTR_BRIGHTNESS in kwargs: + brightness = kwargs[ATTR_BRIGHTNESS] + else: + brightness = self._brightness + if brightness == 0: + brightness = 255 + + if self._dimmable: + self.ihc_controller.set_runtime_value_int( + self.ihc_id, int(brightness * 100 / 255)) + else: + self.ihc_controller.set_runtime_value_bool(self.ihc_id, True) + + def turn_off(self, **kwargs) -> None: + """Turn the light off.""" + if self._dimmable: + self.ihc_controller.set_runtime_value_int(self.ihc_id, 0) + else: + self.ihc_controller.set_runtime_value_bool(self.ihc_id, False) + + def on_ihc_change(self, ihc_id, value): + """Callback from Ihc notifications.""" + if isinstance(value, bool): + self._dimmable = False + self._state = value != 0 + else: + self._dimmable = True + self._state = value > 0 + if self._state: + self._brightness = int(value * 255 / 100) + self.schedule_update_ha_state() diff --git a/homeassistant/components/sensor/ihc.py b/homeassistant/components/sensor/ihc.py new file mode 100644 index 00000000000..3ad86e51f97 --- /dev/null +++ b/homeassistant/components/sensor/ihc.py @@ -0,0 +1,84 @@ +"""IHC sensor platform. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.ihc/ +""" +from xml.etree.ElementTree import Element + +import voluptuous as vol + +from homeassistant.components.ihc import ( + validate_name, IHC_DATA, IHC_CONTROLLER, IHC_INFO) +from homeassistant.components.ihc.ihcdevice import IHCDevice +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_ID, CONF_NAME, CONF_UNIT_OF_MEASUREMENT, CONF_SENSORS, + TEMP_CELSIUS) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity + +DEPENDENCIES = ['ihc'] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_SENSORS, default=[]): + vol.All(cv.ensure_list, [ + vol.All({ + vol.Required(CONF_ID): cv.positive_int, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_UNIT_OF_MEASUREMENT, + default=TEMP_CELSIUS): cv.string + }, validate_name) + ]) +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the ihc sensor platform.""" + ihc_controller = hass.data[IHC_DATA][IHC_CONTROLLER] + info = hass.data[IHC_DATA][IHC_INFO] + devices = [] + if discovery_info: + for name, device in discovery_info.items(): + ihc_id = device['ihc_id'] + product_cfg = device['product_cfg'] + product = device['product'] + sensor = IHCSensor(ihc_controller, name, ihc_id, info, + product_cfg[CONF_UNIT_OF_MEASUREMENT], + product) + devices.append(sensor) + else: + sensors = config[CONF_SENSORS] + for sensor_cfg in sensors: + ihc_id = sensor_cfg[CONF_ID] + name = sensor_cfg[CONF_NAME] + unit = sensor_cfg[CONF_UNIT_OF_MEASUREMENT] + sensor = IHCSensor(ihc_controller, name, ihc_id, info, unit) + devices.append(sensor) + + add_devices(devices) + + +class IHCSensor(IHCDevice, Entity): + """Implementation of the IHC sensor.""" + + def __init__(self, ihc_controller, name, ihc_id: int, info: bool, + unit, product: Element=None): + """Initialize the IHC sensor.""" + super().__init__(ihc_controller, name, ihc_id, info, product) + self._state = None + self._unit_of_measurement = unit + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return self._unit_of_measurement + + def on_ihc_change(self, ihc_id, value): + """Callback when ihc resource changes.""" + self._state = value + self.schedule_update_ha_state() diff --git a/homeassistant/components/switch/ihc.py b/homeassistant/components/switch/ihc.py new file mode 100644 index 00000000000..4bab1378acd --- /dev/null +++ b/homeassistant/components/switch/ihc.py @@ -0,0 +1,77 @@ +"""IHC switch platform. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.ihc/ +""" +from xml.etree.ElementTree import Element + +import voluptuous as vol + +from homeassistant.components.ihc import ( + validate_name, IHC_DATA, IHC_CONTROLLER, IHC_INFO) +from homeassistant.components.ihc.ihcdevice import IHCDevice +from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.const import CONF_ID, CONF_NAME, CONF_SWITCHES +import homeassistant.helpers.config_validation as cv + +DEPENDENCIES = ['ihc'] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_SWITCHES, default=[]): + vol.All(cv.ensure_list, [ + vol.All({ + vol.Required(CONF_ID): cv.positive_int, + vol.Optional(CONF_NAME): cv.string, + }, validate_name) + ]) +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the ihc switch platform.""" + ihc_controller = hass.data[IHC_DATA][IHC_CONTROLLER] + info = hass.data[IHC_DATA][IHC_INFO] + devices = [] + if discovery_info: + for name, device in discovery_info.items(): + ihc_id = device['ihc_id'] + product = device['product'] + switch = IHCSwitch(ihc_controller, name, ihc_id, info, product) + devices.append(switch) + else: + switches = config[CONF_SWITCHES] + for switch in switches: + ihc_id = switch[CONF_ID] + name = switch[CONF_NAME] + sensor = IHCSwitch(ihc_controller, name, ihc_id, info) + devices.append(sensor) + + add_devices(devices) + + +class IHCSwitch(IHCDevice, SwitchDevice): + """IHC Switch.""" + + def __init__(self, ihc_controller, name: str, ihc_id: int, + info: bool, product: Element=None): + """Initialize the IHC switch.""" + super().__init__(ihc_controller, name, ihc_id, product) + self._state = False + + @property + def is_on(self): + """Return true if switch is on.""" + return self._state + + def turn_on(self, **kwargs): + """Turn the switch on.""" + self.ihc_controller.set_runtime_value_bool(self.ihc_id, True) + + def turn_off(self, **kwargs): + """Turn the device off.""" + self.ihc_controller.set_runtime_value_bool(self.ihc_id, False) + + def on_ihc_change(self, ihc_id, value): + """Callback when the ihc resource changes.""" + self._state = value + self.schedule_update_ha_state() diff --git a/requirements_all.txt b/requirements_all.txt index ffa7c4e1cbd..1bee68301d6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -398,6 +398,9 @@ https://github.com/wokar/pylgnetcast/archive/v0.2.0.zip#pylgnetcast==0.2.0 # homeassistant.components.light.iglo iglo==1.1.3 +# homeassistant.components.ihc +ihcsdk==2.1.1 + # homeassistant.components.influxdb # homeassistant.components.sensor.influxdb influxdb==4.1.1 From f7b129d79011e86dcc689c802780e41f1f9b8d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Arnauts?= Date: Sat, 20 Jan 2018 17:07:45 +0100 Subject: [PATCH 096/150] Change telldus domain to download.telldus.com (#11825) --- Dockerfile | 2 +- virtualization/Docker/scripts/tellstick | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8082b452d60..5081b4ba721 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ FROM python:3.6 LABEL maintainer="Paulus Schoutsen " # Uncomment any of the following lines to disable the installation. -ENV INSTALL_TELLSTICK no +#ENV INSTALL_TELLSTICK no #ENV INSTALL_OPENALPR no #ENV INSTALL_FFMPEG no #ENV INSTALL_LIBCEC no diff --git a/virtualization/Docker/scripts/tellstick b/virtualization/Docker/scripts/tellstick index 805e411f47b..d4bf8b331f3 100755 --- a/virtualization/Docker/scripts/tellstick +++ b/virtualization/Docker/scripts/tellstick @@ -11,7 +11,7 @@ PACKAGES=( # Add Tellstick repository echo "deb http://download.telldus.com/debian/ stable main" >> /etc/apt/sources.list.d/telldus.list -wget -qO - http://download.telldus.se/debian/telldus-public.key | apt-key add - +wget -qO - http://download.telldus.com/debian/telldus-public.key | apt-key add - apt-get update apt-get install -y --no-install-recommends ${PACKAGES[@]} \ No newline at end of file From a470cc212e8a96385560e9d54ce7bad93d193e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Bedn=C3=A1rik?= Date: Sat, 20 Jan 2018 17:09:05 +0100 Subject: [PATCH 097/150] Add more workday sensor countries and update holidays library to version 0.9.3 (#11826) --- .../components/binary_sensor/workday.py | 20 +++++++++++-------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/binary_sensor/workday.py b/homeassistant/components/binary_sensor/workday.py index 83dc51a2e0f..af814cfd464 100644 --- a/homeassistant/components/binary_sensor/workday.py +++ b/homeassistant/components/binary_sensor/workday.py @@ -17,17 +17,21 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['holidays==0.8.1'] +REQUIREMENTS = ['holidays==0.9.3'] # List of all countries currently supported by holidays # There seems to be no way to get the list out at runtime -ALL_COUNTRIES = ['Australia', 'AU', 'Austria', 'AT', 'Canada', 'CA', - 'Colombia', 'CO', 'Czech', 'CZ', 'Denmark', 'DK', 'England', - 'EuropeanCentralBank', 'ECB', 'TAR', 'Germany', 'DE', - 'Ireland', 'Isle of Man', 'Mexico', 'MX', 'Netherlands', 'NL', - 'NewZealand', 'NZ', 'Northern Ireland', 'Norway', 'NO', - 'Portugal', 'PT', 'PortugalExt', 'PTE', 'Scotland', 'Spain', - 'ES', 'UnitedKingdom', 'UK', 'UnitedStates', 'US', 'Wales'] +ALL_COUNTRIES = ['Australia', 'AU', 'Austria', 'AT', 'Belgium', 'BE', 'Canada', + 'CA', 'Colombia', 'CO', 'Czech', 'CZ', 'Denmark', 'DK', + 'England', 'EuropeanCentralBank', 'ECB', 'TAR', 'Finland', + 'FI', 'France', 'FRA', 'Germany', 'DE', 'Ireland', + 'Isle of Man', 'Italy', 'IT', 'Japan', 'JP', 'Mexico', 'MX', + 'Netherlands', 'NL', 'NewZealand', 'NZ', 'Northern Ireland', + 'Norway', 'NO', 'Polish', 'PL', 'Portugal', 'PT', + 'PortugalExt', 'PTE', 'Scotland', 'Slovenia', 'SI', + 'Slovakia', 'SK', 'South Africa', 'ZA', 'Spain', 'ES', + 'Sweden', 'SE', 'UnitedKingdom', 'UK', 'UnitedStates', 'US', + 'Wales'] CONF_COUNTRY = 'country' CONF_PROVINCE = 'province' CONF_WORKDAYS = 'workdays' diff --git a/requirements_all.txt b/requirements_all.txt index 1bee68301d6..23a641a0be0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -346,7 +346,7 @@ hikvision==0.4 hipnotify==1.0.8 # homeassistant.components.binary_sensor.workday -holidays==0.8.1 +holidays==0.9.3 # homeassistant.components.frontend home-assistant-frontend==20180119.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 83f2b8df1e9..b3882a00b3b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -72,7 +72,7 @@ haversine==0.4.5 hbmqtt==0.9.1 # homeassistant.components.binary_sensor.workday -holidays==0.8.1 +holidays==0.9.3 # homeassistant.components.frontend home-assistant-frontend==20180119.0 From 2cfbd0dc1dcf7b6ae8b2a9a7af4d7450f10ba30a Mon Sep 17 00:00:00 2001 From: Dan Nixon Date: Sat, 20 Jan 2018 16:11:04 +0000 Subject: [PATCH 098/150] Add missing availability schema to MQTT alarm panel (#11829) * Add missing availability schema to MQTT alarm panel * Add tests for default MQTT availability payloads --- .../components/alarm_control_panel/mqtt.py | 2 +- .../alarm_control_panel/test_mqtt.py | 38 ++++++++++++----- tests/components/fan/test_mqtt.py | 40 ++++++++++++++++++ tests/components/light/test_mqtt.py | 28 +++++++++++++ tests/components/light/test_mqtt_json.py | 27 ++++++++++++ tests/components/light/test_mqtt_template.py | 28 +++++++++++++ tests/components/lock/test_mqtt.py | 29 +++++++++++++ tests/components/sensor/test_mqtt.py | 26 ++++++++++++ tests/components/switch/test_mqtt.py | 42 +++++++++++++++++++ tests/components/vacuum/test_mqtt.py | 25 +++++++++++ 10 files changed, 274 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/mqtt.py b/homeassistant/components/alarm_control_panel/mqtt.py index a4559160e3b..1422136c405 100644 --- a/homeassistant/components/alarm_control_panel/mqtt.py +++ b/homeassistant/components/alarm_control_panel/mqtt.py @@ -42,7 +42,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({ 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, -}) +}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) @asyncio.coroutine diff --git a/tests/components/alarm_control_panel/test_mqtt.py b/tests/components/alarm_control_panel/test_mqtt.py index 200978ea1a0..5a93a55254d 100644 --- a/tests/components/alarm_control_panel/test_mqtt.py +++ b/tests/components/alarm_control_panel/test_mqtt.py @@ -192,6 +192,34 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): self.hass.block_till_done() self.assertEqual(call_count, self.mock_publish.call_count) + def test_default_availability_payload(self): + """Test availability by default payload with defined topic.""" + 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', + 'availability_topic': 'availability-topic' + } + }) + + state = self.hass.states.get('alarm_control_panel.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability-topic', 'online') + self.hass.block_till_done() + + state = self.hass.states.get('alarm_control_panel.test') + self.assertNotEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability-topic', 'offline') + self.hass.block_till_done() + + state = self.hass.states.get('alarm_control_panel.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + def test_custom_availability_payload(self): """Test availability by custom payload with defined topic.""" assert setup_component(self.hass, alarm_control_panel.DOMAIN, { @@ -211,13 +239,3 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): self.assertEqual(STATE_UNAVAILABLE, state.state) fire_mqtt_message(self.hass, 'availability-topic', 'good') - self.hass.block_till_done() - - state = self.hass.states.get('alarm_control_panel.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) - - fire_mqtt_message(self.hass, 'availability-topic', 'nogood') - self.hass.block_till_done() - - state = self.hass.states.get('alarm_control_panel.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) diff --git a/tests/components/fan/test_mqtt.py b/tests/components/fan/test_mqtt.py index 3846887f21c..ec68492ed1e 100644 --- a/tests/components/fan/test_mqtt.py +++ b/tests/components/fan/test_mqtt.py @@ -21,6 +21,46 @@ class TestMqttFan(unittest.TestCase): """"Stop everything that was started.""" self.hass.stop() + def test_default_availability_payload(self): + """Test the availability payload.""" + assert setup_component(self.hass, fan.DOMAIN, { + fan.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'availability_topic': 'availability_topic' + } + }) + + state = self.hass.states.get('fan.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability_topic', 'online') + self.hass.block_till_done() + + state = self.hass.states.get('fan.test') + self.assertNotEqual(STATE_UNAVAILABLE, state.state) + self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + + fire_mqtt_message(self.hass, 'availability_topic', 'offline') + self.hass.block_till_done() + + state = self.hass.states.get('fan.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'state-topic', '1') + self.hass.block_till_done() + + state = self.hass.states.get('fan.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability_topic', 'online') + self.hass.block_till_done() + + state = self.hass.states.get('fan.test') + self.assertNotEqual(STATE_UNAVAILABLE, state.state) + def test_custom_availability_payload(self): """Test the availability payload.""" assert setup_component(self.hass, fan.DOMAIN, { diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index d6dabaf9a4f..7ef33aad2d9 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -796,6 +796,34 @@ class TestLightMQTT(unittest.TestCase): self.assertEqual(('test_light/bright', 50, 0, False), self.mock_publish.mock_calls[-2][1]) + def test_default_availability_payload(self): + """Test availability by default payload with defined topic.""" + self.assertTrue(setup_component(self.hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'command_topic': 'test_light/set', + 'brightness_command_topic': 'test_light/bright', + 'rgb_command_topic': "test_light/rgb", + 'availability_topic': 'availability-topic' + } + })) + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability-topic', 'online') + self.hass.block_till_done() + + state = self.hass.states.get('light.test') + self.assertNotEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability-topic', 'offline') + self.hass.block_till_done() + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + def test_custom_availability_payload(self): """Test availability by custom payload with defined topic.""" self.assertTrue(setup_component(self.hass, light.DOMAIN, { diff --git a/tests/components/light/test_mqtt_json.py b/tests/components/light/test_mqtt_json.py index d0412df22f5..5cb0d0cdc1b 100644 --- a/tests/components/light/test_mqtt_json.py +++ b/tests/components/light/test_mqtt_json.py @@ -522,6 +522,33 @@ class TestLightMQTTJSON(unittest.TestCase): self.assertEqual(STATE_ON, state.state) self.assertEqual(255, state.attributes.get('white_value')) + def test_default_availability_payload(self): + """Test availability by default payload with defined topic.""" + self.assertTrue(setup_component(self.hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_json', + 'name': 'test', + 'state_topic': 'test_light_rgb', + 'command_topic': 'test_light_rgb/set', + 'availability_topic': 'availability-topic' + } + })) + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability-topic', 'online') + self.hass.block_till_done() + + state = self.hass.states.get('light.test') + self.assertNotEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability-topic', 'offline') + self.hass.block_till_done() + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + def test_custom_availability_payload(self): """Test availability by custom payload with defined topic.""" self.assertTrue(setup_component(self.hass, light.DOMAIN, { diff --git a/tests/components/light/test_mqtt_template.py b/tests/components/light/test_mqtt_template.py index fddb75880cc..be1f119fc14 100644 --- a/tests/components/light/test_mqtt_template.py +++ b/tests/components/light/test_mqtt_template.py @@ -465,6 +465,34 @@ class TestLightMQTTTemplate(unittest.TestCase): state = self.hass.states.get('light.test') self.assertEqual('rainbow', state.attributes.get('effect')) + def test_default_availability_payload(self): + """Test availability by default payload with defined topic.""" + self.assertTrue(setup_component(self.hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_template', + 'name': 'test', + 'command_topic': 'test_light_rgb/set', + 'command_on_template': 'on,{{ transition }}', + 'command_off_template': 'off,{{ transition|d }}', + 'availability_topic': 'availability-topic' + } + })) + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability-topic', 'online') + self.hass.block_till_done() + + state = self.hass.states.get('light.test') + self.assertNotEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability-topic', 'offline') + self.hass.block_till_done() + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + def test_custom_availability_payload(self): """Test availability by custom payload with defined topic.""" self.assertTrue(setup_component(self.hass, light.DOMAIN, { diff --git a/tests/components/lock/test_mqtt.py b/tests/components/lock/test_mqtt.py index 667908e13fa..0f4df75d1a2 100644 --- a/tests/components/lock/test_mqtt.py +++ b/tests/components/lock/test_mqtt.py @@ -112,6 +112,35 @@ class TestLockMQTT(unittest.TestCase): state = self.hass.states.get('lock.test') self.assertEqual(STATE_UNLOCKED, state.state) + def test_default_availability_payload(self): + """Test availability by default payload with defined topic.""" + self.assertTrue(setup_component(self.hass, lock.DOMAIN, { + lock.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'payload_lock': 'LOCK', + 'payload_unlock': 'UNLOCK', + 'availability_topic': 'availability-topic' + } + })) + + state = self.hass.states.get('lock.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability-topic', 'online') + self.hass.block_till_done() + + state = self.hass.states.get('lock.test') + self.assertNotEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability-topic', 'offline') + self.hass.block_till_done() + + state = self.hass.states.get('lock.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + def test_custom_availability_payload(self): """Test availability by custom payload with defined topic.""" self.assertTrue(setup_component(self.hass, lock.DOMAIN, { diff --git a/tests/components/sensor/test_mqtt.py b/tests/components/sensor/test_mqtt.py index d5cfad407d5..efcd44658c3 100644 --- a/tests/components/sensor/test_mqtt.py +++ b/tests/components/sensor/test_mqtt.py @@ -185,6 +185,32 @@ class TestSensorMQTT(unittest.TestCase): self.hass.block_till_done() self.assertEqual(2, len(events)) + def test_default_availability_payload(self): + """Test availability by default payload with defined topic.""" + self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { + sensor.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'test-topic', + 'availability_topic': 'availability-topic' + } + })) + + state = self.hass.states.get('sensor.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability-topic', 'online') + self.hass.block_till_done() + + state = self.hass.states.get('sensor.test') + self.assertNotEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability-topic', 'offline') + self.hass.block_till_done() + + state = self.hass.states.get('sensor.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + def test_custom_availability_payload(self): """Test availability by custom payload with defined topic.""" self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { diff --git a/tests/components/switch/test_mqtt.py b/tests/components/switch/test_mqtt.py index a3118f8ebf0..661f570e698 100644 --- a/tests/components/switch/test_mqtt.py +++ b/tests/components/switch/test_mqtt.py @@ -156,6 +156,48 @@ class TestSwitchMQTT(unittest.TestCase): state = self.hass.states.get('switch.test') self.assertEqual(STATE_ON, state.state) + def test_default_availability_payload(self): + """Test the availability payload.""" + assert setup_component(self.hass, switch.DOMAIN, { + switch.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'availability_topic': 'availability_topic', + 'payload_on': 1, + 'payload_off': 0 + } + }) + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability_topic', 'online') + self.hass.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_OFF, state.state) + self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + + fire_mqtt_message(self.hass, 'availability_topic', 'offline') + self.hass.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'state-topic', '1') + self.hass.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability_topic', 'online') + self.hass.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_ON, state.state) + def test_custom_availability_payload(self): """Test the availability payload.""" assert setup_component(self.hass, switch.DOMAIN, { diff --git a/tests/components/vacuum/test_mqtt.py b/tests/components/vacuum/test_mqtt.py index f81a5c849ec..8c3b5fa4eeb 100644 --- a/tests/components/vacuum/test_mqtt.py +++ b/tests/components/vacuum/test_mqtt.py @@ -199,6 +199,31 @@ class TestVacuumMQTT(unittest.TestCase): self.assertEqual(STATE_OFF, state.state) self.assertEqual("Stopped", state.attributes.get(ATTR_STATUS)) + def test_default_availability_payload(self): + """Test availability by default payload with defined topic.""" + self.default_config.update({ + 'availability_topic': 'availability-topic' + }) + + self.assertTrue(setup_component(self.hass, vacuum.DOMAIN, { + vacuum.DOMAIN: self.default_config, + })) + + state = self.hass.states.get('vacuum.mqtttest') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability-topic', 'online') + self.hass.block_till_done() + + state = self.hass.states.get('vacuum.mqtttest') + self.assertNotEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability-topic', 'offline') + self.hass.block_till_done() + + state = self.hass.states.get('vacuum.mqtttest') + self.assertEqual(STATE_UNAVAILABLE, state.state) + def test_custom_availability_payload(self): """Test availability by custom payload with defined topic.""" self.default_config.update({ From dd81af4cd5042f3f8b56695bb0761bec0ecb383f Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Sat, 20 Jan 2018 21:27:52 +0100 Subject: [PATCH 099/150] python-miio version bumped. Fixes all xiaomi_miio components. (Closes: #11768) (#11837) --- homeassistant/components/fan/xiaomi_miio.py | 2 +- homeassistant/components/light/xiaomi_miio.py | 2 +- homeassistant/components/switch/xiaomi_miio.py | 2 +- homeassistant/components/vacuum/xiaomi_miio.py | 2 +- requirements_all.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/fan/xiaomi_miio.py b/homeassistant/components/fan/xiaomi_miio.py index 694a68d7f8e..910e33627a6 100644 --- a/homeassistant/components/fan/xiaomi_miio.py +++ b/homeassistant/components/fan/xiaomi_miio.py @@ -29,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, }) -REQUIREMENTS = ['python-miio==0.3.3'] +REQUIREMENTS = ['python-miio==0.3.4'] ATTR_TEMPERATURE = 'temperature' ATTR_HUMIDITY = 'humidity' diff --git a/homeassistant/components/light/xiaomi_miio.py b/homeassistant/components/light/xiaomi_miio.py index cd3ea9f0f39..43c8860e77b 100644 --- a/homeassistant/components/light/xiaomi_miio.py +++ b/homeassistant/components/light/xiaomi_miio.py @@ -29,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, }) -REQUIREMENTS = ['python-miio==0.3.3'] +REQUIREMENTS = ['python-miio==0.3.4'] # The light does not accept cct values < 1 CCT_MIN = 1 diff --git a/homeassistant/components/switch/xiaomi_miio.py b/homeassistant/components/switch/xiaomi_miio.py index 49a400f4a23..f2fdf3177aa 100644 --- a/homeassistant/components/switch/xiaomi_miio.py +++ b/homeassistant/components/switch/xiaomi_miio.py @@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, }) -REQUIREMENTS = ['python-miio==0.3.3'] +REQUIREMENTS = ['python-miio==0.3.4'] ATTR_POWER = 'power' ATTR_TEMPERATURE = 'temperature' diff --git a/homeassistant/components/vacuum/xiaomi_miio.py b/homeassistant/components/vacuum/xiaomi_miio.py index 294d4db9900..06690a0909f 100644 --- a/homeassistant/components/vacuum/xiaomi_miio.py +++ b/homeassistant/components/vacuum/xiaomi_miio.py @@ -19,7 +19,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_TOKEN, STATE_OFF, STATE_ON) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-miio==0.3.3'] +REQUIREMENTS = ['python-miio==0.3.4'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 23a641a0be0..c88f87528ce 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -899,7 +899,7 @@ python-juicenet==0.0.5 # homeassistant.components.light.xiaomi_miio # homeassistant.components.switch.xiaomi_miio # homeassistant.components.vacuum.xiaomi_miio -python-miio==0.3.3 +python-miio==0.3.4 # homeassistant.components.media_player.mpd python-mpd2==0.5.5 From 8c78a210efd627f4ef24cc6236734f37b3a73e16 Mon Sep 17 00:00:00 2001 From: Sean Dague Date: Sat, 20 Jan 2018 16:51:59 -0500 Subject: [PATCH 100/150] Add waterfurnace platform (#11732) Add waterfurnace platform This adds support for waterfurnace geothermal systems. This is implemented as a component as there will eventually be some active control elements. This is not done as a climate platform because geothermal systems work best when set at a constant temperature as they are tuned to keep within 0.5 degrees F of a setpoint, and large temperature shifts are slow and expensive. This is done in the Data + Sensors model, with the Data component having a regular update thread. That thread needs to call the read() function at least every 30 seconds otherwise the underlying websocket is closed by the server. --- .coveragerc | 3 + .../components/sensor/waterfurnace.py | 114 +++++++++++++++ homeassistant/components/waterfurnace.py | 136 ++++++++++++++++++ requirements_all.txt | 3 + 4 files changed, 256 insertions(+) create mode 100644 homeassistant/components/sensor/waterfurnace.py create mode 100644 homeassistant/components/waterfurnace.py diff --git a/.coveragerc b/.coveragerc index e4d5bca95d3..ef421af6875 100644 --- a/.coveragerc +++ b/.coveragerc @@ -247,6 +247,9 @@ omit = homeassistant/components/volvooncall.py homeassistant/components/*/volvooncall.py + homeassistant/components/waterfurnace.py + homeassistant/components/*/waterfurnace.py + homeassistant/components/*/webostv.py homeassistant/components/wemo.py diff --git a/homeassistant/components/sensor/waterfurnace.py b/homeassistant/components/sensor/waterfurnace.py new file mode 100644 index 00000000000..7d8c71f8d51 --- /dev/null +++ b/homeassistant/components/sensor/waterfurnace.py @@ -0,0 +1,114 @@ +""" +Support for Waterfurnace. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.waterfurnace/ +""" +import asyncio + +from homeassistant.components.sensor import ENTITY_ID_FORMAT +from homeassistant.components.waterfurnace import ( + DOMAIN as WF_DOMAIN, UPDATE_TOPIC +) +from homeassistant.const import TEMP_FAHRENHEIT +from homeassistant.core import callback +from homeassistant.helpers.entity import Entity +from homeassistant.util import slugify + + +class WFSensorConfig(object): + """Water Furnace Sensor configuration.""" + + def __init__(self, friendly_name, field, icon="mdi:guage", + unit_of_measurement=None): + """Initialize configuration.""" + self.friendly_name = friendly_name + self.field = field + self.icon = icon + self.unit_of_measurement = unit_of_measurement + + +SENSORS = [ + WFSensorConfig("Furnace Mode", "mode"), + WFSensorConfig("Total Power", "totalunitpower", "mdi:flash", "W"), + WFSensorConfig("Active Setpoint", "tstatactivesetpoint", + "mdi:thermometer", TEMP_FAHRENHEIT), + WFSensorConfig("Leaving Air", "leavingairtemp", + "mdi:thermometer", TEMP_FAHRENHEIT), + WFSensorConfig("Room Temp", "tstatroomtemp", + "mdi:thermometer", TEMP_FAHRENHEIT), + WFSensorConfig("Loop Temp", "enteringwatertemp", + "mdi:thermometer", TEMP_FAHRENHEIT), + WFSensorConfig("Humidity Set Point", "tstathumidsetpoint", + "mdi:water-percent", "%"), + WFSensorConfig("Humidity", "tstatrelativehumidity", + "mdi:water-percent", "%"), +] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Waterfurnace sensor.""" + if discovery_info is None: + return + + sensors = [] + client = hass.data[WF_DOMAIN] + for sconfig in SENSORS: + sensors.append(WaterFurnaceSensor(client, sconfig)) + + add_devices(sensors) + + +class WaterFurnaceSensor(Entity): + """Implementing the Waterfurnace sensor.""" + + def __init__(self, client, config): + """Initialize the sensor.""" + self.client = client + self._name = config.friendly_name + self._attr = config.field + self._state = None + self._icon = config.icon + self._unit_of_measurement = config.unit_of_measurement + + # This ensures that the sensors are isolated per waterfurnace unit + self.entity_id = ENTITY_ID_FORMAT.format( + 'wf_{}_{}'.format(slugify(self.client.unit), slugify(self._attr))) + + @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 icon(self): + """Return icon.""" + return self._icon + + @property + def unit_of_measurement(self): + """Return the units of measurement.""" + return self._unit_of_measurement + + @property + def should_poll(self): + """Return the polling state.""" + return False + + @asyncio.coroutine + def async_added_to_hass(self): + """Register callbacks.""" + self.hass.helpers.dispatcher.async_dispatcher_connect( + UPDATE_TOPIC, self.async_update_callback) + + @callback + def async_update_callback(self): + """Update state.""" + if self.client.data is not None: + self._state = getattr(self.client.data, self._attr, None) + self.async_schedule_update_ha_state() diff --git a/homeassistant/components/waterfurnace.py b/homeassistant/components/waterfurnace.py new file mode 100644 index 00000000000..18e6cff020c --- /dev/null +++ b/homeassistant/components/waterfurnace.py @@ -0,0 +1,136 @@ +""" +Support for Waterfurnace component. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/waterfurnace/ +""" +from datetime import timedelta +import logging +import time +import threading + +import voluptuous as vol + +from homeassistant.const import ( + CONF_USERNAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP +) +from homeassistant.core import callback +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import discovery + +REQUIREMENTS = ["waterfurnace==0.2.0"] + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = "waterfurnace" +UPDATE_TOPIC = DOMAIN + "_update" +CONF_UNIT = "unit" +SCAN_INTERVAL = timedelta(seconds=10) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_UNIT): cv.string, + }), +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, base_config): + """Setup waterfurnace platform.""" + import waterfurnace.waterfurnace as wf + config = base_config.get(DOMAIN) + + username = config.get(CONF_USERNAME) + password = config.get(CONF_PASSWORD) + unit = config.get(CONF_UNIT) + + wfconn = wf.WaterFurnace(username, password, unit) + # NOTE(sdague): login will throw an exception if this doesn't + # work, which will abort the setup. + try: + wfconn.login() + except wf.WFCredentialError: + _LOGGER.error("Invalid credentials for waterfurnace login.") + return False + + hass.data[DOMAIN] = WaterFurnaceData(hass, wfconn) + hass.data[DOMAIN].start() + + discovery.load_platform(hass, 'sensor', DOMAIN, {}, config) + return True + + +class WaterFurnaceData(threading.Thread): + """WaterFurnace Data collector. + + This is implemented as a dedicated thread polling a websocket in a + tight loop. The websocket will shut itself from the server side if + a packet is not sent at least every 30 seconds. The reading is + cheap, the login is less cheap, so keeping this open and polling + on a very regular cadence is actually the least io intensive thing + to do. + """ + + def __init__(self, hass, client): + """Initialize the data object.""" + super().__init__() + self.hass = hass + self.client = client + self.unit = client.unit + self.data = None + self._shutdown = False + + def run(self): + """Thread run loop.""" + @callback + def register(): + """Connect to hass for shutdown.""" + def shutdown(event): + """Shutdown the thread.""" + _LOGGER.debug("Signaled to shutdown.") + self._shutdown = True + self.join() + + self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown) + + self.hass.add_job(register) + + # This does a tight loop in sending read calls to the + # websocket. That's a blocking call, which returns pretty + # quickly (1 second). It's important that we do this + # frequently though, because if we don't call the websocket at + # least every 30 seconds the server side closes the + # connection. + while True: + if self._shutdown: + _LOGGER.debug("Graceful shutdown") + return + + try: + self.data = self.client.read() + + except ConnectionError: + # attempt to log back in if there was a session expiration. + try: + self.client.login() + except Exception: # pylint: disable=broad-except + # nested exception handling, something really bad + # happened during the login, which means we're not + # in a recoverable state. Stop the thread so we + # don't do just keep poking at the service. + _LOGGER.error( + "Failed to refresh login credentials. Thread stopped.") + return + else: + _LOGGER.error( + "Lost our connection to websocket, trying again") + time.sleep(SCAN_INTERVAL.seconds) + + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Error updating waterfurnace data.") + time.sleep(SCAN_INTERVAL.seconds) + + else: + self.hass.helpers.dispatcher.dispatcher_send(UPDATE_TOPIC) + time.sleep(SCAN_INTERVAL.seconds) diff --git a/requirements_all.txt b/requirements_all.txt index c88f87528ce..1c1f46db3c9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1195,6 +1195,9 @@ waqiasync==1.0.0 # homeassistant.components.cloud warrant==0.6.1 +# homeassistant.components.waterfurnace +waterfurnace==0.2.0 + # homeassistant.components.media_player.gpmdp websocket-client==0.37.0 From 0100f87ff27d51839259611f4f76b867e1c3f3af Mon Sep 17 00:00:00 2001 From: Frantz Date: Sun, 21 Jan 2018 07:08:42 +0200 Subject: [PATCH 101/150] (Re)Enable Daikin autodiscovery (#11842) --- homeassistant/components/discovery.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 9ceb82d5c69..980ac7d661c 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -53,6 +53,7 @@ SERVICE_HANDLERS = { SERVICE_TELLDUSLIVE: ('tellduslive', None), SERVICE_HUE: ('hue', None), SERVICE_DECONZ: ('deconz', None), + SERVICE_DAIKIN: ('daikin', None), 'google_cast': ('media_player', 'cast'), 'panasonic_viera': ('media_player', 'panasonic_viera'), 'plex_mediaserver': ('media_player', 'plex'), From 47e31dc9ee4a7d5bbc38b06d4436c3ae20328d6f Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 21 Jan 2018 07:35:38 +0100 Subject: [PATCH 102/150] Fixes for PEP257 (#11810) * Fixes for PEP257 * More updates --- homeassistant/components/ads/__init__.py | 56 ++++----- .../components/alarm_control_panel/abode.py | 18 +-- .../alarm_control_panel/alarmdecoder.py | 8 +- .../alarm_control_panel/alarmdotcom.py | 23 ++-- .../alarm_control_panel/concord232.py | 8 +- .../components/alarm_control_panel/egardia.py | 26 ++--- .../components/alarm_control_panel/ialarm.py | 10 +- .../components/alarm_control_panel/manual.py | 26 +++-- .../alarm_control_panel/manual_mqtt.py | 27 +++-- .../components/alarm_control_panel/nx584.py | 12 +- .../alarm_control_panel/satel_integra.py | 17 ++- .../alarm_control_panel/simplisafe.py | 17 +-- .../components/alarm_control_panel/spc.py | 19 +-- .../alarm_control_panel/verisure.py | 4 +- .../components/alarm_control_panel/wink.py | 11 +- homeassistant/components/alexa/__init__.py | 8 +- .../components/alexa/flash_briefings.py | 15 ++- homeassistant/components/alexa/intent.py | 27 +++-- homeassistant/components/alexa/smart_home.py | 19 ++- homeassistant/components/apple_tv.py | 21 ++-- homeassistant/components/axis.py | 71 +++++------ homeassistant/components/binary_sensor/ads.py | 25 ++-- .../components/binary_sensor/axis.py | 15 ++- .../components/binary_sensor/concord232.py | 2 +- homeassistant/components/binary_sensor/knx.py | 25 ++-- .../components/binary_sensor/mysensors.py | 11 +- .../components/binary_sensor/mystrom.py | 4 +- .../components/binary_sensor/raspihats.py | 47 +++----- .../components/binary_sensor/rfxtrx.py | 92 +++++++-------- .../components/binary_sensor/rpi_pfio.py | 29 +++-- homeassistant/components/binary_sensor/spc.py | 49 ++++---- .../components/binary_sensor/tapsaff.py | 10 +- .../components/binary_sensor/tesla.py | 2 +- .../components/binary_sensor/verisure.py | 8 +- .../components/binary_sensor/vultr.py | 6 +- .../components/binary_sensor/wink.py | 28 ++--- homeassistant/components/calendar/caldav.py | 45 +++---- homeassistant/components/calendar/todoist.py | 40 +++---- homeassistant/components/camera/axis.py | 33 +++--- homeassistant/components/camera/blink.py | 12 +- homeassistant/components/climate/daikin.py | 33 +++--- homeassistant/components/climate/econet.py | 34 +++--- homeassistant/components/climate/knx.py | 47 +++----- homeassistant/components/climate/mysensors.py | 19 +-- homeassistant/components/climate/tesla.py | 12 +- homeassistant/components/climate/toon.py | 20 ++-- homeassistant/components/climate/wink.py | 22 ++-- homeassistant/components/cloud/__init__.py | 33 +++--- homeassistant/components/cloud/auth_api.py | 7 +- homeassistant/components/cloud/http_api.py | 6 +- homeassistant/components/cloud/iot.py | 33 +++--- homeassistant/components/comfoconnect.py | 10 +- homeassistant/components/cover/knx.py | 27 ++--- homeassistant/components/cover/mysensors.py | 6 +- homeassistant/components/cover/rfxtrx.py | 8 +- homeassistant/components/cover/wink.py | 4 +- homeassistant/components/datadog.py | 34 ++---- homeassistant/components/deconz/__init__.py | 9 +- .../components/device_tracker/automatic.py | 55 +++++---- .../components/device_tracker/ubus.py | 18 +-- .../components/device_tracker/upc_connect.py | 2 +- homeassistant/components/fan/dyson.py | 110 ++++++++++-------- homeassistant/components/fan/wink.py | 18 ++- homeassistant/components/frontend/__init__.py | 4 +- homeassistant/components/hassio.py | 69 +++++------ homeassistant/components/influxdb.py | 13 ++- homeassistant/components/insteon_local.py | 19 ++- homeassistant/components/kira.py | 36 +++--- homeassistant/components/knx.py | 42 +++---- homeassistant/components/light/__init__.py | 22 ++-- homeassistant/components/light/greenwave.py | 15 ++- homeassistant/components/light/hue.py | 21 ++-- homeassistant/components/light/iglo.py | 9 +- homeassistant/components/light/knx.py | 20 ++-- homeassistant/components/light/lifx.py | 27 +++-- homeassistant/components/light/mysensors.py | 2 +- .../components/light/osramlightify.py | 6 +- homeassistant/components/light/wink.py | 4 +- homeassistant/components/lock/nuki.py | 14 ++- homeassistant/components/lock/tesla.py | 7 +- homeassistant/components/lock/wink.py | 26 +++-- .../components/media_player/bluesound.py | 31 ++--- .../components/media_player/monoprice.py | 42 +++---- homeassistant/components/media_player/mpd.py | 20 ++-- .../components/media_player/nadtcp.py | 15 ++- .../components/media_player/snapcast.py | 24 ++-- .../components/media_player/sonos.py | 23 ++-- .../components/media_player/vizio.py | 48 +++----- .../components/media_player/webostv.py | 33 +++--- homeassistant/components/mychevy.py | 18 ++- homeassistant/components/mysensors.py | 47 ++++---- homeassistant/components/notify/pushbullet.py | 2 +- homeassistant/components/python_script.py | 6 +- homeassistant/components/raincloud.py | 19 ++- homeassistant/components/raspihats.py | 30 ++--- .../components/remember_the_milk/__init__.py | 99 +++++++--------- homeassistant/components/remote/harmony.py | 20 ++-- homeassistant/components/remote/kira.py | 6 +- homeassistant/components/scene/wink.py | 7 +- homeassistant/components/sensor/abode.py | 4 +- homeassistant/components/sensor/ads.py | 31 +++-- homeassistant/components/sensor/deconz.py | 4 +- homeassistant/components/sensor/dyson.py | 22 ++-- homeassistant/components/sensor/imap.py | 10 +- homeassistant/components/sensor/kira.py | 31 +++-- homeassistant/components/sensor/knx.py | 19 ++- homeassistant/components/sensor/lacrosse.py | 14 +-- homeassistant/components/sensor/london_air.py | 13 ++- homeassistant/components/sensor/mopar.py | 52 ++++----- .../sensor/nederlandse_spoorwegen.py | 15 ++- .../components/sensor/openhardwaremonitor.py | 26 ++--- homeassistant/components/sensor/rfxtrx.py | 23 ++-- homeassistant/components/sensor/tesla.py | 9 +- homeassistant/components/sensor/usps.py | 7 +- .../components/sensor/viaggiatreno.py | 15 +-- homeassistant/components/sensor/vultr.py | 40 +++---- homeassistant/components/sensor/whois.py | 10 +- homeassistant/components/sensor/wink.py | 7 +- homeassistant/components/spc.py | 80 +++++++------ homeassistant/components/switch/ads.py | 25 ++-- homeassistant/components/switch/broadlink.py | 29 +++-- homeassistant/components/switch/doorbird.py | 13 ++- homeassistant/components/switch/knx.py | 21 ++-- homeassistant/components/switch/rachio.py | 83 ++++++------- homeassistant/components/switch/raspihats.py | 87 +++++--------- homeassistant/components/switch/rpi_pfio.py | 13 ++- homeassistant/components/switch/telnet.py | 20 ++-- homeassistant/components/switch/tesla.py | 9 +- homeassistant/components/switch/toon.py | 12 +- homeassistant/components/switch/vultr.py | 2 +- homeassistant/components/switch/wink.py | 4 +- homeassistant/components/tesla.py | 14 +-- homeassistant/components/vacuum/dyson.py | 48 ++++---- homeassistant/components/vacuum/mqtt.py | 2 +- homeassistant/components/vacuum/neato.py | 8 +- homeassistant/components/weather/yweather.py | 11 +- homeassistant/components/wink/__init__.py | 76 ++++++------ homeassistant/components/xiaomi_aqara.py | 47 +++++--- homeassistant/helpers/intent.py | 2 +- homeassistant/helpers/template.py | 14 ++- homeassistant/loader.py | 10 +- homeassistant/scripts/benchmark/__init__.py | 10 +- tests/components/sensor/test_vultr.py | 14 +-- 143 files changed, 1584 insertions(+), 1761 deletions(-) diff --git a/homeassistant/components/ads/__init__.py b/homeassistant/components/ads/__init__.py index 20a4489da90..d603843f51f 100644 --- a/homeassistant/components/ads/__init__.py +++ b/homeassistant/components/ads/__init__.py @@ -1,9 +1,8 @@ """ -ADS Component. +Support for Automation Device Specification (ADS). For more details about this component, please refer to the documentation. https://home-assistant.io/components/ads/ - """ import threading import struct @@ -29,7 +28,6 @@ ADSTYPE_BOOL = 'bool' DOMAIN = 'ads' -# config variable names CONF_ADS_VAR = 'adsvar' CONF_ADS_VAR_BRIGHTNESS = 'adsvar_brightness' CONF_ADS_TYPE = 'adstype' @@ -47,10 +45,10 @@ CONFIG_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) SCHEMA_SERVICE_WRITE_DATA_BY_NAME = vol.Schema({ + vol.Required(CONF_ADS_TYPE): + vol.In([ADSTYPE_INT, ADSTYPE_UINT, ADSTYPE_BYTE]), + vol.Required(CONF_ADS_VALUE): cv.match_all, vol.Required(CONF_ADS_VAR): cv.string, - vol.Required(CONF_ADS_TYPE): vol.In([ADSTYPE_INT, ADSTYPE_UINT, - ADSTYPE_BYTE]), - vol.Required(CONF_ADS_VALUE): cv.match_all }) @@ -59,15 +57,12 @@ def setup(hass, config): import pyads conf = config[DOMAIN] - # get ads connection parameters from config net_id = conf.get(CONF_DEVICE) ip_address = conf.get(CONF_IP_ADDRESS) port = conf.get(CONF_PORT) - # create a new ads connection client = pyads.Connection(net_id, port, ip_address) - # add some constants to AdsHub AdsHub.ADS_TYPEMAP = { ADSTYPE_BOOL: pyads.PLCTYPE_BOOL, ADSTYPE_BYTE: pyads.PLCTYPE_BYTE, @@ -81,16 +76,13 @@ def setup(hass, config): AdsHub.PLCTYPE_UINT = pyads.PLCTYPE_UINT AdsHub.ADSError = pyads.ADSError - # connect to ads client and try to connect try: ads = AdsHub(client) except pyads.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, port=%s)", net_id, port) return False - # add ads hub to hass data collection, listen to shutdown hass.data[DATA_ADS] = ads hass.bus.listen(EVENT_HOMEASSISTANT_STOP, ads.shutdown) @@ -107,43 +99,41 @@ def setup(hass, config): hass.services.register( DOMAIN, SERVICE_WRITE_DATA_BY_NAME, handle_write_data_by_name, - schema=SCHEMA_SERVICE_WRITE_DATA_BY_NAME - ) + schema=SCHEMA_SERVICE_WRITE_DATA_BY_NAME) return True -# tuple to hold data needed for notification +# Tuple to hold data needed for notification NotificationItem = namedtuple( 'NotificationItem', 'hnotify huser name plc_datatype callback' ) -class AdsHub: - """Representation of a PyADS connection.""" +class AdsHub(object): + """Representation of an ADS connection.""" def __init__(self, ads_client): - """Initialize the ADS Hub.""" + """Initialize the ADS hub.""" self._client = ads_client self._client.open() - # all ADS devices are registered here + # All ADS devices are registered here self._devices = [] self._notification_items = {} self._lock = threading.Lock() def shutdown(self, *args, **kwargs): """Shutdown ADS connection.""" - _LOGGER.debug('Shutting down ADS') + _LOGGER.debug("Shutting down ADS") for notification_item in self._notification_items.values(): self._client.del_device_notification( notification_item.hnotify, notification_item.huser ) _LOGGER.debug( - 'Deleting device notification %d, %d', - notification_item.hnotify, notification_item.huser - ) + "Deleting device notification %d, %d", + notification_item.hnotify, notification_item.huser) self._client.close() def register_device(self, device): @@ -167,33 +157,30 @@ class AdsHub: with self._lock: hnotify, huser = self._client.add_device_notification( - name, attr, self._device_notification_callback - ) + name, attr, self._device_notification_callback) hnotify = int(hnotify) _LOGGER.debug( - 'Added Device Notification %d for variable %s', hnotify, name - ) + "Added device notification %d for variable %s", hnotify, name) self._notification_items[hnotify] = NotificationItem( - hnotify, huser, name, plc_datatype, callback - ) + hnotify, huser, name, plc_datatype, callback) def _device_notification_callback(self, addr, notification, huser): """Handle device notifications.""" contents = notification.contents hnotify = int(contents.hNotification) - _LOGGER.debug('Received Notification %d', hnotify) + _LOGGER.debug("Received notification %d", hnotify) data = contents.data try: notification_item = self._notification_items[hnotify] except KeyError: - _LOGGER.debug('Unknown Device Notification handle: %d', hnotify) + _LOGGER.debug("Unknown device notification handle: %d", hnotify) return - # parse data to desired datatype + # Parse data to desired datatype if notification_item.plc_datatype == self.PLCTYPE_BOOL: value = bool(struct.unpack(' dt_util.utcnow() @property def code_format(self): - """One or more characters.""" + """Return one or more characters.""" return None if self._code is None else '.+' def alarm_disarm(self, code=None): @@ -250,6 +255,7 @@ class ManualAlarm(alarm.AlarmControlPanel): self._update_state(STATE_ALARM_TRIGGERED) def _update_state(self, state): + """Update the state.""" if self._state == state: return diff --git a/homeassistant/components/alarm_control_panel/manual_mqtt.py b/homeassistant/components/alarm_control_panel/manual_mqtt.py index 9e388806e73..ef12cbe365f 100644 --- a/homeassistant/components/alarm_control_panel/manual_mqtt.py +++ b/homeassistant/components/alarm_control_panel/manual_mqtt.py @@ -26,6 +26,8 @@ from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_point_in_time +_LOGGER = logging.getLogger(__name__) + CONF_CODE_TEMPLATE = 'code_template' CONF_PAYLOAD_DISARM = 'payload_disarm' @@ -58,6 +60,7 @@ ATTR_POST_PENDING_STATE = 'post_pending_state' def _state_validator(config): + """Validate the state.""" config = copy.deepcopy(config) for state in SUPPORTED_PRETRIGGER_STATES: if CONF_DELAY_TIME not in config[state]: @@ -72,6 +75,7 @@ def _state_validator(config): def _state_schema(state): + """Validate the state.""" schema = {} if state in SUPPORTED_PRETRIGGER_STATES: schema[vol.Optional(CONF_DELAY_TIME)] = vol.All( @@ -117,8 +121,6 @@ PLATFORM_SCHEMA = vol.Schema(vol.All(mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({ vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string, }), _state_validator)) -_LOGGER = logging.getLogger(__name__) - def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the manual MQTT alarm platform.""" @@ -150,11 +152,10 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel): A trigger_time of zero disables the alarm_trigger service. """ - def __init__(self, hass, name, code, code_template, - disarm_after_trigger, - state_topic, command_topic, qos, - payload_disarm, payload_arm_home, payload_arm_away, - payload_arm_night, config): + def __init__(self, hass, name, code, code_template, disarm_after_trigger, + state_topic, command_topic, qos, payload_disarm, + payload_arm_home, payload_arm_away, payload_arm_night, + config): """Init the manual MQTT alarm panel.""" self._state = STATE_ALARM_DISARMED self._hass = hass @@ -219,23 +220,26 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel): @property def _active_state(self): + """Get the current state.""" if self.state == STATE_ALARM_PENDING: return self._previous_state else: return self._state def _pending_time(self, state): + """Get the pending time.""" pending_time = self._pending_time_by_state[state] if state == STATE_ALARM_TRIGGERED: pending_time += self._delay_time_by_state[self._previous_state] return pending_time def _within_pending_time(self, state): + """Get if the action is in the pending time window.""" return self._state_ts + self._pending_time(state) > dt_util.utcnow() @property def code_format(self): - """One or more characters.""" + """Return one or more characters.""" return None if self._code is None else '.+' def alarm_disarm(self, code=None): @@ -280,6 +284,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel): self._update_state(STATE_ALARM_TRIGGERED) def _update_state(self, state): + """Update the state.""" if self._state == state: return @@ -329,7 +334,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel): return state_attr def async_added_to_hass(self): - """Subscribe mqtt events. + """Subscribe to MQTT events. This method must be run in the event loop and returns a coroutine. """ @@ -358,5 +363,5 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel): @asyncio.coroutine def _async_state_changed_listener(self, entity_id, old_state, new_state): """Publish state change to MQTT.""" - mqtt.async_publish(self.hass, self._state_topic, new_state.state, - self._qos, True) + mqtt.async_publish( + self.hass, self._state_topic, new_state.state, self._qos, True) diff --git a/homeassistant/components/alarm_control_panel/nx584.py b/homeassistant/components/alarm_control_panel/nx584.py index 81a8b02cc64..ceb79c1dc7b 100644 --- a/homeassistant/components/alarm_control_panel/nx584.py +++ b/homeassistant/components/alarm_control_panel/nx584.py @@ -12,8 +12,8 @@ import voluptuous as vol import homeassistant.components.alarm_control_panel as alarm from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA from homeassistant.const import ( - STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, - STATE_UNKNOWN, CONF_NAME, CONF_HOST, CONF_PORT) + CONF_HOST, CONF_NAME, CONF_PORT, STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['pynx584==0.4'] @@ -25,14 +25,14 @@ DEFAULT_NAME = 'NX584' DEFAULT_PORT = 5007 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, }) def setup_platform(hass, config, add_devices, discovery_info=None): - """Set up the nx584 platform.""" + """Set up the NX584 platform.""" name = config.get(CONF_NAME) host = config.get(CONF_HOST) port = config.get(CONF_PORT) @@ -88,7 +88,7 @@ class NX584Alarm(alarm.AlarmControlPanel): self._state = STATE_UNKNOWN zones = [] except IndexError: - _LOGGER.error("nx584 reports no partitions") + _LOGGER.error("NX584 reports no partitions") self._state = STATE_UNKNOWN zones = [] diff --git a/homeassistant/components/alarm_control_panel/satel_integra.py b/homeassistant/components/alarm_control_panel/satel_integra.py index 6115311f873..964047f91e9 100644 --- a/homeassistant/components/alarm_control_panel/satel_integra.py +++ b/homeassistant/components/alarm_control_panel/satel_integra.py @@ -8,9 +8,8 @@ import asyncio import logging import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.satel_integra import (CONF_ARM_HOME_MODE, - DATA_SATEL, - SIGNAL_PANEL_MESSAGE) +from homeassistant.components.satel_integra import ( + CONF_ARM_HOME_MODE, DATA_SATEL, SIGNAL_PANEL_MESSAGE) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -21,12 +20,12 @@ DEPENDENCIES = ['satel_integra'] @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Set up for AlarmDecoder alarm panels.""" + """Set up for Satel Integra alarm panels.""" if not discovery_info: return - device = SatelIntegraAlarmPanel("Alarm Panel", - discovery_info.get(CONF_ARM_HOME_MODE)) + device = SatelIntegraAlarmPanel( + "Alarm Panel", discovery_info.get(CONF_ARM_HOME_MODE)) async_add_devices([device]) @@ -47,7 +46,7 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel): @callback def _message_callback(self, message): - + """Handle received messages.""" if message != self._state: self._state = message self.async_schedule_update_ha_state() @@ -90,5 +89,5 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel): def async_alarm_arm_home(self, code=None): """Send arm home command.""" if code: - yield from self.hass.data[DATA_SATEL].arm(code, - self._arm_home_mode) + yield from self.hass.data[DATA_SATEL].arm( + code, self._arm_home_mode) diff --git a/homeassistant/components/alarm_control_panel/simplisafe.py b/homeassistant/components/alarm_control_panel/simplisafe.py index 7f4e4dfa756..3b991c5b236 100644 --- a/homeassistant/components/alarm_control_panel/simplisafe.py +++ b/homeassistant/components/alarm_control_panel/simplisafe.py @@ -11,9 +11,9 @@ import voluptuous as vol import homeassistant.components.alarm_control_panel as alarm from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_PASSWORD, CONF_USERNAME, STATE_UNKNOWN, CONF_CODE, CONF_NAME, - STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, - EVENT_HOMEASSISTANT_STOP) + CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, + EVENT_HOMEASSISTANT_STOP, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, + STATE_ALARM_DISARMED, STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['simplisafe-python==1.0.5'] @@ -22,6 +22,7 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'SimpliSafe' DOMAIN = 'simplisafe' + NOTIFICATION_ID = 'simplisafe_notification' NOTIFICATION_TITLE = 'SimpliSafe Setup' @@ -65,7 +66,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class SimpliSafeAlarm(alarm.AlarmControlPanel): - """Representation a SimpliSafe alarm.""" + """Representation of a SimpliSafe alarm.""" def __init__(self, simplisafe, name, code): """Initialize the SimpliSafe alarm.""" @@ -82,7 +83,7 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel): @property def code_format(self): - """One or more characters if code is defined.""" + """Return one or more characters if code is defined.""" return None if self._code is None else '.+' @property @@ -103,12 +104,12 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel): def device_state_attributes(self): """Return the state attributes.""" return { - 'temperature': self.simplisafe.temperature(), + 'alarm': self.simplisafe.alarm(), 'co': self.simplisafe.carbon_monoxide(), 'fire': self.simplisafe.fire(), - 'alarm': self.simplisafe.alarm(), + 'flood': self.simplisafe.flood(), 'last_event': self.simplisafe.last_event(), - 'flood': self.simplisafe.flood() + 'temperature': self.simplisafe.temperature(), } def update(self): diff --git a/homeassistant/components/alarm_control_panel/spc.py b/homeassistant/components/alarm_control_panel/spc.py index 4d9c72df2f1..5d5b2284bab 100644 --- a/homeassistant/components/alarm_control_panel/spc.py +++ b/homeassistant/components/alarm_control_panel/spc.py @@ -9,26 +9,27 @@ import logging import homeassistant.components.alarm_control_panel as alarm from homeassistant.components.spc import ( - SpcWebGateway, ATTR_DISCOVER_AREAS, DATA_API, DATA_REGISTRY) + ATTR_DISCOVER_AREAS, DATA_API, DATA_REGISTRY, SpcWebGateway) from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN) - _LOGGER = logging.getLogger(__name__) -SPC_AREA_MODE_TO_STATE = {'0': STATE_ALARM_DISARMED, - '1': STATE_ALARM_ARMED_HOME, - '3': STATE_ALARM_ARMED_AWAY} +SPC_AREA_MODE_TO_STATE = { + '0': STATE_ALARM_DISARMED, + '1': STATE_ALARM_ARMED_HOME, + '3': STATE_ALARM_ARMED_AWAY, +} def _get_alarm_state(spc_mode): + """Get the alarm state.""" return SPC_AREA_MODE_TO_STATE.get(spc_mode, STATE_UNKNOWN) @asyncio.coroutine -def async_setup_platform(hass, config, async_add_devices, - discovery_info=None): +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the SPC alarm control panel platform.""" if (discovery_info is None or discovery_info[ATTR_DISCOVER_AREAS] is None): @@ -42,7 +43,7 @@ def async_setup_platform(hass, config, async_add_devices, class SpcAlarm(alarm.AlarmControlPanel): - """Represents the SPC alarm panel.""" + """Representation of the SPC alarm panel.""" def __init__(self, api, area): """Initialize the SPC alarm panel.""" @@ -57,7 +58,7 @@ class SpcAlarm(alarm.AlarmControlPanel): @asyncio.coroutine def async_added_to_hass(self): - """Calbback for init handlers.""" + """Call for adding new entities.""" self.hass.data[DATA_REGISTRY].register_alarm_device( self._area_id, self) diff --git a/homeassistant/components/alarm_control_panel/verisure.py b/homeassistant/components/alarm_control_panel/verisure.py index 66764f58c26..74d63b1fb9c 100644 --- a/homeassistant/components/alarm_control_panel/verisure.py +++ b/homeassistant/components/alarm_control_panel/verisure.py @@ -8,8 +8,8 @@ import logging from time import sleep import homeassistant.components.alarm_control_panel as alarm +from homeassistant.components.verisure import CONF_ALARM, CONF_CODE_DIGITS from homeassistant.components.verisure import HUB as hub -from homeassistant.components.verisure import (CONF_ALARM, CONF_CODE_DIGITS) from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN) @@ -43,7 +43,7 @@ class VerisureAlarm(alarm.AlarmControlPanel): """Representation of a Verisure alarm status.""" def __init__(self): - """Initalize the Verisure alarm panel.""" + """Initialize the Verisure alarm panel.""" self._state = STATE_UNKNOWN self._digits = hub.config.get(CONF_CODE_DIGITS) self._changed_by = None diff --git a/homeassistant/components/alarm_control_panel/wink.py b/homeassistant/components/alarm_control_panel/wink.py index 8bc2539f772..771d157efe0 100644 --- a/homeassistant/components/alarm_control_panel/wink.py +++ b/homeassistant/components/alarm_control_panel/wink.py @@ -8,11 +8,10 @@ import asyncio import logging import homeassistant.components.alarm_control_panel as alarm -from homeassistant.const import (STATE_UNKNOWN, - STATE_ALARM_DISARMED, - STATE_ALARM_ARMED_HOME, - STATE_ALARM_ARMED_AWAY) -from homeassistant.components.wink import WinkDevice, DOMAIN +from homeassistant.components.wink import DOMAIN, WinkDevice +from homeassistant.const import ( + STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, + STATE_UNKNOWN) _LOGGER = logging.getLogger(__name__) @@ -41,7 +40,7 @@ class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel): @asyncio.coroutine def async_added_to_hass(self): - """Callback when entity is added to hass.""" + """Call when entity is added to hass.""" self.hass.data[DOMAIN]['entities']['alarm_control_panel'].append(self) @property diff --git a/homeassistant/components/alexa/__init__.py b/homeassistant/components/alexa/__init__.py index 65243aa83ce..a5ed4b59628 100644 --- a/homeassistant/components/alexa/__init__.py +++ b/homeassistant/components/alexa/__init__.py @@ -11,17 +11,15 @@ import voluptuous as vol from homeassistant.helpers import config_validation as cv -from .const import ( - DOMAIN, CONF_UID, CONF_TITLE, CONF_AUDIO, CONF_TEXT, CONF_DISPLAY_URL) from . import flash_briefings, intent +from .const import ( + CONF_AUDIO, CONF_DISPLAY_URL, CONF_TEXT, CONF_TITLE, CONF_UID, DOMAIN) _LOGGER = logging.getLogger(__name__) - -DEPENDENCIES = ['http'] - CONF_FLASH_BRIEFINGS = 'flash_briefings' +DEPENDENCIES = ['http'] CONFIG_SCHEMA = vol.Schema({ DOMAIN: { diff --git a/homeassistant/components/alexa/flash_briefings.py b/homeassistant/components/alexa/flash_briefings.py index ec7e3521c0a..02f47b05617 100644 --- a/homeassistant/components/alexa/flash_briefings.py +++ b/homeassistant/components/alexa/flash_briefings.py @@ -5,19 +5,18 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/alexa/ """ import copy -import logging from datetime import datetime +import logging import uuid +from homeassistant.components import http from homeassistant.core import callback from homeassistant.helpers import template -from homeassistant.components import http from .const import ( - CONF_UID, CONF_TITLE, CONF_AUDIO, CONF_TEXT, CONF_DISPLAY_URL, ATTR_UID, - ATTR_UPDATE_DATE, ATTR_TITLE_TEXT, ATTR_STREAM_URL, ATTR_MAIN_TEXT, - ATTR_REDIRECTION_URL, DATE_FORMAT) - + ATTR_MAIN_TEXT, ATTR_REDIRECTION_URL, ATTR_STREAM_URL, ATTR_TITLE_TEXT, + ATTR_UID, ATTR_UPDATE_DATE, CONF_AUDIO, CONF_DISPLAY_URL, CONF_TEXT, + CONF_TITLE, CONF_UID, DATE_FORMAT) _LOGGER = logging.getLogger(__name__) @@ -46,11 +45,11 @@ class AlexaFlashBriefingView(http.HomeAssistantView): @callback def get(self, request, briefing_id): """Handle Alexa Flash Briefing request.""" - _LOGGER.debug('Received Alexa flash briefing request for: %s', + _LOGGER.debug("Received Alexa flash briefing request for: %s", briefing_id) if self.flash_briefings.get(briefing_id) is None: - err = 'No configured Alexa flash briefing was found for: %s' + err = "No configured Alexa flash briefing was found for: %s" _LOGGER.error(err, briefing_id) return b'', 404 diff --git a/homeassistant/components/alexa/intent.py b/homeassistant/components/alexa/intent.py index 8283b563591..b6d406bd550 100644 --- a/homeassistant/components/alexa/intent.py +++ b/homeassistant/components/alexa/intent.py @@ -3,30 +3,31 @@ 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/ - """ import asyncio import enum import logging -from homeassistant.exceptions import HomeAssistantError -from homeassistant.core import callback -from homeassistant.helpers import intent from homeassistant.components import http +from homeassistant.core import callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import intent from homeassistant.util.decorator import Registry from .const import DOMAIN, SYN_RESOLUTION_MATCH -INTENTS_API_ENDPOINT = '/api/alexa' -HANDLERS = Registry() _LOGGER = logging.getLogger(__name__) +HANDLERS = Registry() + +INTENTS_API_ENDPOINT = '/api/alexa' + class SpeechType(enum.Enum): """The Alexa speech types.""" - plaintext = "PlainText" - ssml = "SSML" + plaintext = 'PlainText' + ssml = 'SSML' SPEECH_MAPPINGS = { @@ -38,8 +39,8 @@ SPEECH_MAPPINGS = { class CardType(enum.Enum): """The Alexa card types.""" - simple = "Simple" - link_account = "LinkAccount" + simple = 'Simple' + link_account = 'LinkAccount' @callback @@ -64,7 +65,7 @@ class AlexaIntentsView(http.HomeAssistantView): hass = request.app['hass'] message = yield from request.json() - _LOGGER.debug('Received Alexa request: %s', message) + _LOGGER.debug("Received Alexa request: %s", message) try: response = yield from async_handle_message(hass, message) @@ -81,7 +82,7 @@ class AlexaIntentsView(http.HomeAssistantView): "This intent is not yet configured within Home Assistant.")) except intent.InvalidSlotInfo as err: - _LOGGER.error('Received invalid slot data from Alexa: %s', err) + _LOGGER.error("Received invalid slot data from Alexa: %s", err) return self.json(intent_error_response( hass, message, "Invalid slot information received for this intent.")) @@ -109,6 +110,7 @@ def async_handle_message(hass, message): - intent.UnknownIntent - intent.InvalidSlotInfo - intent.IntentError + """ req = message.get('request') req_type = req['type'] @@ -138,6 +140,7 @@ def async_handle_intent(hass, message): - intent.UnknownIntent - intent.InvalidSlotInfo - intent.IntentError + """ req = message.get('request') alexa_intent_info = req.get('intent') diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index 3c14826037c..b5115025c08 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -4,20 +4,19 @@ import logging import math from uuid import uuid4 -import homeassistant.core as ha +from homeassistant.components import ( + alert, automation, cover, fan, group, input_boolean, light, lock, + media_player, scene, script, switch) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, SERVICE_LOCK, + ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, 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) -from homeassistant.components import ( - alert, automation, cover, fan, group, input_boolean, light, lock, - media_player, scene, script, switch) +import homeassistant.core as ha import homeassistant.util.color as color_util from homeassistant.util.decorator import Registry -HANDLERS = Registry() _LOGGER = logging.getLogger(__name__) API_DIRECTIVE = 'directive' @@ -28,8 +27,8 @@ API_PAYLOAD = 'payload' CONF_DESCRIPTION = 'description' CONF_DISPLAY_CATEGORIES = 'display_categories' -CONF_NAME = 'name' +HANDLERS = Registry() MAPPING_COMPONENT = { alert.DOMAIN: ['OTHER', ('Alexa.PowerController',), None], @@ -174,8 +173,8 @@ def async_api_discovery(hass, config, request): scene_fmt = '{} (Scene connected via Home Assistant)' description = scene_fmt.format(description) - display_categories = entity_conf.get(CONF_DISPLAY_CATEGORIES, - class_data[0]) + display_categories = entity_conf.get( + CONF_DISPLAY_CATEGORIES, class_data[0]) endpoint = { 'displayCategories': [display_categories], @@ -216,7 +215,7 @@ def async_api_discovery(hass, config, request): def extract_entity(funct): - """Decorator for extract entity object from request.""" + """Decorate for extract entity object from request.""" @asyncio.coroutine def async_api_entity_wrapper(hass, config, request): """Process a turn on request.""" diff --git a/homeassistant/components/apple_tv.py b/homeassistant/components/apple_tv.py index beacb3840ef..230b0ea8a1b 100644 --- a/homeassistant/components/apple_tv.py +++ b/homeassistant/components/apple_tv.py @@ -7,13 +7,14 @@ https://home-assistant.io/components/apple_tv/ import asyncio import logging +from typing import Sequence, TypeVar, Union + import voluptuous as vol -from typing import Union, TypeVar, Sequence -from homeassistant.const import (CONF_HOST, CONF_NAME, ATTR_ENTITY_ID) -from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers import discovery from homeassistant.components.discovery import SERVICE_APPLE_TV +from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME +from homeassistant.helpers import discovery +from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['pyatv==0.3.9'] @@ -59,9 +60,9 @@ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.All(ensure_list, [vol.Schema({ vol.Required(CONF_HOST): cv.string, vol.Required(CONF_LOGIN_ID): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_CREDENTIALS, default=None): cv.string, - vol.Optional(CONF_START_OFF, default=False): cv.boolean + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_START_OFF, default=False): cv.boolean, })]) }, extra=vol.ALLOW_EXTRA) @@ -140,7 +141,7 @@ def async_setup(hass, config): @asyncio.coroutine def async_service_handler(service): - """Handler for service calls.""" + """Handle service calls.""" entity_ids = service.data.get(ATTR_ENTITY_ID) if service.service == SERVICE_SCAN: @@ -167,7 +168,7 @@ def async_setup(hass, config): @asyncio.coroutine def atv_discovered(service, info): - """Setup an Apple TV that was auto discovered.""" + """Set up an Apple TV that was auto discovered.""" yield from _setup_atv(hass, { CONF_NAME: info['name'], CONF_HOST: info['host'], @@ -194,7 +195,7 @@ def async_setup(hass, config): @asyncio.coroutine def _setup_atv(hass, atv_config): - """Setup an Apple TV.""" + """Set up an Apple TV.""" import pyatv name = atv_config.get(CONF_NAME) host = atv_config.get(CONF_HOST) @@ -245,7 +246,7 @@ class AppleTVPowerManager: @property def turned_on(self): - """If device is on or off.""" + """Return true if device is on or off.""" return self._is_on def set_power_on(self, value): diff --git a/homeassistant/components/axis.py b/homeassistant/components/axis.py index 16bd544e96e..fab7d98ed98 100644 --- a/homeassistant/components/axis.py +++ b/homeassistant/components/axis.py @@ -4,24 +4,21 @@ Support for Axis devices. For more details about this component, please refer to the documentation at https://home-assistant.io/components/axis/ """ - import logging import voluptuous as vol from homeassistant.components.discovery import SERVICE_AXIS -from homeassistant.const import (ATTR_LOCATION, ATTR_TRIPPED, - CONF_EVENT, CONF_HOST, CONF_INCLUDE, - CONF_NAME, CONF_PASSWORD, CONF_PORT, - CONF_TRIGGER_TIME, CONF_USERNAME, - EVENT_HOMEASSISTANT_STOP) +from homeassistant.const import ( + ATTR_LOCATION, ATTR_TRIPPED, CONF_EVENT, CONF_HOST, CONF_INCLUDE, + CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_TRIGGER_TIME, CONF_USERNAME, + EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers import config_validation as cv from homeassistant.helpers import discovery from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.entity import Entity from homeassistant.util.json import load_json, save_json - REQUIREMENTS = ['axis==14'] _LOGGER = logging.getLogger(__name__) @@ -81,10 +78,10 @@ def request_configuration(hass, config, name, host, serialnumber): configurator = hass.components.configurator def configuration_callback(callback_data): - """Called when config is submitted.""" + """Call when configuration is submitted.""" if CONF_INCLUDE not in callback_data: - configurator.notify_errors(request_id, - "Functionality mandatory.") + configurator.notify_errors( + request_id, "Functionality mandatory.") return False callback_data[CONF_INCLUDE] = callback_data[CONF_INCLUDE].split() @@ -96,8 +93,8 @@ def request_configuration(hass, config, name, host, serialnumber): try: device_config = DEVICE_SCHEMA(callback_data) except vol.Invalid: - configurator.notify_errors(request_id, - "Bad input, please check spelling.") + configurator.notify_errors( + request_id, "Bad input, please check spelling.") return False if setup_device(hass, config, device_config): @@ -108,8 +105,8 @@ def request_configuration(hass, config, name, host, serialnumber): save_json(hass.config.path(CONFIG_FILE), config_file) configurator.request_done(request_id) else: - configurator.notify_errors(request_id, - "Failed to register, please try again.") + configurator.notify_errors( + request_id, "Failed to register, please try again.") return False title = '{} ({})'.format(name, host) @@ -147,7 +144,7 @@ def request_configuration(hass, config, name, host, serialnumber): def setup(hass, config): - """Common setup for Axis devices.""" + """Set up for Axis devices.""" def _shutdown(call): # pylint: disable=unused-argument """Stop the event stream on shutdown.""" for serialnumber, device in AXIS_DEVICES.items(): @@ -157,7 +154,7 @@ def setup(hass, config): hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown) def axis_device_discovered(service, discovery_info): - """Called when axis devices has been found.""" + """Call when axis devices has been found.""" host = discovery_info[CONF_HOST] name = discovery_info['hostname'] serialnumber = discovery_info['properties']['macaddress'] @@ -173,8 +170,8 @@ def setup(hass, config): _LOGGER.error("Bad data from %s. %s", CONFIG_FILE, err) return False if not setup_device(hass, config, device_config): - _LOGGER.error("Couldn\'t set up %s", - device_config[CONF_NAME]) + _LOGGER.error( + "Couldn't set up %s", device_config[CONF_NAME]) else: # New device, create configuration request for UI request_configuration(hass, config, name, host, serialnumber) @@ -193,7 +190,7 @@ def setup(hass, config): if CONF_NAME not in device_config: device_config[CONF_NAME] = device if not setup_device(hass, config, device_config): - _LOGGER.error("Couldn\'t set up %s", device_config[CONF_NAME]) + _LOGGER.error("Couldn't set up %s", device_config[CONF_NAME]) def vapix_service(call): """Service to send a message.""" @@ -205,23 +202,21 @@ def setup(hass, config): call.data[SERVICE_PARAM]) hass.bus.fire(SERVICE_VAPIX_CALL_RESPONSE, response) return True - _LOGGER.info("Couldn\'t find device %s", call.data[CONF_NAME]) + _LOGGER.info("Couldn't find device %s", call.data[CONF_NAME]) return False # Register service with Home Assistant. - hass.services.register(DOMAIN, - SERVICE_VAPIX_CALL, - vapix_service, - schema=SERVICE_SCHEMA) + hass.services.register( + DOMAIN, SERVICE_VAPIX_CALL, vapix_service, schema=SERVICE_SCHEMA) return True def setup_device(hass, config, device_config): - """Set up device.""" + """Set up an Axis device.""" from axis import AxisDevice def signal_callback(action, event): - """Callback to configure events when initialized on event stream.""" + """Call to configure events when initialized on event stream.""" if action == 'add': event_config = { CONF_EVENT: event, @@ -230,11 +225,8 @@ def setup_device(hass, config, device_config): CONF_TRIGGER_TIME: device_config[CONF_TRIGGER_TIME] } component = event.event_platform - discovery.load_platform(hass, - component, - DOMAIN, - event_config, - config) + discovery.load_platform( + hass, component, DOMAIN, event_config, config) event_types = list(filter(lambda x: x in device_config[CONF_INCLUDE], EVENT_TYPES)) @@ -245,7 +237,7 @@ def setup_device(hass, config, device_config): if device.serial_number is None: # If there is no serial number a connection could not be made - _LOGGER.error("Couldn\'t connect to %s", device_config[CONF_HOST]) + _LOGGER.error("Couldn't connect to %s", device_config[CONF_HOST]) return False for component in device_config[CONF_INCLUDE]: @@ -257,11 +249,8 @@ def setup_device(hass, config, device_config): CONF_USERNAME: device_config[CONF_USERNAME], CONF_PASSWORD: device_config[CONF_PASSWORD] } - discovery.load_platform(hass, - component, - DOMAIN, - camera_config, - config) + discovery.load_platform( + hass, component, DOMAIN, camera_config, config) AXIS_DEVICES[device.serial_number] = device if event_types: @@ -275,9 +264,9 @@ class AxisDeviceEvent(Entity): def __init__(self, event_config): """Initialize the event.""" self.axis_event = event_config[CONF_EVENT] - self._name = '{}_{}_{}'.format(event_config[CONF_NAME], - self.axis_event.event_type, - self.axis_event.id) + self._name = '{}_{}_{}'.format( + event_config[CONF_NAME], self.axis_event.event_type, + self.axis_event.id) self.location = event_config[ATTR_LOCATION] self.axis_event.callback = self._update_callback @@ -298,7 +287,7 @@ class AxisDeviceEvent(Entity): @property def should_poll(self): - """No polling needed.""" + """Return the polling state. No polling needed.""" return False @property diff --git a/homeassistant/components/binary_sensor/ads.py b/homeassistant/components/binary_sensor/ads.py index e6b86ed97e6..b7f0ebcc9d3 100644 --- a/homeassistant/components/binary_sensor/ads.py +++ b/homeassistant/components/binary_sensor/ads.py @@ -3,23 +3,22 @@ Support for ADS binary sensors. For more details about this platform, please refer to the documentation. https://home-assistant.io/components/binary_sensor.ads/ - """ import asyncio import logging -import voluptuous as vol -from homeassistant.components.binary_sensor import BinarySensorDevice, \ - PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA -from homeassistant.components.ads import DATA_ADS, CONF_ADS_VAR -from homeassistant.const import CONF_NAME, CONF_DEVICE_CLASS -import homeassistant.helpers.config_validation as cv +import voluptuous as vol + +from homeassistant.components.ads import CONF_ADS_VAR, DATA_ADS +from homeassistant.components.binary_sensor import ( + DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice) +from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['ads'] DEFAULT_NAME = 'ADS binary sensor' - +DEPENDENCIES = ['ads'] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_ADS_VAR): cv.string, @@ -44,7 +43,7 @@ class AdsBinarySensor(BinarySensorDevice): """Representation of ADS binary sensors.""" def __init__(self, ads_hub, name, ads_var, device_class): - """Initialize AdsBinarySensor entity.""" + """Initialize ADS binary sensor.""" self._name = name self._state = False self._device_class = device_class or 'moving' @@ -56,15 +55,13 @@ class AdsBinarySensor(BinarySensorDevice): """Register device notification.""" def update(name, value): """Handle device notifications.""" - _LOGGER.debug('Variable %s changed its value to %d', - name, value) + _LOGGER.debug('Variable %s changed its value to %d', name, value) self._state = value self.schedule_update_ha_state() self.hass.async_add_job( self._ads_hub.add_device_notification, - self.ads_var, self._ads_hub.PLCTYPE_BOOL, update - ) + self.ads_var, self._ads_hub.PLCTYPE_BOOL, update) @property def name(self): diff --git a/homeassistant/components/binary_sensor/axis.py b/homeassistant/components/binary_sensor/axis.py index a6e80dbf97f..84137d95b06 100644 --- a/homeassistant/components/binary_sensor/axis.py +++ b/homeassistant/components/binary_sensor/axis.py @@ -4,13 +4,12 @@ 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/ """ - -import logging from datetime import timedelta +import logging -from homeassistant.components.binary_sensor import (BinarySensorDevice) -from homeassistant.components.axis import (AxisDeviceEvent) -from homeassistant.const import (CONF_TRIGGER_TIME) +from homeassistant.components.axis import AxisDeviceEvent +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.const import CONF_TRIGGER_TIME from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.util.dt import utcnow @@ -20,7 +19,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Axis device event.""" + """Set up the Axis binary devices.""" add_devices([AxisBinarySensor(hass, discovery_info)], True) @@ -28,7 +27,7 @@ class AxisBinarySensor(AxisDeviceEvent, BinarySensorDevice): """Representation of a binary Axis event.""" def __init__(self, hass, event_config): - """Initialize the binary sensor.""" + """Initialize the Axis binary sensor.""" self.hass = hass self._state = False self._delay = event_config[CONF_TRIGGER_TIME] @@ -56,7 +55,7 @@ class AxisBinarySensor(AxisDeviceEvent, BinarySensorDevice): # Set timer to wait until updating the state def _delay_update(now): """Timer callback for sensor update.""" - _LOGGER.debug("%s Called delayed (%s sec) update.", + _LOGGER.debug("%s called delayed (%s sec) update", self._name, self._delay) self.schedule_update_ha_state() self._timer = None diff --git a/homeassistant/components/binary_sensor/concord232.py b/homeassistant/components/binary_sensor/concord232.py index c8442491b29..3bac561700a 100644 --- a/homeassistant/components/binary_sensor/concord232.py +++ b/homeassistant/components/binary_sensor/concord232.py @@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): sensors = [] try: - _LOGGER.debug("Initializing Client") + _LOGGER.debug("Initializing client") client = concord232_client.Client('http://{}:{}'.format(host, port)) client.zones = client.list_zones() client.last_zone_update = datetime.datetime.now() diff --git a/homeassistant/components/binary_sensor/knx.py b/homeassistant/components/binary_sensor/knx.py index 9e5ddf5cac4..c01654a3663 100644 --- a/homeassistant/components/binary_sensor/knx.py +++ b/homeassistant/components/binary_sensor/knx.py @@ -5,12 +5,13 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.knx/ """ import asyncio + import voluptuous as vol -from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES, \ - KNXAutomation -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, \ - BinarySensorDevice +from homeassistant.components.binary_sensor import ( + PLATFORM_SCHEMA, BinarySensorDevice) +from homeassistant.components.knx import ( + ATTR_DISCOVER_DEVICES, DATA_KNX, KNXAutomation) from homeassistant.const import CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv @@ -53,20 +54,16 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine -def async_setup_platform(hass, config, async_add_devices, - discovery_info=None): +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up binary sensor(s) for KNX platform.""" - if DATA_KNX not in hass.data \ - or not hass.data[DATA_KNX].initialized: - return False + if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized: + return if discovery_info is not None: async_add_devices_discovery(hass, discovery_info, async_add_devices) else: async_add_devices_config(hass, config, async_add_devices) - return True - @callback def async_add_devices_discovery(hass, discovery_info, async_add_devices): @@ -80,7 +77,7 @@ def async_add_devices_discovery(hass, discovery_info, async_add_devices): @callback def async_add_devices_config(hass, config, async_add_devices): - """Set up binary senor for KNX platform configured within plattform.""" + """Set up binary senor for KNX platform configured within platform.""" name = config.get(CONF_NAME) import xknx binary_sensor = xknx.devices.BinarySensor( @@ -108,7 +105,7 @@ class KNXBinarySensor(BinarySensorDevice): """Representation of a KNX binary sensor.""" def __init__(self, hass, device): - """Initialization of KNXBinarySensor.""" + """Initialize of KNX binary sensor.""" self.device = device self.hass = hass self.async_register_callbacks() @@ -119,7 +116,7 @@ class KNXBinarySensor(BinarySensorDevice): """Register callbacks to update hass after device was changed.""" @asyncio.coroutine def after_update_callback(device): - """Callback after device was updated.""" + """Call after device was updated.""" # pylint: disable=unused-argument yield from self.async_update_ha_state() self.device.register_device_updated_cb(after_update_callback) diff --git a/homeassistant/components/binary_sensor/mysensors.py b/homeassistant/components/binary_sensor/mysensors.py index 4b83f0c8f2d..19fa02f63df 100644 --- a/homeassistant/components/binary_sensor/mysensors.py +++ b/homeassistant/components/binary_sensor/mysensors.py @@ -5,21 +5,20 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.mysensors/ """ from homeassistant.components import mysensors -from homeassistant.components.binary_sensor import (DEVICE_CLASSES, DOMAIN, - BinarySensorDevice) +from homeassistant.components.binary_sensor import ( + DEVICE_CLASSES, DOMAIN, BinarySensorDevice) from homeassistant.const import STATE_ON def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the mysensors platform for binary sensors.""" + """Set up the MySensors platform for binary sensors.""" mysensors.setup_mysensors_platform( hass, DOMAIN, discovery_info, MySensorsBinarySensor, add_devices=add_devices) -class MySensorsBinarySensor( - mysensors.MySensorsEntity, BinarySensorDevice): - """Represent the value of a MySensors Binary Sensor child node.""" +class MySensorsBinarySensor(mysensors.MySensorsEntity, BinarySensorDevice): + """Representation of a MySensors Binary Sensor child node.""" @property def is_on(self): diff --git a/homeassistant/components/binary_sensor/mystrom.py b/homeassistant/components/binary_sensor/mystrom.py index 2afaa032745..93d56a97c42 100644 --- a/homeassistant/components/binary_sensor/mystrom.py +++ b/homeassistant/components/binary_sensor/mystrom.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.mystrom/ import asyncio import logging -from homeassistant.components.binary_sensor import (BinarySensorDevice, DOMAIN) +from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice from homeassistant.components.http import HomeAssistantView from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY @@ -37,7 +37,7 @@ class MyStromView(HomeAssistantView): @asyncio.coroutine def get(self, request): - """The GET request received from a myStrom button.""" + """Handle the GET request received from a myStrom button.""" res = yield from self._handle(request.app['hass'], request.query) return res diff --git a/homeassistant/components/binary_sensor/raspihats.py b/homeassistant/components/binary_sensor/raspihats.py index ad19fb525a1..9d489a59711 100644 --- a/homeassistant/components/binary_sensor/raspihats.py +++ b/homeassistant/components/binary_sensor/raspihats.py @@ -5,18 +5,17 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.raspihats/ """ import logging + import voluptuous as vol -from homeassistant.const import ( - CONF_NAME, CONF_DEVICE_CLASS, DEVICE_DEFAULT_NAME -) -import homeassistant.helpers.config_validation as cv + from homeassistant.components.binary_sensor import ( - PLATFORM_SCHEMA, BinarySensorDevice -) + PLATFORM_SCHEMA, BinarySensorDevice) from homeassistant.components.raspihats import ( - CONF_I2C_HATS, CONF_BOARD, CONF_ADDRESS, CONF_CHANNELS, CONF_INDEX, - CONF_INVERT_LOGIC, I2C_HAT_NAMES, I2C_HATS_MANAGER, I2CHatsException -) + CONF_ADDRESS, CONF_BOARD, CONF_CHANNELS, CONF_I2C_HATS, CONF_INDEX, + CONF_INVERT_LOGIC, I2C_HAT_NAMES, I2C_HATS_MANAGER, I2CHatsException) +from homeassistant.const import ( + CONF_DEVICE_CLASS, CONF_NAME, DEVICE_DEFAULT_NAME) +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -45,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the raspihats binary_sensor devices.""" + """Set up the raspihats binary_sensor devices.""" I2CHatBinarySensor.I2C_HATS_MANAGER = hass.data[I2C_HATS_MANAGER] binary_sensors = [] i2c_hat_configs = config.get(CONF_I2C_HATS) @@ -65,39 +64,32 @@ def setup_platform(hass, config, add_devices, discovery_info=None): ) ) except I2CHatsException as ex: - _LOGGER.error( - "Failed to register " + board + "I2CHat@" + hex(address) + " " - + str(ex) - ) + _LOGGER.error("Failed to register %s I2CHat@%s %s", + board, hex(address), str(ex)) add_devices(binary_sensors) class I2CHatBinarySensor(BinarySensorDevice): - """Represents a binary sensor that uses a I2C-HAT digital input.""" + """Representation of a binary sensor that uses a I2C-HAT digital input.""" I2C_HATS_MANAGER = None def __init__(self, address, channel, name, invert_logic, device_class): - """Initialize sensor.""" + """Initialize the raspihats sensor.""" self._address = address self._channel = channel self._name = name or DEVICE_DEFAULT_NAME self._invert_logic = invert_logic self._device_class = device_class self._state = self.I2C_HATS_MANAGER.read_di( - self._address, - self._channel - ) + self._address, self._channel) def online_callback(): - """Callback fired when board is online.""" + """Call fired when board is online.""" self.schedule_update_ha_state() self.I2C_HATS_MANAGER.register_online_callback( - self._address, - self._channel, - online_callback - ) + self._address, self._channel, online_callback) def edge_callback(state): """Read digital input state.""" @@ -105,10 +97,7 @@ class I2CHatBinarySensor(BinarySensorDevice): self.schedule_update_ha_state() self.I2C_HATS_MANAGER.register_di_callback( - self._address, - self._channel, - edge_callback - ) + self._address, self._channel, edge_callback) @property def device_class(self): @@ -122,7 +111,7 @@ class I2CHatBinarySensor(BinarySensorDevice): @property def should_poll(self): - """Polling not needed for this sensor.""" + """No polling needed for this sensor.""" return False @property diff --git a/homeassistant/components/binary_sensor/rfxtrx.py b/homeassistant/components/binary_sensor/rfxtrx.py index 9a23b419cfc..763003cab03 100644 --- a/homeassistant/components/binary_sensor/rfxtrx.py +++ b/homeassistant/components/binary_sensor/rfxtrx.py @@ -1,33 +1,30 @@ """ Support for RFXtrx binary sensors. -Lighting4 devices (sensors based on PT2262 encoder) are supported and -tested. Other types may need some work. - +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.rfxtrx/ """ - import logging import voluptuous as vol -from homeassistant.const import ( - CONF_DEVICE_CLASS, CONF_COMMAND_ON, CONF_COMMAND_OFF, CONF_NAME) from homeassistant.components import rfxtrx -from homeassistant.helpers import event as evt -from homeassistant.helpers import config_validation as cv from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) + PLATFORM_SCHEMA, BinarySensorDevice) from homeassistant.components.rfxtrx import ( - ATTR_NAME, CONF_AUTOMATIC_ADD, CONF_FIRE_EVENT, - CONF_OFF_DELAY, CONF_DATA_BITS, CONF_DEVICES) -from homeassistant.util import slugify + ATTR_NAME, CONF_AUTOMATIC_ADD, CONF_DATA_BITS, CONF_DEVICES, + CONF_FIRE_EVENT, CONF_OFF_DELAY) +from homeassistant.const import ( + CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_DEVICE_CLASS, CONF_NAME) +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import event as evt from homeassistant.util import dt as dt_util - - -DEPENDENCIES = ["rfxtrx"] +from homeassistant.util import slugify _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['rfxtrx'] + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_DEVICES, default={}): { cv.string: vol.Schema({ @@ -45,8 +42,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }, extra=vol.ALLOW_EXTRA) -def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup the Binary Sensor platform to rfxtrx.""" +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Binary Sensor platform to RFXtrx.""" import RFXtrx as rfxtrxmod sensors = [] @@ -58,29 +55,26 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): continue if entity[CONF_DATA_BITS] is not None: - _LOGGER.debug("Masked device id: %s", - rfxtrx.get_pt2262_deviceid(device_id, - entity[CONF_DATA_BITS])) + _LOGGER.debug( + "Masked device id: %s", rfxtrx.get_pt2262_deviceid( + device_id, entity[CONF_DATA_BITS])) _LOGGER.debug("Add %s rfxtrx.binary_sensor (class %s)", entity[ATTR_NAME], entity[CONF_DEVICE_CLASS]) - device = RfxtrxBinarySensor(event, entity[ATTR_NAME], - entity[CONF_DEVICE_CLASS], - entity[CONF_FIRE_EVENT], - entity[CONF_OFF_DELAY], - entity[CONF_DATA_BITS], - entity[CONF_COMMAND_ON], - entity[CONF_COMMAND_OFF]) + device = RfxtrxBinarySensor( + event, entity[ATTR_NAME], entity[CONF_DEVICE_CLASS], + entity[CONF_FIRE_EVENT], entity[CONF_OFF_DELAY], + entity[CONF_DATA_BITS], entity[CONF_COMMAND_ON], + entity[CONF_COMMAND_OFF]) device.hass = hass sensors.append(device) rfxtrx.RFX_DEVICES[device_id] = device - add_devices_callback(sensors) + add_devices(sensors) - # pylint: disable=too-many-branches def binary_sensor_update(event): - """Callback for control updates from the RFXtrx gateway.""" + """Call for control updates from the RFXtrx gateway.""" if not isinstance(event, rfxtrxmod.ControlEvent): return @@ -100,29 +94,26 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): poss_dev = rfxtrx.find_possible_pt2262_device(device_id) if poss_dev is not None: poss_id = slugify(poss_dev.event.device.id_string.lower()) - _LOGGER.debug("Found possible matching deviceid %s.", - poss_id) + _LOGGER.debug( + "Found possible matching device ID: %s", poss_id) pkt_id = "".join("{0:02x}".format(x) for x in event.data) sensor = RfxtrxBinarySensor(event, pkt_id) sensor.hass = hass rfxtrx.RFX_DEVICES[device_id] = sensor - add_devices_callback([sensor]) - _LOGGER.info("Added binary sensor %s " - "(Device_id: %s Class: %s Sub: %s)", - pkt_id, - slugify(event.device.id_string.lower()), - event.device.__class__.__name__, - event.device.subtype) + add_devices([sensor]) + _LOGGER.info( + "Added binary sensor %s (Device ID: %s Class: %s Sub: %s)", + pkt_id, slugify(event.device.id_string.lower()), + event.device.__class__.__name__, event.device.subtype) elif not isinstance(sensor, RfxtrxBinarySensor): return else: - _LOGGER.debug("Binary sensor update " - "(Device_id: %s Class: %s Sub: %s)", - slugify(event.device.id_string.lower()), - event.device.__class__.__name__, - event.device.subtype) + _LOGGER.debug( + "Binary sensor update (Device ID: %s Class: %s Sub: %s)", + slugify(event.device.id_string.lower()), + event.device.__class__.__name__, event.device.subtype) if sensor.is_lighting4: if sensor.data_bits is not None: @@ -142,22 +133,20 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): sensor.update_state(False) sensor.delay_listener = evt.track_point_in_time( - hass, off_delay_listener, dt_util.utcnow() + sensor.off_delay - ) + hass, off_delay_listener, dt_util.utcnow() + sensor.off_delay) - # Subscribe to main rfxtrx events + # Subscribe to main RFXtrx events if binary_sensor_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS: rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(binary_sensor_update) -# pylint: disable=too-many-instance-attributes,too-many-arguments class RfxtrxBinarySensor(BinarySensorDevice): - """An Rfxtrx binary sensor.""" + """A representation of a RFXtrx binary sensor.""" def __init__(self, event, name, device_class=None, should_fire=False, off_delay=None, data_bits=None, cmd_on=None, cmd_off=None): - """Initialize the sensor.""" + """Initialize the RFXtrx sensor.""" self.event = event self._name = name self._should_fire_event = should_fire @@ -172,8 +161,7 @@ class RfxtrxBinarySensor(BinarySensorDevice): if data_bits is not None: self._masked_id = rfxtrx.get_pt2262_deviceid( - event.device.id_string.lower(), - data_bits) + event.device.id_string.lower(), data_bits) else: self._masked_id = None diff --git a/homeassistant/components/binary_sensor/rpi_pfio.py b/homeassistant/components/binary_sensor/rpi_pfio.py index 92d02067dfc..7acbadf873a 100644 --- a/homeassistant/components/binary_sensor/rpi_pfio.py +++ b/homeassistant/components/binary_sensor/rpi_pfio.py @@ -8,18 +8,17 @@ import logging import voluptuous as vol -import homeassistant.components.rpi_pfio as rpi_pfio from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) -from homeassistant.const import DEVICE_DEFAULT_NAME + PLATFORM_SCHEMA, BinarySensorDevice) +import homeassistant.components.rpi_pfio as rpi_pfio +from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -ATTR_NAME = 'name' -ATTR_INVERT_LOGIC = 'invert_logic' -ATTR_SETTLE_TIME = 'settle_time' +CONF_INVERT_LOGIC = 'invert_logic' CONF_PORTS = 'ports' +CONF_SETTLE_TIME = 'settle_time' DEFAULT_INVERT_LOGIC = False DEFAULT_SETTLE_TIME = 20 @@ -27,27 +26,27 @@ DEFAULT_SETTLE_TIME = 20 DEPENDENCIES = ['rpi_pfio'] PORT_SCHEMA = vol.Schema({ - vol.Optional(ATTR_NAME, default=None): cv.string, - vol.Optional(ATTR_SETTLE_TIME, default=DEFAULT_SETTLE_TIME): + vol.Optional(CONF_NAME, default=None): cv.string, + vol.Optional(CONF_SETTLE_TIME, default=DEFAULT_SETTLE_TIME): cv.positive_int, - vol.Optional(ATTR_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean + vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_PORTS, default={}): vol.Schema({ - cv.positive_int: PORT_SCHEMA + cv.positive_int: PORT_SCHEMA, }) }) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the PiFace Digital Input devices.""" + """Set up the PiFace Digital Input devices.""" binary_sensors = [] - ports = config.get('ports') + ports = config.get(CONF_PORTS) for port, port_entity in ports.items(): - name = port_entity[ATTR_NAME] - settle_time = port_entity[ATTR_SETTLE_TIME] / 1000 - invert_logic = port_entity[ATTR_INVERT_LOGIC] + name = port_entity[CONF_NAME] + settle_time = port_entity[CONF_SETTLE_TIME] / 1000 + invert_logic = port_entity[CONF_INVERT_LOGIC] binary_sensors.append(RPiPFIOBinarySensor( hass, port, name, settle_time, invert_logic)) diff --git a/homeassistant/components/binary_sensor/spc.py b/homeassistant/components/binary_sensor/spc.py index a3a84580edd..95723f93870 100644 --- a/homeassistant/components/binary_sensor/spc.py +++ b/homeassistant/components/binary_sensor/spc.py @@ -4,46 +4,49 @@ 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/binary_sensor.spc/ """ -import logging import asyncio +import logging -from homeassistant.components.spc import ( - ATTR_DISCOVER_DEVICES, DATA_REGISTRY) from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.const import (STATE_UNAVAILABLE, STATE_ON, STATE_OFF) - +from homeassistant.components.spc import ATTR_DISCOVER_DEVICES, DATA_REGISTRY +from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE _LOGGER = logging.getLogger(__name__) -SPC_TYPE_TO_DEVICE_CLASS = {'0': 'motion', - '1': 'opening', - '3': 'smoke'} +SPC_TYPE_TO_DEVICE_CLASS = { + '0': 'motion', + '1': 'opening', + '3': 'smoke', +} - -SPC_INPUT_TO_SENSOR_STATE = {'0': STATE_OFF, - '1': STATE_ON} +SPC_INPUT_TO_SENSOR_STATE = { + '0': STATE_OFF, + '1': STATE_ON, +} def _get_device_class(spc_type): + """Get the device class.""" return SPC_TYPE_TO_DEVICE_CLASS.get(spc_type, None) def _get_sensor_state(spc_input): + """Get the sensor state.""" return SPC_INPUT_TO_SENSOR_STATE.get(spc_input, STATE_UNAVAILABLE) def _create_sensor(hass, zone): - return SpcBinarySensor(zone_id=zone['id'], - name=zone['zone_name'], - state=_get_sensor_state(zone['input']), - device_class=_get_device_class(zone['type']), - spc_registry=hass.data[DATA_REGISTRY]) + """Create a SPC sensor.""" + return SpcBinarySensor( + zone_id=zone['id'], name=zone['zone_name'], + state=_get_sensor_state(zone['input']), + device_class=_get_device_class(zone['type']), + spc_registry=hass.data[DATA_REGISTRY]) @asyncio.coroutine -def async_setup_platform(hass, config, async_add_devices, - discovery_info=None): - """Initialize the platform.""" +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up the SPC binary sensor.""" if (discovery_info is None or discovery_info[ATTR_DISCOVER_DEVICES] is None): return @@ -55,7 +58,7 @@ def async_setup_platform(hass, config, async_add_devices, class SpcBinarySensor(BinarySensorDevice): - """Represents a sensor based on an SPC zone.""" + """Representation of a sensor based on a SPC zone.""" def __init__(self, zone_id, name, state, device_class, spc_registry): """Initialize the sensor device.""" @@ -74,7 +77,7 @@ class SpcBinarySensor(BinarySensorDevice): @property def name(self): - """The name of the device.""" + """Return the name of the device.""" return self._name @property @@ -85,7 +88,7 @@ class SpcBinarySensor(BinarySensorDevice): @property def hidden(self) -> bool: """Whether the device is hidden by default.""" - # these type of sensors are probably mainly used for automations + # These type of sensors are probably mainly used for automations return True @property @@ -95,5 +98,5 @@ class SpcBinarySensor(BinarySensorDevice): @property def device_class(self): - """The device class.""" + """Return the device class.""" return self._device_class diff --git a/homeassistant/components/binary_sensor/tapsaff.py b/homeassistant/components/binary_sensor/tapsaff.py index 565abb73b36..09d28b96f72 100644 --- a/homeassistant/components/binary_sensor/tapsaff.py +++ b/homeassistant/components/binary_sensor/tapsaff.py @@ -4,15 +4,15 @@ Support for Taps Affs. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.tapsaff/ """ -import logging from datetime import timedelta +import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) -from homeassistant.const import (CONF_NAME) + PLATFORM_SCHEMA, BinarySensorDevice) +from homeassistant.const import CONF_NAME +import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['tapsaff==0.1.3'] @@ -67,7 +67,7 @@ class TapsAffData(object): """Class for handling the data retrieval for pins.""" def __init__(self, location): - """Initialize the sensor.""" + """Initialize the data object.""" from tapsaff import TapsAff self._is_taps_aff = None diff --git a/homeassistant/components/binary_sensor/tesla.py b/homeassistant/components/binary_sensor/tesla.py index a7cda90b3f6..3d494a28ea8 100644 --- a/homeassistant/components/binary_sensor/tesla.py +++ b/homeassistant/components/binary_sensor/tesla.py @@ -28,7 +28,7 @@ class TeslaBinarySensor(TeslaDevice, BinarySensorDevice): """Implement an Tesla binary sensor for parking and charger.""" def __init__(self, tesla_device, controller, sensor_type): - """Initialisation of binary sensor.""" + """Initialise of a Tesla binary sensor.""" super().__init__(tesla_device, controller) self._state = False self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id) diff --git a/homeassistant/components/binary_sensor/verisure.py b/homeassistant/components/binary_sensor/verisure.py index 8702c8bd770..4a1b99f4b9b 100644 --- a/homeassistant/components/binary_sensor/verisure.py +++ b/homeassistant/components/binary_sensor/verisure.py @@ -6,15 +6,15 @@ https://home-assistant.io/components/binary_sensor.verisure/ """ import logging -from homeassistant.components.verisure import HUB as hub from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.verisure import CONF_DOOR_WINDOW +from homeassistant.components.verisure import HUB as hub _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Verisure binary sensors.""" + """Set up the Verisure binary sensors.""" sensors = [] hub.update_overview() @@ -27,10 +27,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class VerisureDoorWindowSensor(BinarySensorDevice): - """Verisure door window sensor.""" + """Representation of a Verisure door window sensor.""" def __init__(self, device_label): - """Initialize the modbus coil sensor.""" + """Initialize the Verisure door window sensor.""" self._device_label = device_label @property diff --git a/homeassistant/components/binary_sensor/vultr.py b/homeassistant/components/binary_sensor/vultr.py index 66b5a127be1..eecd3f87c40 100644 --- a/homeassistant/components/binary_sensor/vultr.py +++ b/homeassistant/components/binary_sensor/vultr.py @@ -31,7 +31,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Set up the Vultr subscription (server) sensor.""" + """Set up the Vultr subscription (server) binary sensor.""" vultr = hass.data[DATA_VULTR] subscription = config.get(CONF_SUBSCRIPTION) @@ -39,7 +39,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if subscription not in vultr.data: _LOGGER.error("Subscription %s not found", subscription) - return False + return add_devices([VultrBinarySensor(vultr, subscription, name)], True) @@ -48,7 +48,7 @@ class VultrBinarySensor(BinarySensorDevice): """Representation of a Vultr subscription sensor.""" def __init__(self, vultr, subscription, name): - """Initialize a new Vultr sensor.""" + """Initialize a new Vultr binary sensor.""" self._vultr = vultr self._name = name diff --git a/homeassistant/components/binary_sensor/wink.py b/homeassistant/components/binary_sensor/wink.py index e0bf23ecee2..55f6af3719e 100644 --- a/homeassistant/components/binary_sensor/wink.py +++ b/homeassistant/components/binary_sensor/wink.py @@ -8,7 +8,7 @@ import asyncio import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.wink import WinkDevice, DOMAIN +from homeassistant.components.wink import DOMAIN, WinkDevice _LOGGER = logging.getLogger(__name__) @@ -16,18 +16,18 @@ DEPENDENCIES = ['wink'] # These are the available sensors mapped to binary_sensor class SENSOR_TYPES = { - 'opened': 'opening', 'brightness': 'light', - 'vibration': 'vibration', - 'loudness': 'sound', - 'noise': 'sound', 'capturing_audio': 'sound', - 'liquid_detected': 'moisture', - 'motion': 'motion', - 'presence': 'occupancy', + 'capturing_video': None, 'co_detected': 'gas', + 'liquid_detected': 'moisture', + 'loudness': 'sound', + 'motion': 'motion', + 'noise': 'sound', + 'opened': 'opening', + 'presence': 'occupancy', 'smoke_detected': 'smoke', - 'capturing_video': None + 'vibration': 'vibration', } @@ -103,7 +103,7 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice): @asyncio.coroutine def async_added_to_hass(self): - """Callback when entity is added to hass.""" + """Call when entity is added to hass.""" self.hass.data[DOMAIN]['entities']['binary_sensor'].append(self) @property @@ -118,7 +118,7 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice): @property def device_state_attributes(self): - """Return the state attributes.""" + """Return the device state attributes.""" return super().device_state_attributes @@ -127,7 +127,7 @@ class WinkSmokeDetector(WinkBinarySensorDevice): @property def device_state_attributes(self): - """Return the state attributes.""" + """Return the device state attributes.""" _attributes = super().device_state_attributes _attributes['test_activated'] = self.wink.test_activated() return _attributes @@ -138,7 +138,7 @@ class WinkHub(WinkBinarySensorDevice): @property def device_state_attributes(self): - """Return the state attributes.""" + """Return the device state attributes.""" _attributes = super().device_state_attributes _attributes['update_needed'] = self.wink.update_needed() _attributes['firmware_version'] = self.wink.firmware_version() @@ -170,7 +170,7 @@ class WinkButton(WinkBinarySensorDevice): @property def device_state_attributes(self): - """Return the state attributes.""" + """Return the device state attributes.""" _attributes = super().device_state_attributes _attributes['pressed'] = self.wink.pressed() _attributes['long_pressed'] = self.wink.long_pressed() diff --git a/homeassistant/components/calendar/caldav.py b/homeassistant/components/calendar/caldav.py index 36894dcab61..8b2401aa589 100644 --- a/homeassistant/components/calendar/caldav.py +++ b/homeassistant/components/calendar/caldav.py @@ -4,18 +4,18 @@ Support for WebDav Calendar. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/calendar.caldav/ """ +from datetime import datetime, timedelta import logging import re -from datetime import datetime, timedelta import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.calendar import ( - CalendarEventDevice, PLATFORM_SCHEMA) + PLATFORM_SCHEMA, CalendarEventDevice) from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, CONF_URL, CONF_USERNAME) -from homeassistant.util import dt, Throttle +import homeassistant.helpers.config_validation as cv +from homeassistant.util import Throttle, dt REQUIREMENTS = ['caldav==0.5.0'] @@ -39,9 +39,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_CUSTOM_CALENDARS, default=[]): vol.All(cv.ensure_list, vol.Schema([ vol.Schema({ - vol.Required(CONF_NAME): cv.string, vol.Required(CONF_CALENDAR): cv.string, - vol.Required(CONF_SEARCH): cv.string + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_SEARCH): cv.string, }) ])) }) @@ -53,12 +53,12 @@ def setup_platform(hass, config, add_devices, disc_info=None): """Set up the WebDav Calendar platform.""" import caldav - client = caldav.DAVClient(config.get(CONF_URL), - None, - config.get(CONF_USERNAME), - config.get(CONF_PASSWORD)) + url = config.get(CONF_URL) + username = config.get(CONF_USERNAME) + password = config.get(CONF_PASSWORD) + + client = caldav.DAVClient(url, None, username, password) - # Retrieve all the remote calendars calendars = client.principal().calendars() calendar_devices = [] @@ -70,8 +70,7 @@ def setup_platform(hass, config, add_devices, disc_info=None): _LOGGER.debug("Ignoring calendar '%s'", calendar.name) continue - # Create additional calendars based on custom filtering - # rules + # Create additional calendars based on custom filtering rules for cust_calendar in config.get(CONF_CUSTOM_CALENDARS): # Check that the base calendar matches if cust_calendar.get(CONF_CALENDAR) != calendar.name: @@ -85,12 +84,9 @@ def setup_platform(hass, config, add_devices, disc_info=None): } calendar_devices.append( - WebDavCalendarEventDevice(hass, - device_data, - calendar, - True, - cust_calendar.get(CONF_SEARCH)) - ) + WebDavCalendarEventDevice( + hass, device_data, calendar, True, + cust_calendar.get(CONF_SEARCH))) # Create a default calendar if there was no custom one if not config.get(CONF_CUSTOM_CALENDARS): @@ -102,18 +98,13 @@ def setup_platform(hass, config, add_devices, disc_info=None): WebDavCalendarEventDevice(hass, device_data, calendar) ) - # Finally add all the calendars we've created add_devices(calendar_devices) class WebDavCalendarEventDevice(CalendarEventDevice): """A device for getting the next Task from a WebDav Calendar.""" - def __init__(self, - hass, - device_data, - calendar, - all_day=False, + def __init__(self, hass, device_data, calendar, all_day=False, search=None): """Create the WebDav Calendar Event Device.""" self.data = WebDavCalendarData(calendar, all_day, search) @@ -167,9 +158,7 @@ class WebDavCalendarData(object): if vevent is None: _LOGGER.debug( "No matching event found in the %d results for %s", - len(results), - self.calendar.name, - ) + len(results), self.calendar.name) self.event = None return True diff --git a/homeassistant/components/calendar/todoist.py b/homeassistant/components/calendar/todoist.py index ecf8bfb7cf7..81191e3025e 100644 --- a/homeassistant/components/calendar/todoist.py +++ b/homeassistant/components/calendar/todoist.py @@ -4,29 +4,28 @@ Support for Todoist task management (https://todoist.com). For more details about this platform, please refer to the documentation at https://home-assistant.io/components/calendar.todoist/ """ - - -from datetime import datetime -from datetime import timedelta +from datetime import datetime, timedelta import logging import voluptuous as vol from homeassistant.components.calendar import ( - CalendarEventDevice, DOMAIN, PLATFORM_SCHEMA) -from homeassistant.components.google import ( - CONF_DEVICE_ID) -from homeassistant.const import ( - CONF_ID, CONF_NAME, CONF_TOKEN) + DOMAIN, PLATFORM_SCHEMA, CalendarEventDevice) +from homeassistant.components.google import CONF_DEVICE_ID +from homeassistant.const import CONF_ID, CONF_NAME, CONF_TOKEN import homeassistant.helpers.config_validation as cv from homeassistant.helpers.template import DATE_STR_FORMAT -from homeassistant.util import dt -from homeassistant.util import Throttle +from homeassistant.util import Throttle, dt REQUIREMENTS = ['todoist-python==7.0.17'] _LOGGER = logging.getLogger(__name__) +CONF_EXTRA_PROJECTS = 'custom_projects' +CONF_PROJECT_DUE_DATE = 'due_date_days' +CONF_PROJECT_LABEL_WHITELIST = 'labels' +CONF_PROJECT_WHITELIST = 'include_projects' + # Calendar Platform: Does this calendar event last all day? ALL_DAY = 'all_day' # Attribute: All tasks in this project @@ -78,20 +77,15 @@ SUMMARY = 'summary' TASKS = 'items' SERVICE_NEW_TASK = 'todoist_new_task' + NEW_TASK_SERVICE_SCHEMA = vol.Schema({ vol.Required(CONTENT): cv.string, vol.Optional(PROJECT_NAME, default='inbox'): vol.All(cv.string, vol.Lower), vol.Optional(LABELS): cv.ensure_list_csv, - vol.Optional(PRIORITY): vol.All(vol.Coerce(int), - vol.Range(min=1, max=4)), - vol.Optional(DUE_DATE): cv.string + vol.Optional(PRIORITY): vol.All(vol.Coerce(int), vol.Range(min=1, max=4)), + vol.Optional(DUE_DATE): cv.string, }) -CONF_EXTRA_PROJECTS = 'custom_projects' -CONF_PROJECT_DUE_DATE = 'due_date_days' -CONF_PROJECT_WHITELIST = 'include_projects' -CONF_PROJECT_LABEL_WHITELIST = 'labels' - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_TOKEN): cv.string, vol.Optional(CONF_EXTRA_PROJECTS, default=[]): @@ -111,8 +105,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Todoist platform.""" - # Check token: + """Set up the Todoist platform.""" token = config.get(CONF_TOKEN) # Look up IDs based on (lowercase) names. @@ -176,7 +169,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(project_devices) def handle_new_task(call): - """Called when a user creates a new Todoist Task from HASS.""" + """Call when a user creates a new Todoist Task from HASS.""" project_name = call.data[PROJECT_NAME] project_id = project_id_lookup[project_name] @@ -528,8 +521,7 @@ class TodoistProjectData(object): # Let's set our "due date" to tomorrow self.event[END] = { DATETIME: ( - datetime.utcnow() + - timedelta(days=1) + datetime.utcnow() + timedelta(days=1) ).strftime(DATE_STR_FORMAT) } _LOGGER.debug("Updated %s", self._name) diff --git a/homeassistant/components/camera/axis.py b/homeassistant/components/camera/axis.py index 492c2a47729..51c3bc89b05 100644 --- a/homeassistant/components/camera/axis.py +++ b/homeassistant/components/camera/axis.py @@ -6,11 +6,11 @@ https://home-assistant.io/components/camera.axis/ """ import logging -from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT, - CONF_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION) from homeassistant.components.camera.mjpeg import ( CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera) +from homeassistant.const import ( + CONF_AUTHENTICATION, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, + CONF_USERNAME, HTTP_DIGEST_AUTHENTICATION) from homeassistant.helpers.dispatcher import dispatcher_connect _LOGGER = logging.getLogger(__name__) @@ -20,6 +20,7 @@ DEPENDENCIES = [DOMAIN] def _get_image_url(host, port, mode): + """Set the URL to get the image.""" if mode == 'mjpeg': return 'http://{}:{}/axis-cgi/mjpg/video.cgi'.format(host, port) elif mode == 'single': @@ -27,34 +28,32 @@ def _get_image_url(host, port, mode): def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Axis camera.""" + """Set up the Axis camera.""" camera_config = { CONF_NAME: discovery_info[CONF_NAME], CONF_USERNAME: discovery_info[CONF_USERNAME], CONF_PASSWORD: discovery_info[CONF_PASSWORD], - CONF_MJPEG_URL: _get_image_url(discovery_info[CONF_HOST], - str(discovery_info[CONF_PORT]), - 'mjpeg'), - CONF_STILL_IMAGE_URL: _get_image_url(discovery_info[CONF_HOST], - str(discovery_info[CONF_PORT]), - 'single'), + CONF_MJPEG_URL: _get_image_url( + discovery_info[CONF_HOST], str(discovery_info[CONF_PORT]), + 'mjpeg'), + CONF_STILL_IMAGE_URL: _get_image_url( + discovery_info[CONF_HOST], str(discovery_info[CONF_PORT]), + 'single'), CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION, } - add_devices([AxisCamera(hass, - camera_config, - str(discovery_info[CONF_PORT]))]) + add_devices([AxisCamera( + hass, camera_config, str(discovery_info[CONF_PORT]))]) class AxisCamera(MjpegCamera): - """AxisCamera class.""" + """Representation of a Axis camera.""" def __init__(self, hass, config, port): """Initialize Axis Communications camera component.""" super().__init__(hass, config) self.port = port - dispatcher_connect(hass, - DOMAIN + '_' + config[CONF_NAME] + '_new_ip', - self._new_ip) + dispatcher_connect( + hass, DOMAIN + '_' + config[CONF_NAME] + '_new_ip', self._new_ip) def _new_ip(self, host): """Set new IP for video stream.""" diff --git a/homeassistant/components/camera/blink.py b/homeassistant/components/camera/blink.py index 4b708817cfd..8475ca8fd1e 100644 --- a/homeassistant/components/camera/blink.py +++ b/homeassistant/components/camera/blink.py @@ -4,21 +4,21 @@ Support for Blink system camera. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/camera.blink/ """ +from datetime import timedelta import logging -from datetime import timedelta import requests from homeassistant.components.blink import DOMAIN from homeassistant.components.camera import Camera from homeassistant.util import Throttle +_LOGGER = logging.getLogger(__name__) + DEPENDENCIES = ['blink'] MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90) -_LOGGER = logging.getLogger(__name__) - def setup_platform(hass, config, add_devices, discovery_info=None): """Set up a Blink Camera.""" @@ -45,7 +45,7 @@ class BlinkCamera(Camera): self.notifications = self.data.cameras[self._name].notifications self.response = None - _LOGGER.info("Initialized blink camera %s", self._name) + _LOGGER.debug("Initialized blink camera %s", self._name) @property def name(self): @@ -55,7 +55,7 @@ class BlinkCamera(Camera): @Throttle(MIN_TIME_BETWEEN_UPDATES) def request_image(self): """Request a new image from Blink servers.""" - _LOGGER.info("Requesting new image from blink servers") + _LOGGER.debug("Requesting new image from blink servers") image_url = self.check_for_motion() header = self.data.cameras[self._name].header self.response = requests.get(image_url, headers=header, stream=True) @@ -68,7 +68,7 @@ class BlinkCamera(Camera): # We detected motion at some point self.data.last_motion() self.notifications = notifs - # returning motion image currently not working + # Returning motion image currently not working # return self.data.cameras[self._name].motion['image'] elif notifs < self.notifications: self.notifications = notifs diff --git a/homeassistant/components/climate/daikin.py b/homeassistant/components/climate/daikin.py index 8f6df034b89..1f3515b7b98 100644 --- a/homeassistant/components/climate/daikin.py +++ b/homeassistant/components/climate/daikin.py @@ -9,25 +9,18 @@ import re import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.climate import ( - ATTR_OPERATION_MODE, ATTR_FAN_MODE, ATTR_SWING_MODE, - ATTR_CURRENT_TEMPERATURE, ClimateDevice, PLATFORM_SCHEMA, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE, - SUPPORT_SWING_MODE, STATE_OFF, STATE_AUTO, STATE_HEAT, STATE_COOL, - STATE_DRY, STATE_FAN_ONLY -) + ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, ATTR_OPERATION_MODE, + ATTR_SWING_MODE, PLATFORM_SCHEMA, STATE_AUTO, STATE_COOL, STATE_DRY, + STATE_FAN_ONLY, STATE_HEAT, STATE_OFF, SUPPORT_FAN_MODE, + SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE, + ClimateDevice) from homeassistant.components.daikin import ( - daikin_api_setup, - ATTR_TARGET_TEMPERATURE, - ATTR_INSIDE_TEMPERATURE, - ATTR_OUTSIDE_TEMPERATURE -) + ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, ATTR_TARGET_TEMPERATURE, + daikin_api_setup) from homeassistant.const import ( - CONF_HOST, CONF_NAME, - TEMP_CELSIUS, - ATTR_TEMPERATURE -) + ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, TEMP_CELSIUS) +import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['pydaikin==0.4'] @@ -60,15 +53,15 @@ HA_ATTR_TO_DAIKIN = { def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Daikin HVAC platform.""" + """Set up the Daikin HVAC platform.""" if discovery_info is not None: host = discovery_info.get('ip') name = None - _LOGGER.info("Discovered a Daikin AC on %s", host) + _LOGGER.debug("Discovered a Daikin AC on %s", host) else: host = config.get(CONF_HOST) name = config.get(CONF_NAME) - _LOGGER.info("Added Daikin AC on %s", host) + _LOGGER.debug("Added Daikin AC on %s", host) api = daikin_api_setup(hass, host, name) add_devices([DaikinClimate(api)], True) @@ -130,7 +123,7 @@ class DaikinClimate(ClimateDevice): ).title() if value is None: - _LOGGER.warning("Invalid value requested for key %s", key) + _LOGGER.error("Invalid value requested for key %s", key) else: if value == "-" or value == "--": value = None diff --git a/homeassistant/components/climate/econet.py b/homeassistant/components/climate/econet.py index 8f2342782ed..bb92a92467a 100644 --- a/homeassistant/components/climate/econet.py +++ b/homeassistant/components/climate/econet.py @@ -10,17 +10,12 @@ import logging import voluptuous as vol from homeassistant.components.climate import ( - DOMAIN, - PLATFORM_SCHEMA, - STATE_ECO, STATE_GAS, STATE_ELECTRIC, - STATE_HEAT_PUMP, STATE_HIGH_DEMAND, - STATE_OFF, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_OPERATION_MODE, - STATE_PERFORMANCE, - ClimateDevice) -from homeassistant.const import (ATTR_ENTITY_ID, - CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT, - ATTR_TEMPERATURE) + DOMAIN, PLATFORM_SCHEMA, STATE_ECO, STATE_ELECTRIC, STATE_GAS, + STATE_HEAT_PUMP, STATE_HIGH_DEMAND, STATE_OFF, STATE_PERFORMANCE, + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice) +from homeassistant.const import ( + ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_PASSWORD, CONF_USERNAME, + TEMP_FAHRENHEIT) import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['pyeconet==0.0.4'] @@ -89,7 +84,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): hass.data[ECONET_DATA]['water_heaters'].extend(hass_water_heaters) def service_handle(service): - """Handler for services.""" + """Handle the service calls.""" entity_ids = service.data.get('entity_id') all_heaters = hass.data[ECONET_DATA]['water_heaters'] _heaters = [ @@ -107,12 +102,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _water_heater.schedule_update_ha_state(True) - hass.services.register(DOMAIN, SERVICE_ADD_VACATION, - service_handle, + hass.services.register(DOMAIN, SERVICE_ADD_VACATION, service_handle, schema=ADD_VACATION_SCHEMA) - hass.services.register(DOMAIN, SERVICE_DELETE_VACATION, - service_handle, + hass.services.register(DOMAIN, SERVICE_DELETE_VACATION, service_handle, schema=DELETE_VACATION_SCHEMA) @@ -140,7 +133,7 @@ class EcoNetWaterHeater(ClimateDevice): @property def device_state_attributes(self): - """Return the optional state attributes.""" + """Return the optional device state attributes.""" data = {} vacations = self.water_heater.get_vacations() if vacations: @@ -159,8 +152,7 @@ class EcoNetWaterHeater(ClimateDevice): """ Return current operation as one of the following. - ["eco", "heat_pump", - "high_demand", "electric_only"] + ["eco", "heat_pump", "high_demand", "electric_only"] """ current_op = ECONET_STATE_TO_HA.get(self.water_heater.mode) return current_op @@ -191,7 +183,7 @@ class EcoNetWaterHeater(ClimateDevice): if target_temp is not None: self.water_heater.set_target_set_point(target_temp) else: - _LOGGER.error("A target temperature must be provided.") + _LOGGER.error("A target temperature must be provided") def set_operation_mode(self, operation_mode): """Set operation mode.""" @@ -199,7 +191,7 @@ class EcoNetWaterHeater(ClimateDevice): if op_mode_to_set is not None: self.water_heater.set_mode(op_mode_to_set) else: - _LOGGER.error("An operation mode must be provided.") + _LOGGER.error("An operation mode must be provided") def add_vacation(self, start, end): """Add a vacation to this water heater.""" diff --git a/homeassistant/components/climate/knx.py b/homeassistant/components/climate/knx.py index 97bd3e9503c..a78c277fa33 100644 --- a/homeassistant/components/climate/knx.py +++ b/homeassistant/components/climate/knx.py @@ -5,13 +5,14 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/climate.knx/ """ import asyncio + import voluptuous as vol -from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES from homeassistant.components.climate import ( - PLATFORM_SCHEMA, ClimateDevice, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_OPERATION_MODE) -from homeassistant.const import CONF_NAME, TEMP_CELSIUS, ATTR_TEMPERATURE + PLATFORM_SCHEMA, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, + ClimateDevice) +from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX +from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, TEMP_CELSIUS from homeassistant.core import callback import homeassistant.helpers.config_validation as cv @@ -61,24 +62,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine -def async_setup_platform(hass, config, async_add_devices, - discovery_info=None): +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up climate(s) for KNX platform.""" - if DATA_KNX not in hass.data \ - or not hass.data[DATA_KNX].initialized: - return False + if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized: + return if discovery_info is not None: async_add_devices_discovery(hass, discovery_info, async_add_devices) else: async_add_devices_config(hass, config, async_add_devices) - return True - @callback def async_add_devices_discovery(hass, discovery_info, async_add_devices): - """Set up climates for KNX platform configured within plattform.""" + """Set up climates for KNX platform configured within platform.""" entities = [] for device_name in discovery_info[ATTR_DISCOVER_DEVICES]: device = hass.data[DATA_KNX].xknx.devices[device_name] @@ -88,28 +85,22 @@ def async_add_devices_discovery(hass, discovery_info, async_add_devices): @callback def async_add_devices_config(hass, config, async_add_devices): - """Set up climate for KNX platform configured within plattform.""" + """Set up climate for KNX platform configured within platform.""" import xknx climate = xknx.devices.Climate( hass.data[DATA_KNX].xknx, name=config.get(CONF_NAME), - group_address_temperature=config.get( - CONF_TEMPERATURE_ADDRESS), + group_address_temperature=config.get(CONF_TEMPERATURE_ADDRESS), group_address_target_temperature=config.get( CONF_TARGET_TEMPERATURE_ADDRESS), - group_address_setpoint_shift=config.get( - CONF_SETPOINT_SHIFT_ADDRESS), + group_address_setpoint_shift=config.get(CONF_SETPOINT_SHIFT_ADDRESS), group_address_setpoint_shift_state=config.get( CONF_SETPOINT_SHIFT_STATE_ADDRESS), - setpoint_shift_step=config.get( - CONF_SETPOINT_SHIFT_STEP), - setpoint_shift_max=config.get( - CONF_SETPOINT_SHIFT_MAX), - setpoint_shift_min=config.get( - CONF_SETPOINT_SHIFT_MIN), - group_address_operation_mode=config.get( - CONF_OPERATION_MODE_ADDRESS), + setpoint_shift_step=config.get(CONF_SETPOINT_SHIFT_STEP), + setpoint_shift_max=config.get(CONF_SETPOINT_SHIFT_MAX), + setpoint_shift_min=config.get(CONF_SETPOINT_SHIFT_MIN), + group_address_operation_mode=config.get(CONF_OPERATION_MODE_ADDRESS), group_address_operation_mode_state=config.get( CONF_OPERATION_MODE_STATE_ADDRESS), group_address_controller_status=config.get( @@ -127,10 +118,10 @@ def async_add_devices_config(hass, config, async_add_devices): class KNXClimate(ClimateDevice): - """Representation of a KNX climate.""" + """Representation of a KNX climate device.""" def __init__(self, hass, device): - """Initialization of KNXClimate.""" + """Initialize of a KNX climate device.""" self.device = device self.hass = hass self.async_register_callbacks() @@ -149,7 +140,7 @@ class KNXClimate(ClimateDevice): """Register callbacks to update hass after device was changed.""" @asyncio.coroutine def after_update_callback(device): - """Callback after device was updated.""" + """Call after device was updated.""" # pylint: disable=unused-argument yield from self.async_update_ha_state() self.device.register_device_updated_cb(after_update_callback) diff --git a/homeassistant/components/climate/mysensors.py b/homeassistant/components/climate/mysensors.py index db43a6d3be4..ff1400a8fae 100644 --- a/homeassistant/components/climate/mysensors.py +++ b/homeassistant/components/climate/mysensors.py @@ -7,9 +7,10 @@ https://home-assistant.io/components/climate.mysensors/ from homeassistant.components import mysensors from homeassistant.components.climate import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, STATE_AUTO, - STATE_COOL, STATE_HEAT, STATE_OFF, ClimateDevice, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH, - SUPPORT_TARGET_TEMPERATURE_LOW, SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE) + STATE_COOL, 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 ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT DICT_HA_TO_MYS = { @@ -31,7 +32,7 @@ SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_HIGH | def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the mysensors climate.""" + """Set up the MySensors climate.""" mysensors.setup_mysensors_platform( hass, DOMAIN, discovery_info, MySensorsHVAC, add_devices=add_devices) @@ -52,8 +53,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice): @property def temperature_unit(self): """Return the unit of measurement.""" - return (TEMP_CELSIUS - if self.gateway.metric else TEMP_FAHRENHEIT) + return TEMP_CELSIUS if self.gateway.metric else TEMP_FAHRENHEIT @property def current_temperature(self): @@ -139,7 +139,8 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice): self.gateway.set_child_value( self.node_id, self.child_id, value_type, value) if self.gateway.optimistic: - # optimistically assume that device has changed state + # O + # ptimistically assume that device has changed state self._values[value_type] = value self.schedule_update_ha_state() @@ -149,7 +150,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice): self.gateway.set_child_value( self.node_id, self.child_id, set_req.V_HVAC_SPEED, fan) if self.gateway.optimistic: - # optimistically assume that device has changed state + # Optimistically assume that device has changed state self._values[set_req.V_HVAC_SPEED] = fan self.schedule_update_ha_state() @@ -159,7 +160,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice): self.node_id, self.child_id, self.value_type, DICT_HA_TO_MYS[operation_mode]) if self.gateway.optimistic: - # optimistically assume that device has changed state + # Optimistically assume that device has changed state self._values[self.value_type] = operation_mode self.schedule_update_ha_state() diff --git a/homeassistant/components/climate/tesla.py b/homeassistant/components/climate/tesla.py index 6295b85a1b7..459d9c666fd 100644 --- a/homeassistant/components/climate/tesla.py +++ b/homeassistant/components/climate/tesla.py @@ -6,13 +6,13 @@ https://home-assistant.io/components/climate.tesla/ """ import logging -from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.components.climate import ( - ClimateDevice, ENTITY_ID_FORMAT, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_OPERATION_MODE) -from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN, TeslaDevice + ENTITY_ID_FORMAT, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, + ClimateDevice) +from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN +from homeassistant.components.tesla import TeslaDevice from homeassistant.const import ( - TEMP_FAHRENHEIT, TEMP_CELSIUS, ATTR_TEMPERATURE) + ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT) _LOGGER = logging.getLogger(__name__) @@ -60,7 +60,7 @@ class TeslaThermostat(TeslaDevice, ClimateDevice): return OPERATION_LIST def update(self): - """Called by the Tesla device callback to update state.""" + """Call by the Tesla device callback to update state.""" _LOGGER.debug("Updating: %s", self._name) self.tesla_device.update() self._target_temperature = self.tesla_device.get_goal_temp() diff --git a/homeassistant/components/climate/toon.py b/homeassistant/components/climate/toon.py index 0ff9f129081..330801fc231 100644 --- a/homeassistant/components/climate/toon.py +++ b/homeassistant/components/climate/toon.py @@ -7,25 +7,25 @@ Eneco. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/climate.toon/ """ -import homeassistant.components.toon as toon_main from homeassistant.components.climate import ( - ClimateDevice, ATTR_TEMPERATURE, STATE_PERFORMANCE, STATE_HEAT, STATE_ECO, - STATE_COOL, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE) + ATTR_TEMPERATURE, STATE_COOL, STATE_ECO, STATE_HEAT, STATE_PERFORMANCE, + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice) +import homeassistant.components.toon as toon_main from homeassistant.const import TEMP_CELSIUS SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE def setup_platform(hass, config, add_devices, discovery_info=None): - """Set up the Toon thermostat.""" + """Set up the Toon climate device.""" add_devices([ThermostatDevice(hass)], True) class ThermostatDevice(ClimateDevice): - """Interface class for the toon module and HA.""" + """Representation of a Toon climate device.""" def __init__(self, hass): - """Initialize the device.""" + """Initialize the Toon climate device.""" self._name = 'Toon van Eneco' self.hass = hass self.thermos = hass.data[toon_main.TOON_HANDLE] @@ -47,12 +47,12 @@ class ThermostatDevice(ClimateDevice): @property def name(self): - """Name of this Thermostat.""" + """Return the name of this thermostat.""" return self._name @property def temperature_unit(self): - """The unit of measurement used by the platform.""" + """Return the unit of measurement used by the platform.""" return TEMP_CELSIUS @property @@ -63,7 +63,7 @@ class ThermostatDevice(ClimateDevice): @property def operation_list(self): - """List of available operation modes.""" + """Return a list of available operation modes.""" return self._operation_list @property @@ -82,7 +82,7 @@ class ThermostatDevice(ClimateDevice): self.thermos.set_temp(temp) def set_operation_mode(self, operation_mode): - """Set new operation mode as toonlib requires it.""" + """Set new operation mode.""" toonlib_values = { STATE_PERFORMANCE: 'Comfort', STATE_HEAT: 'Home', diff --git a/homeassistant/components/climate/wink.py b/homeassistant/components/climate/wink.py index 33ba0f56d33..2c287fc6846 100644 --- a/homeassistant/components/climate/wink.py +++ b/homeassistant/components/climate/wink.py @@ -8,16 +8,16 @@ import asyncio import logging from homeassistant.components.climate import ( - STATE_ECO, STATE_GAS, STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_ELECTRIC, - STATE_FAN_ONLY, STATE_HEAT_PUMP, ATTR_TEMPERATURE, STATE_HIGH_DEMAND, - STATE_PERFORMANCE, ATTR_TARGET_TEMP_LOW, ATTR_CURRENT_HUMIDITY, - ATTR_TARGET_TEMP_HIGH, ClimateDevice, SUPPORT_TARGET_TEMPERATURE, + ATTR_CURRENT_HUMIDITY, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, + ATTR_TEMPERATURE, STATE_AUTO, STATE_COOL, STATE_ECO, STATE_ELECTRIC, + STATE_FAN_ONLY, STATE_GAS, STATE_HEAT, STATE_HEAT_PUMP, STATE_HIGH_DEMAND, + STATE_PERFORMANCE, SUPPORT_AUX_HEAT, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE, + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW, - SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE, - SUPPORT_AUX_HEAT) + ClimateDevice) from homeassistant.components.wink import DOMAIN, WinkDevice from homeassistant.const import ( - STATE_ON, STATE_OFF, TEMP_CELSIUS, STATE_UNKNOWN, PRECISION_TENTHS) + PRECISION_TENTHS, STATE_OFF, STATE_ON, STATE_UNKNOWN, TEMP_CELSIUS) from homeassistant.helpers.temperature import display_temp as show_temp _LOGGER = logging.getLogger(__name__) @@ -93,7 +93,7 @@ class WinkThermostat(WinkDevice, ClimateDevice): @asyncio.coroutine def async_added_to_hass(self): - """Callback when entity is added to hass.""" + """Call when entity is added to hass.""" self.hass.data[DOMAIN]['entities']['climate'].append(self) @property @@ -104,7 +104,7 @@ class WinkThermostat(WinkDevice, ClimateDevice): @property def device_state_attributes(self): - """Return the optional state attributes.""" + """Return the optional device state attributes.""" data = {} target_temp_high = self.target_temperature_high target_temp_low = self.target_temperature_low @@ -385,7 +385,7 @@ class WinkAC(WinkDevice, ClimateDevice): @property def device_state_attributes(self): - """Return the optional state attributes.""" + """Return the optional device state attributes.""" data = {} target_temp_high = self.target_temperature_high target_temp_low = self.target_temperature_low @@ -508,7 +508,7 @@ class WinkWaterHeater(WinkDevice, ClimateDevice): @property def device_state_attributes(self): - """Return the optional state attributes.""" + """Return the optional device state attributes.""" data = {} data[ATTR_VACATION_MODE] = self.wink.vacation_mode_enabled() data[ATTR_RHEEM_TYPE] = self.wink.rheem_type() diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index e497f4677e4..a5bbf805d42 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -1,4 +1,9 @@ -"""Component to integrate the Home Assistant cloud.""" +""" +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/ +""" import asyncio from datetime import datetime import json @@ -26,18 +31,18 @@ REQUIREMENTS = ['warrant==0.6.1'] _LOGGER = logging.getLogger(__name__) CONF_ALEXA = 'alexa' -CONF_GOOGLE_ACTIONS = 'google_actions' -CONF_FILTER = 'filter' +CONF_ALIASES = 'aliases' CONF_COGNITO_CLIENT_ID = 'cognito_client_id' +CONF_ENTITY_CONFIG = 'entity_config' +CONF_FILTER = 'filter' +CONF_GOOGLE_ACTIONS = 'google_actions' CONF_RELAYER = 'relayer' CONF_USER_POOL_ID = 'user_pool_id' -CONF_ALIASES = 'aliases' -MODE_DEV = 'development' DEFAULT_MODE = 'production' DEPENDENCIES = ['http'] -CONF_ENTITY_CONFIG = 'entity_config' +MODE_DEV = 'development' ALEXA_ENTITY_SCHEMA = vol.Schema({ vol.Optional(alexa_sh.CONF_DESCRIPTION): cv.string, @@ -149,7 +154,7 @@ class Cloud: @property def subscription_expired(self): - """Return a boolen if the subscription has expired.""" + """Return a boolean if the subscription has expired.""" return dt_util.utcnow() > self.expiration_date @property @@ -195,8 +200,8 @@ class Cloud: if not jwt_success: return False - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, - self._start_cloud) + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_START, self._start_cloud) return True @@ -248,7 +253,7 @@ class Cloud: for token in 'id_token', 'access_token': self._decode_claims(info[token]) except ValueError as err: # Raised when token is invalid - _LOGGER.warning('Found invalid token %s: %s', token, err) + _LOGGER.warning("Found invalid token %s: %s", token, err) return self.id_token = info['id_token'] @@ -282,15 +287,15 @@ class Cloud: header = jwt.get_unverified_header(token) except jose_exceptions.JWTError as err: raise ValueError(str(err)) from None - kid = header.get("kid") + kid = header.get('kid') if kid is None: - raise ValueError('No kid in header') + raise ValueError("No kid in header") # Locate the key for this kid key = None - for key_dict in self.jwt_keyset["keys"]: - if key_dict["kid"] == kid: + for key_dict in self.jwt_keyset['keys']: + if key_dict['kid'] == kid: key = key_dict break if not key: diff --git a/homeassistant/components/cloud/auth_api.py b/homeassistant/components/cloud/auth_api.py index 500ff062a0f..e96f2a2d8a5 100644 --- a/homeassistant/components/cloud/auth_api.py +++ b/homeassistant/components/cloud/auth_api.py @@ -1,7 +1,6 @@ """Package to communicate with the authentication API.""" import logging - _LOGGER = logging.getLogger(__name__) @@ -22,7 +21,7 @@ class UserNotConfirmed(CloudError): class ExpiredCode(CloudError): - """Raised when an expired code is encoutered.""" + """Raised when an expired code is encountered.""" class InvalidCode(CloudError): @@ -38,7 +37,7 @@ class PasswordChangeRequired(CloudError): class UnknownError(CloudError): - """Raised when an unknown error occurrs.""" + """Raised when an unknown error occurs.""" AWS_EXCEPTIONS = { @@ -98,7 +97,7 @@ def resend_email_confirm(cloud, email): def forgot_password(cloud, email): - """Initiate forgotten password flow.""" + """Initialize forgotten password flow.""" from botocore.exceptions import ClientError cognito = _cognito(cloud, username=email) diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index 338e004ce52..af966e180eb 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -3,8 +3,8 @@ import asyncio from functools import wraps import logging -import voluptuous as vol import async_timeout +import voluptuous as vol from homeassistant.components.http import ( HomeAssistantView, RequestDataValidator) @@ -17,7 +17,7 @@ _LOGGER = logging.getLogger(__name__) @asyncio.coroutine def async_setup(hass): - """Initialize the HTTP api.""" + """Initialize the HTTP API.""" hass.http.register_view(CloudLoginView) hass.http.register_view(CloudLogoutView) hass.http.register_view(CloudAccountView) @@ -40,7 +40,7 @@ _CLOUD_ERRORS = { def _handle_cloud_errors(handler): - """Helper method to handle auth errors.""" + """Handle auth errors.""" @asyncio.coroutine @wraps(handler) def error_handler(view, request, *args, **kwargs): diff --git a/homeassistant/components/cloud/iot.py b/homeassistant/components/cloud/iot.py index ffe68c3c877..2d3ab025e43 100644 --- a/homeassistant/components/cloud/iot.py +++ b/homeassistant/components/cloud/iot.py @@ -12,7 +12,6 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from . import auth_api from .const import MESSAGE_EXPIRATION - HANDLERS = Registry() _LOGGER = logging.getLogger(__name__) @@ -85,7 +84,7 @@ class CloudIoT: }) self.tries = 0 - _LOGGER.info('Connected') + _LOGGER.info("Connected") self.state = STATE_CONNECTED while not client.closed: @@ -107,7 +106,7 @@ class CloudIoT: disconnect_warn = 'Received invalid JSON.' break - _LOGGER.debug('Received message: %s', msg) + _LOGGER.debug("Received message: %s", msg) response = { 'msgid': msg['msgid'], @@ -126,14 +125,14 @@ class CloudIoT: response['error'] = 'unknown-handler' except Exception: # pylint: disable=broad-except - _LOGGER.exception('Error handling message') + _LOGGER.exception("Error handling message") response['error'] = 'exception' - _LOGGER.debug('Publishing message: %s', response) + _LOGGER.debug("Publishing message: %s", response) yield from client.send_json(response) except auth_api.CloudError: - _LOGGER.warning('Unable to connect: Unable to refresh token.') + _LOGGER.warning("Unable to connect: Unable to refresh token.") except client_exceptions.WSServerHandshakeError as err: if err.code == 401: @@ -141,18 +140,18 @@ class CloudIoT: self.close_requested = True # Should we notify user? else: - _LOGGER.warning('Unable to connect: %s', err) + _LOGGER.warning("Unable to connect: %s", err) except client_exceptions.ClientError as err: - _LOGGER.warning('Unable to connect: %s', err) + _LOGGER.warning("Unable to connect: %s", err) except Exception: # pylint: disable=broad-except if not self.close_requested: - _LOGGER.exception('Unexpected error') + _LOGGER.exception("Unexpected error") finally: if disconnect_warn is not None: - _LOGGER.warning('Connection closed: %s', disconnect_warn) + _LOGGER.warning("Connection closed: %s", disconnect_warn) if remove_hass_stop_listener is not None: remove_hass_stop_listener() @@ -169,7 +168,7 @@ class CloudIoT: self.tries += 1 try: - # Sleep 0, 5, 10, 15 … up to 30 seconds between retries + # Sleep 0, 5, 10, 15 ... up to 30 seconds between retries self.retry_task = hass.async_add_job(asyncio.sleep( min(30, (self.tries - 1) * 5), loop=hass.loop)) yield from self.retry_task @@ -205,8 +204,8 @@ def async_handle_message(hass, cloud, handler_name, payload): @asyncio.coroutine def async_handle_alexa(hass, cloud, payload): """Handle an incoming IoT message for Alexa.""" - result = yield from alexa.async_handle_message(hass, cloud.alexa_config, - payload) + result = yield from alexa.async_handle_message( + hass, cloud.alexa_config, payload) return result @@ -214,8 +213,8 @@ def async_handle_alexa(hass, cloud, payload): @asyncio.coroutine def async_handle_google_actions(hass, cloud, payload): """Handle an incoming IoT message for Google Actions.""" - result = yield from ga.async_handle_message(hass, cloud.gactions_config, - payload) + result = yield from ga.async_handle_message( + hass, cloud.gactions_config, payload) return result @@ -227,9 +226,9 @@ def async_handle_cloud(hass, cloud, payload): if action == 'logout': yield from cloud.logout() - _LOGGER.error('You have been logged out from Home Assistant cloud: %s', + _LOGGER.error("You have been logged out from Home Assistant cloud: %s", payload['reason']) else: - _LOGGER.warning('Received unknown cloud action: %s', action) + _LOGGER.warning("Received unknown cloud action: %s", action) return None diff --git a/homeassistant/components/comfoconnect.py b/homeassistant/components/comfoconnect.py index ba2180078e3..425ed6f9c9a 100644 --- a/homeassistant/components/comfoconnect.py +++ b/homeassistant/components/comfoconnect.py @@ -8,11 +8,11 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_TOKEN, CONF_PIN, EVENT_HOMEASSISTANT_STOP) -from homeassistant.helpers import (discovery) -from homeassistant.helpers.dispatcher import (dispatcher_send) + CONF_HOST, CONF_NAME, CONF_PIN, CONF_TOKEN, EVENT_HOMEASSISTANT_STOP) +from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import dispatcher_send REQUIREMENTS = ['pycomfoconnect==0.3'] @@ -115,7 +115,7 @@ class ComfoConnectBridge(object): self.comfoconnect.disconnect() def sensor_callback(self, var, value): - """Callback function for sensor updates.""" + """Call function for sensor updates.""" _LOGGER.debug("Got value from bridge: %d = %d", var, value) from pycomfoconnect import ( diff --git a/homeassistant/components/cover/knx.py b/homeassistant/components/cover/knx.py index d8313caeb5f..79c57c41e90 100644 --- a/homeassistant/components/cover/knx.py +++ b/homeassistant/components/cover/knx.py @@ -5,17 +5,18 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/cover.knx/ """ import asyncio + import voluptuous as vol -from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES -from homeassistant.helpers.event import async_track_utc_time_change from homeassistant.components.cover import ( - CoverDevice, PLATFORM_SCHEMA, SUPPORT_OPEN, SUPPORT_CLOSE, - SUPPORT_SET_POSITION, SUPPORT_STOP, SUPPORT_SET_TILT_POSITION, - ATTR_POSITION, ATTR_TILT_POSITION) -from homeassistant.core import callback + ATTR_POSITION, ATTR_TILT_POSITION, PLATFORM_SCHEMA, SUPPORT_CLOSE, + SUPPORT_OPEN, SUPPORT_SET_POSITION, SUPPORT_SET_TILT_POSITION, + SUPPORT_STOP, CoverDevice) +from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX from homeassistant.const import CONF_NAME +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import async_track_utc_time_change CONF_MOVE_LONG_ADDRESS = 'move_long_address' CONF_MOVE_SHORT_ADDRESS = 'move_short_address' @@ -50,20 +51,16 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine -def async_setup_platform(hass, config, async_add_devices, - discovery_info=None): +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up cover(s) for KNX platform.""" - if DATA_KNX not in hass.data \ - or not hass.data[DATA_KNX].initialized: - return False + if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized: + return if discovery_info is not None: async_add_devices_discovery(hass, discovery_info, async_add_devices) else: async_add_devices_config(hass, config, async_add_devices) - return True - @callback def async_add_devices_discovery(hass, discovery_info, async_add_devices): @@ -114,7 +111,7 @@ class KNXCover(CoverDevice): """Register callbacks to update hass after device was changed.""" @asyncio.coroutine def after_update_callback(device): - """Callback after device was updated.""" + """Call after device was updated.""" # pylint: disable=unused-argument yield from self.async_update_ha_state() self.device.register_device_updated_cb(after_update_callback) @@ -209,7 +206,7 @@ class KNXCover(CoverDevice): @callback def auto_updater_hook(self, now): - """Callback for autoupdater.""" + """Call for the autoupdater.""" # pylint: disable=unused-argument self.async_schedule_update_ha_state() if self.device.position_reached(): diff --git a/homeassistant/components/cover/mysensors.py b/homeassistant/components/cover/mysensors.py index cd4ff62b3e9..391d2a22bda 100644 --- a/homeassistant/components/cover/mysensors.py +++ b/homeassistant/components/cover/mysensors.py @@ -5,12 +5,12 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/cover.mysensors/ """ from homeassistant.components import mysensors -from homeassistant.components.cover import CoverDevice, ATTR_POSITION, DOMAIN -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.components.cover import ATTR_POSITION, DOMAIN, CoverDevice +from homeassistant.const import STATE_OFF, STATE_ON def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the mysensors platform for covers.""" + """Set up the MySensors platform for covers.""" mysensors.setup_mysensors_platform( hass, DOMAIN, discovery_info, MySensorsCover, add_devices=add_devices) diff --git a/homeassistant/components/cover/rfxtrx.py b/homeassistant/components/cover/rfxtrx.py index 66f2fde52f4..aefb7ab89d7 100644 --- a/homeassistant/components/cover/rfxtrx.py +++ b/homeassistant/components/cover/rfxtrx.py @@ -29,12 +29,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the RFXtrx cover.""" import RFXtrx as rfxtrxmod covers = rfxtrx.get_devices_from_config(config, RfxtrxCover) - add_devices_callback(covers) + add_devices(covers) def cover_update(event): """Handle cover updates from the RFXtrx gateway.""" @@ -45,7 +45,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): new_device = rfxtrx.get_new_device(event, config, RfxtrxCover) if new_device: - add_devices_callback([new_device]) + add_devices([new_device]) rfxtrx.apply_received_command(event) @@ -59,7 +59,7 @@ class RfxtrxCover(rfxtrx.RfxtrxDevice, CoverDevice): @property def should_poll(self): - """No polling available in RFXtrx cover.""" + """Return the polling state. No polling available in RFXtrx cover.""" return False @property diff --git a/homeassistant/components/cover/wink.py b/homeassistant/components/cover/wink.py index ce96b4d75e0..b3276bdf8cc 100644 --- a/homeassistant/components/cover/wink.py +++ b/homeassistant/components/cover/wink.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/cover.wink/ import asyncio from homeassistant.components.cover import CoverDevice -from homeassistant.components.wink import WinkDevice, DOMAIN +from homeassistant.components.wink import DOMAIN, WinkDevice DEPENDENCIES = ['wink'] @@ -31,7 +31,7 @@ class WinkCoverDevice(WinkDevice, CoverDevice): @asyncio.coroutine def async_added_to_hass(self): - """Callback when entity is added to hass.""" + """Call when entity is added to hass.""" self.hass.data[DOMAIN]['entities']['cover'].append(self) def close_cover(self, **kwargs): diff --git a/homeassistant/components/datadog.py b/homeassistant/components/datadog.py index 2c8145177b7..58503d7187b 100644 --- a/homeassistant/components/datadog.py +++ b/homeassistant/components/datadog.py @@ -5,11 +5,12 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/datadog/ """ import logging + import voluptuous as vol -from homeassistant.const import (CONF_HOST, CONF_PORT, CONF_PREFIX, - EVENT_LOGBOOK_ENTRY, EVENT_STATE_CHANGED, - STATE_UNKNOWN) +from homeassistant.const import ( + CONF_HOST, CONF_PORT, CONF_PREFIX, EVENT_LOGBOOK_ENTRY, + EVENT_STATE_CHANGED, STATE_UNKNOWN) from homeassistant.helpers import state as state_helper import homeassistant.helpers.config_validation as cv @@ -36,7 +37,7 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): - """Setup the Datadog component.""" + """Set up the Datadog component.""" from datadog import initialize, statsd conf = config[DOMAIN] @@ -81,36 +82,19 @@ def setup(hass, config): if isinstance(value, (float, int)): attribute = "{}.{}".format(metric, key.replace(' ', '_')) statsd.gauge( - attribute, - value, - sample_rate=sample_rate, - tags=tags - ) + attribute, value, sample_rate=sample_rate, tags=tags) _LOGGER.debug( - 'Sent metric %s: %s (tags: %s)', - attribute, - value, - tags - ) + "Sent metric %s: %s (tags: %s)", attribute, value, tags) try: value = state_helper.state_as_number(state) except ValueError: _LOGGER.debug( - 'Error sending %s: %s (tags: %s)', - metric, - state.state, - tags - ) + "Error sending %s: %s (tags: %s)", metric, state.state, tags) return - statsd.gauge( - metric, - value, - sample_rate=sample_rate, - tags=tags - ) + statsd.gauge(metric, value, sample_rate=sample_rate, tags=tags) _LOGGER.debug('Sent metric %s: %s (tags: %s)', metric, value, tags) diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 72845ee671f..269b8136020 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -60,7 +60,7 @@ def async_setup(hass, config): @asyncio.coroutine def async_deconz_discovered(service, discovery_info): - """Called when deCONZ gateway has been found.""" + """Call when deCONZ gateway has been found.""" deconz_config = {} deconz_config[CONF_HOST] = discovery_info.get(CONF_HOST) deconz_config[CONF_PORT] = discovery_info.get(CONF_PORT) @@ -90,13 +90,13 @@ def async_setup_deconz(hass, config, deconz_config): Load config, group, light and sensor data for server information. Start websocket for push notification of state changes from deCONZ. """ - _LOGGER.debug('deCONZ config %s', deconz_config) + _LOGGER.debug("deCONZ config %s", deconz_config) from pydeconz import DeconzSession websession = async_get_clientsession(hass) deconz = DeconzSession(hass.loop, websession, **deconz_config) result = yield from deconz.async_load_parameters() if result is False: - _LOGGER.error("Failed to communicate with deCONZ.") + _LOGGER.error("Failed to communicate with deCONZ") return False hass.data[DOMAIN] = deconz @@ -126,8 +126,7 @@ def async_setup_deconz(hass, config, deconz_config): data = call.data.get(SERVICE_DATA) yield from deconz.async_put_state(field, data) hass.services.async_register( - DOMAIN, 'configure', async_configure, - schema=SERVICE_SCHEMA) + DOMAIN, 'configure', async_configure, schema=SERVICE_SCHEMA) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, deconz.close) return True diff --git a/homeassistant/components/device_tracker/automatic.py b/homeassistant/components/device_tracker/automatic.py index ef747657cb4..5ad3995ad2a 100644 --- a/homeassistant/components/device_tracker/automatic.py +++ b/homeassistant/components/device_tracker/automatic.py @@ -14,8 +14,8 @@ from aiohttp import web import voluptuous as vol from homeassistant.components.device_tracker import ( - PLATFORM_SCHEMA, ATTR_ATTRIBUTES, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_MAC, - ATTR_GPS, ATTR_GPS_ACCURACY) + ATTR_ATTRIBUTES, ATTR_DEV_ID, ATTR_GPS, ATTR_GPS_ACCURACY, ATTR_HOST_NAME, + ATTR_MAC, PLATFORM_SCHEMA) from homeassistant.components.http import HomeAssistantView from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback @@ -24,35 +24,33 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_time_interval REQUIREMENTS = ['aioautomatic==0.6.4'] -DEPENDENCIES = ['http'] _LOGGER = logging.getLogger(__name__) -CONF_CLIENT_ID = 'client_id' -CONF_SECRET = 'secret' -CONF_DEVICES = 'devices' -CONF_CURRENT_LOCATION = 'current_location' - -DEFAULT_TIMEOUT = 5 - -DEFAULT_SCOPE = ['location', 'trip', 'vehicle:events', 'vehicle:profile'] -FULL_SCOPE = DEFAULT_SCOPE + ['current_location'] - ATTR_FUEL_LEVEL = 'fuel_level' - -EVENT_AUTOMATIC_UPDATE = 'automatic_update' - AUTOMATIC_CONFIG_FILE = '.automatic/session-{}.json' +CONF_CLIENT_ID = 'client_id' +CONF_CURRENT_LOCATION = 'current_location' +CONF_DEVICES = 'devices' +CONF_SECRET = 'secret' + DATA_CONFIGURING = 'automatic_configurator_clients' DATA_REFRESH_TOKEN = 'refresh_token' +DEFAULT_SCOPE = ['location', 'trip', 'vehicle:events', 'vehicle:profile'] +DEFAULT_TIMEOUT = 5 +DEPENDENCIES = ['http'] + +EVENT_AUTOMATIC_UPDATE = 'automatic_update' + +FULL_SCOPE = DEFAULT_SCOPE + ['current_location'] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_CLIENT_ID): cv.string, vol.Required(CONF_SECRET): cv.string, vol.Optional(CONF_CURRENT_LOCATION, default=False): cv.boolean, - vol.Optional(CONF_DEVICES, default=None): vol.All( - cv.ensure_list, [cv.string]) + vol.Optional(CONF_DEVICES, default=None): + vol.All(cv.ensure_list, [cv.string]), }) @@ -142,7 +140,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): @asyncio.coroutine def initialize_callback(code, state): - """Callback after OAuth2 response is returned.""" + """Call after OAuth2 response is returned.""" try: session = yield from client.create_session_from_oauth_code( code, state) @@ -181,12 +179,12 @@ class AutomaticAuthCallbackView(HomeAssistantView): return response else: _LOGGER.error( - "Error authorizing Automatic. Invalid response returned.") + "Error authorizing Automatic. Invalid response returned") return response if DATA_CONFIGURING not in hass.data or \ params['state'] not in hass.data[DATA_CONFIGURING]: - _LOGGER.error("Automatic configuration request not found.") + _LOGGER.error("Automatic configuration request not found") return response code = params['code'] @@ -220,16 +218,15 @@ class AutomaticData(object): @asyncio.coroutine def handle_event(self, name, event): - """Coroutine to update state for a realtime event.""" + """Coroutine to update state for a real time event.""" import aioautomatic - # Fire a hass event self.hass.bus.async_fire(EVENT_AUTOMATIC_UPDATE, event.data) if event.vehicle.id not in self.vehicle_info: # If vehicle hasn't been seen yet, request the detailed # info for this vehicle. - _LOGGER.info("New vehicle found.") + _LOGGER.info("New vehicle found") try: vehicle = yield from event.get_vehicle() except aioautomatic.exceptions.AutomaticError as err: @@ -240,7 +237,7 @@ class AutomaticData(object): if event.created_at < self.vehicle_seen[event.vehicle.id]: # Skip events received out of order _LOGGER.debug("Skipping out of order event. Event Created %s. " - "Last seen event: %s.", event.created_at, + "Last seen event: %s", event.created_at, self.vehicle_seen[event.vehicle.id]) return self.vehicle_seen[event.vehicle.id] = event.created_at @@ -270,13 +267,13 @@ class AutomaticData(object): self.ws_close_requested = False if self.ws_reconnect_handle is not None: - _LOGGER.debug("Retrying websocket connection.") + _LOGGER.debug("Retrying websocket connection") try: ws_loop_future = yield from self.client.ws_connect() except aioautomatic.exceptions.UnauthorizedClientError: _LOGGER.error("Client unauthorized for websocket connection. " "Ensure Websocket is selected in the Automatic " - "developer application event delivery preferences.") + "developer application event delivery preferences") return except aioautomatic.exceptions.AutomaticError as err: if self.ws_reconnect_handle is None: @@ -290,14 +287,14 @@ class AutomaticData(object): self.ws_reconnect_handle() self.ws_reconnect_handle = None - _LOGGER.info("Websocket connected.") + _LOGGER.info("Websocket connected") try: yield from ws_loop_future except aioautomatic.exceptions.AutomaticError as err: _LOGGER.error(str(err)) - _LOGGER.info("Websocket closed.") + _LOGGER.info("Websocket closed") # If websocket was close was not requested, attempt to reconnect if not self.ws_close_requested: diff --git a/homeassistant/components/device_tracker/ubus.py b/homeassistant/components/device_tracker/ubus.py index 99f20d4385e..2306a66070b 100644 --- a/homeassistant/components/device_tracker/ubus.py +++ b/homeassistant/components/device_tracker/ubus.py @@ -11,11 +11,11 @@ import re import requests import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.exceptions import HomeAssistantError +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -30,8 +30,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_DHCP_SOFTWARE, - default=DEFAULT_DHCP_SOFTWARE): vol.In(DHCP_SOFTWARES) + vol.Optional(CONF_DHCP_SOFTWARE, default=DEFAULT_DHCP_SOFTWARE): + vol.In(DHCP_SOFTWARES), }) @@ -49,14 +49,14 @@ def get_scanner(hass, config): def _refresh_on_acccess_denied(func): """If remove rebooted, it lost our session so rebuld one and try again.""" def decorator(self, *args, **kwargs): - """Wrapper function to refresh session_id on PermissionError.""" + """Wrap the function to refresh session_id on PermissionError.""" try: return func(self, *args, **kwargs) except PermissionError: _LOGGER.warning("Invalid session detected." + - " Tryign to refresh session_id and re-run the rpc") - self.session_id = _get_session_id(self.url, self.username, - self.password) + " Trying to refresh session_id and re-run RPC") + self.session_id = _get_session_id( + self.url, self.username, self.password) return func(self, *args, **kwargs) @@ -80,8 +80,8 @@ class UbusDeviceScanner(DeviceScanner): self.last_results = {} self.url = 'http://{}/ubus'.format(host) - self.session_id = _get_session_id(self.url, self.username, - self.password) + self.session_id = _get_session_id( + self.url, self.username, self.password) self.hostapd = [] self.mac2name = None self.success_init = self.session_id is not None diff --git a/homeassistant/components/device_tracker/upc_connect.py b/homeassistant/components/device_tracker/upc_connect.py index fbcd753713c..ea0645e012f 100644 --- a/homeassistant/components/device_tracker/upc_connect.py +++ b/homeassistant/components/device_tracker/upc_connect.py @@ -84,7 +84,7 @@ class UPCDeviceScanner(DeviceScanner): @asyncio.coroutine def async_get_device_name(self, device): - """The firmware doesn't save the name of the wireless device.""" + """Get the device name (the name of the wireless device not used).""" return None @asyncio.coroutine diff --git a/homeassistant/components/fan/dyson.py b/homeassistant/components/fan/dyson.py index f2630aa98d2..c5e5b8736ae 100644 --- a/homeassistant/components/fan/dyson.py +++ b/homeassistant/components/fan/dyson.py @@ -3,38 +3,42 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/fan.dyson/ """ -import logging import asyncio -import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.fan import (FanEntity, SUPPORT_OSCILLATE, - SUPPORT_SET_SPEED, - DOMAIN) -from homeassistant.helpers.entity import ToggleEntity -from homeassistant.components.dyson import DYSON_DEVICES +import logging -DEPENDENCIES = ['dyson'] +import voluptuous as vol + +from homeassistant.components.dyson import DYSON_DEVICES +from homeassistant.components.fan import ( + DOMAIN, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity) +from homeassistant.const import CONF_ENTITY_ID +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import ToggleEntity _LOGGER = logging.getLogger(__name__) +CONF_NIGHT_MODE = 'night_mode' + +DEPENDENCIES = ['dyson'] +DYSON_FAN_DEVICES = 'dyson_fan_devices' -DYSON_FAN_DEVICES = "dyson_fan_devices" SERVICE_SET_NIGHT_MODE = 'dyson_set_night_mode' DYSON_SET_NIGHT_MODE_SCHEMA = vol.Schema({ - vol.Required('entity_id'): cv.entity_id, - vol.Required('night_mode'): cv.boolean + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_NIGHT_MODE): cv.boolean, }) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Dyson fan components.""" - _LOGGER.info("Creating new Dyson fans") + """Set up the Dyson fan components.""" + from libpurecoollink.dyson_pure_cool_link import DysonPureCoolLink + + _LOGGER.debug("Creating new Dyson fans") if DYSON_FAN_DEVICES not in hass.data: hass.data[DYSON_FAN_DEVICES] = [] # Get Dyson Devices from parent component - from libpurecoollink.dyson_pure_cool_link import DysonPureCoolLink for device in [d for d in hass.data[DYSON_DEVICES] if isinstance(d, DysonPureCoolLink)]: dyson_entity = DysonPureCoolLinkDevice(hass, device) @@ -43,9 +47,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(hass.data[DYSON_FAN_DEVICES]) def service_handle(service): - """Handle dyson services.""" - entity_id = service.data.get('entity_id') - night_mode = service.data.get('night_mode') + """Handle the Dyson services.""" + entity_id = service.data.get(CONF_ENTITY_ID) + night_mode = service.data.get(CONF_NIGHT_MODE) fan_device = next([fan for fan in hass.data[DYSON_FAN_DEVICES] if fan.entity_id == entity_id].__iter__(), None) if fan_device is None: @@ -57,9 +61,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): fan_device.night_mode(night_mode) # Register dyson service(s) - hass.services.register(DOMAIN, SERVICE_SET_NIGHT_MODE, - service_handle, - schema=DYSON_SET_NIGHT_MODE_SCHEMA) + hass.services.register( + DOMAIN, SERVICE_SET_NIGHT_MODE, service_handle, + schema=DYSON_SET_NIGHT_MODE_SCHEMA) class DysonPureCoolLinkDevice(FanEntity): @@ -67,21 +71,22 @@ class DysonPureCoolLinkDevice(FanEntity): def __init__(self, hass, device): """Initialize the fan.""" - _LOGGER.info("Creating device %s", device.name) + _LOGGER.debug("Creating device %s", device.name) self.hass = hass self._device = device @asyncio.coroutine def async_added_to_hass(self): - """Callback when entity is added to hass.""" + """Call when entity is added to hass.""" self.hass.async_add_job( self._device.add_message_listener, self.on_message) def on_message(self, message): - """Called when new messages received from the fan.""" + """Call when new messages received from the fan.""" from libpurecoollink.dyson_pure_state import DysonPureCoolState + if isinstance(message, DysonPureCoolState): - _LOGGER.debug("Message received for fan device %s : %s", self.name, + _LOGGER.debug("Message received for fan device %s: %s", self.name, message) self.schedule_update_ha_state() @@ -97,41 +102,46 @@ class DysonPureCoolLinkDevice(FanEntity): def set_speed(self: ToggleEntity, speed: str) -> None: """Set the speed of the fan. Never called ??.""" - _LOGGER.debug("Set fan speed to: " + speed) from libpurecoollink.const import FanSpeed, FanMode + + _LOGGER.debug("Set fan speed to: %s", speed) + if speed == FanSpeed.FAN_SPEED_AUTO.value: self._device.set_configuration(fan_mode=FanMode.AUTO) else: fan_speed = FanSpeed('{0:04d}'.format(int(speed))) - self._device.set_configuration(fan_mode=FanMode.FAN, - fan_speed=fan_speed) + self._device.set_configuration( + fan_mode=FanMode.FAN, fan_speed=fan_speed) def turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None: """Turn on the fan.""" - _LOGGER.debug("Turn on fan %s with speed %s", self.name, speed) from libpurecoollink.const import FanSpeed, FanMode + + _LOGGER.debug("Turn on fan %s with speed %s", self.name, speed) if speed: if speed == FanSpeed.FAN_SPEED_AUTO.value: self._device.set_configuration(fan_mode=FanMode.AUTO) else: fan_speed = FanSpeed('{0:04d}'.format(int(speed))) - self._device.set_configuration(fan_mode=FanMode.FAN, - fan_speed=fan_speed) + self._device.set_configuration( + fan_mode=FanMode.FAN, fan_speed=fan_speed) else: # Speed not set, just turn on self._device.set_configuration(fan_mode=FanMode.FAN) def turn_off(self: ToggleEntity, **kwargs) -> None: """Turn off the fan.""" - _LOGGER.debug("Turn off fan %s", self.name) from libpurecoollink.const import FanMode + + _LOGGER.debug("Turn off fan %s", self.name) self._device.set_configuration(fan_mode=FanMode.OFF) def oscillate(self: ToggleEntity, oscillating: bool) -> None: """Turn on/off oscillating.""" + from libpurecoollink.const import Oscillation + _LOGGER.debug("Turn oscillation %s for device %s", oscillating, self.name) - from libpurecoollink.const import Oscillation if oscillating: self._device.set_configuration( @@ -155,8 +165,9 @@ class DysonPureCoolLinkDevice(FanEntity): @property def speed(self) -> str: """Return the current speed.""" + from libpurecoollink.const import FanSpeed + if self._device.state: - from libpurecoollink.const import FanSpeed if self._device.state.speed == FanSpeed.FAN_SPEED_AUTO.value: return self._device.state.speed return int(self._device.state.speed) @@ -174,8 +185,9 @@ class DysonPureCoolLinkDevice(FanEntity): def night_mode(self: ToggleEntity, night_mode: bool) -> None: """Turn fan in night mode.""" - _LOGGER.debug("Set %s night mode %s", self.name, night_mode) from libpurecoollink.const import NightMode + + _LOGGER.debug("Set %s night mode %s", self.name, night_mode) if night_mode: self._device.set_configuration(night_mode=NightMode.NIGHT_MODE_ON) else: @@ -188,8 +200,9 @@ class DysonPureCoolLinkDevice(FanEntity): def auto_mode(self: ToggleEntity, auto_mode: bool) -> None: """Turn fan in auto mode.""" - _LOGGER.debug("Set %s auto mode %s", self.name, auto_mode) from libpurecoollink.const import FanMode + + _LOGGER.debug("Set %s auto mode %s", self.name, auto_mode) if auto_mode: self._device.set_configuration(fan_mode=FanMode.AUTO) else: @@ -199,17 +212,20 @@ class DysonPureCoolLinkDevice(FanEntity): def speed_list(self: ToggleEntity) -> list: """Get the list of available speeds.""" from libpurecoollink.const import FanSpeed - supported_speeds = [FanSpeed.FAN_SPEED_AUTO.value, - int(FanSpeed.FAN_SPEED_1.value), - int(FanSpeed.FAN_SPEED_2.value), - int(FanSpeed.FAN_SPEED_3.value), - int(FanSpeed.FAN_SPEED_4.value), - int(FanSpeed.FAN_SPEED_5.value), - int(FanSpeed.FAN_SPEED_6.value), - int(FanSpeed.FAN_SPEED_7.value), - int(FanSpeed.FAN_SPEED_8.value), - int(FanSpeed.FAN_SPEED_9.value), - int(FanSpeed.FAN_SPEED_10.value)] + + supported_speeds = [ + FanSpeed.FAN_SPEED_AUTO.value, + int(FanSpeed.FAN_SPEED_1.value), + int(FanSpeed.FAN_SPEED_2.value), + int(FanSpeed.FAN_SPEED_3.value), + int(FanSpeed.FAN_SPEED_4.value), + int(FanSpeed.FAN_SPEED_5.value), + int(FanSpeed.FAN_SPEED_6.value), + int(FanSpeed.FAN_SPEED_7.value), + int(FanSpeed.FAN_SPEED_8.value), + int(FanSpeed.FAN_SPEED_9.value), + int(FanSpeed.FAN_SPEED_10.value), + ] return supported_speeds diff --git a/homeassistant/components/fan/wink.py b/homeassistant/components/fan/wink.py index 3920e606d90..827f134cc08 100644 --- a/homeassistant/components/fan/wink.py +++ b/homeassistant/components/fan/wink.py @@ -7,20 +7,18 @@ https://home-assistant.io/components/fan.wink/ import asyncio import logging -from homeassistant.components.fan import (FanEntity, SPEED_HIGH, - SPEED_LOW, SPEED_MEDIUM, - STATE_UNKNOWN, SUPPORT_SET_SPEED, - SUPPORT_DIRECTION) +from homeassistant.components.fan import ( + SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, STATE_UNKNOWN, SUPPORT_DIRECTION, + SUPPORT_SET_SPEED, FanEntity) +from homeassistant.components.wink import DOMAIN, WinkDevice from homeassistant.helpers.entity import ToggleEntity -from homeassistant.components.wink import WinkDevice, DOMAIN - -DEPENDENCIES = ['wink'] _LOGGER = logging.getLogger(__name__) -SPEED_LOWEST = 'lowest' -SPEED_AUTO = 'auto' +DEPENDENCIES = ['wink'] +SPEED_AUTO = 'auto' +SPEED_LOWEST = 'lowest' SUPPORTED_FEATURES = SUPPORT_DIRECTION + SUPPORT_SET_SPEED @@ -38,7 +36,7 @@ class WinkFanDevice(WinkDevice, FanEntity): @asyncio.coroutine def async_added_to_hass(self): - """Callback when entity is added to hass.""" + """Call when entity is added to hass.""" self.hass.data[DOMAIN]['entities']['fan'].append(self) def set_direction(self: ToggleEntity, direction: str) -> None: diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 002f680927e..ea8a4d92540 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -260,10 +260,10 @@ def async_register_panel(hass, component_name, path, md5=None, component_name: name of the web component path: path to the HTML of the web component (required unless url is provided) - md5: the md5 hash of the web component (for versioning in url, optional) + md5: the md5 hash of the web component (for versioning in URL, optional) sidebar_title: title to show in the sidebar (optional) sidebar_icon: icon to show next to title in sidebar (optional) - url_path: name to use in the url (defaults to component_name) + url_path: name to use in the URL (defaults to component_name) config: config to be passed into the web component """ panel = ExternalPanel(component_name, path, md5, sidebar_title, diff --git a/homeassistant/components/hassio.py b/homeassistant/components/hassio.py index 4e4e6e7e64c..efd83c7ea9e 100644 --- a/homeassistant/components/hassio.py +++ b/homeassistant/components/hassio.py @@ -12,21 +12,22 @@ import re import aiohttp from aiohttp import web -from aiohttp.web_exceptions import HTTPBadGateway from aiohttp.hdrs import CONTENT_TYPE +from aiohttp.web_exceptions import HTTPBadGateway import async_timeout import voluptuous as vol -from homeassistant.core import callback, DOMAIN as HASS_DOMAIN -from homeassistant.const import ( - CONTENT_TYPE_TEXT_PLAIN, SERVER_PORT, CONF_TIME_ZONE, - SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART) from homeassistant.components import SERVICE_CHECK_CONFIG from homeassistant.components.http import ( - HomeAssistantView, KEY_AUTHENTICATED, CONF_API_PASSWORD, CONF_SERVER_PORT, - CONF_SERVER_HOST, CONF_SSL_CERTIFICATE) -from homeassistant.loader import bind_hass + CONF_API_PASSWORD, CONF_SERVER_HOST, CONF_SERVER_PORT, + CONF_SSL_CERTIFICATE, KEY_AUTHENTICATED, HomeAssistantView) +from homeassistant.const import ( + CONF_TIME_ZONE, CONTENT_TYPE_TEXT_PLAIN, SERVER_PORT, + SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP) +from homeassistant.core import DOMAIN as HASS_DOMAIN +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from homeassistant.loader import bind_hass from homeassistant.util.dt import utcnow _LOGGER = logging.getLogger(__name__) @@ -126,7 +127,7 @@ MAP_SERVICE_API = { @callback @bind_hass def get_homeassistant_version(hass): - """Return latest available HomeAssistant version. + """Return latest available Home Assistant version. Async friendly. """ @@ -136,7 +137,7 @@ def get_homeassistant_version(hass): @callback @bind_hass def is_hassio(hass): - """Return True if hass.io is loaded. + """Return true if hass.io is loaded. Async friendly. """ @@ -146,7 +147,7 @@ def is_hassio(hass): @bind_hass @asyncio.coroutine def async_check_config(hass): - """Check config over Hass.io API.""" + """Check configuration over Hass.io API.""" result = yield from hass.data[DOMAIN].send_command( '/homeassistant/check', timeout=300) @@ -159,18 +160,18 @@ def async_check_config(hass): @asyncio.coroutine def async_setup(hass, config): - """Set up the HASSio component.""" + """Set up the Hass.io component.""" try: host = os.environ['HASSIO'] except KeyError: - _LOGGER.error("No HassIO supervisor detect!") + _LOGGER.error("No Hass.io supervisor detect") return False websession = hass.helpers.aiohttp_client.async_get_clientsession() hass.data[DOMAIN] = hassio = HassIO(hass.loop, websession, host) if not (yield from hassio.is_connected()): - _LOGGER.error("Not connected with HassIO!") + _LOGGER.error("Not connected with Hass.io") return False hass.http.register_view(HassIOView(hassio)) @@ -187,7 +188,7 @@ def async_setup(hass, config): @asyncio.coroutine def async_service_handler(service): - """Handle service calls for HassIO.""" + """Handle service calls for Hass.io.""" api_command = MAP_SERVICE_API[service.service][0] data = service.data.copy() addon = data.pop(ATTR_ADDON, None) @@ -215,7 +216,7 @@ def async_setup(hass, config): @asyncio.coroutine def update_homeassistant_version(now): - """Update last available HomeAssistant version.""" + """Update last available Home Assistant version.""" data = yield from hassio.get_homeassistant_info() if data: hass.data[DATA_HOMEASSISTANT_VERSION] = \ @@ -255,10 +256,10 @@ def async_setup(hass, config): def _api_bool(funct): - """API wrapper to return Boolean.""" + """Return a boolean.""" @asyncio.coroutine def _wrapper(*argv, **kwargs): - """Wrapper function.""" + """Wrap function.""" data = yield from funct(*argv, **kwargs) return data and data['result'] == "ok" @@ -266,24 +267,24 @@ def _api_bool(funct): class HassIO(object): - """Small API wrapper for HassIO.""" + """Small API wrapper for Hass.io.""" def __init__(self, loop, websession, ip): - """Initialze HassIO api.""" + """Initialize Hass.io API.""" self.loop = loop self.websession = websession self._ip = ip @_api_bool def is_connected(self): - """Return True if it connected to HassIO supervisor. + """Return true if it connected to Hass.io supervisor. This method return a coroutine. """ return self.send_command("/supervisor/ping", method="get") def get_homeassistant_info(self): - """Return data for HomeAssistant. + """Return data for Home Assistant. This method return a coroutine. """ @@ -291,7 +292,7 @@ class HassIO(object): @_api_bool def update_hass_api(self, http_config): - """Update Home-Assistant API data on HassIO. + """Update Home Assistant API data on Hass.io. This method return a coroutine. """ @@ -305,13 +306,13 @@ class HassIO(object): if CONF_SERVER_HOST in http_config: options['watchdog'] = False - _LOGGER.warning("Don't use 'server_host' options with Hass.io!") + _LOGGER.warning("Don't use 'server_host' options with Hass.io") return self.send_command("/homeassistant/options", payload=options) @_api_bool def update_hass_timezone(self, core_config): - """Update Home-Assistant timezone data on HassIO. + """Update Home-Assistant timezone data on Hass.io. This method return a coroutine. """ @@ -321,7 +322,7 @@ class HassIO(object): @asyncio.coroutine def send_command(self, command, method="post", payload=None, timeout=10): - """Send API command to HassIO. + """Send API command to Hass.io. This method is a coroutine. """ @@ -351,7 +352,7 @@ class HassIO(object): @asyncio.coroutine def command_proxy(self, path, request): - """Return a client request with proxy origin for HassIO supervisor. + """Return a client request with proxy origin for Hass.io supervisor. This method is a coroutine. """ @@ -376,28 +377,28 @@ class HassIO(object): return client except aiohttp.ClientError as err: - _LOGGER.error("Client error on api %s request %s.", path, err) + _LOGGER.error("Client error on api %s request %s", path, err) except asyncio.TimeoutError: - _LOGGER.error("Client timeout error on api request %s.", path) + _LOGGER.error("Client timeout error on API request %s", path) raise HTTPBadGateway() class HassIOView(HomeAssistantView): - """HassIO view to handle base part.""" + """Hass.io view to handle base part.""" name = "api:hassio" url = "/api/hassio/{path:.+}" requires_auth = False def __init__(self, hassio): - """Initialize a hassio base view.""" + """Initialize a Hass.io base view.""" self.hassio = hassio @asyncio.coroutine def _handle(self, request, path): - """Route data to hassio.""" + """Route data to Hass.io.""" if _need_auth(path) and not request[KEY_AUTHENTICATED]: return web.Response(status=401) @@ -434,7 +435,7 @@ def _create_response_log(client, data): def _get_timeout(path): - """Return timeout for a url path.""" + """Return timeout for a URL path.""" for re_path in NO_TIMEOUT: if re_path.match(path): return 0 @@ -442,7 +443,7 @@ def _get_timeout(path): def _need_auth(path): - """Return if a path need a auth.""" + """Return if a path need authentication.""" for re_path in NO_AUTH: if re_path.match(path): return False diff --git a/homeassistant/components/influxdb.py b/homeassistant/components/influxdb.py index d31d1e96431..82f98449411 100644 --- a/homeassistant/components/influxdb.py +++ b/homeassistant/components/influxdb.py @@ -5,7 +5,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/influxdb/ """ from datetime import timedelta -from functools import wraps, partial +from functools import partial, wraps import logging import re @@ -13,13 +13,13 @@ import requests.exceptions import voluptuous as vol from homeassistant.const import ( - EVENT_STATE_CHANGED, STATE_UNAVAILABLE, STATE_UNKNOWN, CONF_HOST, - CONF_PORT, CONF_SSL, CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD, - CONF_EXCLUDE, CONF_INCLUDE, CONF_DOMAINS, CONF_ENTITIES) + CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_HOST, CONF_INCLUDE, + CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_USERNAME, CONF_VERIFY_SSL, + EVENT_STATE_CHANGED, STATE_UNAVAILABLE, STATE_UNKNOWN) from homeassistant.helpers import state as state_helper +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_values import EntityValues from homeassistant.util import utcnow -import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['influxdb==4.1.1'] @@ -227,6 +227,7 @@ def setup(hass, config): @RetryOnError(hass, retry_limit=max_tries, retry_delay=20, queue_limit=queue_limit) def _write_data(json_body): + """Write the data.""" try: influx.write_points(json_body) except exceptions.InfluxDBClientError: @@ -268,7 +269,7 @@ class RetryOnError(object): @wraps(method) def wrapper(*args, **kwargs): - """Wrapped method.""" + """Wrap method.""" # pylint: disable=protected-access if not hasattr(wrapper, "_retry_queue"): wrapper._retry_queue = [] diff --git a/homeassistant/components/insteon_local.py b/homeassistant/components/insteon_local.py index dbe8597be3d..a18d4e0aa14 100644 --- a/homeassistant/components/insteon_local.py +++ b/homeassistant/components/insteon_local.py @@ -11,7 +11,7 @@ import requests import voluptuous as vol from homeassistant.const import ( - CONF_PASSWORD, CONF_USERNAME, CONF_HOST, CONF_PORT, CONF_TIMEOUT) + CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_TIMEOUT, CONF_USERNAME) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform @@ -37,14 +37,15 @@ CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_USERNAME): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, }) }, extra=vol.ALLOW_EXTRA) def setup(hass, config): - """Setup insteon hub.""" + """Set up the local Insteon hub.""" from insteonlocal.Hub import Hub + conf = config[DOMAIN] username = conf.get(CONF_USERNAME) password = conf.get(CONF_PASSWORD) @@ -62,20 +63,16 @@ def setup(hass, config): # Check for successful connection insteonhub.get_buffer_status() except requests.exceptions.ConnectTimeout: - _LOGGER.error( - "Could not connect. Check config", - exc_info=True) + _LOGGER.error("Could not connect", exc_info=True) return False except requests.exceptions.ConnectionError: - _LOGGER.error( - "Could not connect. Check config", - exc_info=True) + _LOGGER.error("Could not connect", exc_info=True) return False except requests.exceptions.RequestException: if insteonhub.http_code == 401: - _LOGGER.error("Bad user/pass for insteon_local hub") + _LOGGER.error("Bad username or password for Insteon_local hub") else: - _LOGGER.error("Error on insteon_local hub check", exc_info=True) + _LOGGER.error("Error on Insteon_local hub check", exc_info=True) return False linked = insteonhub.get_linked() diff --git a/homeassistant/components/kira.py b/homeassistant/components/kira.py index 98d1228d541..3a5ee25f05e 100644 --- a/homeassistant/components/kira.py +++ b/homeassistant/components/kira.py @@ -1,26 +1,23 @@ -"""KIRA interface to receive UDP packets from an IR-IP bridge.""" -# pylint: disable=import-error +""" +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/ +""" import logging import os -import yaml import voluptuous as vol from voluptuous.error import Error as VoluptuousError +import yaml +from homeassistant.const import ( + CONF_DEVICE, CONF_HOST, CONF_NAME, CONF_PORT, CONF_SENSORS, CONF_TYPE, + EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv -from homeassistant.const import ( - CONF_DEVICE, - CONF_HOST, - CONF_NAME, - CONF_PORT, - CONF_SENSORS, - CONF_TYPE, - EVENT_HOMEASSISTANT_STOP, - STATE_UNKNOWN) - -REQUIREMENTS = ["pykira==0.1.1"] +REQUIREMENTS = ['pykira==0.1.1'] DOMAIN = 'kira' @@ -67,7 +64,7 @@ CONFIG_SCHEMA = vol.Schema({ def load_codes(path): - """Load Kira codes from specified file.""" + """Load KIRA codes from specified file.""" codes = [] if os.path.exists(path): with open(path) as code_file: @@ -77,7 +74,7 @@ def load_codes(path): codes.append(CODE_SCHEMA(code)) except VoluptuousError as exception: # keep going - _LOGGER.warning('Kira Code Invalid Data: %s', exception) + _LOGGER.warning("KIRA code invalid data: %s", exception) else: with open(path, 'w') as code_file: code_file.write('') @@ -85,7 +82,7 @@ def load_codes(path): def setup(hass, config): - """Setup KIRA capability.""" + """Set up the KIRA component.""" import pykira sensors = config.get(DOMAIN, {}).get(CONF_SENSORS, []) @@ -99,10 +96,10 @@ def setup(hass, config): hass.data[DOMAIN] = { CONF_SENSOR: {}, CONF_REMOTE: {}, - } + } def load_module(platform, idx, module_conf): - """Set up Kira module and load platform.""" + """Set up the KIRA module and load platform.""" # note: module_name is not the HA device name. it's just a unique name # to ensure the component and platform can share information module_name = ("%s_%d" % (DOMAIN, idx)) if idx else DOMAIN @@ -133,6 +130,7 @@ def setup(hass, config): load_module(CONF_REMOTE, idx, module_conf) def _stop_kira(_event): + """Stop the KIRA receiver.""" for receiver in hass.data[DOMAIN][CONF_SENSOR].values(): receiver.stop() _LOGGER.info("Terminated receivers") diff --git a/homeassistant/components/knx.py b/homeassistant/components/knx.py index f9747351bdd..eb5ae9a4590 100644 --- a/homeassistant/components/knx.py +++ b/homeassistant/components/knx.py @@ -1,22 +1,21 @@ """ - Connects to KNX platform. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/knx/ - """ -import logging import asyncio +import logging import voluptuous as vol +from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv -from homeassistant.const import EVENT_HOMEASSISTANT_STOP, \ - CONF_HOST, CONF_PORT from homeassistant.helpers.script import Script +REQUIREMENTS = ['xknx==0.7.18'] + DOMAIN = "knx" DATA_KNX = "data_knx" CONF_KNX_CONFIG = "config_file" @@ -36,12 +35,10 @@ ATTR_DISCOVER_DEVICES = 'devices' _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['xknx==0.7.18'] - TUNNELING_SCHEMA = vol.Schema({ vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PORT): cv.port, vol.Required(CONF_KNX_LOCAL_IP): cv.string, + vol.Optional(CONF_PORT): cv.port, }) ROUTING_SCHEMA = vol.Schema({ @@ -57,9 +54,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Inclusive(CONF_KNX_FIRE_EVENT, 'fire_ev'): cv.boolean, vol.Inclusive(CONF_KNX_FIRE_EVENT_FILTER, 'fire_ev'): - vol.All( - cv.ensure_list, - [cv.string]), + vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_KNX_STATE_UPDATER, default=True): cv.boolean, }) }, extra=vol.ALLOW_EXTRA) @@ -73,7 +68,7 @@ SERVICE_KNX_SEND_SCHEMA = vol.Schema({ @asyncio.coroutine def async_setup(hass, config): - """Set up knx component.""" + """Set up the KNX component.""" from xknx.exceptions import XKNXException try: hass.data[DATA_KNX] = KNXModule(hass, config) @@ -109,6 +104,7 @@ def async_setup(hass, config): def _get_devices(hass, discovery_type): + """Get the KNX devices.""" return list( map(lambda device: device.name, filter( @@ -120,7 +116,7 @@ class KNXModule(object): """Representation of KNX Object.""" def __init__(self, hass, config): - """Initialization of KNXModule.""" + """Initialize of KNX module.""" self.hass = hass self.config = config self.connected = False @@ -129,11 +125,9 @@ class KNXModule(object): self.register_callbacks() def init_xknx(self): - """Initialization of KNX object.""" + """Initialize of KNX object.""" from xknx import XKNX - self.xknx = XKNX( - config=self.config_file(), - loop=self.hass.loop) + self.xknx = XKNX(config=self.config_file(), loop=self.hass.loop) @asyncio.coroutine def start(self): @@ -189,10 +183,8 @@ class KNXModule(object): if gateway_port is None: gateway_port = DEFAULT_MCAST_PORT return ConnectionConfig( - connection_type=ConnectionType.TUNNELING, - gateway_ip=gateway_ip, - gateway_port=gateway_port, - local_ip=local_ip) + connection_type=ConnectionType.TUNNELING, gateway_ip=gateway_ip, + gateway_port=gateway_port, local_ip=local_ip) def connection_config_auto(self): """Return the connection_config if auto is configured.""" @@ -213,7 +205,7 @@ class KNXModule(object): @asyncio.coroutine def telegram_received_cb(self, telegram): - """Callback invoked after a KNX telegram was received.""" + """Call invoked after a KNX telegram was received.""" self.hass.bus.fire('knx_event', { 'address': telegram.group_address.str(), 'data': telegram.payload.value @@ -254,8 +246,6 @@ class KNXAutomation(): import xknx self.action = xknx.devices.ActionCallback( - hass.data[DATA_KNX].xknx, - self.script.async_run, - hook=hook, - counter=counter) + hass.data[DATA_KNX].xknx, self.script.async_run, + hook=hook, counter=counter) device.actions.append(self.action) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 3d333e229fa..b761b04c705 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -5,33 +5,33 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/light/ """ import asyncio +import csv from datetime import timedelta import logging import os -import csv import voluptuous as vol -from homeassistant.core import callback -from homeassistant.loader import bind_hass from homeassistant.components import group from homeassistant.const import ( - STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, - ATTR_ENTITY_ID) + ATTR_ENTITY_ID, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, + STATE_ON) +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa -import homeassistant.helpers.config_validation as cv +from homeassistant.loader import bind_hass import homeassistant.util.color as color_util -DOMAIN = "light" +DOMAIN = 'light' DEPENDENCIES = ['group'] SCAN_INTERVAL = timedelta(seconds=30) GROUP_NAME_ALL_LIGHTS = 'all lights' ENTITY_ID_ALL_LIGHTS = group.ENTITY_ID_FORMAT.format('all_lights') -ENTITY_ID_FORMAT = DOMAIN + ".{}" +ENTITY_ID_FORMAT = DOMAIN + '.{}' # Bitfield of features supported by the light entity SUPPORT_BRIGHTNESS = 1 @@ -220,7 +220,7 @@ def toggle(hass, entity_id=None, transition=None): def preprocess_turn_on_alternatives(params): - """Processing extra data for turn light on request.""" + """Process extra data for turn light on request.""" profile = Profiles.get(params.pop(ATTR_PROFILE, None)) if profile is not None: params.setdefault(ATTR_XY_COLOR, profile[:2]) @@ -242,7 +242,7 @@ def preprocess_turn_on_alternatives(params): @asyncio.coroutine def async_setup(hass, config): - """Expose light control via statemachine and services.""" + """Expose light control via state machine and services.""" component = EntityComponent( _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_LIGHTS) yield from component.async_setup(config) diff --git a/homeassistant/components/light/greenwave.py b/homeassistant/components/light/greenwave.py index 0e99a49eaa9..c8f06a3d0f8 100644 --- a/homeassistant/components/light/greenwave.py +++ b/homeassistant/components/light/greenwave.py @@ -9,28 +9,31 @@ import logging import voluptuous as vol from homeassistant.components.light import ( - ATTR_BRIGHTNESS, Light, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS) + ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, Light) from homeassistant.const import CONF_HOST import homeassistant.helpers.config_validation as cv -SUPPORTED_FEATURES = (SUPPORT_BRIGHTNESS) - REQUIREMENTS = ['greenwavereality==0.2.9'] + _LOGGER = logging.getLogger(__name__) +CONF_VERSION = 'version' + +SUPPORTED_FEATURES = (SUPPORT_BRIGHTNESS) + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, - vol.Required("version"): cv.positive_int, + vol.Required(CONF_VERSION): cv.positive_int, }) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Greenwave Reality Platform.""" + """Set up the Greenwave Reality Platform.""" import greenwavereality as greenwave import os host = config.get(CONF_HOST) tokenfile = hass.config.path('.greenwave') - if config.get("version") == 3: + if config.get(CONF_VERSION) == 3: if os.path.exists(tokenfile): tokenfile = open(tokenfile) token = tokenfile.read() diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py index f4ea04240f1..cbabaafd3fb 100644 --- a/homeassistant/components/light/hue.py +++ b/homeassistant/components/light/hue.py @@ -14,13 +14,12 @@ import socket import voluptuous as vol import homeassistant.components.hue as hue - from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_RGB_COLOR, ATTR_TRANSITION, ATTR_XY_COLOR, EFFECT_COLORLOOP, EFFECT_RANDOM, - FLASH_LONG, FLASH_SHORT, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, - SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, - SUPPORT_XY_COLOR, Light, PLATFORM_SCHEMA) + FLASH_LONG, FLASH_SHORT, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, + SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_RGB_COLOR, + SUPPORT_TRANSITION, SUPPORT_XY_COLOR, Light) from homeassistant.const import CONF_FILENAME, CONF_HOST, DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv import homeassistant.util as util @@ -114,7 +113,7 @@ def update_lights(hass, bridge, add_devices): def unthrottled_update_lights(hass, bridge, add_devices): - """Internal version of update_lights.""" + """Update the lights (Internal version of update_lights).""" import phue if not bridge.configured: @@ -123,14 +122,14 @@ def unthrottled_update_lights(hass, bridge, add_devices): try: api = bridge.get_api() except phue.PhueRequestTimeout: - _LOGGER.warning('Timeout trying to reach the bridge') + _LOGGER.warning("Timeout trying to reach the bridge") return except ConnectionRefusedError: - _LOGGER.error('The bridge refused the connection') + _LOGGER.error("The bridge refused the connection") return except socket.error: # socket.error when we cannot reach Hue - _LOGGER.exception('Cannot reach the bridge') + _LOGGER.exception("Cannot reach the bridge") return new_lights = process_lights( @@ -151,7 +150,7 @@ def process_lights(hass, api, bridge, update_lights_cb): api_lights = api.get('lights') if not isinstance(api_lights, dict): - _LOGGER.error('Got unexpected result from Hue API') + _LOGGER.error("Got unexpected result from Hue API") return [] new_lights = [] @@ -186,8 +185,8 @@ def process_groups(hass, api, bridge, update_lights_cb): for lightgroup_id, info in api_groups.items(): if 'state' not in info: - _LOGGER.warning('Group info does not contain state. ' - 'Please update your hub.') + _LOGGER.warning( + "Group info does not contain state. Please update your hub") return [] if lightgroup_id not in bridge.lightgroups: diff --git a/homeassistant/components/light/iglo.py b/homeassistant/components/light/iglo.py index 11366ffc45c..a2eed36a089 100644 --- a/homeassistant/components/light/iglo.py +++ b/homeassistant/components/light/iglo.py @@ -9,13 +9,10 @@ import math import voluptuous as vol -from homeassistant.const import (CONF_HOST, CONF_NAME, CONF_PORT) from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_COLOR_TEMP, - SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR, - Light, PLATFORM_SCHEMA -) - + ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, PLATFORM_SCHEMA, + SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR, Light) +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT import homeassistant.helpers.config_validation as cv import homeassistant.util.color as color_util diff --git a/homeassistant/components/light/knx.py b/homeassistant/components/light/knx.py index c1caf91db45..732cfe2a644 100644 --- a/homeassistant/components/light/knx.py +++ b/homeassistant/components/light/knx.py @@ -5,11 +5,12 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.knx/ """ import asyncio + import voluptuous as vol -from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES -from homeassistant.components.light import PLATFORM_SCHEMA, Light, \ - SUPPORT_BRIGHTNESS, ATTR_BRIGHTNESS +from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, Light) from homeassistant.const import CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv @@ -32,20 +33,17 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine -def async_setup_platform(hass, config, async_add_devices, - discovery_info=None): - """Set up light(s) for KNX platform.""" +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up lights for KNX platform.""" if DATA_KNX not in hass.data \ or not hass.data[DATA_KNX].initialized: - return False + return if discovery_info is not None: async_add_devices_discovery(hass, discovery_info, async_add_devices) else: async_add_devices_config(hass, config, async_add_devices) - return True - @callback def async_add_devices_discovery(hass, discovery_info, async_add_devices): @@ -77,7 +75,7 @@ class KNXLight(Light): """Representation of a KNX light.""" def __init__(self, hass, device): - """Initialization of KNXLight.""" + """Initialize of KNX light.""" self.device = device self.hass = hass self.async_register_callbacks() @@ -87,7 +85,7 @@ class KNXLight(Light): """Register callbacks to update hass after device was changed.""" @asyncio.coroutine def after_update_callback(device): - """Callback after device was updated.""" + """Call after device was updated.""" # pylint: disable=unused-argument yield from self.async_update_ha_state() self.device.register_device_updated_cb(after_update_callback) diff --git a/homeassistant/components/light/lifx.py b/homeassistant/components/light/lifx.py index 22ec58f65cd..090341e4255 100644 --- a/homeassistant/components/light/lifx.py +++ b/homeassistant/components/light/lifx.py @@ -4,29 +4,28 @@ 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/ """ -import logging import asyncio -import sys -import math -from functools import partial from datetime import timedelta +from functools import partial +import logging +import math +import sys import voluptuous as vol +from homeassistant import util from homeassistant.components.light import ( - Light, DOMAIN, PLATFORM_SCHEMA, LIGHT_TURN_ON_SCHEMA, - ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, ATTR_COLOR_NAME, ATTR_RGB_COLOR, - ATTR_XY_COLOR, ATTR_COLOR_TEMP, ATTR_KELVIN, ATTR_TRANSITION, ATTR_EFFECT, - SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR, - SUPPORT_XY_COLOR, SUPPORT_TRANSITION, SUPPORT_EFFECT, - VALID_BRIGHTNESS, VALID_BRIGHTNESS_PCT, + ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, ATTR_COLOR_NAME, ATTR_COLOR_TEMP, + ATTR_EFFECT, ATTR_KELVIN, ATTR_RGB_COLOR, ATTR_TRANSITION, ATTR_XY_COLOR, + DOMAIN, LIGHT_TURN_ON_SCHEMA, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, + SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, + SUPPORT_XY_COLOR, VALID_BRIGHTNESS, VALID_BRIGHTNESS_PCT, Light, preprocess_turn_on_alternatives) from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_STOP -from homeassistant import util from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.service import extract_entity_ids -import homeassistant.helpers.config_validation as cv import homeassistant.util.color as color_util _LOGGER = logging.getLogger(__name__) @@ -296,12 +295,12 @@ class LIFXManager(object): @callback def register(self, device): - """Handler for newly detected bulb.""" + """Handle newly detected bulb.""" self.hass.async_add_job(self.async_register(device)) @asyncio.coroutine def async_register(self, device): - """Handler for newly detected bulb.""" + """Handle newly detected bulb.""" if device.mac_addr in self.entities: entity = self.entities[device.mac_addr] entity.registered = True diff --git a/homeassistant/components/light/mysensors.py b/homeassistant/components/light/mysensors.py index c41f480c67e..9a48b13ed3b 100644 --- a/homeassistant/components/light/mysensors.py +++ b/homeassistant/components/light/mysensors.py @@ -16,7 +16,7 @@ SUPPORT_MYSENSORS = (SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR | def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the mysensors platform for lights.""" + """Set up the MySensors platform for lights.""" device_class_map = { 'S_DIMMER': MySensorsLightDimmer, 'S_RGB_LIGHT': MySensorsLightRGB, diff --git a/homeassistant/components/light/osramlightify.py b/homeassistant/components/light/osramlightify.py index 5785f0f1fc7..ff526c4783d 100644 --- a/homeassistant/components/light/osramlightify.py +++ b/homeassistant/components/light/osramlightify.py @@ -63,7 +63,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): setup_bridge(bridge, add_devices, add_groups) -def setup_bridge(bridge, add_devices_callback, add_groups): +def setup_bridge(bridge, add_devices, add_groups): """Set up the Lightify bridge.""" lights = {} @@ -100,7 +100,7 @@ def setup_bridge(bridge, add_devices_callback, add_groups): lights[group_name].group = group if new_lights: - add_devices_callback(new_lights) + add_devices(new_lights) update_lights() @@ -109,7 +109,7 @@ class Luminary(Light): """Representation of Luminary Lights and Groups.""" def __init__(self, luminary, update_lights): - """Initize a Luminary light.""" + """Initialize a Luminary light.""" self.update_lights = update_lights self._luminary = luminary self._brightness = None diff --git a/homeassistant/components/light/wink.py b/homeassistant/components/light/wink.py index 445fe8ceb25..02605d24faf 100644 --- a/homeassistant/components/light/wink.py +++ b/homeassistant/components/light/wink.py @@ -10,7 +10,7 @@ import colorsys from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR, Light) -from homeassistant.components.wink import WinkDevice, DOMAIN +from homeassistant.components.wink import DOMAIN, WinkDevice from homeassistant.util import color as color_util from homeassistant.util.color import \ color_temperature_mired_to_kelvin as mired_to_kelvin @@ -39,7 +39,7 @@ class WinkLight(WinkDevice, Light): @asyncio.coroutine def async_added_to_hass(self): - """Callback when entity is added to hass.""" + """Call when entity is added to hass.""" self.hass.data[DOMAIN]['entities']['light'].append(self) @property diff --git a/homeassistant/components/lock/nuki.py b/homeassistant/components/lock/nuki.py index 6efa3dcb80c..4fe05279919 100644 --- a/homeassistant/components/lock/nuki.py +++ b/homeassistant/components/lock/nuki.py @@ -10,10 +10,10 @@ import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.components.lock import (DOMAIN, LockDevice, PLATFORM_SCHEMA) +from homeassistant.components.lock import DOMAIN, PLATFORM_SCHEMA, LockDevice from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, CONF_PORT, CONF_TOKEN) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.service import extract_entity_ids REQUIREMENTS = ['pynuki==1.3.1'] @@ -25,7 +25,12 @@ DEFAULT_PORT = 8080 ATTR_BATTERY_CRITICAL = 'battery_critical' ATTR_NUKI_ID = 'nuki_id' ATTR_UNLATCH = 'unlatch' + +MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=5) +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=30) + NUKI_DATA = 'nuki' + SERVICE_LOCK_N_GO = 'nuki_lock_n_go' SERVICE_UNLATCH = 'nuki_unlatch' @@ -44,9 +49,6 @@ UNLATCH_SERVICE_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids }) -MIN_TIME_BETWEEN_SCANS = timedelta(seconds=30) -MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=5) - # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): @@ -93,7 +95,7 @@ class NukiLock(LockDevice): @asyncio.coroutine def async_added_to_hass(self): - """Callback when entity is added to hass.""" + """Call when entity is added to hass.""" if NUKI_DATA not in self.hass.data: self.hass.data[NUKI_DATA] = {} if DOMAIN not in self.hass.data[NUKI_DATA]: diff --git a/homeassistant/components/lock/tesla.py b/homeassistant/components/lock/tesla.py index 80a35adb5fb..4d24ed20003 100644 --- a/homeassistant/components/lock/tesla.py +++ b/homeassistant/components/lock/tesla.py @@ -7,7 +7,8 @@ https://home-assistant.io/components/lock.tesla/ import logging from homeassistant.components.lock import ENTITY_ID_FORMAT, LockDevice -from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN, TeslaDevice +from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN +from homeassistant.components.tesla import TeslaDevice from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED _LOGGER = logging.getLogger(__name__) @@ -26,7 +27,7 @@ class TeslaLock(TeslaDevice, LockDevice): """Representation of a Tesla door lock.""" def __init__(self, tesla_device, controller): - """Initialisation of the lock.""" + """Initialise of the lock.""" self._state = None super().__init__(tesla_device, controller) self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id) @@ -47,7 +48,7 @@ class TeslaLock(TeslaDevice, LockDevice): return self._state == STATE_LOCKED def update(self): - """Updating state of the lock.""" + """Update state of the lock.""" _LOGGER.debug("Updating state for: %s", self._name) self.tesla_device.update() self._state = STATE_LOCKED if self.tesla_device.is_locked() \ diff --git a/homeassistant/components/lock/wink.py b/homeassistant/components/lock/wink.py index 118a8d8f664..a5cd18454df 100644 --- a/homeassistant/components/lock/wink.py +++ b/homeassistant/components/lock/wink.py @@ -10,9 +10,9 @@ import logging import voluptuous as vol from homeassistant.components.lock import LockDevice -from homeassistant.components.wink import WinkDevice, DOMAIN +from homeassistant.components.wink import DOMAIN, WinkDevice +from homeassistant.const import ATTR_CODE, ATTR_ENTITY_ID, STATE_UNKNOWN import homeassistant.helpers.config_validation as cv -from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN, ATTR_CODE DEPENDENCIES = ['wink'] @@ -30,13 +30,19 @@ ATTR_SENSITIVITY = 'sensitivity' ATTR_MODE = 'mode' ATTR_NAME = 'name' -ALARM_SENSITIVITY_MAP = {"low": 0.2, "medium_low": 0.4, - "medium": 0.6, "medium_high": 0.8, - "high": 1.0} +ALARM_SENSITIVITY_MAP = { + 'low': 0.2, + 'medium_low': 0.4, + 'medium': 0.6, + 'medium_high': 0.8, + 'high': 1.0, +} -ALARM_MODES_MAP = {"tamper": "tamper", - "activity": "alert", - "forced_entry": "forced_entry"} +ALARM_MODES_MAP = { + 'activity': 'alert', + 'forced_entry': 'forced_entry', + 'tamper': 'tamper', +} SET_ENABLED_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, @@ -70,7 +76,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices([WinkLockDevice(lock, hass)]) def service_handle(service): - """Handler for services.""" + """Handle for services.""" entity_ids = service.data.get('entity_id') all_locks = hass.data[DOMAIN]['entities']['lock'] locks_to_set = [] @@ -127,7 +133,7 @@ class WinkLockDevice(WinkDevice, LockDevice): @asyncio.coroutine def async_added_to_hass(self): - """Callback when entity is added to hass.""" + """Call when entity is added to hass.""" self.hass.data[DOMAIN]['entities']['lock'].append(self) @property diff --git a/homeassistant/components/media_player/bluesound.py b/homeassistant/components/media_player/bluesound.py index 1f86056efb5..ca6b152a37e 100644 --- a/homeassistant/components/media_player/bluesound.py +++ b/homeassistant/components/media_player/bluesound.py @@ -1,5 +1,5 @@ """ -Bluesound. +Support for Bluesound devices. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.bluesound/ @@ -16,14 +16,14 @@ import async_timeout import voluptuous as vol from homeassistant.components.media_player import ( - SUPPORT_PLAY, SUPPORT_SEEK, SUPPORT_STOP, SUPPORT_PAUSE, PLATFORM_SCHEMA, - MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PLAY_MEDIA, - SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, - SUPPORT_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST, SUPPORT_PREVIOUS_TRACK, + MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, 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) from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PORT, CONF_HOSTS, STATE_IDLE, STATE_PAUSED, - STATE_PLAYING, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START) + CONF_HOST, CONF_HOSTS, CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_PAUSED, STATE_PLAYING) from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv @@ -60,6 +60,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def _add_player(hass, async_add_devices, host, port=None, name=None): + """Add Bluesound players.""" if host in [x.host for x in hass.data[DATA_BLUESOUND]]: return @@ -108,8 +109,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): hass.data[DATA_BLUESOUND] = [] if discovery_info: - _add_player(hass, async_add_devices, discovery_info.get('host'), - discovery_info.get('port', None)) + _add_player(hass, async_add_devices, discovery_info.get(CONF_HOST), + discovery_info.get(CONF_PORT, None)) return hosts = config.get(CONF_HOSTS, None) @@ -117,11 +118,11 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): for host in hosts: _add_player( hass, async_add_devices, host.get(CONF_HOST), - host.get(CONF_PORT), host.get(CONF_NAME, None)) + host.get(CONF_PORT), host.get(CONF_NAME)) class BluesoundPlayer(MediaPlayerDevice): - """Bluesound Player Object.""" + """Represenatation of a Bluesound Player.""" def __init__(self, hass, host, port=None, name=None, init_callback=None): """Initialize the media player.""" @@ -150,6 +151,7 @@ class BluesoundPlayer(MediaPlayerDevice): @staticmethod def _try_get_index(string, seach_string): + """Get the index.""" try: return string.index(seach_string) except ValueError: @@ -158,6 +160,7 @@ class BluesoundPlayer(MediaPlayerDevice): @asyncio.coroutine def _internal_update_sync_status( self, on_updated_cb=None, raise_timeout=False): + """Update the internal status.""" resp = None try: resp = yield from self.send_bluesound_command( @@ -186,7 +189,7 @@ class BluesoundPlayer(MediaPlayerDevice): @asyncio.coroutine def _start_poll_command(self): - """"Loop which polls the status of the player.""" + """Loop which polls the status of the player.""" try: while True: yield from self.async_update_status() @@ -214,7 +217,7 @@ class BluesoundPlayer(MediaPlayerDevice): @asyncio.coroutine def async_init(self): - """Initiate the player async.""" + """Initialize the player async.""" try: if self._retry_remove is not None: self._retry_remove() @@ -284,7 +287,7 @@ class BluesoundPlayer(MediaPlayerDevice): @asyncio.coroutine def async_update_status(self): - """Using the poll session to always get the status of the player.""" + """Use the poll session to always get the status of the player.""" import xmltodict response = None diff --git a/homeassistant/components/media_player/monoprice.py b/homeassistant/components/media_player/monoprice.py index c95ddcab97e..d26fce0ea88 100644 --- a/homeassistant/components/media_player/monoprice.py +++ b/homeassistant/components/media_player/monoprice.py @@ -8,14 +8,13 @@ import logging import voluptuous as vol -from homeassistant.const import (ATTR_ENTITY_ID, CONF_NAME, CONF_PORT, - STATE_OFF, STATE_ON) -import homeassistant.helpers.config_validation as cv from homeassistant.components.media_player import ( - DOMAIN, MediaPlayerDevice, MEDIA_PLAYER_SCHEMA, PLATFORM_SCHEMA, - SUPPORT_VOLUME_MUTE, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_ON, - SUPPORT_TURN_OFF, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) - + DOMAIN, MEDIA_PLAYER_SCHEMA, PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, + SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, + SUPPORT_VOLUME_STEP, MediaPlayerDevice) +from homeassistant.const import ( + ATTR_ENTITY_ID, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON) +import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['pymonoprice==0.3'] @@ -42,9 +41,9 @@ SERVICE_SNAPSHOT = 'snapshot' SERVICE_RESTORE = 'restore' # Valid zone ids: 11-16 or 21-26 or 31-36 -ZONE_IDS = vol.All(vol.Coerce(int), vol.Any(vol.Range(min=11, max=16), - vol.Range(min=21, max=26), - vol.Range(min=31, max=36))) +ZONE_IDS = vol.All(vol.Coerce(int), vol.Any( + vol.Range(min=11, max=16), vol.Range(min=21, max=26), + vol.Range(min=31, max=36))) # Valid source ids: 1-6 SOURCE_IDS = vol.All(vol.Coerce(int), vol.Range(min=1, max=6)) @@ -66,7 +65,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): try: monoprice = get_monoprice(port) except SerialException: - _LOGGER.error('Error connecting to Monoprice controller.') + _LOGGER.error("Error connecting to Monoprice controller") return sources = {source_id: extra[CONF_NAME] for source_id, extra @@ -75,9 +74,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): hass.data[DATA_MONOPRICE] = [] for zone_id, extra in config[CONF_ZONES].items(): _LOGGER.info("Adding zone %d - %s", zone_id, extra[CONF_NAME]) - hass.data[DATA_MONOPRICE].append(MonopriceZone(monoprice, sources, - zone_id, - extra[CONF_NAME])) + hass.data[DATA_MONOPRICE].append(MonopriceZone( + monoprice, sources, zone_id, extra[CONF_NAME])) add_devices(hass.data[DATA_MONOPRICE], True) @@ -98,19 +96,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None): device.restore() hass.services.register( - DOMAIN, SERVICE_SNAPSHOT, service_handle, - schema=MEDIA_PLAYER_SCHEMA) + DOMAIN, SERVICE_SNAPSHOT, service_handle, schema=MEDIA_PLAYER_SCHEMA) hass.services.register( - DOMAIN, SERVICE_RESTORE, service_handle, - schema=MEDIA_PLAYER_SCHEMA) + DOMAIN, SERVICE_RESTORE, service_handle, schema=MEDIA_PLAYER_SCHEMA) class MonopriceZone(MediaPlayerDevice): """Representation of a a Monoprice amplifier zone.""" - # pylint: disable=too-many-public-methods - def __init__(self, monoprice, sources, zone_id, zone_name): """Initialize new zone.""" self._monoprice = monoprice @@ -179,7 +173,7 @@ class MonopriceZone(MediaPlayerDevice): @property def source(self): - """"Return the current input source of the device.""" + """Return the current input source of the device.""" return self._source @property @@ -224,12 +218,10 @@ class MonopriceZone(MediaPlayerDevice): """Volume up the media player.""" if self._volume is None: return - self._monoprice.set_volume(self._zone_id, - min(self._volume + 1, 38)) + self._monoprice.set_volume(self._zone_id, min(self._volume + 1, 38)) def volume_down(self): """Volume down media player.""" if self._volume is None: return - self._monoprice.set_volume(self._zone_id, - max(self._volume - 1, 0)) + self._monoprice.set_volume(self._zone_id, max(self._volume - 1, 0)) diff --git a/homeassistant/components/media_player/mpd.py b/homeassistant/components/media_player/mpd.py index c661e2a3b58..4307b68e709 100644 --- a/homeassistant/components/media_player/mpd.py +++ b/homeassistant/components/media_player/mpd.py @@ -4,22 +4,22 @@ Support to interact with a Music Player Daemon. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.mpd/ """ +from datetime import timedelta import logging import os -from datetime import timedelta import voluptuous as vol from homeassistant.components.media_player import ( - MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, PLATFORM_SCHEMA, - SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_PLAY, - SUPPORT_VOLUME_SET, SUPPORT_PLAY_MEDIA, MEDIA_TYPE_PLAYLIST, - SUPPORT_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST, SUPPORT_SHUFFLE_SET, - SUPPORT_SEEK, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, - SUPPORT_TURN_OFF, SUPPORT_TURN_ON, MediaPlayerDevice) + MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, PLATFORM_SCHEMA, + 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) from homeassistant.const import ( - STATE_OFF, STATE_PAUSED, STATE_PLAYING, - CONF_PORT, CONF_PASSWORD, CONF_HOST, CONF_NAME) + CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, STATE_OFF, STATE_PAUSED, + STATE_PLAYING) import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle @@ -116,7 +116,7 @@ class MpdDevice(MediaPlayerDevice): @property def available(self): - """True if MPD is available and connected.""" + """Return true if MPD is available and connected.""" return self._is_connected def update(self): diff --git a/homeassistant/components/media_player/nadtcp.py b/homeassistant/components/media_player/nadtcp.py index 707a0c29eaf..06ec3c04cbe 100644 --- a/homeassistant/components/media_player/nadtcp.py +++ b/homeassistant/components/media_player/nadtcp.py @@ -5,14 +5,14 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.nadtcp/ """ import logging + import voluptuous as vol + from homeassistant.components.media_player import ( - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_MUTE, SUPPORT_TURN_ON, SUPPORT_TURN_OFF, - SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, MediaPlayerDevice, - PLATFORM_SCHEMA) -from homeassistant.const import ( - CONF_NAME, STATE_OFF, STATE_ON) + PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, + MediaPlayerDevice) +from homeassistant.const import CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['nad_receiver==0.0.9'] @@ -30,7 +30,6 @@ SUPPORT_NAD = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | SUPPORT_TURN_ON | \ CONF_MIN_VOLUME = 'min_volume' CONF_MAX_VOLUME = 'max_volume' CONF_VOLUME_STEP = 'volume_step' -CONF_HOST = 'host' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, @@ -42,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the NAD platform.""" + """Set up the NAD platform.""" from nad_receiver import NADReceiverTCP add_devices([NADtcp( NADReceiverTCP(config.get(CONF_HOST)), diff --git a/homeassistant/components/media_player/snapcast.py b/homeassistant/components/media_player/snapcast.py index 2413de136ab..793800a3d22 100644 --- a/homeassistant/components/media_player/snapcast.py +++ b/homeassistant/components/media_player/snapcast.py @@ -11,11 +11,11 @@ import socket import voluptuous as vol from homeassistant.components.media_player import ( - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_SELECT_SOURCE, - DOMAIN, PLATFORM_SCHEMA, MediaPlayerDevice) + DOMAIN, PLATFORM_SCHEMA, SUPPORT_SELECT_SOURCE, SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, MediaPlayerDevice) from homeassistant.const import ( - STATE_ON, STATE_OFF, STATE_IDLE, STATE_PLAYING, STATE_UNKNOWN, CONF_HOST, - CONF_PORT, ATTR_ENTITY_ID) + ATTR_ENTITY_ID, CONF_HOST, CONF_PORT, STATE_IDLE, STATE_OFF, STATE_ON, + STATE_PLAYING, STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['snapcast==2.0.8'] @@ -42,14 +42,14 @@ SERVICE_SCHEMA = vol.Schema({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PORT): cv.port + vol.Optional(CONF_PORT): cv.port, }) # pylint: disable=unused-argument @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the Snapcast platform.""" + """Set up the Snapcast platform.""" import snapcast.control from snapcast.control.server import CONTROL_PORT host = config.get(CONF_HOST) @@ -68,25 +68,23 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): yield from device.async_restore() hass.services.async_register( - DOMAIN, SERVICE_SNAPSHOT, _handle_service, - schema=SERVICE_SCHEMA) + DOMAIN, SERVICE_SNAPSHOT, _handle_service, schema=SERVICE_SCHEMA) hass.services.async_register( - DOMAIN, SERVICE_RESTORE, _handle_service, - schema=SERVICE_SCHEMA) + DOMAIN, SERVICE_RESTORE, _handle_service, schema=SERVICE_SCHEMA) try: server = yield from snapcast.control.create_server( hass.loop, host, port, reconnect=True) except socket.gaierror: - _LOGGER.error('Could not connect to Snapcast server at %s:%d', + _LOGGER.error("Could not connect to Snapcast server at %s:%d", host, port) - return False + return + groups = [SnapcastGroupDevice(group) for group in server.groups] clients = [SnapcastClientDevice(client) for client in server.clients] devices = groups + clients hass.data[DATA_KEY] = devices async_add_devices(devices) - return True class SnapcastGroupDevice(MediaPlayerDevice): diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py index 947b86ce281..d4a7fd3adb5 100644 --- a/homeassistant/components/media_player/sonos.py +++ b/homeassistant/components/media_player/sonos.py @@ -14,14 +14,14 @@ import urllib import voluptuous as vol from homeassistant.components.media_player import ( - ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_CLEAR_PLAYLIST, - SUPPORT_SELECT_SOURCE, MediaPlayerDevice, PLATFORM_SCHEMA, SUPPORT_STOP, - SUPPORT_PLAY, SUPPORT_SHUFFLE_SET) + ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, + 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) from homeassistant.const import ( - STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_OFF, ATTR_ENTITY_ID, - CONF_HOSTS, ATTR_TIME) + ATTR_ENTITY_ID, ATTR_TIME, CONF_HOSTS, STATE_IDLE, STATE_OFF, STATE_PAUSED, + STATE_PLAYING) import homeassistant.helpers.config_validation as cv from homeassistant.util.dt import utcnow @@ -126,7 +126,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if discovery_info: player = soco.SoCo(discovery_info.get('host')) - # if device already exists by config + # If device already exists by config if player.uid in [x.unique_id for x in hass.data[DATA_SONOS]]: return @@ -167,7 +167,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(coordinators, True) if slaves: add_devices(slaves, True) - _LOGGER.info("Added %s Sonos speakers", len(players)) + _LOGGER.debug("Added %s Sonos speakers", len(players)) def service_handle(service): """Handle for services.""" @@ -242,10 +242,11 @@ def _parse_timespan(timespan): reversed(timespan.split(':')))) -class _ProcessSonosEventQueue(): +class _ProcessSonosEventQueue(object): """Queue like object for dispatching sonos events.""" def __init__(self, sonos_device): + """Initialize Sonos event queue.""" self._sonos_device = sonos_device def put(self, item, block=True, timeout=None): @@ -266,7 +267,7 @@ def _get_entity_from_soco(hass, soco): def soco_error(errorcodes=None): """Filter out specified UPnP errors from logs and avoid exceptions.""" def decorator(funct): - """Decorator function.""" + """Decorate functions.""" @ft.wraps(funct) def wrapper(*args, **kwargs): """Wrap for all soco UPnP exception.""" diff --git a/homeassistant/components/media_player/vizio.py b/homeassistant/components/media_player/vizio.py index 5d6e6fcf6dd..64d1f642e6e 100644 --- a/homeassistant/components/media_player/vizio.py +++ b/homeassistant/components/media_player/vizio.py @@ -1,36 +1,23 @@ """ Vizio SmartCast TV support. -Usually only 2016+ models come with SmartCast capabilities. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.vizio/ """ -import logging from datetime import timedelta +import logging import voluptuous as vol -import homeassistant.util as util from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, - SUPPORT_TURN_ON, - SUPPORT_TURN_OFF, - SUPPORT_SELECT_SOURCE, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_NEXT_TRACK, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_STEP, - MediaPlayerDevice -) + PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK, + SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, MediaPlayerDevice) from homeassistant.const import ( - STATE_UNKNOWN, - STATE_OFF, - STATE_ON, - CONF_NAME, - CONF_HOST, - CONF_ACCESS_TOKEN -) + CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, + STATE_UNKNOWN) from homeassistant.helpers import config_validation as cv +import homeassistant.util as util REQUIREMENTS = ['pyvizio==0.0.2'] @@ -39,13 +26,16 @@ _LOGGER = logging.getLogger(__name__) CONF_SUPPRESS_WARNING = 'suppress_warning' CONF_VOLUME_STEP = 'volume_step' -ICON = 'mdi:television' DEFAULT_NAME = 'Vizio SmartCast' DEFAULT_VOLUME_STEP = 1 -DEVICE_NAME = 'Python Vizio' DEVICE_ID = 'pyvizio' -MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) +DEVICE_NAME = 'Python Vizio' + +ICON = 'mdi:television' + MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) + SUPPORTED_COMMANDS = SUPPORT_TURN_ON | SUPPORT_TURN_OFF \ | SUPPORT_SELECT_SOURCE \ | SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK \ @@ -70,14 +60,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): device = VizioDevice(host, token, name, volume_step) if device.validate_setup() is False: - _LOGGER.error('Failed to setup Vizio TV platform, ' - 'please check if host and API key are correct.') - return False + _LOGGER.error("Failed to setup Vizio TV platform, " + "please check if host and API key are correct") + return if config.get(CONF_SUPPRESS_WARNING): from requests.packages import urllib3 - _LOGGER.warning('InsecureRequestWarning is disabled ' - 'because of Vizio platform configuration.') + _LOGGER.warning("InsecureRequestWarning is disabled " + "because of Vizio platform configuration") urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) add_devices([device], True) @@ -184,5 +174,5 @@ class VizioDevice(MediaPlayerDevice): self._device.vol_down(num=self._volume_step) def validate_setup(self): - """Validating if host is available and key is correct.""" + """Validate if host is available and key is correct.""" return self._device.get_current_volume() is not None diff --git a/homeassistant/components/media_player/webostv.py b/homeassistant/components/media_player/webostv.py index 9d3e0b90fa4..55179ed60a9 100644 --- a/homeassistant/components/media_player/webostv.py +++ b/homeassistant/components/media_player/webostv.py @@ -4,30 +4,26 @@ 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/ """ -import logging import asyncio from datetime import timedelta +import logging from urllib.parse import urlparse import voluptuous as vol -import homeassistant.util as util from homeassistant.components.media_player import ( - SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PLAY, - SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, - SUPPORT_SELECT_SOURCE, SUPPORT_PLAY_MEDIA, MEDIA_TYPE_CHANNEL, - MediaPlayerDevice, PLATFORM_SCHEMA) + MEDIA_TYPE_CHANNEL, PLATFORM_SCHEMA, 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) from homeassistant.const import ( - CONF_HOST, CONF_CUSTOMIZE, CONF_TIMEOUT, STATE_OFF, - STATE_PLAYING, STATE_PAUSED, - STATE_UNKNOWN, CONF_NAME, CONF_FILENAME) + CONF_CUSTOMIZE, CONF_FILENAME, CONF_HOST, CONF_NAME, CONF_TIMEOUT, + STATE_OFF, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.script import Script +import homeassistant.util as util -REQUIREMENTS = ['pylgtv==0.1.7', - 'websockets==3.2', - 'wakeonlan==0.2.2'] +REQUIREMENTS = ['pylgtv==0.1.7', 'websockets==3.2', 'wakeonlan==0.2.2'] _CONFIGURING = {} # type: Dict[str, str] _LOGGER = logging.getLogger(__name__) @@ -48,17 +44,16 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) CUSTOMIZE_SCHEMA = vol.Schema({ - vol.Optional(CONF_SOURCES): - vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_SOURCES): vol.All(cv.ensure_list, [cv.string]), }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_HOST): cv.string, vol.Optional(CONF_CUSTOMIZE, default={}): CUSTOMIZE_SCHEMA, vol.Optional(CONF_FILENAME, default=WEBOSTV_CONFIG_FILE): cv.string, - vol.Optional(CONF_TIMEOUT, default=8): cv.positive_int, + vol.Optional(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_ON_ACTION): cv.SCRIPT_SCHEMA, + vol.Optional(CONF_TIMEOUT, default=8): cv.positive_int, }) @@ -142,7 +137,7 @@ def request_configuration( # pylint: disable=unused-argument def lgtv_configuration_callback(data): - """The actions to do when our configuration callback is called.""" + """Handle actions when configuration callback is called.""" setup_tv(host, name, customize, config, timeout, hass, add_devices, turn_on_action) diff --git a/homeassistant/components/mychevy.py b/homeassistant/components/mychevy.py index 3a4894a2a66..678cdf10c56 100644 --- a/homeassistant/components/mychevy.py +++ b/homeassistant/components/mychevy.py @@ -4,16 +4,14 @@ MyChevy Component. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/mychevy/ """ - from datetime import timedelta import logging -import time import threading +import time import voluptuous as vol -from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD) +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import config_validation as cv from homeassistant.helpers import discovery from homeassistant.util import Throttle @@ -38,16 +36,16 @@ ERROR_SLEEP_TIME = timedelta(minutes=30) 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) class EVSensorConfig(object): - """EV Sensor Config.""" + """The EV sensor configuration.""" def __init__(self, name, attr, unit_of_measurement=None, icon=None): - """Create new Sensor Config.""" + """Create new sensor configuration.""" self.name = name self.attr = attr self.unit_of_measurement = unit_of_measurement @@ -55,17 +53,17 @@ class EVSensorConfig(object): class EVBinarySensorConfig(object): - """EV Binary Sensor Config.""" + """The EV binary sensor configuration.""" def __init__(self, name, attr, device_class=None): - """Create new Binary Sensor Config.""" + """Create new binary sensor configuration.""" self.name = name self.attr = attr self.device_class = device_class def setup(hass, base_config): - """Setup mychevy platform.""" + """Set up the mychevy component.""" import mychevy.mychevy as mc config = base_config.get(DOMAIN) diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index 71be416c59c..91053b41bf6 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -1,8 +1,8 @@ """ Connect to a MySensors gateway via pymysensors API. -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.mysensors/ +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/mysensors/ """ import asyncio from collections import defaultdict @@ -115,21 +115,20 @@ def is_serial_port(value): if value in ports: return value else: - raise vol.Invalid( - '{} is not a serial port'.format(value)) + raise vol.Invalid('{} is not a serial port'.format(value)) else: return cv.isdevice(value) def deprecated(key): - """Mark key as deprecated in config.""" + """Mark key as deprecated in configuration.""" def validator(config): """Check if key is in config, log warning and remove key.""" if key not in config: return config _LOGGER.warning( '%s option for %s is deprecated. Please remove %s from your ' - 'configuration file.', key, DOMAIN, key) + 'configuration file', key, DOMAIN, key) config.pop(key) return config return validator @@ -150,16 +149,13 @@ CONFIG_SCHEMA = vol.Schema({ vol.Any(MQTT_COMPONENT, is_socket_address, is_serial_port), vol.Optional(CONF_PERSISTENCE_FILE): vol.All(cv.string, is_persistence_file, has_parent_dir), - vol.Optional( - CONF_BAUD_RATE, - default=DEFAULT_BAUD_RATE): cv.positive_int, - vol.Optional( - CONF_TCP_PORT, - default=DEFAULT_TCP_PORT): cv.port, - vol.Optional( - CONF_TOPIC_IN_PREFIX, default=''): valid_subscribe_topic, - vol.Optional( - CONF_TOPIC_OUT_PREFIX, default=''): valid_publish_topic, + vol.Optional(CONF_BAUD_RATE, default=DEFAULT_BAUD_RATE): + cv.positive_int, + vol.Optional(CONF_TCP_PORT, default=DEFAULT_TCP_PORT): cv.port, + vol.Optional(CONF_TOPIC_IN_PREFIX, default=''): + valid_subscribe_topic, + vol.Optional(CONF_TOPIC_OUT_PREFIX, default=''): + valid_publish_topic, vol.Optional(CONF_NODES, default={}): NODE_SCHEMA, }] ), @@ -171,7 +167,7 @@ CONFIG_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) -# mysensors const schemas +# MySensors const schemas BINARY_SENSOR_SCHEMA = {PLATFORM: 'binary_sensor', TYPE: 'V_TRIPPED'} CLIMATE_SCHEMA = {PLATFORM: 'climate', TYPE: 'V_HVAC_FLOW_STATE'} LIGHT_DIMMER_SCHEMA = { @@ -439,7 +435,7 @@ def validate_child(gateway, node_id, child): def discover_mysensors_platform(hass, platform, new_devices): - """Discover a mysensors platform.""" + """Discover a MySensors platform.""" discovery.load_platform( hass, platform, DOMAIN, {ATTR_DEVICES: new_devices, CONF_NAME: DOMAIN}) @@ -458,7 +454,7 @@ def discover_persistent_devices(hass, gateway): def get_mysensors_devices(hass, domain): - """Return mysensors devices for a platform.""" + """Return MySensors devices for a platform.""" if MYSENSORS_PLATFORM_DEVICES.format(domain) not in hass.data: hass.data[MYSENSORS_PLATFORM_DEVICES.format(domain)] = {} return hass.data[MYSENSORS_PLATFORM_DEVICES.format(domain)] @@ -467,15 +463,14 @@ def get_mysensors_devices(hass, domain): def gw_callback_factory(hass): """Return a new callback for the gateway.""" def mysensors_callback(msg): - """Default callback for a mysensors gateway.""" + """Handle messages from a MySensors gateway.""" start = timer() _LOGGER.debug( "Node update: node %s child %s", msg.node_id, msg.child_id) child = msg.gateway.sensors[msg.node_id].children.get(msg.child_id) if child is None: - _LOGGER.debug( - "Not a child update for node %s", msg.node_id) + _LOGGER.debug("Not a child update for node %s", msg.node_id) return signals = [] @@ -518,7 +513,7 @@ def get_mysensors_name(gateway, node_id, child_id): def get_mysensors_gateway(hass, gateway_id): - """Return gateway.""" + """Return MySensors gateway.""" if MYSENSORS_GATEWAYS not in hass.data: hass.data[MYSENSORS_GATEWAYS] = {} gateways = hass.data.get(MYSENSORS_GATEWAYS) @@ -528,8 +523,8 @@ def get_mysensors_gateway(hass, gateway_id): def setup_mysensors_platform( hass, domain, discovery_info, device_class, device_args=None, add_devices=None): - """Set up a mysensors platform.""" - # Only act if called via mysensors by discovery event. + """Set up a MySensors platform.""" + # Only act if called via MySensors by discovery event. # Otherwise gateway is not setup. if not discovery_info: return @@ -627,7 +622,7 @@ class MySensorsEntity(MySensorsDevice, Entity): @property def should_poll(self): - """Mysensor gateway pushes its state to HA.""" + """Return the polling state. The gateway pushes its states.""" return False @property diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/notify/pushbullet.py index 359810bb6bc..13cb2fd579b 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -129,7 +129,7 @@ class PushBulletNotificationService(BaseNotificationService): continue def _push_data(self, message, title, data, pusher, email=None): - """Helper for creating the message content.""" + """Create the message content.""" from pushbullet import PushError if data is None: data = {} diff --git a/homeassistant/components/python_script.py b/homeassistant/components/python_script.py index a56b40f3064..b49b280791a 100644 --- a/homeassistant/components/python_script.py +++ b/homeassistant/components/python_script.py @@ -12,11 +12,11 @@ import time import voluptuous as vol -import homeassistant.util.dt as dt_util from homeassistant.const import SERVICE_RELOAD from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import bind_hass from homeassistant.util import sanitize_filename +import homeassistant.util.dt as dt_util REQUIREMENTS = ['restrictedpython==4.0b2'] @@ -185,7 +185,7 @@ class StubPrinter: class TimeWrapper: - """Wrapper of the time module.""" + """Wrap the time module.""" # Class variable, only going to warn once per Home Assistant run warned = False @@ -205,7 +205,7 @@ class TimeWrapper: attribute = getattr(time, attr) if callable(attribute): def wrapper(*args, **kw): - """Wrapper to return callable method if callable.""" + """Wrap to return callable method if callable.""" return attribute(*args, **kw) return wrapper else: diff --git a/homeassistant/components/raincloud.py b/homeassistant/components/raincloud.py index 1668fce0f45..505c3a7b2b0 100644 --- a/homeassistant/components/raincloud.py +++ b/homeassistant/components/raincloud.py @@ -5,20 +5,19 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/raincloud/ """ import asyncio -import logging from datetime import timedelta +import logging +from requests.exceptions import ConnectTimeout, HTTPError import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL) -from homeassistant.helpers.event import track_time_interval -from homeassistant.helpers.entity import Entity + ATTR_ATTRIBUTION, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, dispatcher_send) - -from requests.exceptions import HTTPError, ConnectTimeout +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.event import track_time_interval REQUIREMENTS = ['raincloudy==0.0.4'] @@ -115,7 +114,7 @@ def setup(hass, config): def hub_refresh(event_time): """Call Raincloud hub to refresh information.""" - _LOGGER.debug("Updating RainCloud Hub component.") + _LOGGER.debug("Updating RainCloud Hub component") hass.data[DATA_RAINCLOUD].data.update() dispatcher_send(hass, SIGNAL_UPDATE_RAINCLOUD) @@ -156,7 +155,7 @@ class RainCloudEntity(Entity): self.hass, SIGNAL_UPDATE_RAINCLOUD, self._update_callback) def _update_callback(self): - """Callback update method.""" + """Call update method.""" self.schedule_update_ha_state(True) @property @@ -175,5 +174,5 @@ class RainCloudEntity(Entity): @property def icon(self): - """Icon to use in the frontend, if any.""" + """Return the icon to use in the frontend, if any.""" return ICON_MAP.get(self._sensor_type) diff --git a/homeassistant/components/raspihats.py b/homeassistant/components/raspihats.py index e9d65b85c81..e3c1ab8ff88 100644 --- a/homeassistant/components/raspihats.py +++ b/homeassistant/components/raspihats.py @@ -9,11 +9,9 @@ import threading import time from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP -) + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) -REQUIREMENTS = ['raspihats==2.2.3', - 'smbus-cffi==0.5.1'] +REQUIREMENTS = ['raspihats==2.2.3', 'smbus-cffi==0.5.1'] _LOGGER = logging.getLogger(__name__) @@ -37,7 +35,7 @@ I2C_HATS_MANAGER = 'I2CH_MNG' # pylint: disable=unused-argument def setup(hass, config): - """Setup the raspihats component.""" + """Set up the raspihats component.""" hass.data[I2C_HATS_MANAGER] = I2CHatsManager() def start_i2c_hats_keep_alive(event): @@ -73,13 +71,13 @@ class I2CHatsDIScanner(object): _CALLBACKS = "callbacks" def setup(self, i2c_hat): - """Setup I2C-HAT instance for digital inputs scanner.""" + """Set up the I2C-HAT instance for digital inputs scanner.""" if hasattr(i2c_hat, self._DIGITAL_INPUTS): digital_inputs = getattr(i2c_hat, self._DIGITAL_INPUTS) old_value = None - # add old value attribute + # Add old value attribute setattr(digital_inputs, self._OLD_VALUE, old_value) - # add callbacks dict attribute {channel: callback} + # Add callbacks dict attribute {channel: callback} setattr(digital_inputs, self._CALLBACKS, {}) def register_callback(self, i2c_hat, channel, callback): @@ -141,17 +139,15 @@ class I2CHatsManager(threading.Thread): self._i2c_hats[address] = i2c_hat status_word = i2c_hat.status # read status_word to reset bits _LOGGER.info( - log_message(self, i2c_hat, "registered", status_word) - ) + log_message(self, i2c_hat, "registered", status_word)) def run(self): """Keep alive for I2C-HATs.""" # pylint: disable=import-error from raspihats.i2c_hats import ResponseException - _LOGGER.info( - log_message(self, "starting") - ) + _LOGGER.info(log_message(self, "starting")) + while self._run: with self._lock: for i2c_hat in list(self._i2c_hats.values()): @@ -176,17 +172,13 @@ class I2CHatsManager(threading.Thread): ) setattr(i2c_hat, self._EXCEPTION, ex) time.sleep(0.05) - _LOGGER.info( - log_message(self, "exiting") - ) + _LOGGER.info(log_message(self, "exiting")) def _read_status(self, i2c_hat): """Read I2C-HATs status.""" status_word = i2c_hat.status if status_word.value != 0x00: - _LOGGER.error( - log_message(self, i2c_hat, status_word) - ) + _LOGGER.error(log_message(self, i2c_hat, status_word)) def start_keep_alive(self): """Start keep alive mechanism.""" diff --git a/homeassistant/components/remember_the_milk/__init__.py b/homeassistant/components/remember_the_milk/__init__.py index 08c371fcf0a..581380e3667 100644 --- a/homeassistant/components/remember_the_milk/__init__.py +++ b/homeassistant/components/remember_the_milk/__init__.py @@ -1,21 +1,17 @@ -"""Component to interact with Remember The Milk. +""" +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/ - -Minimum viable product, it currently only support creating new tasks in your -Remember The Milk (https://www.rememberthemilk.com/) account. - -This product uses the Remember The Milk API but is not endorsed or certified -by Remember The Milk. """ +import json import logging import os -import json + import voluptuous as vol -from homeassistant.const import (CONF_API_KEY, STATE_OK, CONF_TOKEN, - CONF_NAME, CONF_ID) +from homeassistant.const import ( + CONF_API_KEY, CONF_ID, CONF_NAME, CONF_TOKEN, STATE_OK) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent @@ -61,9 +57,9 @@ SERVICE_SCHEMA_COMPLETE_TASK = vol.Schema({ def setup(hass, config): - """Set up the remember_the_milk component.""" - component = EntityComponent(_LOGGER, DOMAIN, hass, - group_name=GROUP_NAME_RTM) + """Set up the Remember the milk component.""" + component = EntityComponent( + _LOGGER, DOMAIN, hass, group_name=GROUP_NAME_RTM) stored_rtm_config = RememberTheMilkConfiguration(hass) for rtm_config in config[DOMAIN]: @@ -107,21 +103,21 @@ def _register_new_account(hass, account_name, api_key, shared_secret, configurator = hass.components.configurator api = Rtm(api_key, shared_secret, "write", None) url, frob = api.authenticate_desktop() - _LOGGER.debug('sent authentication request to server') + _LOGGER.debug("Sent authentication request to server") def register_account_callback(_): - """Callback for configurator.""" + """Call for register the configurator.""" api.retrieve_token(frob) token = api.token if api.token is None: - _LOGGER.error('Failed to register, please try again.') + _LOGGER.error("Failed to register, please try again") configurator.notify_errors( request_id, 'Failed to register, please try again.') return stored_rtm_config.set_token(account_name, token) - _LOGGER.debug('retrieved new token from server') + _LOGGER.debug("Retrieved new token from server") _create_instance( hass, account_name, api_key, shared_secret, token, @@ -155,13 +151,13 @@ class RememberTheMilkConfiguration(object): self._config = dict() return try: - _LOGGER.debug('loading configuration from file: %s', + _LOGGER.debug("Loading configuration from file: %s", self._config_file_path) with open(self._config_file_path, 'r') as config_file: self._config = json.load(config_file) except ValueError: - _LOGGER.error('failed to load configuration file, creating a ' - 'new one: %s', self._config_file_path) + _LOGGER.error("Failed to load configuration file, creating a " + "new one: %s", self._config_file_path) self._config = dict() def save_config(self): @@ -197,9 +193,9 @@ class RememberTheMilkConfiguration(object): self._config[profile_name][CONF_ID_MAP] = dict() def get_rtm_id(self, profile_name, hass_id): - """Get the rtm ids for a home assistant task id. + """Get the RTM ids for a Home Assistant task ID. - The id of a rtm tasks consists of the tuple: + The id of a RTM tasks consists of the tuple: list id, timeseries id and the task id. """ self._initialize_profile(profile_name) @@ -210,7 +206,7 @@ class RememberTheMilkConfiguration(object): def set_rtm_id(self, profile_name, hass_id, list_id, time_series_id, rtm_task_id): - """Add/Update the rtm task id for a home assistant task id.""" + """Add/Update the RTM task ID for a Home Assistant task IS.""" self._initialize_profile(profile_name) id_tuple = { CONF_LIST_ID: list_id, @@ -229,7 +225,7 @@ class RememberTheMilkConfiguration(object): class RememberTheMilk(Entity): - """MVP implementation of an interface to Remember The Milk.""" + """Representation of an interface to Remember The Milk.""" def __init__(self, name, api_key, shared_secret, token, rtm_config): """Create new instance of Remember The Milk component.""" @@ -243,7 +239,7 @@ class RememberTheMilk(Entity): self._rtm_api = rtmapi.Rtm(api_key, shared_secret, "delete", token) self._token_valid = None self._check_token() - _LOGGER.debug("instance created for account %s", self._name) + _LOGGER.debug("Instance created for account %s", self._name) def _check_token(self): """Check if the API token is still valid. @@ -253,8 +249,8 @@ class RememberTheMilk(Entity): """ valid = self._rtm_api.token_valid() if not valid: - _LOGGER.error('Token for account %s is invalid. You need to ' - 'register again!', self.name) + _LOGGER.error("Token for account %s is invalid. You need to " + "register again!", self.name) self._rtm_config.delete_token(self._name) self._token_valid = False else: @@ -264,7 +260,7 @@ class RememberTheMilk(Entity): def create_task(self, call): """Create a new task on Remember The Milk. - You can use the smart syntax to define the attribues of a new task, + You can use the smart syntax to define the attributes of a new task, e.g. "my task #some_tag ^today" will add tag "some_tag" and set the due date to today. """ @@ -282,25 +278,20 @@ class RememberTheMilk(Entity): if hass_id is None or rtm_id is None: result = self._rtm_api.rtm.tasks.add( timeline=timeline, name=task_name, parse='1') - _LOGGER.debug('created new task "%s" in account %s', + _LOGGER.debug("Created new task '%s' in account %s", task_name, self.name) - self._rtm_config.set_rtm_id(self._name, - hass_id, - result.list.id, - result.list.taskseries.id, - result.list.taskseries.task.id) + self._rtm_config.set_rtm_id( + self._name, hass_id, result.list.id, + result.list.taskseries.id, result.list.taskseries.task.id) else: - self._rtm_api.rtm.tasks.setName(name=task_name, - list_id=rtm_id[0], - taskseries_id=rtm_id[1], - task_id=rtm_id[2], - timeline=timeline) - _LOGGER.debug('updated task with id "%s" in account ' - '%s to name %s', - hass_id, self.name, task_name) + self._rtm_api.rtm.tasks.setName( + name=task_name, list_id=rtm_id[0], taskseries_id=rtm_id[1], + task_id=rtm_id[2], timeline=timeline) + _LOGGER.debug("Updated task with id '%s' in account " + "%s to name %s", hass_id, self.name, task_name) except rtmapi.RtmRequestFailedException as rtm_exception: - _LOGGER.error('Error creating new Remember The Milk task for ' - 'account %s: %s', self._name, rtm_exception) + _LOGGER.error("Error creating new Remember The Milk task for " + "account %s: %s", self._name, rtm_exception) return False return True @@ -311,23 +302,21 @@ class RememberTheMilk(Entity): hass_id = call.data.get(CONF_ID) rtm_id = self._rtm_config.get_rtm_id(self._name, hass_id) if rtm_id is None: - _LOGGER.error('Could not find task with id %s in account %s. ' - 'So task could not be closed.', - hass_id, self._name) + _LOGGER.error("Could not find task with ID %s in account %s. " + "So task could not be closed", hass_id, self._name) return False try: result = self._rtm_api.rtm.timelines.create() timeline = result.timeline.value - self._rtm_api.rtm.tasks.complete(list_id=rtm_id[0], - taskseries_id=rtm_id[1], - task_id=rtm_id[2], - timeline=timeline) + self._rtm_api.rtm.tasks.complete( + list_id=rtm_id[0], taskseries_id=rtm_id[1], task_id=rtm_id[2], + timeline=timeline) self._rtm_config.delete_rtm_id(self._name, hass_id) - _LOGGER.debug('Completed task with id %s in account %s', + _LOGGER.debug("Completed task with id %s in account %s", hass_id, self._name) except rtmapi.RtmRequestFailedException as rtm_exception: - _LOGGER.error('Error creating new Remember The Milk task for ' - 'account %s: %s', self._name, rtm_exception) + _LOGGER.error("Error creating new Remember The Milk task for " + "account %s: %s", self._name, rtm_exception) return True @property @@ -339,5 +328,5 @@ class RememberTheMilk(Entity): def state(self): """Return the state of the device.""" if not self._token_valid: - return 'API token invalid' + return "API token invalid" return STATE_OK diff --git a/homeassistant/components/remote/harmony.py b/homeassistant/components/remote/harmony.py index 4d241ed5913..89cdc7529cb 100644 --- a/homeassistant/components/remote/harmony.py +++ b/homeassistant/components/remote/harmony.py @@ -4,19 +4,19 @@ Support for Harmony Hub devices. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/remote.harmony/ """ -import logging import asyncio +import logging import time import voluptuous as vol import homeassistant.components.remote as remote -import homeassistant.helpers.config_validation as cv -from homeassistant.const import ( - CONF_NAME, CONF_HOST, CONF_PORT, ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_STOP) from homeassistant.components.remote import ( - PLATFORM_SCHEMA, DOMAIN, ATTR_DEVICE, ATTR_ACTIVITY, ATTR_NUM_REPEATS, - ATTR_DELAY_SECS, DEFAULT_DELAY_SECS) + ATTR_ACTIVITY, ATTR_DELAY_SECS, ATTR_DEVICE, ATTR_NUM_REPEATS, + DEFAULT_DELAY_SECS, DOMAIN, PLATFORM_SCHEMA) +from homeassistant.const import ( + ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_STOP) +import homeassistant.helpers.config_validation as cv from homeassistant.util import slugify REQUIREMENTS = ['pyharmony==1.0.18'] @@ -30,12 +30,12 @@ CONF_DEVICE_CACHE = 'harmony_device_cache' SERVICE_SYNC = 'harmony_sync' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Required(ATTR_ACTIVITY, default=None): cv.string, + vol.Required(CONF_NAME): cv.string, vol.Optional(ATTR_DELAY_SECS, default=DEFAULT_DELAY_SECS): vol.Coerce(float), + vol.Optional(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, }) HARMONY_SYNC_SCHEMA = vol.Schema({ @@ -182,7 +182,7 @@ class HarmonyRemote(remote.RemoteDevice): return self._current_activity not in [None, 'PowerOff'] def new_activity(self, activity_id): - """Callback for updating the current activity.""" + """Call for updating the current activity.""" import pyharmony activity_name = pyharmony.activity_name(self._config, activity_id) _LOGGER.debug("%s activity reported as: %s", self._name, activity_name) diff --git a/homeassistant/components/remote/kira.py b/homeassistant/components/remote/kira.py index c1a04718d33..7a04949dbeb 100644 --- a/homeassistant/components/remote/kira.py +++ b/homeassistant/components/remote/kira.py @@ -4,15 +4,13 @@ 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/ """ -import logging import functools as ft +import logging import homeassistant.components.remote as remote +from homeassistant.const import CONF_DEVICE, CONF_NAME from homeassistant.helpers.entity import Entity -from homeassistant.const import ( - CONF_DEVICE, CONF_NAME) - DOMAIN = 'kira' _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/scene/wink.py b/homeassistant/components/scene/wink.py index 008edf6f131..2d4a6d0621c 100644 --- a/homeassistant/components/scene/wink.py +++ b/homeassistant/components/scene/wink.py @@ -8,10 +8,11 @@ import asyncio import logging from homeassistant.components.scene import Scene -from homeassistant.components.wink import WinkDevice, DOMAIN +from homeassistant.components.wink import DOMAIN, WinkDevice + +_LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['wink'] -_LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): @@ -34,7 +35,7 @@ class WinkScene(WinkDevice, Scene): @asyncio.coroutine def async_added_to_hass(self): - """Callback when entity is added to hass.""" + """Call when entity is added to hass.""" self.hass.data[DOMAIN]['entities']['scene'].append(self) @property diff --git a/homeassistant/components/sensor/abode.py b/homeassistant/components/sensor/abode.py index 3a465db4488..1a700e24de6 100644 --- a/homeassistant/components/sensor/abode.py +++ b/homeassistant/components/sensor/abode.py @@ -47,8 +47,8 @@ class AbodeSensor(AbodeDevice): super().__init__(data, device) self._sensor_type = sensor_type self._icon = 'mdi:{}'.format(SENSOR_TYPES[self._sensor_type][1]) - self._name = '{0} {1}'.format(self._device.name, - SENSOR_TYPES[self._sensor_type][0]) + self._name = '{0} {1}'.format( + self._device.name, SENSOR_TYPES[self._sensor_type][0]) @property def icon(self): diff --git a/homeassistant/components/sensor/ads.py b/homeassistant/components/sensor/ads.py index 725cbb555f1..659ac6d7e5d 100644 --- a/homeassistant/components/sensor/ads.py +++ b/homeassistant/components/sensor/ads.py @@ -3,33 +3,32 @@ Support for ADS sensors. For more details about this platform, please refer to the documentation. https://home-assistant.io/components/sensor.ads/ - """ import asyncio import logging + import voluptuous as vol + +from homeassistant.components import ads +from homeassistant.components.ads import ( + CONF_ADS_FACTOR, CONF_ADS_TYPE, CONF_ADS_VAR) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT -from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv -from homeassistant.components import ads -from homeassistant.components.ads import CONF_ADS_VAR, CONF_ADS_TYPE, \ - CONF_ADS_FACTOR - +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'ADS sensor' +DEFAULT_NAME = "ADS sensor" DEPENDENCIES = ['ads'] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_ADS_VAR): cv.string, + vol.Optional(CONF_ADS_FACTOR): cv.positive_int, + vol.Optional(CONF_ADS_TYPE, default=ads.ADSTYPE_INT): + vol.In([ads.ADSTYPE_INT, ads.ADSTYPE_UINT, ads.ADSTYPE_BYTE]), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=''): cv.string, - vol.Optional(CONF_ADS_TYPE, default=ads.ADSTYPE_INT): vol.In( - [ads.ADSTYPE_INT, ads.ADSTYPE_UINT, ads.ADSTYPE_BYTE] - ), - vol.Optional(CONF_ADS_FACTOR): cv.positive_int, }) @@ -43,8 +42,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): unit_of_measurement = config.get(CONF_UNIT_OF_MEASUREMENT) factor = config.get(CONF_ADS_FACTOR) - entity = AdsSensor(ads_hub, ads_var, ads_type, name, - unit_of_measurement, factor) + entity = AdsSensor( + ads_hub, ads_var, ads_type, name, unit_of_measurement, factor) add_devices([entity]) @@ -68,9 +67,9 @@ class AdsSensor(Entity): """Register device notification.""" def update(name, value): """Handle device notifications.""" - _LOGGER.debug('Variable %s changed its value to %d', name, value) + _LOGGER.debug("Variable %s changed its value to %d", name, value) - # if factor is set use it otherwise not + # If factor is set use it otherwise not if self.factor is None: self._value = value else: @@ -99,5 +98,5 @@ class AdsSensor(Entity): @property def should_poll(self): - """Return False because entity pushes its state to HA.""" + """Return False because entity pushes its state.""" return False diff --git a/homeassistant/components/sensor/deconz.py b/homeassistant/components/sensor/deconz.py index b3118d3906d..7c2c1e0895f 100644 --- a/homeassistant/components/sensor/deconz.py +++ b/homeassistant/components/sensor/deconz.py @@ -86,12 +86,12 @@ class DeconzSensor(Entity): @property def unit_of_measurement(self): - """Unit of measurement of this sensor.""" + """Return the unit of measurement of this sensor.""" return self._sensor.sensor_unit @property def available(self): - """Return True if sensor is available.""" + """Return true if sensor is available.""" return self._sensor.reachable @property diff --git a/homeassistant/components/sensor/dyson.py b/homeassistant/components/sensor/dyson.py index 62c77bb768f..91629a18f68 100644 --- a/homeassistant/components/sensor/dyson.py +++ b/homeassistant/components/sensor/dyson.py @@ -1,23 +1,23 @@ -"""Support for Dyson Pure Cool Link Sensors. +""" +Support for Dyson Pure Cool Link Sensors. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.dyson/ """ -import logging import asyncio +import logging -from homeassistant.const import TEMP_CELSIUS, STATE_OFF from homeassistant.components.dyson import DYSON_DEVICES - +from homeassistant.const import STATE_OFF, TEMP_CELSIUS from homeassistant.helpers.entity import Entity DEPENDENCIES = ['dyson'] SENSOR_UNITS = { - "filter_life": "hours", - "humidity": "%", - "dust": "level", - "air_quality": "level" + 'air_quality': 'level', + 'dust': 'level', + 'filter_life': 'hours', + 'humidity': '%', } _LOGGER = logging.getLogger(__name__) @@ -25,7 +25,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Dyson Sensors.""" - _LOGGER.info("Creating new Dyson fans") + _LOGGER.debug("Creating new Dyson fans") devices = [] unit = hass.config.units.temperature_unit # Get Dyson Devices from parent component @@ -52,12 +52,12 @@ class DysonSensor(Entity): @asyncio.coroutine def async_added_to_hass(self): - """Callback when entity is added to hass.""" + """Call when entity is added to hass.""" self.hass.async_add_job( self._device.add_message_listener, self.on_message) def on_message(self, message): - """Called when new messages received from the fan.""" + """Handle new messages which are received from the fan.""" # Prevent refreshing if not needed if self._old_value is None or self._old_value != self.state: _LOGGER.debug("Message received for %s device: %s", self.name, diff --git a/homeassistant/components/sensor/imap.py b/homeassistant/components/sensor/imap.py index 9f12353221d..f0585228851 100644 --- a/homeassistant/components/sensor/imap.py +++ b/homeassistant/components/sensor/imap.py @@ -4,19 +4,19 @@ IMAP sensor support. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.imap/ """ -import logging import asyncio -import async_timeout +import logging +import async_timeout import voluptuous as vol -from homeassistant.helpers.entity import Entity from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, + CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup the IMAP platform.""" + """Set up the IMAP platform.""" sensor = ImapSensor(config.get(CONF_NAME), config.get(CONF_USERNAME), config.get(CONF_PASSWORD), diff --git a/homeassistant/components/sensor/kira.py b/homeassistant/components/sensor/kira.py index 232e50b85ed..b5d3073ea9a 100644 --- a/homeassistant/components/sensor/kira.py +++ b/homeassistant/components/sensor/kira.py @@ -1,12 +1,12 @@ -"""KIRA interface to receive UDP packets from an IR-IP bridge.""" -# pylint: disable=import-error +""" +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/ +""" import logging -from homeassistant.const import ( - CONF_DEVICE, - CONF_NAME, - STATE_UNKNOWN) - +from homeassistant.const import CONF_DEVICE, CONF_NAME, STATE_UNKNOWN from homeassistant.helpers.entity import Entity DOMAIN = 'kira' @@ -15,17 +15,18 @@ _LOGGER = logging.getLogger(__name__) ICON = 'mdi:remote' -CONF_SENSOR = "sensor" +CONF_SENSOR = 'sensor' # pylint: disable=unused-argument, too-many-function-args -def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup Kira sensor.""" +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up a Kira sensor.""" if discovery_info is not None: name = discovery_info.get(CONF_NAME) device = discovery_info.get(CONF_DEVICE) kira = hass.data[DOMAIN][CONF_SENSOR][name] - add_devices_callback([KiraReceiver(device, kira)]) + + add_devices([KiraReceiver(device, kira)]) class KiraReceiver(Entity): @@ -34,14 +35,14 @@ class KiraReceiver(Entity): def __init__(self, name, kira): """Initialize the sensor.""" self._name = name - self._state = STATE_UNKNOWN + self._state = None self._device = STATE_UNKNOWN kira.registerCallback(self._update_callback) def _update_callback(self, code): code_name, device = code - _LOGGER.info("Kira Code: %s", code_name) + _LOGGER.debug("Kira Code: %s", code_name) self._state = code_name self._device = device self.schedule_update_ha_state() @@ -64,9 +65,7 @@ class KiraReceiver(Entity): @property def device_state_attributes(self): """Return the state attributes of the device.""" - attr = {} - attr[CONF_DEVICE] = self._device - return attr + return {CONF_DEVICE: self._device} @property def should_poll(self) -> bool: diff --git a/homeassistant/components/sensor/knx.py b/homeassistant/components/sensor/knx.py index f803f406e1e..0a455099597 100644 --- a/homeassistant/components/sensor/knx.py +++ b/homeassistant/components/sensor/knx.py @@ -5,14 +5,15 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.knx/ """ import asyncio + import voluptuous as vol -from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES -from homeassistant.helpers.entity import Entity +from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity CONF_ADDRESS = 'address' CONF_TYPE = 'type' @@ -28,20 +29,16 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine -def async_setup_platform(hass, config, async_add_devices, - discovery_info=None): +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up sensor(s) for KNX platform.""" - if DATA_KNX not in hass.data \ - or not hass.data[DATA_KNX].initialized: - return False + if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized: + return if discovery_info is not None: async_add_devices_discovery(hass, discovery_info, async_add_devices) else: async_add_devices_config(hass, config, async_add_devices) - return True - @callback def async_add_devices_discovery(hass, discovery_info, async_add_devices): @@ -70,7 +67,7 @@ class KNXSensor(Entity): """Representation of a KNX sensor.""" def __init__(self, hass, device): - """Initialization of KNXSensor.""" + """Initialize of a KNX sensor.""" self.device = device self.hass = hass self.async_register_callbacks() @@ -80,7 +77,7 @@ class KNXSensor(Entity): """Register callbacks to update hass after device was changed.""" @asyncio.coroutine def after_update_callback(device): - """Callback after device was updated.""" + """Call after device was updated.""" # pylint: disable=unused-argument yield from self.async_update_ha_state() self.device.register_device_updated_cb(after_update_callback) diff --git a/homeassistant/components/sensor/lacrosse.py b/homeassistant/components/sensor/lacrosse.py index d284e3012dc..b402fc5c70f 100644 --- a/homeassistant/components/sensor/lacrosse.py +++ b/homeassistant/components/sensor/lacrosse.py @@ -4,17 +4,17 @@ Support for LaCrosse sensor components. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.lacrosse/ """ -import logging from datetime import timedelta +import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.core import callback -from homeassistant.components.sensor import (ENTITY_ID_FORMAT, PLATFORM_SCHEMA) +from homeassistant.components.sensor import ENTITY_ID_FORMAT, PLATFORM_SCHEMA from homeassistant.const import ( - EVENT_HOMEASSISTANT_STOP, CONF_DEVICE, CONF_NAME, CONF_ID, - CONF_SENSORS, CONF_TYPE, TEMP_CELSIUS) + CONF_DEVICE, CONF_ID, CONF_NAME, CONF_SENSORS, CONF_TYPE, + EVENT_HOMEASSISTANT_STOP, TEMP_CELSIUS) +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity, async_generate_entity_id from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util import dt as dt_util @@ -147,7 +147,7 @@ class LaCrosseSensor(Entity): return attributes def _callback_lacrosse(self, lacrosse_sensor, user_data): - """Callback function that is called from pylacrosse with new values.""" + """Handle a function that is called from pylacrosse with new values.""" if self._expire_after is not None and self._expire_after > 0: # Reset old trigger if self._expiration_trigger: diff --git a/homeassistant/components/sensor/london_air.py b/homeassistant/components/sensor/london_air.py index 848e1255833..2ffbb914275 100644 --- a/homeassistant/components/sensor/london_air.py +++ b/homeassistant/components/sensor/london_air.py @@ -4,22 +4,24 @@ Sensor for checking the status of London air. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.london_air/ """ -import logging from datetime import timedelta +import logging -import voluptuous as vol import requests +import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import STATE_UNKNOWN +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) CONF_LOCATIONS = 'locations' + SCAN_INTERVAL = timedelta(minutes=30) + AUTHORITIES = [ 'Barking and Dagenham', 'Bexley', @@ -48,6 +50,7 @@ AUTHORITIES = [ 'Tower Hamlets', 'Wandsworth', 'Westminster'] + URL = ('http://api.erg.kcl.ac.uk/AirQuality/Hourly/' 'MonitoringIndex/GroupName=London/Json') @@ -58,7 +61,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Set up the Tube sensor.""" + """Set up the London Air sensor.""" data = APIData() data.update() sensors = [] @@ -199,7 +202,7 @@ def parse_site(entry_sites_data): def parse_api_response(response): - """API can return dict or list of data so need to check.""" + """Parse return dict or list of data from API.""" data = dict.fromkeys(AUTHORITIES) for authority in AUTHORITIES: for entry in response['HourlyAirQualityIndex']['LocalAuthority']: diff --git a/homeassistant/components/sensor/mopar.py b/homeassistant/components/sensor/mopar.py index fdf6c9132d5..99ea4ef6135 100644 --- a/homeassistant/components/sensor/mopar.py +++ b/homeassistant/components/sensor/mopar.py @@ -4,33 +4,35 @@ Sensor for Mopar vehicles. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.mopar/ """ -import logging from datetime import timedelta +import logging import voluptuous as vol from homeassistant.components.sensor import DOMAIN, PLATFORM_SCHEMA -from homeassistant.helpers.entity import Entity -from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, CONF_PIN, - ATTR_ATTRIBUTION, ATTR_COMMAND, - LENGTH_KILOMETERS) -from homeassistant.util import Throttle +from homeassistant.const import ( + ATTR_ATTRIBUTION, ATTR_COMMAND, CONF_PASSWORD, CONF_PIN, CONF_USERNAME, + LENGTH_KILOMETERS) import homeassistant.helpers.config_validation as cv - +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle REQUIREMENTS = ['motorparts==1.0.2'] _LOGGER = logging.getLogger(__name__) -MIN_TIME_BETWEEN_UPDATES = timedelta(days=7) ATTR_VEHICLE_INDEX = 'vehicle_index' -SERVICE_REMOTE_COMMAND = 'mopar_remote_command' + COOKIE_FILE = 'mopar_cookies.pickle' +MIN_TIME_BETWEEN_UPDATES = timedelta(days=7) + +SERVICE_REMOTE_COMMAND = 'mopar_remote_command' + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_PIN): cv.positive_int + vol.Required(CONF_PIN): cv.positive_int, }) REMOTE_COMMAND_SCHEMA = vol.Schema({ @@ -41,17 +43,16 @@ REMOTE_COMMAND_SCHEMA = vol.Schema({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Mopar platform.""" + """Set up the Mopar platform.""" import motorparts cookie = hass.config.path(COOKIE_FILE) try: - session = motorparts.get_session(config.get(CONF_USERNAME), - config.get(CONF_PASSWORD), - config.get(CONF_PIN), - cookie_path=cookie) + session = motorparts.get_session( + config.get(CONF_USERNAME), config.get(CONF_PASSWORD), + config.get(CONF_PIN), cookie_path=cookie) except motorparts.MoparError: - _LOGGER.error("failed to login") - return False + _LOGGER.error("Failed to login") + return def _handle_service(service): """Handle service call.""" @@ -67,12 +68,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): data = MoparData(session) add_devices([MoparSensor(data, index) - for index, _ in enumerate(data.vehicles)], - True) - return True + for index, _ in enumerate(data.vehicles)], True) -# pylint: disable=too-few-public-methods class MoparData(object): """Container for Mopar vehicle data. @@ -91,11 +89,11 @@ class MoparData(object): def update(self, **kwargs): """Update data.""" import motorparts - _LOGGER.info("updating vehicle data") + _LOGGER.info("Updating vehicle data") try: self.vehicles = motorparts.get_summary(self._session)['vehicles'] except motorparts.MoparError: - _LOGGER.exception("failed to get summary") + _LOGGER.exception("Failed to get summary") return for index, _ in enumerate(self.vehicles): try: @@ -103,7 +101,7 @@ class MoparData(object): self.tow_guides[index] = motorparts.get_tow_guide( self._session, index) except motorparts.MoparError: - _LOGGER.warning("failed to update for vehicle index %s", index) + _LOGGER.warning("Failed to update for vehicle index %s", index) class MoparSensor(Entity): @@ -132,9 +130,9 @@ class MoparSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return '{} {} {}'.format(self._vehicle['year'], - self._vehicle['make'], - self._vehicle['model']) + return '{} {} {}'.format( + self._vehicle['year'], self._vehicle['make'], + self._vehicle['model']) @property def state(self): diff --git a/homeassistant/components/sensor/nederlandse_spoorwegen.py b/homeassistant/components/sensor/nederlandse_spoorwegen.py index 3535e00d79b..ec534047ccc 100644 --- a/homeassistant/components/sensor/nederlandse_spoorwegen.py +++ b/homeassistant/components/sensor/nederlandse_spoorwegen.py @@ -4,17 +4,16 @@ Support for Nederlandse Spoorwegen public transport. For more details on this platform, please refer to the documentation at https://home-assistant.io/components/sensor.nederlandse_spoorwegen/ """ -from datetime import datetime -from datetime import timedelta +from datetime import datetime, timedelta import logging -import voluptuous as vol import requests +import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_EMAIL, CONF_NAME, - CONF_PASSWORD, ATTR_ATTRIBUTION) +from homeassistant.const import ( + ATTR_ATTRIBUTION, CONF_EMAIL, CONF_NAME, CONF_PASSWORD) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -50,7 +49,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the departure sensor.""" + """Set up the departure sensor.""" import ns_api nsapi = ns_api.NSAPI( config.get(CONF_EMAIL), config.get(CONF_PASSWORD)) @@ -77,7 +76,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def valid_stations(stations, given_stations): - """Verify the existance of the given station codes.""" + """Verify the existence of the given station codes.""" for station in given_stations: if station is None: continue diff --git a/homeassistant/components/sensor/openhardwaremonitor.py b/homeassistant/components/sensor/openhardwaremonitor.py index 54ce5dbd6da..1b5867836bc 100644 --- a/homeassistant/components/sensor/openhardwaremonitor.py +++ b/homeassistant/components/sensor/openhardwaremonitor.py @@ -1,16 +1,21 @@ -"""Support for Open Hardware Monitor Sensor Platform.""" +""" +Support for Open Hardware Monitor Sensor Platform. +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.openhardwaremonitor/ +""" from datetime import timedelta import logging + import requests import voluptuous as vol -from homeassistant.util.dt import utcnow +from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_HOST, CONF_PORT import homeassistant.helpers.config_validation as cv -from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle +from homeassistant.util.dt import utcnow _LOGGER = logging.getLogger(__name__) @@ -37,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Open Hardware Monitor platform.""" + """Set up the Open Hardware Monitor platform.""" data = OpenHardwareMonitorData(config, hass) add_devices(data.devices, True) @@ -124,9 +129,8 @@ class OpenHardwareMonitorData(object): def refresh(self): """Download and parse JSON from OHM.""" - data_url = "http://%s:%d/data.json" % ( - self._config.get(CONF_HOST), - self._config.get(CONF_PORT)) + data_url = "http://{}:{}/data.json".format( + self._config.get(CONF_HOST), self._config.get(CONF_PORT)) try: response = requests.get(data_url, timeout=30) @@ -135,7 +139,7 @@ class OpenHardwareMonitorData(object): _LOGGER.error("ConnectionError: Is OpenHardwareMonitor running?") def initialize(self, now): - """Initial parsing of the sensors and adding of devices.""" + """Parse of the sensors and adding of devices.""" self.refresh() if self.data is None: @@ -173,11 +177,7 @@ class OpenHardwareMonitorData(object): fullname = ' '.join(child_names) dev = OpenHardwareMonitorDevice( - self, - fullname, - path, - unit_of_measurement - ) + self, fullname, path, unit_of_measurement) result.append(dev) return result diff --git a/homeassistant/components/sensor/rfxtrx.py b/homeassistant/components/sensor/rfxtrx.py index 1c09bc01909..1696e8e3770 100644 --- a/homeassistant/components/sensor/rfxtrx.py +++ b/homeassistant/components/sensor/rfxtrx.py @@ -9,14 +9,14 @@ import logging import voluptuous as vol import homeassistant.components.rfxtrx as rfxtrx -import homeassistant.helpers.config_validation as cv +from homeassistant.components.rfxtrx import ( + ATTR_DATA_TYPE, ATTR_FIRE_EVENT, ATTR_NAME, CONF_AUTOMATIC_ADD, + CONF_DATA_TYPE, CONF_DEVICES, CONF_FIRE_EVENT, DATA_TYPES) +from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ATTR_ENTITY_ID, CONF_NAME +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import slugify -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.components.rfxtrx import ( - ATTR_NAME, ATTR_FIRE_EVENT, ATTR_DATA_TYPE, CONF_AUTOMATIC_ADD, - CONF_FIRE_EVENT, CONF_DEVICES, DATA_TYPES, CONF_DATA_TYPE) DEPENDENCIES = ['rfxtrx'] @@ -35,13 +35,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }, extra=vol.ALLOW_EXTRA) -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the RFXtrx platform.""" from RFXtrx import SensorEvent sensors = [] for packet_id, entity_info in config[CONF_DEVICES].items(): event = rfxtrx.get_rfx_object(packet_id) - device_id = "sensor_" + slugify(event.device.id_string.lower()) + device_id = "sensor_{}".format(slugify(event.device.id_string.lower())) if device_id in rfxtrx.RFX_DEVICES: continue _LOGGER.info("Add %s rfxtrx.sensor", entity_info[ATTR_NAME]) @@ -60,7 +60,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): sensors.append(new_sensor) sub_sensors[_data_type] = new_sensor rfxtrx.RFX_DEVICES[device_id] = sub_sensors - add_devices_callback(sensors) + add_devices(sensors) def sensor_update(event): """Handle sensor updates from the RFXtrx gateway.""" @@ -78,8 +78,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): if sensors[key].should_fire_event: sensor.hass.bus.fire( "signal_received", { - ATTR_ENTITY_ID: - sensors[key].entity_id, + ATTR_ENTITY_ID: sensors[key].entity_id, } ) return @@ -100,7 +99,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): sub_sensors = {} sub_sensors[new_sensor.data_type] = new_sensor rfxtrx.RFX_DEVICES[device_id] = sub_sensors - add_devices_callback([new_sensor]) + add_devices([new_sensor]) if sensor_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS: rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(sensor_update) @@ -135,7 +134,7 @@ class RfxtrxSensor(Entity): @property def device_state_attributes(self): - """Return the state attributes.""" + """Return the device state attributes.""" if not self.event: return None return self.event.values diff --git a/homeassistant/components/sensor/tesla.py b/homeassistant/components/sensor/tesla.py index 3f36a1128d6..4534c8d6203 100644 --- a/homeassistant/components/sensor/tesla.py +++ b/homeassistant/components/sensor/tesla.py @@ -4,12 +4,13 @@ Sensors for the Tesla sensors. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.tesla/ """ -import logging from datetime import timedelta +import logging -from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.components.sensor import ENTITY_ID_FORMAT -from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN, TeslaDevice +from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN +from homeassistant.components.tesla import TeslaDevice +from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -37,7 +38,7 @@ class TeslaSensor(TeslaDevice, Entity): """Representation of Tesla sensors.""" def __init__(self, tesla_device, controller, sensor_type=None): - """Initialisation of the sensor.""" + """Initialize of the sensor.""" self.current_value = None self._unit = None self.last_changed_time = None diff --git a/homeassistant/components/sensor/usps.py b/homeassistant/components/sensor/usps.py index a789f566896..6ca18442883 100644 --- a/homeassistant/components/sensor/usps.py +++ b/homeassistant/components/sensor/usps.py @@ -21,13 +21,12 @@ STATUS_DELIVERED = 'delivered' def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the USPS platform.""" + """Set up the USPS platform.""" if discovery_info is None: return usps = hass.data[DATA_USPS] - add_devices([USPSPackageSensor(usps), - USPSMailSensor(usps)], True) + add_devices([USPSPackageSensor(usps), USPSMailSensor(usps)], True) class USPSPackageSensor(Entity): @@ -73,7 +72,7 @@ class USPSPackageSensor(Entity): @property def icon(self): - """Icon to use in the frontend.""" + """Return the icon to use in the frontend.""" return 'mdi:package-variant-closed' @property diff --git a/homeassistant/components/sensor/viaggiatreno.py b/homeassistant/components/sensor/viaggiatreno.py index 37e7e020cc9..a7f4b070f2d 100644 --- a/homeassistant/components/sensor/viaggiatreno.py +++ b/homeassistant/components/sensor/viaggiatreno.py @@ -4,17 +4,16 @@ Support for information about the Italian train system using ViaggiaTreno API. For more details about this platform please refer to the documentation at https://home-assistant.io/components/sensor.viaggiatreno """ +import asyncio import logging -import asyncio -import async_timeout import aiohttp - +import async_timeout import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ATTR_ATTRIBUTION +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -58,9 +57,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine -def async_setup_platform(hass, config, - async_add_devices, discovery_info=None): - """Setup the ViaggiaTreno platform.""" +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up the ViaggiaTreno platform.""" train_id = config.get(CONF_TRAIN_ID) station_id = config.get(CONF_STATION_ID) name = config.get(CONF_NAME) @@ -100,8 +98,7 @@ class ViaggiaTrenoSensor(Entity): self._name = name self.uri = VIAGGIATRENO_ENDPOINT.format( - station_id=station_id, - train_id=train_id) + station_id=station_id, train_id=train_id) @property def name(self): diff --git a/homeassistant/components/sensor/vultr.py b/homeassistant/components/sensor/vultr.py index 7a3db3895dc..012c6eb7398 100644 --- a/homeassistant/components/sensor/vultr.py +++ b/homeassistant/components/sensor/vultr.py @@ -5,29 +5,26 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/sensor.vultr/ """ import logging + import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity -from homeassistant.const import ( - CONF_MONITORED_CONDITIONS, CONF_NAME) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.components.vultr import ( - CONF_SUBSCRIPTION, ATTR_CURRENT_BANDWIDTH_USED, ATTR_PENDING_CHARGES, + ATTR_CURRENT_BANDWIDTH_USED, ATTR_PENDING_CHARGES, CONF_SUBSCRIPTION, DATA_VULTR) - -# Name defaults to {subscription label} {sensor name} -DEFAULT_NAME = 'Vultr {} {}' -DEPENDENCIES = ['vultr'] +from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_NAME +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -# Monitored conditions: name, units, icon +DEFAULT_NAME = 'Vultr {} {}' +DEPENDENCIES = ['vultr'] + MONITORED_CONDITIONS = { ATTR_CURRENT_BANDWIDTH_USED: ['Current Bandwidth Used', 'GB', 'mdi:chart-histogram'], - ATTR_PENDING_CHARGES: ['Pending Charges', 'US$', - 'mdi:currency-usd'] + ATTR_PENDING_CHARGES: ['Pending Charges', 'US$', 'mdi:currency-usd'], } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -48,15 +45,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if subscription not in vultr.data: _LOGGER.error("Subscription %s not found", subscription) - return False + return sensors = [] for condition in monitored_conditions: - sensors.append(VultrSensor(vultr, - subscription, - condition, - name)) + sensors.append(VultrSensor(vultr, subscription, condition, name)) add_devices(sensors, True) @@ -84,21 +78,21 @@ class VultrSensor(Entity): """Return the name of the sensor.""" try: return self._name.format(self._condition_name) - except IndexError: # name contains more {} than fulfilled + except IndexError: try: - return self._name.format(self.data['label'], - self._condition_name) - except (KeyError, TypeError): # label key missing or data is None + return self._name.format( + self.data['label'], self._condition_name) + except (KeyError, TypeError): return self._name @property def icon(self): - """Icon used in the frontend if any.""" + """Return the icon used in the frontend if any.""" return self._icon @property def unit_of_measurement(self): - """The unit of measurement to present the value in.""" + """Return the unit of measurement to present the value in.""" return self._units @property diff --git a/homeassistant/components/sensor/whois.py b/homeassistant/components/sensor/whois.py index 771c4bc9d73..21b1b99ca13 100644 --- a/homeassistant/components/sensor/whois.py +++ b/homeassistant/components/sensor/whois.py @@ -4,13 +4,13 @@ Get WHOIS information for a given host. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.whois/ """ -import logging from datetime import timedelta +import logging import voluptuous as vol -from homeassistant.const import CONF_NAME from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -22,10 +22,10 @@ CONF_DOMAIN = 'domain' DEFAULT_NAME = 'Whois' +ATTR_EXPIRES = 'expires' ATTR_NAME_SERVERS = 'name_servers' ATTR_REGISTRAR = 'registrar' ATTR_UPDATED = 'updated' -ATTR_EXPIRES = 'expires' SCAN_INTERVAL = timedelta(hours=24) @@ -79,12 +79,12 @@ class WhoisSensor(Entity): @property def icon(self): - """The icon to represent this sensor.""" + """Return the icon to represent this sensor.""" return 'mdi:calendar-clock' @property def unit_of_measurement(self): - """The unit of measurement to present the value in.""" + """Return the unit of measurement to present the value in.""" return 'days' @property diff --git a/homeassistant/components/sensor/wink.py b/homeassistant/components/sensor/wink.py index b8c2b8a6236..75751bbbf8a 100644 --- a/homeassistant/components/sensor/wink.py +++ b/homeassistant/components/sensor/wink.py @@ -7,12 +7,13 @@ at https://home-assistant.io/components/sensor.wink/ import asyncio import logging +from homeassistant.components.wink import DOMAIN, WinkDevice from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity -from homeassistant.components.wink import WinkDevice, DOMAIN + +_LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['wink'] -_LOGGER = logging.getLogger(__name__) SENSOR_TYPES = ['temperature', 'humidity', 'balance', 'proximity'] @@ -61,7 +62,7 @@ class WinkSensorDevice(WinkDevice, Entity): @asyncio.coroutine def async_added_to_hass(self): - """Callback when entity is added to hass.""" + """Call when entity is added to hass.""" self.hass.data[DOMAIN]['entities']['sensor'].append(self) @property diff --git a/homeassistant/components/spc.py b/homeassistant/components/spc.py index c186559c91a..141d06768e3 100644 --- a/homeassistant/components/spc.py +++ b/homeassistant/components/spc.py @@ -4,23 +4,22 @@ 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/ """ -import logging import asyncio import json +import logging from urllib.parse import urljoin import aiohttp import async_timeout import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers import discovery from homeassistant.const import ( - STATE_UNKNOWN, STATE_ON, STATE_OFF, STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED, - STATE_UNAVAILABLE) + STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, + STATE_ALARM_TRIGGERED, STATE_OFF, STATE_ON, STATE_UNAVAILABLE, + STATE_UNKNOWN) +from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv -DOMAIN = 'spc' REQUIREMENTS = ['websockets==3.2'] _LOGGER = logging.getLogger(__name__) @@ -33,6 +32,7 @@ CONF_API_URL = 'api_url' DATA_REGISTRY = 'spc_registry' DATA_API = 'spc_api' +DOMAIN = 'spc' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -44,7 +44,7 @@ CONFIG_SCHEMA = vol.Schema({ @asyncio.coroutine def async_setup(hass, config): - """Setup the SPC platform.""" + """Set up the SPC platform.""" hass.data[DATA_REGISTRY] = SpcRegistry() api = SpcWebGateway(hass, @@ -96,29 +96,31 @@ def _async_process_message(sia_message, spc_registry): if len(data) == 3: extra['changed_by'] = data[1] else: - # change in zone status, notify sensor device + # Change in zone status, notify sensor device device = spc_registry.get_sensor_device(spc_id) - sia_code_to_state_map = {'BA': STATE_ALARM_TRIGGERED, - 'CG': STATE_ALARM_ARMED_AWAY, - 'NL': STATE_ALARM_ARMED_HOME, - 'OG': STATE_ALARM_DISARMED, - 'ZO': STATE_ON, - 'ZC': STATE_OFF, - 'ZX': STATE_UNKNOWN, - 'ZD': STATE_UNAVAILABLE} + sia_code_to_state_map = { + 'BA': STATE_ALARM_TRIGGERED, + 'CG': STATE_ALARM_ARMED_AWAY, + 'NL': STATE_ALARM_ARMED_HOME, + 'OG': STATE_ALARM_DISARMED, + 'ZO': STATE_ON, + 'ZC': STATE_OFF, + 'ZX': STATE_UNKNOWN, + 'ZD': STATE_UNAVAILABLE, + } new_state = sia_code_to_state_map.get(sia_code, None) if new_state and not device: - _LOGGER.warning("No device mapping found for SPC area/zone id %s.", - spc_id) + _LOGGER.warning( + "No device mapping found for SPC area/zone id %s", spc_id) elif new_state: yield from device.async_update_from_spc(new_state, extra) class SpcRegistry: - """Maintains mappings between SPC zones/areas and HA entities.""" + """Maintain mappings between SPC zones/areas and HA entities.""" def __init__(self): """Initialize the registry.""" @@ -145,12 +147,12 @@ class SpcRegistry: @asyncio.coroutine def _ws_process_message(message, async_callback, *args): if message.get('status', '') != 'success': - _LOGGER.warning("Unsuccessful websocket message " - "delivered, ignoring: %s", message) + _LOGGER.warning( + "Unsuccessful websocket message delivered, ignoring: %s", message) try: yield from async_callback(message['data']['sia'], *args) except: # pylint: disable=bare-except - _LOGGER.exception("Exception in callback, ignoring.") + _LOGGER.exception("Exception in callback, ignoring") class SpcWebGateway: @@ -180,8 +182,8 @@ class SpcWebGateway: @asyncio.coroutine def send_area_command(self, area_id, command): """Send an area command.""" - _LOGGER.debug("Sending SPC area command '%s' to area %s.", - command, area_id) + _LOGGER.debug( + "Sending SPC area command '%s' to area %s", command, area_id) resource = "area/{}/{}".format(area_id, command) return (yield from self._call_web_gateway(resource, use_get=False)) @@ -199,37 +201,39 @@ class SpcWebGateway: @asyncio.coroutine def _get_data(self, resource): + """Get the data from the resource.""" data = yield from self._call_web_gateway(resource) if not data: return False if data['status'] != 'success': - _LOGGER.error("SPC Web Gateway call unsuccessful " - "for resource '%s'.", resource) + _LOGGER.error( + "SPC Web Gateway call unsuccessful for resource: %s", resource) return False return [item for item in data['data'][resource]] @asyncio.coroutine def _call_web_gateway(self, resource, use_get=True): + """Call web gateway for data.""" response = None session = None url = self._build_url(resource) try: - _LOGGER.debug("Attempting to retrieve SPC data from %s.", url) + _LOGGER.debug("Attempting to retrieve SPC data from %s", url) session = aiohttp.ClientSession() with async_timeout.timeout(10, loop=self._hass.loop): action = session.get if use_get else session.put response = yield from action(url) if response.status != 200: - _LOGGER.error("SPC Web Gateway returned http " - "status %d, response %s.", - response.status, (yield from response.text())) + _LOGGER.error( + "SPC Web Gateway returned http status %d, response %s", + response.status, (yield from response.text())) return False result = yield from response.json() except asyncio.TimeoutError: - _LOGGER.error("Timeout getting SPC data from %s.", url) + _LOGGER.error("Timeout getting SPC data from %s", url) return False except aiohttp.ClientError: - _LOGGER.exception("Error getting SPC data from %s.", url) + _LOGGER.exception("Error getting SPC data from %s", url) return False finally: if session: @@ -241,12 +245,13 @@ class SpcWebGateway: @asyncio.coroutine def _ws_read(self): + """Read from websocket.""" import websockets as wslib try: if not self._ws: self._ws = yield from wslib.connect(self._ws_url) - _LOGGER.info("Connected to websocket at %s.", self._ws_url) + _LOGGER.info("Connected to websocket at %s", self._ws_url) except Exception as ws_exc: # pylint: disable=broad-except _LOGGER.error("Failed to connect to websocket: %s", ws_exc) return @@ -267,15 +272,16 @@ class SpcWebGateway: @asyncio.coroutine def _ws_listen(self, async_callback, *args): + """Listen on websocket.""" try: while True: result = yield from self._ws_read() if result: - yield from _ws_process_message(json.loads(result), - async_callback, *args) + yield from _ws_process_message( + json.loads(result), async_callback, *args) else: - _LOGGER.info("Trying again in 30 seconds.") + _LOGGER.info("Trying again in 30 seconds") yield from asyncio.sleep(30) finally: diff --git a/homeassistant/components/switch/ads.py b/homeassistant/components/switch/ads.py index f4abf2391e2..b58c4d325e7 100644 --- a/homeassistant/components/switch/ads.py +++ b/homeassistant/components/switch/ads.py @@ -3,21 +3,24 @@ Support for ADS switch platform. For more details about this platform, please refer to the documentation. https://home-assistant.io/components/switch.ads/ - """ import asyncio import logging + import voluptuous as vol + +from homeassistant.components.ads import CONF_ADS_VAR, DATA_ADS from homeassistant.components.switch import PLATFORM_SCHEMA from homeassistant.const import CONF_NAME -from homeassistant.components.ads import DATA_ADS, CONF_ADS_VAR -from homeassistant.helpers.entity import ToggleEntity import homeassistant.helpers.config_validation as cv - +from homeassistant.helpers.entity import ToggleEntity _LOGGER = logging.getLogger(__name__) + DEPENDENCIES = ['ads'] + DEFAULT_NAME = 'ADS Switch' + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_ADS_VAR): cv.string, vol.Optional(CONF_NAME): cv.string, @@ -49,15 +52,13 @@ class AdsSwitch(ToggleEntity): """Register device notification.""" def update(name, value): """Handle device notification.""" - _LOGGER.debug('Variable %s changed its value to %d', - name, value) + _LOGGER.debug('Variable %s changed its value to %d', name, value) self._on_state = value self.schedule_update_ha_state() self.hass.async_add_job( self._ads_hub.add_device_notification, - self.ads_var, self._ads_hub.PLCTYPE_BOOL, update - ) + self.ads_var, self._ads_hub.PLCTYPE_BOOL, update) @property def is_on(self): @@ -76,10 +77,10 @@ class AdsSwitch(ToggleEntity): def turn_on(self, **kwargs): """Turn the switch on.""" - self._ads_hub.write_by_name(self.ads_var, True, - self._ads_hub.PLCTYPE_BOOL) + self._ads_hub.write_by_name( + self.ads_var, True, self._ads_hub.PLCTYPE_BOOL) def turn_off(self, **kwargs): """Turn the switch off.""" - self._ads_hub.write_by_name(self.ads_var, False, - self._ads_hub.PLCTYPE_BOOL) + self._ads_hub.write_by_name( + self.ads_var, False, self._ads_hub.PLCTYPE_BOOL) diff --git a/homeassistant/components/switch/broadlink.py b/homeassistant/components/switch/broadlink.py index bd2ebc1704f..5841642cc00 100644 --- a/homeassistant/components/switch/broadlink.py +++ b/homeassistant/components/switch/broadlink.py @@ -4,24 +4,23 @@ Support for Broadlink RM devices. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.broadlink/ """ -from datetime import timedelta -from base64 import b64encode, b64decode import asyncio +from base64 import b64decode, b64encode import binascii +from datetime import timedelta import logging import socket import voluptuous as vol -from homeassistant.util.dt import utcnow -from homeassistant.util import Throttle from homeassistant.components.switch import ( - SwitchDevice, DOMAIN, PLATFORM_SCHEMA) + DOMAIN, PLATFORM_SCHEMA, SwitchDevice) from homeassistant.const import ( - CONF_FRIENDLY_NAME, CONF_SWITCHES, - CONF_COMMAND_OFF, CONF_COMMAND_ON, - CONF_TIMEOUT, CONF_HOST, CONF_MAC, CONF_TYPE) + CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_FRIENDLY_NAME, CONF_HOST, CONF_MAC, + CONF_SWITCHES, CONF_TIMEOUT, CONF_TYPE) import homeassistant.helpers.config_validation as cv +from homeassistant.util import Throttle +from homeassistant.util.dt import utcnow REQUIREMENTS = ['broadlink==0.5'] @@ -41,7 +40,7 @@ RM_TYPES = ['rm', 'rm2', 'rm_mini', 'rm_pro_phicomm', 'rm2_home_plus', 'rm2_pro_plus_bl', 'rm_mini_shate'] SP1_TYPES = ['sp1'] SP2_TYPES = ['sp2', 'honeywell_sp2', 'sp3', 'spmini2', 'spminiplus'] -MP1_TYPES = ["mp1"] +MP1_TYPES = ['mp1'] SWITCH_TYPES = RM_TYPES + SP1_TYPES + SP2_TYPES + MP1_TYPES @@ -71,7 +70,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Set up Broadlink switches.""" + """Set up the Broadlink switches.""" import broadlink devices = config.get(CONF_SWITCHES) slots = config.get('slots', {}) @@ -83,6 +82,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): @asyncio.coroutine def _learn_command(call): + """Handle a learn command.""" try: auth = yield from hass.async_add_job(broadlink_device.auth) except socket.timeout: @@ -94,7 +94,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): yield from hass.async_add_job(broadlink_device.enter_learning) - _LOGGER.info("Press the key you want HASS to learn") + _LOGGER.info("Press the key you want Home Assistant to learn") start_time = utcnow() while (utcnow() - start_time) < timedelta(seconds=20): packet = yield from hass.async_add_job( @@ -113,6 +113,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): @asyncio.coroutine def _send_packet(call): + """Send a packet.""" packets = call.data.get('packet', []) for packet in packets: for retry in range(DEFAULT_RETRY): @@ -133,6 +134,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error("Failed to send packet to device") def _get_mp1_slot_name(switch_friendly_name, slot): + """Get slot name.""" if not slots['slot_{}'.format(slot)]: return '{} slot {}'.format(switch_friendly_name, slot) return slots['slot_{}'.format(slot)] @@ -288,6 +290,7 @@ class BroadlinkSP2Switch(BroadlinkSP1Switch): self._update() def _update(self, retry=2): + """Update the state of the device.""" try: state = self._device.check_power() except (socket.timeout, ValueError) as error: @@ -333,7 +336,7 @@ class BroadlinkMP1Slot(BroadlinkRMSwitch): @property def should_poll(self): - """Polling needed.""" + """Return the polling state.""" return True def update(self): @@ -360,6 +363,7 @@ class BroadlinkMP1Switch(object): self._update() def _update(self, retry=2): + """Update the state of the device.""" try: states = self._device.check_power() except (socket.timeout, ValueError) as error: @@ -374,6 +378,7 @@ class BroadlinkMP1Switch(object): self._states = states def _auth(self, retry=2): + """Authenticate the device.""" try: auth = self._device.auth() except socket.timeout: diff --git a/homeassistant/components/switch/doorbird.py b/homeassistant/components/switch/doorbird.py index 66c3bf73116..4ab8eea6ec4 100644 --- a/homeassistant/components/switch/doorbird.py +++ b/homeassistant/components/switch/doorbird.py @@ -1,10 +1,11 @@ """Support for powering relays in a DoorBird video doorbell.""" import datetime import logging + import voluptuous as vol from homeassistant.components.doorbird import DOMAIN as DOORBIRD_DOMAIN -from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import CONF_SWITCHES import homeassistant.helpers.config_validation as cv @@ -62,12 +63,12 @@ class DoorBirdSwitch(SwitchDevice): @property def name(self): - """Get the name of the switch.""" + """Return the name of the switch.""" return SWITCHES[self._switch]["name"] @property def icon(self): - """Get an icon to display.""" + """Return the icon to display.""" return "mdi:{}".format(SWITCHES[self._switch]["icon"][self._state]) @property @@ -86,9 +87,9 @@ class DoorBirdSwitch(SwitchDevice): self._assume_off = now + SWITCHES[self._switch]["time"] def turn_off(self, **kwargs): - """The relays are time-based.""" - raise NotImplementedError("DoorBird relays cannot be manually turned " - "off.") + """Turn off the relays is not needed. They are time-based.""" + raise NotImplementedError( + "DoorBird relays cannot be manually turned off.") def update(self): """Wait for the correct amount of assumed time to pass.""" diff --git a/homeassistant/components/switch/knx.py b/homeassistant/components/switch/knx.py index d1c6d717945..e0b656aafe9 100644 --- a/homeassistant/components/switch/knx.py +++ b/homeassistant/components/switch/knx.py @@ -5,9 +5,10 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.knx/ """ import asyncio + import voluptuous as vol -from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES +from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import CONF_NAME from homeassistant.core import callback @@ -27,20 +28,16 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine -def async_setup_platform(hass, config, async_add_devices, - discovery_info=None): +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up switch(es) for KNX platform.""" - if DATA_KNX not in hass.data \ - or not hass.data[DATA_KNX].initialized: - return False + if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized: + return if discovery_info is not None: async_add_devices_discovery(hass, discovery_info, async_add_devices) else: async_add_devices_config(hass, config, async_add_devices) - return True - @callback def async_add_devices_discovery(hass, discovery_info, async_add_devices): @@ -69,7 +66,7 @@ class KNXSwitch(SwitchDevice): """Representation of a KNX switch.""" def __init__(self, hass, device): - """Initialization of KNXSwitch.""" + """Initialize of KNX switch.""" self.device = device self.hass = hass self.async_register_callbacks() @@ -79,7 +76,7 @@ class KNXSwitch(SwitchDevice): """Register callbacks to update hass after device was changed.""" @asyncio.coroutine def after_update_callback(device): - """Callback after device was updated.""" + """Call after device was updated.""" # pylint: disable=unused-argument yield from self.async_update_ha_state() self.device.register_device_updated_cb(after_update_callback) @@ -91,12 +88,12 @@ class KNXSwitch(SwitchDevice): @property def available(self): - """Return True if entity is available.""" + """Return true if entity is available.""" return self.hass.data[DATA_KNX].connected @property def should_poll(self): - """No polling needed within KNX.""" + """Return the polling state. Not needed within KNX.""" return False @property diff --git a/homeassistant/components/switch/rachio.py b/homeassistant/components/switch/rachio.py index c9b6011bcbd..a1ce83b597a 100644 --- a/homeassistant/components/switch/rachio.py +++ b/homeassistant/components/switch/rachio.py @@ -1,20 +1,27 @@ -"""Integration with the Rachio Iro sprinkler system controller.""" -import logging +""" +Integration with the Rachio Iro sprinkler system controller. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.rachio/ +""" from datetime import timedelta +import logging + import voluptuous as vol +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.const import CONF_ACCESS_TOKEN import homeassistant.helpers.config_validation as cv import homeassistant.util as util -from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA -from homeassistant.const import CONF_ACCESS_TOKEN REQUIREMENTS = ['rachiopy==0.1.2'] _LOGGER = logging.getLogger(__name__) +CONF_MANUAL_RUN_MINS = 'manual_run_mins' + DATA_RACHIO = 'rachio' -CONF_MANUAL_RUN_MINS = 'manual_run_mins' DEFAULT_MANUAL_RUN_MINS = 10 MIN_UPDATE_INTERVAL = timedelta(seconds=30) @@ -23,50 +30,49 @@ MIN_FORCED_UPDATE_INTERVAL = timedelta(seconds=1) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_ACCESS_TOKEN): cv.string, vol.Optional(CONF_MANUAL_RUN_MINS, default=DEFAULT_MANUAL_RUN_MINS): - cv.positive_int + cv.positive_int, }) def setup_platform(hass, config, add_devices, discovery_info=None): - """Set up the component.""" + """Set up the Rachio switches.""" + from rachiopy import Rachio + # Get options manual_run_mins = config.get(CONF_MANUAL_RUN_MINS) _LOGGER.debug("Rachio run time is %d min", manual_run_mins) - # Get access token - _LOGGER.debug("Getting Rachio access token...") access_token = config.get(CONF_ACCESS_TOKEN) # Configure API - _LOGGER.debug("Configuring Rachio API...") - from rachiopy import Rachio + _LOGGER.debug("Configuring Rachio API") rachio = Rachio(access_token) + person = None try: person = _get_person(rachio) except KeyError: - _LOGGER.error("Could not reach the Rachio API. " - "Is your access token valid?") - return False + _LOGGER.error( + "Could not reach the Rachio API. Is your access token valid?") + return # Get and persist devices devices = _list_devices(rachio, manual_run_mins) if not devices: - _LOGGER.error("No Rachio devices found in account " + - person['username']) - return False + _LOGGER.error( + "No Rachio devices found in account %s", person['username']) + return hass.data[DATA_RACHIO] = devices[0] if len(devices) > 1: _LOGGER.warning("Multiple Rachio devices found in account, " - "using " + hass.data[DATA_RACHIO].device_id) + "using %s", hass.data[DATA_RACHIO].device_id) else: - _LOGGER.info("Found Rachio device") + _LOGGER.debug("Found Rachio device") hass.data[DATA_RACHIO].update() add_devices(hass.data[DATA_RACHIO].list_zones()) - return True def _get_person(rachio): @@ -82,10 +88,10 @@ def _list_devices(rachio, manual_run_mins): class RachioIro(object): - """Represents one Rachio Iro.""" + """Representation of a Rachio Iro.""" def __init__(self, rachio, device_id, manual_run_mins): - """Initialize a new device.""" + """Initialize a Rachio device.""" self.rachio = rachio self._device_id = device_id self.manual_run_mins = manual_run_mins @@ -95,40 +101,40 @@ class RachioIro(object): def __str__(self): """Display the device as a string.""" - return "Rachio Iro " + self.serial_number + return "Rachio Iro {}".format(self.serial_number) @property def device_id(self): - """How the Rachio API refers to the device.""" + """Return the Rachio API device ID.""" return self._device['id'] @property def status(self): - """The current status of the device.""" + """Return the current status of the device.""" return self._device['status'] @property def serial_number(self): - """The serial number of the device.""" + """Return the serial number of the device.""" return self._device['serialNumber'] @property def is_paused(self): - """Whether the device is temporarily disabled.""" + """Return whether the device is temporarily disabled.""" return self._device['paused'] @property def is_on(self): - """Whether the device is powered on and connected.""" + """Return whether the device is powered on and connected.""" return self._device['on'] @property def current_schedule(self): - """The schedule that the device is running right now.""" + """Return the schedule that the device is running right now.""" return self._running def list_zones(self, include_disabled=False): - """A list of the zones connected to the device and their data.""" + """Return alist of the zones connected to the device, incl. data.""" if not self._zones: self._zones = [RachioZone(self.rachio, self, zone['id'], self.manual_run_mins) @@ -155,7 +161,7 @@ class RachioIro(object): class RachioZone(SwitchDevice): - """Represents one zone of sprinklers connected to the Rachio Iro.""" + """Representation of one zone of sprinklers connected to the Rachio Iro.""" def __init__(self, rachio, device, zone_id, manual_run_mins): """Initialize a new Rachio Zone.""" @@ -167,7 +173,7 @@ class RachioZone(SwitchDevice): def __str__(self): """Display the zone as a string.""" - return "Rachio Zone " + self.name + return "Rachio Zone {}".format(self.name) @property def zone_id(self): @@ -176,29 +182,28 @@ class RachioZone(SwitchDevice): @property def unique_id(self): - """Generate a unique string ID for the zone.""" + """Return the unique string ID for the zone.""" return '{iro}-{zone}'.format( - iro=self._device.device_id, - zone=self.zone_id) + iro=self._device.device_id, zone=self.zone_id) @property def number(self): - """The physical connection of the zone pump.""" + """Return the physical connection of the zone pump.""" return self._zone['zoneNumber'] @property def name(self): - """The friendly name of the zone.""" + """Return the friendly name of the zone.""" return self._zone['name'] @property def is_enabled(self): - """Whether the zone is allowed to run.""" + """Return whether the zone is allowed to run.""" return self._zone['enabled'] @property def is_on(self): - """Whether the zone is currently running.""" + """Return whether the zone is currently running.""" schedule = self._device.current_schedule return self.zone_id == schedule.get('zoneId') diff --git a/homeassistant/components/switch/raspihats.py b/homeassistant/components/switch/raspihats.py index 145697ef7c5..183ee6edb77 100644 --- a/homeassistant/components/switch/raspihats.py +++ b/homeassistant/components/switch/raspihats.py @@ -5,18 +5,17 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.raspihats/ """ import logging + import voluptuous as vol -from homeassistant.const import ( - CONF_NAME, DEVICE_DEFAULT_NAME -) -import homeassistant.helpers.config_validation as cv -from homeassistant.components.switch import PLATFORM_SCHEMA -from homeassistant.helpers.entity import ToggleEntity + from homeassistant.components.raspihats import ( - CONF_I2C_HATS, CONF_BOARD, CONF_ADDRESS, CONF_CHANNELS, CONF_INDEX, - CONF_INVERT_LOGIC, CONF_INITIAL_STATE, I2C_HAT_NAMES, I2C_HATS_MANAGER, - I2CHatsException -) + CONF_ADDRESS, CONF_BOARD, CONF_CHANNELS, CONF_I2C_HATS, CONF_INDEX, + CONF_INITIAL_STATE, CONF_INVERT_LOGIC, I2C_HAT_NAMES, I2C_HATS_MANAGER, + I2CHatsException) +from homeassistant.components.switch import PLATFORM_SCHEMA +from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import ToggleEntity _LOGGER = logging.getLogger(__name__) @@ -32,7 +31,7 @@ _CHANNELS_SCHEMA = vol.Schema([{ _I2C_HATS_SCHEMA = vol.Schema([{ vol.Required(CONF_BOARD): vol.In(I2C_HAT_NAMES), vol.Required(CONF_ADDRESS): vol.Coerce(int), - vol.Required(CONF_CHANNELS): _CHANNELS_SCHEMA + vol.Required(CONF_CHANNELS): _CHANNELS_SCHEMA, }]) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -42,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the raspihats switch devices.""" + """Set up the raspihats switch devices.""" I2CHatSwitch.I2C_HATS_MANAGER = hass.data[I2C_HATS_MANAGER] switches = [] i2c_hat_configs = config.get(CONF_I2C_HATS) @@ -54,9 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): for channel_config in i2c_hat_config[CONF_CHANNELS]: switches.append( I2CHatSwitch( - board, - address, - channel_config[CONF_INDEX], + board, address, channel_config[CONF_INDEX], channel_config[CONF_NAME], channel_config[CONF_INVERT_LOGIC], channel_config[CONF_INITIAL_STATE] @@ -64,25 +61,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None): ) except I2CHatsException as ex: _LOGGER.error( - "Failed to register " + board + "I2CHat@" + hex(address) + " " - + str(ex) - ) + "Failed to register %s I2CHat@%s %s", board, hex(address), + str(ex)) add_devices(switches) class I2CHatSwitch(ToggleEntity): - """Represents a switch that uses a I2C-HAT digital output.""" + """Representation a switch that uses a I2C-HAT digital output.""" I2C_HATS_MANAGER = None - def __init__( - self, - board, - address, - channel, - name, - invert_logic, - initial_state): + def __init__(self, board, address, channel, name, invert_logic, + initial_state): """Initialize switch.""" self._board = board self._address = address @@ -95,22 +85,17 @@ class I2CHatSwitch(ToggleEntity): else: state = initial_state self.I2C_HATS_MANAGER.write_dq( - self._address, - self._channel, - state - ) + self._address, self._channel, state) def online_callback(): - """Callback fired when board is online.""" + """Call fired when board is online.""" self.schedule_update_ha_state() self.I2C_HATS_MANAGER.register_online_callback( - self._address, - self._channel, - online_callback - ) + self._address, self._channel, online_callback) def _log_message(self, message): + """Create log message.""" string = self._name + " " string += self._board + "I2CHat@" + hex(self._address) + " " string += "channel:" + str(self._channel) + message @@ -123,50 +108,34 @@ class I2CHatSwitch(ToggleEntity): @property def should_poll(self): - """Polling not needed.""" + """Return the polling state.""" return False @property def is_on(self): """Return true if device is on.""" try: - state = self.I2C_HATS_MANAGER.read_dq( - self._address, - self._channel - ) + state = self.I2C_HATS_MANAGER.read_dq(self._address, self._channel) return state != self._invert_logic except I2CHatsException as ex: - _LOGGER.error( - self._log_message("Is ON check failed, " + str(ex)) - ) + _LOGGER.error(self._log_message("Is ON check failed, " + str(ex))) return False def turn_on(self): """Turn the device on.""" try: state = True if self._invert_logic is False else False - self.I2C_HATS_MANAGER.write_dq( - self._address, - self._channel, - state - ) + self.I2C_HATS_MANAGER.write_dq(self._address, self._channel, state) self.schedule_update_ha_state() except I2CHatsException as ex: - _LOGGER.error( - self._log_message("Turn ON failed, " + str(ex)) - ) + _LOGGER.error(self._log_message("Turn ON failed, " + str(ex))) def turn_off(self): """Turn the device off.""" try: state = False if self._invert_logic is False else True - self.I2C_HATS_MANAGER.write_dq( - self._address, - self._channel, - state - ) + self.I2C_HATS_MANAGER.write_dq(self._address, self._channel, state) self.schedule_update_ha_state() except I2CHatsException as ex: _LOGGER.error( - self._log_message("Turn OFF failed, " + str(ex)) - ) + self._log_message("Turn OFF failed:, " + str(ex))) diff --git a/homeassistant/components/switch/rpi_pfio.py b/homeassistant/components/switch/rpi_pfio.py index 6e50725b564..a493a8e9589 100644 --- a/homeassistant/components/switch/rpi_pfio.py +++ b/homeassistant/components/switch/rpi_pfio.py @@ -8,11 +8,11 @@ import logging import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA import homeassistant.components.rpi_pfio as rpi_pfio +from homeassistant.components.switch import PLATFORM_SCHEMA from homeassistant.const import DEVICE_DEFAULT_NAME -from homeassistant.helpers.entity import ToggleEntity import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import ToggleEntity _LOGGER = logging.getLogger(__name__) @@ -20,24 +20,25 @@ DEPENDENCIES = ['rpi_pfio'] ATTR_INVERT_LOGIC = 'invert_logic' ATTR_NAME = 'name' + CONF_PORTS = 'ports' DEFAULT_INVERT_LOGIC = False PORT_SCHEMA = vol.Schema({ vol.Optional(ATTR_NAME, default=None): cv.string, - vol.Optional(ATTR_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean + vol.Optional(ATTR_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_PORTS, default={}): vol.Schema({ - cv.positive_int: PORT_SCHEMA + cv.positive_int: PORT_SCHEMA, }) }) def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the PiFace Digital Output devices.""" + """Set up the PiFace Digital Output devices.""" switches = [] ports = config.get(CONF_PORTS) for port, port_entity in ports.items(): @@ -66,7 +67,7 @@ class RPiPFIOSwitch(ToggleEntity): @property def should_poll(self): - """No polling needed.""" + """Return the polling state.""" return False @property diff --git a/homeassistant/components/switch/telnet.py b/homeassistant/components/switch/telnet.py index 4d3db97f56e..7c69b31aa00 100644 --- a/homeassistant/components/switch/telnet.py +++ b/homeassistant/components/switch/telnet.py @@ -4,17 +4,17 @@ Support for switch controlled using a telnet connection. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.telnet/ """ +from datetime import timedelta import logging import telnetlib -from datetime import timedelta import voluptuous as vol -from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA, - ENTITY_ID_FORMAT) +from homeassistant.components.switch import ( + ENTITY_ID_FORMAT, PLATFORM_SCHEMA, SwitchDevice) from homeassistant.const import ( - CONF_RESOURCE, CONF_NAME, CONF_SWITCHES, CONF_VALUE_TEMPLATE, - CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_COMMAND_STATE, CONF_PORT) + CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_COMMAND_STATE, CONF_NAME, + CONF_PORT, CONF_RESOURCE, CONF_SWITCHES, CONF_VALUE_TEMPLATE) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -22,13 +22,13 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_PORT = 23 SWITCH_SCHEMA = vol.Schema({ - vol.Required(CONF_COMMAND_ON): cv.string, vol.Required(CONF_COMMAND_OFF): cv.string, + vol.Required(CONF_COMMAND_ON): cv.string, + vol.Required(CONF_RESOURCE): cv.string, + vol.Required(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_COMMAND_STATE): cv.string, vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Required(CONF_RESOURCE): cv.string, - vol.Required(CONF_VALUE_TEMPLATE): cv.template, }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -66,7 +66,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if not switches: _LOGGER.error("No switches added") - return False + return add_devices(switches) @@ -117,7 +117,7 @@ class TelnetSwitch(SwitchDevice): @property def assumed_state(self): - """Default ist true if no state command is defined, false otherwise.""" + """Return true if no state command is defined, false otherwise.""" return self._command_state is None def update(self): diff --git a/homeassistant/components/switch/tesla.py b/homeassistant/components/switch/tesla.py index 7de0c417d56..2f105a709ad 100644 --- a/homeassistant/components/switch/tesla.py +++ b/homeassistant/components/switch/tesla.py @@ -7,8 +7,9 @@ https://home-assistant.io/components/switch.tesla/ import logging from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice -from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN, TeslaDevice -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN +from homeassistant.components.tesla import TeslaDevice +from homeassistant.const import STATE_OFF, STATE_ON _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['tesla'] @@ -25,7 +26,7 @@ class ChargerSwitch(TeslaDevice, SwitchDevice): """Representation of a Tesla charger switch.""" def __init__(self, tesla_device, controller): - """Initialisation of the switch.""" + """Initialise of the switch.""" self._state = None super().__init__(tesla_device, controller) self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id) @@ -46,7 +47,7 @@ class ChargerSwitch(TeslaDevice, SwitchDevice): return self._state == STATE_ON def update(self): - """Updating state of the switch.""" + """Update the state of the switch.""" _LOGGER.debug("Updating state for: %s", self._name) self.tesla_device.update() self._state = STATE_ON if self.tesla_device.is_charging() \ diff --git a/homeassistant/components/switch/toon.py b/homeassistant/components/switch/toon.py index d5f50be0bef..09dc45c6587 100644 --- a/homeassistant/components/switch/toon.py +++ b/homeassistant/components/switch/toon.py @@ -6,20 +6,20 @@ https://home-assistant.io/components/switch.toon/ """ import logging -import homeassistant.components.toon as toon_main from homeassistant.components.switch import SwitchDevice +import homeassistant.components.toon as toon_main _LOGGER = logging.getLogger(__name__) -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the discovered Toon Smart Plugs.""" _toon_main = hass.data[toon_main.TOON_HANDLE] switch_items = [] for plug in _toon_main.toon.smartplugs: switch_items.append(EnecoSmartPlug(hass, plug)) - add_devices_callback(switch_items) + add_devices(switch_items) class EnecoSmartPlug(SwitchDevice): @@ -42,12 +42,12 @@ class EnecoSmartPlug(SwitchDevice): @property def current_power_w(self): - """Current power usage in W.""" + """Return the current power usage in W.""" return self.toon_data_store.get_data('current_power', self.name) @property def today_energy_kwh(self): - """Today total energy usage in kWh.""" + """Return the today total energy usage in kWh.""" return self.toon_data_store.get_data('today_energy', self.name) @property @@ -57,7 +57,7 @@ class EnecoSmartPlug(SwitchDevice): @property def available(self): - """True if switch is available.""" + """Return true if switch is available.""" return self.smartplug.can_toggle def turn_on(self, **kwargs): diff --git a/homeassistant/components/switch/vultr.py b/homeassistant/components/switch/vultr.py index 888db754f01..a044fca2972 100644 --- a/homeassistant/components/switch/vultr.py +++ b/homeassistant/components/switch/vultr.py @@ -24,7 +24,7 @@ DEPENDENCIES = ['vultr'] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_SUBSCRIPTION): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, }) diff --git a/homeassistant/components/switch/wink.py b/homeassistant/components/switch/wink.py index 8bd4c9fa53b..5a43de9425c 100644 --- a/homeassistant/components/switch/wink.py +++ b/homeassistant/components/switch/wink.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/switch.wink/ import asyncio import logging -from homeassistant.components.wink import WinkDevice, DOMAIN +from homeassistant.components.wink import DOMAIN, WinkDevice from homeassistant.helpers.entity import ToggleEntity DEPENDENCIES = ['wink'] @@ -42,7 +42,7 @@ class WinkToggleDevice(WinkDevice, ToggleEntity): @asyncio.coroutine def async_added_to_hass(self): - """Callback when entity is added to hass.""" + """Call when entity is added to hass.""" self.hass.data[DOMAIN]['entities']['switch'].append(self) @property diff --git a/homeassistant/components/tesla.py b/homeassistant/components/tesla.py index 64ffe40d428..4f76b70432e 100644 --- a/homeassistant/components/tesla.py +++ b/homeassistant/components/tesla.py @@ -6,12 +6,13 @@ https://home-assistant.io/components/tesla/ """ from collections import defaultdict import logging + import voluptuous as vol from homeassistant.const import ( - ATTR_BATTERY_LEVEL, CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL) -from homeassistant.helpers import discovery + ATTR_BATTERY_LEVEL, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME) from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import discovery from homeassistant.helpers.entity import Entity from homeassistant.util import slugify @@ -42,7 +43,7 @@ TESLA_COMPONENTS = [ def setup(hass, base_config): - """Set up of Tesla platform.""" + """Set up of Tesla component.""" from teslajsonpy import Controller as teslaAPI, TeslaException config = base_config.get(DOMAIN) @@ -53,8 +54,7 @@ def setup(hass, base_config): if hass.data.get(DOMAIN) is None: try: hass.data[DOMAIN] = { - 'controller': teslaAPI( - email, password, update_interval), + 'controller': teslaAPI(email, password, update_interval), 'devices': defaultdict(list) } _LOGGER.debug("Connected to the Tesla API.") @@ -95,7 +95,7 @@ class TeslaDevice(Entity): """Representation of a Tesla device.""" def __init__(self, tesla_device, controller): - """Initialisation of the Tesla device.""" + """Initialise of the Tesla device.""" self.tesla_device = tesla_device self.controller = controller self._name = self.tesla_device.name @@ -108,7 +108,7 @@ class TeslaDevice(Entity): @property def should_poll(self): - """Get polling requirement from tesla device.""" + """Return the polling state.""" return self.tesla_device.should_poll @property diff --git a/homeassistant/components/vacuum/dyson.py b/homeassistant/components/vacuum/dyson.py index 476e347055a..aa05d004a35 100644 --- a/homeassistant/components/vacuum/dyson.py +++ b/homeassistant/components/vacuum/dyson.py @@ -8,25 +8,23 @@ import asyncio import logging from homeassistant.components.dyson import DYSON_DEVICES -from homeassistant.components.vacuum import (SUPPORT_BATTERY, - SUPPORT_FAN_SPEED, SUPPORT_PAUSE, - SUPPORT_RETURN_HOME, - SUPPORT_STATUS, SUPPORT_STOP, - SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - VacuumDevice) +from homeassistant.components.vacuum import ( + SUPPORT_BATTERY, SUPPORT_FAN_SPEED, SUPPORT_PAUSE, SUPPORT_RETURN_HOME, + SUPPORT_STATUS, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, + VacuumDevice) from homeassistant.helpers.icon import icon_for_battery_level -ATTR_FULL_CLEAN_TYPE = "full_clean_type" -ATTR_CLEAN_ID = "clean_id" -ATTR_POSITION = "position" - -DEPENDENCIES = ['dyson'] - _LOGGER = logging.getLogger(__name__) +ATTR_CLEAN_ID = 'clean_id' +ATTR_FULL_CLEAN_TYPE = 'full_clean_type' +ATTR_POSITION = 'position' + +DEPENDENCIES = ['dyson'] + DYSON_360_EYE_DEVICES = "dyson_360_eye_devices" -ICON = "mdi:roomba" +ICON = 'mdi:roomba' SUPPORT_DYSON = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PAUSE | \ SUPPORT_RETURN_HOME | SUPPORT_FAN_SPEED | SUPPORT_STATUS | \ @@ -35,12 +33,13 @@ SUPPORT_DYSON = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PAUSE | \ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Dyson 360 Eye robot vacuum platform.""" - _LOGGER.info("Creating new Dyson 360 Eye robot vacuum") + from libpurecoollink.dyson_360_eye import Dyson360Eye + + _LOGGER.debug("Creating new Dyson 360 Eye robot vacuum") if DYSON_360_EYE_DEVICES not in hass.data: hass.data[DYSON_360_EYE_DEVICES] = [] # Get Dyson Devices from parent component - from libpurecoollink.dyson_360_eye import Dyson360Eye for device in [d for d in hass.data[DYSON_DEVICES] if isinstance(d, Dyson360Eye)]: dyson_entity = Dyson360EyeDevice(device) @@ -55,18 +54,18 @@ class Dyson360EyeDevice(VacuumDevice): def __init__(self, device): """Dyson 360 Eye robot vacuum device.""" - _LOGGER.info("Creating device %s", device.name) + _LOGGER.debug("Creating device %s", device.name) self._device = device self._icon = ICON @asyncio.coroutine def async_added_to_hass(self): - """Callback when entity is added to hass.""" + """Call when entity is added to hass.""" self.hass.async_add_job( self._device.add_message_listener, self.on_message) def on_message(self, message): - """Called when new messages received from the vacuum.""" + """Handle a new messages that was received from the vacuum.""" _LOGGER.debug("Message received for %s device: %s", self.name, message) self.schedule_update_ha_state() @@ -105,8 +104,8 @@ class Dyson360EyeDevice(VacuumDevice): Dyson360EyeMode.FULL_CLEAN_FINISHED: "Finished", Dyson360EyeMode.FULL_CLEAN_NEEDS_CHARGE: "Need charging" } - return dyson_labels.get(self._device.state.state, - self._device.state.state) + return dyson_labels.get( + self._device.state.state, self._device.state.state) @property def battery_level(self): @@ -139,6 +138,7 @@ class Dyson360EyeDevice(VacuumDevice): def is_on(self) -> bool: """Return True if entity is on.""" from libpurecoollink.const import Dyson360EyeMode + return self._device.state.state in [ Dyson360EyeMode.FULL_CLEAN_INITIATED, Dyson360EyeMode.FULL_CLEAN_ABORTED, @@ -159,6 +159,7 @@ class Dyson360EyeDevice(VacuumDevice): def battery_icon(self): """Return the battery icon for the vacuum cleaner.""" from libpurecoollink.const import Dyson360EyeMode + charging = self._device.state.state in [ Dyson360EyeMode.INACTIVE_CHARGING] return icon_for_battery_level( @@ -166,8 +167,9 @@ class Dyson360EyeDevice(VacuumDevice): def turn_on(self, **kwargs): """Turn the vacuum on.""" - _LOGGER.debug("Turn on device %s", self.name) from libpurecoollink.const import Dyson360EyeMode + + _LOGGER.debug("Turn on device %s", self.name) if self._device.state.state in [Dyson360EyeMode.FULL_CLEAN_PAUSED]: self._device.resume() else: @@ -185,8 +187,9 @@ class Dyson360EyeDevice(VacuumDevice): def set_fan_speed(self, fan_speed, **kwargs): """Set fan speed.""" - _LOGGER.debug("Set fan speed %s on device %s", fan_speed, self.name) from libpurecoollink.const import PowerMode + + _LOGGER.debug("Set fan speed %s on device %s", fan_speed, self.name) power_modes = { "Quiet": PowerMode.QUIET, "Max": PowerMode.MAX @@ -196,6 +199,7 @@ class Dyson360EyeDevice(VacuumDevice): def start_pause(self, **kwargs): """Start, pause or resume the cleaning task.""" from libpurecoollink.const import Dyson360EyeMode + if self._device.state.state in [Dyson360EyeMode.FULL_CLEAN_PAUSED]: _LOGGER.debug("Resume device %s", self.name) self._device.resume() diff --git a/homeassistant/components/vacuum/mqtt.py b/homeassistant/components/vacuum/mqtt.py index 54aea793a22..f4c640f1fc7 100644 --- a/homeassistant/components/vacuum/mqtt.py +++ b/homeassistant/components/vacuum/mqtt.py @@ -10,7 +10,6 @@ import logging import voluptuous as vol import homeassistant.components.mqtt as mqtt -import homeassistant.helpers.config_validation as cv from homeassistant.components.mqtt import MqttAvailability from homeassistant.components.vacuum import ( DEFAULT_ICON, SUPPORT_BATTERY, SUPPORT_CLEAN_SPOT, SUPPORT_FAN_SPEED, @@ -19,6 +18,7 @@ from homeassistant.components.vacuum import ( VacuumDevice) from homeassistant.const import ATTR_SUPPORTED_FEATURES, CONF_NAME from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.icon import icon_for_battery_level _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/vacuum/neato.py b/homeassistant/components/vacuum/neato.py index e1c4a5952af..29099db5cd5 100644 --- a/homeassistant/components/vacuum/neato.py +++ b/homeassistant/components/vacuum/neato.py @@ -24,7 +24,7 @@ SUPPORT_NEATO = SUPPORT_BATTERY | SUPPORT_PAUSE | SUPPORT_RETURN_HOME | \ SUPPORT_STOP | SUPPORT_TURN_OFF | SUPPORT_TURN_ON | \ SUPPORT_STATUS | SUPPORT_MAP -ICON = "mdi:roomba" +ICON = 'mdi:roomba' ATTR_CLEAN_START = 'clean_start' ATTR_CLEAN_STOP = 'clean_stop' @@ -45,10 +45,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class NeatoConnectedVacuum(VacuumDevice): - """Neato Connected Vacuums.""" + """Representation of a Neato Connected Vacuum.""" def __init__(self, hass, robot): - """Initialize the Neato Connected Vacuums.""" + """Initialize the Neato Connected Vacuum.""" self.robot = robot self.neato = hass.data[NEATO_LOGIN] self._name = '{}'.format(self.robot.name) @@ -66,7 +66,7 @@ class NeatoConnectedVacuum(VacuumDevice): def update(self): """Update the states of Neato Vacuums.""" - _LOGGER.debug("Running Vacuums update") + _LOGGER.debug("Running Neato Vacuums update") self.neato.update_robots() try: self._state = self.robot.state diff --git a/homeassistant/components/weather/yweather.py b/homeassistant/components/weather/yweather.py index 20ed97ec249..bbf9f1ae590 100644 --- a/homeassistant/components/weather/yweather.py +++ b/homeassistant/components/weather/yweather.py @@ -4,16 +4,15 @@ Support for the Yahoo! Weather service. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/weather.yweather/ """ -import logging from datetime import timedelta +import logging import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.weather import ( - WeatherEntity, PLATFORM_SCHEMA, - ATTR_FORECAST_TEMP, ATTR_FORECAST_TIME) -from homeassistant.const import (TEMP_CELSIUS, CONF_NAME, STATE_UNKNOWN) + ATTR_FORECAST_TEMP, ATTR_FORECAST_TIME, PLATFORM_SCHEMA, WeatherEntity) +from homeassistant.const import CONF_NAME, STATE_UNKNOWN, TEMP_CELSIUS +import homeassistant.helpers.config_validation as cv REQUIREMENTS = ["yahooweather==0.10"] @@ -57,7 +56,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Yahoo! weather platform.""" + """Set up the Yahoo! weather platform.""" from yahooweather import get_woeid, UNIT_C, UNIT_F unit = hass.config.units.temperature_unit diff --git a/homeassistant/components/wink/__init__.py b/homeassistant/components/wink/__init__.py index c903b5a0ddf..476f089b592 100644 --- a/homeassistant/components/wink/__init__.py +++ b/homeassistant/components/wink/__init__.py @@ -5,26 +5,25 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/wink/ """ import asyncio -import logging -import time -import json -import os from datetime import timedelta +import json +import logging +import os +import time import voluptuous as vol -from homeassistant.core import callback from homeassistant.components.http import HomeAssistantView -from homeassistant.helpers import discovery -from homeassistant.helpers.event import track_time_interval from homeassistant.const import ( - ATTR_BATTERY_LEVEL, CONF_EMAIL, CONF_PASSWORD, - EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP, __version__, ATTR_ENTITY_ID, - STATE_ON, STATE_OFF) + ATTR_BATTERY_LEVEL, ATTR_ENTITY_ID, CONF_EMAIL, CONF_PASSWORD, + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON, + __version__) +from homeassistant.core import callback +from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import track_time_interval from homeassistant.util.json import load_json, save_json REQUIREMENTS = ['python-wink==1.7.1', 'pubnubsub-handler==1.0.2'] @@ -54,7 +53,7 @@ ATTR_HUB_NAME = 'hub_name' WINK_AUTH_CALLBACK_PATH = '/auth/wink/callback' WINK_AUTH_START = '/auth/wink' WINK_CONFIG_FILE = '.wink.conf' -USER_AGENT = "Manufacturer/Home-Assistant%s python/3 Wink/3" % (__version__) +USER_AGENT = "Manufacturer/Home-Assistant%s python/3 Wink/3" % __version__ DEFAULT_CONFIG = { 'client_id': 'CLIENT_ID_HERE', @@ -175,7 +174,7 @@ def _request_app_setup(hass, config): setup(hass, config) return else: - error_msg = ("Your input was invalid. Please try again.") + error_msg = "Your input was invalid. Please try again." _configurator = hass.data[DOMAIN]['configuring'][DOMAIN] configurator.notify_errors(_configurator, error_msg) @@ -221,9 +220,7 @@ def _request_oauth_completion(hass, config): description = "Please authorize Wink by visiting {}".format(start_url) hass.data[DOMAIN]['configuring'][DOMAIN] = configurator.request_config( - DOMAIN, wink_configuration_callback, - description=description - ) + DOMAIN, wink_configuration_callback, description=description) def setup(hass, config): @@ -255,7 +252,7 @@ def setup(hass, config): local_control = None hass.data[DOMAIN]['configurator'] = True if None not in [client_id, client_secret]: - _LOGGER.info("Using legacy oauth authentication") + _LOGGER.info("Using legacy OAuth authentication") if not local_control: pywink.disable_local_control() hass.data[DOMAIN]["oauth"]["client_id"] = client_id @@ -265,7 +262,7 @@ def setup(hass, config): pywink.legacy_set_wink_credentials(email, password, client_id, client_secret) else: - _LOGGER.info("Using oauth authentication") + _LOGGER.info("Using OAuth authentication") if not local_control: pywink.disable_local_control() config_path = hass.config.path(WINK_CONFIG_FILE) @@ -296,18 +293,17 @@ def setup(hass, config): access_token=access_token, refresh_token=refresh_token) # This is called to create the redirect so the user can Authorize - # Home-Assistant + # Home . else: - redirect_uri = '{}{}'.format(hass.config.api.base_url, - WINK_AUTH_CALLBACK_PATH) + redirect_uri = '{}{}'.format( + hass.config.api.base_url, WINK_AUTH_CALLBACK_PATH) wink_auth_start_url = pywink.get_authorization_url( config_file.get(ATTR_CLIENT_ID), redirect_uri) hass.http.register_redirect(WINK_AUTH_START, wink_auth_start_url) - hass.http.register_view(WinkAuthCallbackView(config, - config_file, - pywink.request_token)) + hass.http.register_view(WinkAuthCallbackView( + config, config_file, pywink.request_token)) _request_oauth_completion(hass, config) return True @@ -327,7 +323,7 @@ def setup(hass, config): def keep_alive_call(event_time): """Call the Wink API endpoints to keep PubNub working.""" - _LOGGER.info("Polling the Wink API to keep PubNub updates flowing.") + _LOGGER.info("Polling the Wink API to keep PubNub updates flowing") pywink.set_user_agent(str(int(time.time()))) _temp_response = pywink.get_user() _LOGGER.debug(str(json.dumps(_temp_response))) @@ -340,20 +336,20 @@ def setup(hass, config): track_time_interval(hass, keep_alive_call, timedelta(minutes=60)) def start_subscription(event): - """Start the pubnub subscription.""" + """Start the PubNub subscription.""" _subscribe() hass.bus.listen(EVENT_HOMEASSISTANT_START, start_subscription) def stop_subscription(event): - """Stop the pubnub subscription.""" + """Stop the PubNub subscription.""" hass.data[DOMAIN]['pubnub'].unsubscribe() hass.data[DOMAIN]['pubnub'] = None hass.bus.listen(EVENT_HOMEASSISTANT_STOP, stop_subscription) def save_credentials(event): - """Save currently set oauth credentials.""" + """Save currently set OAuth credentials.""" if hass.data[DOMAIN]["oauth"].get("email") is None: config_path = hass.config.path(WINK_CONFIG_FILE) _config = pywink.get_current_oauth_credentials() @@ -387,8 +383,7 @@ def setup(hass, config): kidde_code = call.data.get('kidde_radio_code') for hub in WINK_HUBS: if hub.name() == hub_name: - hub.pair_new_device(pairing_mode, - kidde_radio_code=kidde_code) + hub.pair_new_device(pairing_mode, kidde_radio_code=kidde_code) def rename_device(call): """Set specified device's name.""" @@ -436,7 +431,7 @@ def setup(hass, config): schema=SET_PAIRING_MODE_SCHEMA) def service_handle(service): - """Handler for services.""" + """Handle services.""" entity_ids = service.data.get('entity_id') all_sirens = [] for switch in hass.data[DOMAIN]['entities']['switch']: @@ -455,7 +450,7 @@ def setup(hass, config): if (service.service != SERVICE_SET_AUTO_SHUTOFF and service.service != SERVICE_ENABLE_SIREN and (_man != 'dome' and _man != 'wink')): - _LOGGER.error("Service only valid for Dome or Wink sirens.") + _LOGGER.error("Service only valid for Dome or Wink sirens") return if service.service == SERVICE_ENABLE_SIREN: @@ -564,17 +559,16 @@ class WinkAuthCallbackView(HomeAssistantView):

{}

""" if data.get('code') is not None: - response = self.request_token(data.get('code'), - self.config_file["client_secret"]) + response = self.request_token( + data.get('code'), self.config_file['client_secret']) config_contents = { ATTR_ACCESS_TOKEN: response['access_token'], ATTR_REFRESH_TOKEN: response['refresh_token'], - ATTR_CLIENT_ID: self.config_file["client_id"], - ATTR_CLIENT_SECRET: self.config_file["client_secret"] + ATTR_CLIENT_ID: self.config_file['client_id'], + ATTR_CLIENT_SECRET: self.config_file['client_secret'] } - save_json(hass.config.path(WINK_CONFIG_FILE), - config_contents) + save_json(hass.config.path(WINK_CONFIG_FILE), config_contents) hass.async_add_job(setup, hass, self.config) @@ -695,7 +689,7 @@ class WinkSirenDevice(WinkDevice): @asyncio.coroutine def async_added_to_hass(self): - """Callback when entity is added to hass.""" + """Call when entity is added to hass.""" self.hass.data[DOMAIN]['entities']['switch'].append(self) @property @@ -712,7 +706,7 @@ class WinkSirenDevice(WinkDevice): @property def device_state_attributes(self): - """Return the state attributes.""" + """Return the device state attributes.""" attributes = super(WinkSirenDevice, self).device_state_attributes auto_shutoff = self.wink.auto_shutoff() diff --git a/homeassistant/components/xiaomi_aqara.py b/homeassistant/components/xiaomi_aqara.py index 6a76ed48fd8..2aa56f7957f 100644 --- a/homeassistant/components/xiaomi_aqara.py +++ b/homeassistant/components/xiaomi_aqara.py @@ -1,40 +1,51 @@ -"""Support for Xiaomi Gateways.""" +""" +Support for Xiaomi Gateways. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/xiaomi_aqara/ +""" import asyncio import logging + import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers import discovery -from homeassistant.helpers.entity import Entity + from homeassistant.components.discovery import SERVICE_XIAOMI_GW -from homeassistant.const import (ATTR_BATTERY_LEVEL, EVENT_HOMEASSISTANT_STOP, - CONF_MAC, CONF_HOST, CONF_PORT) +from homeassistant.const import ( + ATTR_BATTERY_LEVEL, CONF_HOST, CONF_MAC, CONF_PORT, + EVENT_HOMEASSISTANT_STOP) +from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity REQUIREMENTS = ['PyXiaomiGateway==0.7.1'] +_LOGGER = logging.getLogger(__name__) + ATTR_GW_MAC = 'gw_mac' ATTR_RINGTONE_ID = 'ringtone_id' ATTR_RINGTONE_VOL = 'ringtone_vol' ATTR_DEVICE_ID = 'device_id' + CONF_DISCOVERY_RETRY = 'discovery_retry' CONF_GATEWAYS = 'gateways' CONF_INTERFACE = 'interface' CONF_KEY = 'key' + DOMAIN = 'xiaomi_aqara' -PY_XIAOMI_GATEWAY = "xiaomi_gw" + +PY_XIAOMI_GATEWAY = 'xiaomi_gw' SERVICE_PLAY_RINGTONE = 'play_ringtone' SERVICE_STOP_RINGTONE = 'stop_ringtone' SERVICE_ADD_DEVICE = 'add_device' SERVICE_REMOVE_DEVICE = 'remove_device' - GW_MAC = vol.All( cv.string, lambda value: value.replace(':', '').lower(), vol.Length(min=12, max=12) ) - SERVICE_SCHEMA_PLAY_RINGTONE = vol.Schema({ vol.Required(ATTR_RINGTONE_ID): vol.All(vol.Coerce(int), vol.NotIn([9, 14, 15, 16, 17, 18, 19])), @@ -58,13 +69,13 @@ GATEWAY_CONFIG = vol.Schema({ def _fix_conf_defaults(config): - """Update some config defaults.""" + """Update some configuration defaults.""" config['sid'] = config.pop(CONF_MAC, None) if config.get(CONF_KEY) is None: _LOGGER.warning( 'Key is not provided for gateway %s. Controlling the gateway ' - 'will not be possible.', config['sid']) + 'will not be possible', config['sid']) if config.get(CONF_HOST) is None: config.pop(CONF_PORT) @@ -83,8 +94,6 @@ CONFIG_SCHEMA = vol.Schema({ }) }, extra=vol.ALLOW_EXTRA) -_LOGGER = logging.getLogger(__name__) - def setup(hass, config): """Set up the Xiaomi component.""" @@ -98,8 +107,8 @@ def setup(hass, config): @asyncio.coroutine def xiaomi_gw_discovered(service, discovery_info): - """Called when Xiaomi Gateway device(s) has been found.""" - # We don't need to do anything here, the purpose of HA's + """Perform action when Xiaomi Gateway device(s) has been found.""" + # We don't need to do anything here, the purpose of Home Assistant's # discovery service is to just trigger loading of this # component, and then its own discovery process kicks in. @@ -111,7 +120,7 @@ def setup(hass, config): _LOGGER.debug("Expecting %s gateways", len(gateways)) for k in range(discovery_retry): - _LOGGER.info('Discovering Xiaomi Gateways (Try %s)', k + 1) + _LOGGER.info("Discovering Xiaomi Gateways (Try %s)", k + 1) xiaomi.discover_gateways() if len(xiaomi.gateways) >= len(gateways): break @@ -127,7 +136,7 @@ def setup(hass, config): def stop_xiaomi(event): """Stop Xiaomi Socket.""" - _LOGGER.info("Shutting down Xiaomi Hub.") + _LOGGER.info("Shutting down Xiaomi Hub") xiaomi.stop_listen() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_xiaomi) @@ -190,7 +199,7 @@ class XiaomiDevice(Entity): """Representation a base Xiaomi device.""" def __init__(self, device, name, xiaomi_hub): - """Initialize the xiaomi device.""" + """Initialize the Xiaomi device.""" self._state = None self._sid = device['sid'] self._name = '{}_{}'.format(name, self._sid) @@ -208,7 +217,7 @@ class XiaomiDevice(Entity): @property def should_poll(self): - """No polling needed.""" + """Return the polling state. No polling needed.""" return False @property diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 6268b3cb9f7..0cf9d83863f 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -113,7 +113,7 @@ class IntentHandler: raise NotImplementedError() def __repr__(self): - """String representation of intent handler.""" + """Represent a string of an intent handler.""" return '<{} - {}>'.format(self.__class__.__name__, self.intent_type) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index d56cef1dd83..7eb0a602139 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -2,22 +2,24 @@ from datetime import datetime import json import logging +import math import random import re -import math import jinja2 from jinja2 import contextfilter from jinja2.sandbox import ImmutableSandboxedEnvironment from homeassistant.const import ( - STATE_UNKNOWN, ATTR_LATITUDE, ATTR_LONGITUDE, MATCH_ALL, - ATTR_UNIT_OF_MEASUREMENT) + ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_UNIT_OF_MEASUREMENT, MATCH_ALL, + STATE_UNKNOWN) from homeassistant.core import State from homeassistant.exceptions import TemplateError from homeassistant.helpers import location as loc_helper -from homeassistant.loader import get_component, bind_hass -from homeassistant.util import convert, dt as dt_util, location as loc_util +from homeassistant.loader import bind_hass, get_component +from homeassistant.util import convert +from homeassistant.util import dt as dt_util +from homeassistant.util import location as loc_util from homeassistant.util.async import run_callback_threadsafe _LOGGER = logging.getLogger(__name__) @@ -279,7 +281,7 @@ class TemplateState(State): def _wrap_state(state): - """Helper function to wrap a state.""" + """Wrap a state.""" return None if state is None else TemplateState(state) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index e7a0854f047..ac20f94d243 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -1,5 +1,5 @@ """ -Provides methods for loading Home Assistant components. +The methods for loading Home Assistant components. This module has quite some complex parts. I have tried to add as much documentation as possible to keep it understandable. @@ -16,10 +16,10 @@ import logging import os import pkgutil import sys - from types import ModuleType + # pylint: disable=unused-import -from typing import Optional, Sequence, Set, Dict # NOQA +from typing import Dict, Optional, Sequence, Set # NOQA from homeassistant.const import PLATFORM_FORMAT from homeassistant.util import OrderedSet @@ -224,7 +224,7 @@ class ModuleWrapper: def bind_hass(func): - """Decorator to indicate that first argument is hass.""" + """Decorate function to indicate that first argument is hass.""" # pylint: disable=protected-access func.__bind_hass = True return func @@ -290,4 +290,4 @@ def _check_prepared() -> None: if not PREPARED: _LOGGER.warning(( "You did not call loader.prepare() yet. " - "Certain functionality might not be working.")) + "Certain functionality might not be working")) diff --git a/homeassistant/scripts/benchmark/__init__.py b/homeassistant/scripts/benchmark/__init__.py index 100d3aa3508..470040b8295 100644 --- a/homeassistant/scripts/benchmark/__init__.py +++ b/homeassistant/scripts/benchmark/__init__.py @@ -1,21 +1,21 @@ """Script to run benchmarks.""" -import asyncio import argparse +import asyncio from contextlib import suppress from datetime import datetime import logging from timeit import default_timer as timer -from homeassistant.const import ( - EVENT_TIME_CHANGED, ATTR_NOW, EVENT_STATE_CHANGED) from homeassistant import core +from homeassistant.const import ( + ATTR_NOW, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED) from homeassistant.util import dt as dt_util BENCHMARKS = {} def run(args): - """Handle ensure config commandline script.""" + """Handle ensure configuration commandline script.""" # Disable logging logging.getLogger('homeassistant.core').setLevel(logging.CRITICAL) @@ -44,7 +44,7 @@ def run(args): def benchmark(func): - """Decorator to mark a benchmark.""" + """Decorate to mark a benchmark.""" BENCHMARKS[func.__name__] = func return func diff --git a/tests/components/sensor/test_vultr.py b/tests/components/sensor/test_vultr.py index c5222ab5543..6b2e2341c52 100644 --- a/tests/components/sensor/test_vultr.py +++ b/tests/components/sensor/test_vultr.py @@ -70,10 +70,8 @@ class TestVultrSensorSetup(unittest.TestCase): base_vultr.setup(self.hass, VALID_CONFIG) for config in self.configs: - setup = vultr.setup_platform(self.hass, - config, - self.add_devices, - None) + setup = vultr.setup_platform( + self.hass, config, self.add_devices, None) self.assertIsNone(setup) @@ -161,10 +159,8 @@ class TestVultrSensorSetup(unittest.TestCase): CONF_MONITORED_CONDITIONS: vultr.MONITORED_CONDITIONS, } # No subs at all - no_sub_setup = vultr.setup_platform(self.hass, - bad_conf, - self.add_devices, - None) + no_sub_setup = vultr.setup_platform( + self.hass, bad_conf, self.add_devices, None) - self.assertIsNotNone(no_sub_setup) + self.assertIsNone(no_sub_setup) self.assertEqual(0, len(self.DEVICES)) From 1a789a05dbf4059ed95b170765fae83a5fe8b5b7 Mon Sep 17 00:00:00 2001 From: Teemu R Date: Sun, 21 Jan 2018 11:45:20 +0100 Subject: [PATCH 103/150] bump eq3bt version (#11834) --- homeassistant/components/climate/eq3btsmart.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/climate/eq3btsmart.py b/homeassistant/components/climate/eq3btsmart.py index eb9b5c5ba6e..9b3b7d650a9 100644 --- a/homeassistant/components/climate/eq3btsmart.py +++ b/homeassistant/components/climate/eq3btsmart.py @@ -15,7 +15,7 @@ from homeassistant.const import ( CONF_MAC, CONF_DEVICES, TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_HALVES) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-eq3bt==0.1.6'] +REQUIREMENTS = ['python-eq3bt==0.1.8'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 1c1f46db3c9..fca1e88cc6f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -870,7 +870,7 @@ python-digitalocean==1.13.2 python-ecobee-api==0.0.14 # homeassistant.components.climate.eq3btsmart -# python-eq3bt==0.1.6 +# python-eq3bt==0.1.8 # homeassistant.components.sensor.etherscan python-etherscan-api==0.0.2 From 6a6ea263cfad48d0ec1f21bfc1537cc03acb5a0a Mon Sep 17 00:00:00 2001 From: William Scanlon Date: Sun, 21 Jan 2018 12:13:28 -0500 Subject: [PATCH 104/150] Fix Unifi direct errors caused by AP reboot. (#11835) * Fix Unifi direct errors caused by AP reboot. * Inverted logic in update --- homeassistant/components/device_tracker/unifi_direct.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/unifi_direct.py b/homeassistant/components/device_tracker/unifi_direct.py index 57a0186a2e2..168ab04ec6f 100644 --- a/homeassistant/components/device_tracker/unifi_direct.py +++ b/homeassistant/components/device_tracker/unifi_direct.py @@ -98,11 +98,15 @@ class UnifiDeviceScanner(DeviceScanner): self.connected = False def _get_update(self): - from pexpect import pxssh + from pexpect import pxssh, exceptions try: if not self.connected: self._connect() + # If we still aren't connected at this point + # don't try to send anything to the AP. + if not self.connected: + return None self.ssh.sendline(UNIFI_COMMAND) self.ssh.prompt() return self.ssh.before @@ -110,7 +114,7 @@ class UnifiDeviceScanner(DeviceScanner): _LOGGER.error("Unexpected SSH error: %s", str(err)) self._disconnect() return None - except AssertionError as err: + except (AssertionError, exceptions.EOF) as err: _LOGGER.error("Connection to AP unavailable: %s", str(err)) self._disconnect() return None From 5513ffc33c31d7834716b3855a996f2784dd7af2 Mon Sep 17 00:00:00 2001 From: andrewdolphin <31809425+andrewdolphin@users.noreply.github.com> Date: Sun, 21 Jan 2018 20:57:56 +0000 Subject: [PATCH 105/150] Change 'on' to 'heat' as fallback thermostat mode (#11377) * Change 'on' to 'heat' as fallback thermostat mode 'on' isn't recognised as a mode by Google Assistant, rather is used as a method to return a thermostat to a previous mode. In the case where a thermostat doesn't support the standard google modes (e.g. A homematic radiator thermostat) this means the set_temperature doesn't get returned to the user on a request. * Update test_google_assistant.py --- homeassistant/components/google_assistant/smart_home.py | 2 +- tests/components/google_assistant/test_google_assistant.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py index 0faa9bdc484..f9d6135107b 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -151,7 +151,7 @@ def query_device(entity: Entity, units: UnitSystem) -> dict: if entity.domain == climate.DOMAIN: mode = entity.attributes.get(climate.ATTR_OPERATION_MODE).lower() if mode not in CLIMATE_SUPPORTED_MODES: - mode = 'on' + mode = 'heat' response = { 'thermostatMode': mode, 'thermostatTemperatureSetpoint': diff --git a/tests/components/google_assistant/test_google_assistant.py b/tests/components/google_assistant/test_google_assistant.py index 3b9ad7f3ef7..500e8ece143 100644 --- a/tests/components/google_assistant/test_google_assistant.py +++ b/tests/components/google_assistant/test_google_assistant.py @@ -215,7 +215,7 @@ def test_query_climate_request(hass_fixture, assistant_client): 'climate.ecobee': { 'thermostatTemperatureSetpointHigh': 24, 'thermostatTemperatureAmbient': 23, - 'thermostatMode': 'on', + 'thermostatMode': 'heat', 'thermostatTemperatureSetpointLow': 21 }, 'climate.hvac': { @@ -263,7 +263,7 @@ def test_query_climate_request_f(hass_fixture, assistant_client): 'climate.ecobee': { 'thermostatTemperatureSetpointHigh': -4.4, 'thermostatTemperatureAmbient': -5, - 'thermostatMode': 'on', + 'thermostatMode': 'heat', 'thermostatTemperatureSetpointLow': -6.1, }, 'climate.hvac': { From 0f26ebe954ddef1dd6d2fbb848b2f792d5bafe52 Mon Sep 17 00:00:00 2001 From: kennedyshead Date: Mon, 22 Jan 2018 09:11:16 +0100 Subject: [PATCH 106/150] Use strict timeout when polling Samsung TV (Fix for #6375) (#11759) --- homeassistant/components/media_player/samsungtv.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/media_player/samsungtv.py b/homeassistant/components/media_player/samsungtv.py index d42bd9ea012..57f25873ae7 100644 --- a/homeassistant/components/media_player/samsungtv.py +++ b/homeassistant/components/media_player/samsungtv.py @@ -121,9 +121,14 @@ class SamsungTVDevice(MediaPlayerDevice): self._config['method'] = 'legacy' def update(self): - """Retrieve the latest data.""" - # Send an empty key to see if we are still connected - self.send_key('KEY') + """Update state of device.""" + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(self._config[CONF_TIMEOUT]) + sock.connect((self._config['host'], self._config['port'])) + self._state = STATE_ON + except socket.error: + self._state = STATE_OFF def get_remote(self): """Create or return a remote control instance.""" From c8d26d99f0b9b3a1ac53d0106195ef2fd76fc38c Mon Sep 17 00:00:00 2001 From: Marius Date: Mon, 22 Jan 2018 13:12:03 +0200 Subject: [PATCH 107/150] Fix issues with generic thermostat (#11805) * Fixes for #11757 #11798 #11763 * Adjustments based on feedback --- .../components/climate/generic_thermostat.py | 66 ++++++++++++------- .../climate/test_generic_thermostat.py | 11 ++-- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/climate/generic_thermostat.py b/homeassistant/components/climate/generic_thermostat.py index fdfe56ca62c..9445fc7cfc9 100644 --- a/homeassistant/components/climate/generic_thermostat.py +++ b/homeassistant/components/climate/generic_thermostat.py @@ -17,7 +17,8 @@ from homeassistant.components.climate import ( SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA) from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE, - CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF) + CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, + STATE_UNKNOWN) from homeassistant.helpers import condition from homeassistant.helpers.event import ( async_track_state_change, async_track_time_interval) @@ -30,7 +31,6 @@ DEPENDENCIES = ['switch', 'sensor'] DEFAULT_TOLERANCE = 0.3 DEFAULT_NAME = 'Generic Thermostat' -DEFAULT_AWAY_TEMP = 16 CONF_HEATER = 'heater' CONF_SENSOR = 'target_sensor' @@ -44,7 +44,7 @@ CONF_HOT_TOLERANCE = 'hot_tolerance' CONF_KEEP_ALIVE = 'keep_alive' CONF_INITIAL_OPERATION_MODE = 'initial_operation_mode' CONF_AWAY_TEMP = 'away_temp' -SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE | +SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -64,8 +64,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ cv.time_period, cv.positive_timedelta), vol.Optional(CONF_INITIAL_OPERATION_MODE): vol.In([STATE_AUTO, STATE_OFF]), - vol.Optional(CONF_AWAY_TEMP, - default=DEFAULT_AWAY_TEMP): vol.Coerce(float) + vol.Optional(CONF_AWAY_TEMP): vol.Coerce(float) }) @@ -119,6 +118,7 @@ class GenericThermostat(ClimateDevice): self._operation_list = [STATE_HEAT, STATE_OFF] if initial_operation_mode == STATE_OFF: self._enabled = False + self._current_operation = STATE_OFF else: self._enabled = True self._active = False @@ -127,6 +127,9 @@ class GenericThermostat(ClimateDevice): self._max_temp = max_temp self._target_temp = target_temp self._unit = hass.config.units.temperature_unit + self._support_flags = SUPPORT_FLAGS + if away_temp is not None: + self._support_flags = SUPPORT_FLAGS | SUPPORT_AWAY_MODE self._away_temp = away_temp self._is_away = False @@ -139,6 +142,10 @@ class GenericThermostat(ClimateDevice): async_track_time_interval( hass, self._async_keep_alive, self._keep_alive) + sensor_state = hass.states.get(sensor_entity_id) + if sensor_state and sensor_state.state != STATE_UNKNOWN: + self._async_update_temp(sensor_state) + @asyncio.coroutine def async_added_to_hass(self): """Run when entity about to be added.""" @@ -154,19 +161,29 @@ class GenericThermostat(ClimateDevice): self._target_temp = self.max_temp else: self._target_temp = self.min_temp - _LOGGER.warning('Undefined target temperature, \ - falling back to %s', self._target_temp) + _LOGGER.warning("Undefined target temperature," + "falling back to %s", self._target_temp) else: self._target_temp = float( old_state.attributes[ATTR_TEMPERATURE]) - self._is_away = True if str( - old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON else False - if old_state.attributes[ATTR_OPERATION_MODE] == STATE_OFF: - self._current_operation = STATE_OFF - self._enabled = False - if self._initial_operation_mode is None: - if old_state.attributes[ATTR_OPERATION_MODE] == STATE_OFF: - self._enabled = False + if old_state.attributes[ATTR_AWAY_MODE] is not None: + self._is_away = str( + old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON + if (self._initial_operation_mode is None and + old_state.attributes[ATTR_OPERATION_MODE] is not None): + self._current_operation = \ + old_state.attributes[ATTR_OPERATION_MODE] + if self._current_operation != STATE_OFF: + self._enabled = True + else: + # No previous state, try and restore defaults + if self._target_temp is None: + if self.ac_mode: + self._target_temp = self.max_temp + else: + self._target_temp = self.min_temp + _LOGGER.warning("No previously saved temperature, setting to %s", + self._target_temp) @property def state(self): @@ -230,7 +247,7 @@ class GenericThermostat(ClimateDevice): if self._is_device_active: self._heater_turn_off() else: - _LOGGER.error('Unrecognized operation mode: %s', operation_mode) + _LOGGER.error("Unrecognized operation mode: %s", operation_mode) return # Ensure we updae the current operation after changing the mode self.schedule_update_ha_state() @@ -299,7 +316,7 @@ class GenericThermostat(ClimateDevice): self._cur_temp = self.hass.config.units.temperature( float(state.state), unit) except ValueError as ex: - _LOGGER.error('Unable to update from sensor: %s', ex) + _LOGGER.error("Unable to update from sensor: %s", ex) @callback def _async_control_heating(self): @@ -307,8 +324,9 @@ class GenericThermostat(ClimateDevice): if not self._active and None not in (self._cur_temp, self._target_temp): self._active = True - _LOGGER.info('Obtained current and target temperature. ' - 'Generic thermostat active.') + _LOGGER.info("Obtained current and target temperature. " + "Generic thermostat active. %s, %s", + self._cur_temp, self._target_temp) if not self._active: return @@ -333,13 +351,13 @@ class GenericThermostat(ClimateDevice): too_cold = self._target_temp - self._cur_temp >= \ self._cold_tolerance if too_cold: - _LOGGER.info('Turning off AC %s', self.heater_entity_id) + _LOGGER.info("Turning off AC %s", self.heater_entity_id) self._heater_turn_off() else: too_hot = self._cur_temp - self._target_temp >= \ self._hot_tolerance if too_hot: - _LOGGER.info('Turning on AC %s', self.heater_entity_id) + _LOGGER.info("Turning on AC %s", self.heater_entity_id) self._heater_turn_on() else: is_heating = self._is_device_active @@ -347,14 +365,14 @@ class GenericThermostat(ClimateDevice): too_hot = self._cur_temp - self._target_temp >= \ self._hot_tolerance if too_hot: - _LOGGER.info('Turning off heater %s', + _LOGGER.info("Turning off heater %s", self.heater_entity_id) self._heater_turn_off() else: too_cold = self._target_temp - self._cur_temp >= \ self._cold_tolerance if too_cold: - _LOGGER.info('Turning on heater %s', self.heater_entity_id) + _LOGGER.info("Turning on heater %s", self.heater_entity_id) self._heater_turn_on() @property @@ -365,7 +383,7 @@ class GenericThermostat(ClimateDevice): @property def supported_features(self): """Return the list of supported features.""" - return SUPPORT_FLAGS + return self._support_flags @callback def _heater_turn_on(self): diff --git a/tests/components/climate/test_generic_thermostat.py b/tests/components/climate/test_generic_thermostat.py index 776e79a6827..190eb7e8522 100644 --- a/tests/components/climate/test_generic_thermostat.py +++ b/tests/components/climate/test_generic_thermostat.py @@ -160,7 +160,8 @@ class TestClimateGenericThermostat(unittest.TestCase): 'cold_tolerance': 2, 'hot_tolerance': 4, 'heater': ENT_SWITCH, - 'target_sensor': ENT_SENSOR + 'target_sensor': ENT_SENSOR, + 'away_temp': 16 }}) def tearDown(self): # pylint: disable=invalid-name @@ -176,7 +177,7 @@ class TestClimateGenericThermostat(unittest.TestCase): state = self.hass.states.get(ENTITY) self.assertEqual(7, state.attributes.get('min_temp')) self.assertEqual(35, state.attributes.get('max_temp')) - self.assertEqual(None, state.attributes.get('temperature')) + self.assertEqual(7, state.attributes.get('temperature')) def test_get_operation_modes(self): """Test that the operation list returns the correct modes.""" @@ -266,7 +267,7 @@ class TestClimateGenericThermostat(unittest.TestCase): self.hass.block_till_done() climate.set_temperature(self.hass, 25) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + self.assertEqual(2, len(self.calls)) call = self.calls[0] self.assertEqual('homeassistant', call.domain) self.assertEqual(SERVICE_TURN_OFF, call.service) @@ -414,7 +415,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): self.hass.block_till_done() climate.set_temperature(self.hass, 30) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + self.assertEqual(2, len(self.calls)) call = self.calls[0] self.assertEqual('homeassistant', call.domain) self.assertEqual(SERVICE_TURN_OFF, call.service) @@ -750,6 +751,7 @@ class TestClimateGenericThermostatACKeepAlive(unittest.TestCase): 'cold_tolerance': 0.3, 'hot_tolerance': 0.3, 'heater': ENT_SWITCH, + 'target_temp': 25, 'target_sensor': ENT_SENSOR, 'ac_mode': True, 'keep_alive': datetime.timedelta(minutes=10) @@ -841,6 +843,7 @@ class TestClimateGenericThermostatKeepAlive(unittest.TestCase): 'name': 'test', 'cold_tolerance': 0.3, 'hot_tolerance': 0.3, + 'target_temp': 25, 'heater': ENT_SWITCH, 'target_sensor': ENT_SENSOR, 'keep_alive': datetime.timedelta(minutes=10) From 8c627e2b8bb2a4f5da1a5a7b21dd996f9a184189 Mon Sep 17 00:00:00 2001 From: Oleksii Serdiuk Date: Mon, 22 Jan 2018 12:32:27 +0100 Subject: [PATCH 108/150] maxcube: Set MAX! Window Sensor's class to 'window' (#11799) The sensors are meant to be put on windows to shut down the heating when windows are open. Having 'window' device class instead of 'opening' is much more logical here. --- homeassistant/components/binary_sensor/maxcube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/binary_sensor/maxcube.py b/homeassistant/components/binary_sensor/maxcube.py index cf2be6baed5..1043004243a 100644 --- a/homeassistant/components/binary_sensor/maxcube.py +++ b/homeassistant/components/binary_sensor/maxcube.py @@ -36,7 +36,7 @@ class MaxCubeShutter(BinarySensorDevice): def __init__(self, hass, name, rf_address): """Initialize MAX! Cube BinarySensorDevice.""" self._name = name - self._sensor_type = 'opening' + self._sensor_type = 'window' self._rf_address = rf_address self._cubehandle = hass.data[MAXCUBE_HANDLE] self._state = STATE_UNKNOWN From b224fd324d34df08720bb04f360cbde151de377e Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 22 Jan 2018 14:45:48 +0100 Subject: [PATCH 109/150] Fix new iframe panel url (#11850) * Fix new iframe panel url * fix tests --- homeassistant/components/hassio.py | 2 +- tests/components/test_hassio.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/hassio.py b/homeassistant/components/hassio.py index efd83c7ea9e..8ab51dec31c 100644 --- a/homeassistant/components/hassio.py +++ b/homeassistant/components/hassio.py @@ -71,7 +71,7 @@ NO_TIMEOUT = { } NO_AUTH = { - re.compile(r'^panel_(es5|latest)$'), re.compile(r'^addons/[^/]*/logo$') + re.compile(r'^app-(es5|latest)$'), re.compile(r'^addons/[^/]*/logo$') } SCHEMA_NO_DATA = vol.Schema({}) diff --git a/tests/components/test_hassio.py b/tests/components/test_hassio.py index 48443658fc4..4d8b03d4e8b 100644 --- a/tests/components/test_hassio.py +++ b/tests/components/test_hassio.py @@ -395,7 +395,7 @@ def test_forward_request_no_auth_for_panel(hassio_client, build_type): patch('homeassistant.components.hassio._create_response') as mresp: mresp.return_value = 'response' resp = yield from hassio_client.get( - '/api/hassio/panel_{}'.format(build_type)) + '/api/hassio/app-{}'.format(build_type)) # Check we got right response assert resp.status == 200 From d478517c51c8428c5606bfa9dfd6f734aff5df13 Mon Sep 17 00:00:00 2001 From: Sean Dague Date: Mon, 22 Jan 2018 09:21:56 -0500 Subject: [PATCH 110/150] Fix races on recorder test (#11857) --- tests/components/recorder/test_purge.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index bbb87fb5016..c429ee2fbbb 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -1,7 +1,6 @@ """Test data purging.""" import json from datetime import datetime, timedelta -from time import sleep import unittest from homeassistant.components import recorder @@ -162,7 +161,7 @@ class TestRecorderPurge(unittest.TestCase): self.hass.async_block_till_done() # Small wait for recorder thread - sleep(0.1) + self.hass.data[DATA_INSTANCE].block_till_done() # we should still have everything from before self.assertEqual(states.count(), 6) @@ -174,7 +173,7 @@ class TestRecorderPurge(unittest.TestCase): self.hass.async_block_till_done() # Small wait for recorder thread - sleep(0.1) + self.hass.data[DATA_INSTANCE].block_till_done() # we should only have 3 states left after purging self.assertEqual(states.count(), 3) From 183e0543b4124708c7a73cb2afb0a096be2f6468 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 22 Jan 2018 22:54:41 -0800 Subject: [PATCH 111/150] Clean up entity component (#11691) * Clean up entity component * Lint * List -> Tuple * Add Entity.async_remove back * Unflake setting up group test --- .../components/automation/__init__.py | 5 +- .../components/binary_sensor/flic.py | 5 +- homeassistant/components/camera/__init__.py | 12 +- homeassistant/components/group/__init__.py | 76 +++--- homeassistant/components/mailbox/__init__.py | 2 +- .../components/media_player/__init__.py | 8 +- homeassistant/components/microsoft_face.py | 2 +- .../components/remember_the_milk/__init__.py | 2 +- homeassistant/components/script.py | 12 +- homeassistant/helpers/entity.py | 26 +- homeassistant/helpers/entity_component.py | 234 ++++++++++-------- tests/components/group/test_init.py | 10 +- tests/helpers/test_entity.py | 12 + tests/helpers/test_entity_component.py | 15 +- 14 files changed, 230 insertions(+), 191 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index bc3c17e41da..8c490754f40 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -338,10 +338,9 @@ class AutomationEntity(ToggleEntity): yield from self.async_update_ha_state() @asyncio.coroutine - def async_remove(self): - """Remove automation from HASS.""" + def async_will_remove_from_hass(self): + """Remove listeners when removing automation from HASS.""" yield from self.async_turn_off() - yield from super().async_remove() @asyncio.coroutine def async_enable(self): diff --git a/homeassistant/components/binary_sensor/flic.py b/homeassistant/components/binary_sensor/flic.py index 51fffae5cc0..170f1818a0e 100644 --- a/homeassistant/components/binary_sensor/flic.py +++ b/homeassistant/components/binary_sensor/flic.py @@ -238,6 +238,5 @@ class FlicButton(BinarySensorDevice): import pyflic if connection_status == pyflic.ConnectionStatus.Disconnected: - _LOGGER.info("Button (%s) disconnected. Reason: %s", - self.address, disconnect_reason) - self.remove() + _LOGGER.warning("Button (%s) disconnected. Reason: %s", + self.address, disconnect_reason) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 6839c2c3b9c..1bb88050b2f 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -124,15 +124,15 @@ def async_setup(hass, config): """Set up the camera component.""" component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) - hass.http.register_view(CameraImageView(component.entities)) - hass.http.register_view(CameraMjpegStream(component.entities)) + hass.http.register_view(CameraImageView(component)) + hass.http.register_view(CameraMjpegStream(component)) yield from component.async_setup(config) @callback def update_tokens(time): """Update tokens of the entities.""" - for entity in component.entities.values(): + for entity in component.entities: entity.async_update_token() hass.async_add_job(entity.async_update_ha_state()) @@ -358,14 +358,14 @@ class CameraView(HomeAssistantView): requires_auth = False - def __init__(self, entities): + def __init__(self, component): """Initialize a basic camera view.""" - self.entities = entities + self.component = component @asyncio.coroutine def get(self, request, entity_id): """Start a GET request.""" - camera = self.entities.get(entity_id) + camera = self.component.get_entity(entity_id) if camera is None: status = 404 if request[KEY_AUTHENTICATED] else 401 diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index 8b1e05e3122..a8529f18b69 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -42,8 +42,6 @@ ATTR_ORDER = 'order' ATTR_VIEW = 'view' ATTR_VISIBLE = 'visible' -DATA_ALL_GROUPS = 'data_all_groups' - SERVICE_SET_VISIBILITY = 'set_visibility' SERVICE_SET = 'set' SERVICE_REMOVE = 'remove' @@ -250,8 +248,10 @@ def get_entity_ids(hass, entity_id, domain_filter=None): @asyncio.coroutine def async_setup(hass, config): """Set up all groups found definded in the configuration.""" - component = EntityComponent(_LOGGER, DOMAIN, hass) - hass.data[DATA_ALL_GROUPS] = {} + component = hass.data.get(DOMAIN) + + if component is None: + component = hass.data[DOMAIN] = EntityComponent(_LOGGER, DOMAIN, hass) yield from _async_process_config(hass, config, component) @@ -271,10 +271,11 @@ def async_setup(hass, config): def groups_service_handler(service): """Handle dynamic group service functions.""" object_id = service.data[ATTR_OBJECT_ID] - service_groups = hass.data[DATA_ALL_GROUPS] + entity_id = ENTITY_ID_FORMAT.format(object_id) + group = component.get_entity(entity_id) # new group - if service.service == SERVICE_SET and object_id not in service_groups: + if service.service == SERVICE_SET and group is None: entity_ids = service.data.get(ATTR_ENTITIES) or \ service.data.get(ATTR_ADD_ENTITIES) or None @@ -289,12 +290,15 @@ def async_setup(hass, config): user_defined=False, **extra_arg ) + return + if group is None: + _LOGGER.warning("%s:Group '%s' doesn't exist!", + service.service, object_id) return # update group if service.service == SERVICE_SET: - group = service_groups[object_id] need_update = False if ATTR_ADD_ENTITIES in service.data: @@ -333,12 +337,7 @@ def async_setup(hass, config): # remove group if service.service == SERVICE_REMOVE: - if object_id not in service_groups: - _LOGGER.warning("Group '%s' doesn't exist!", object_id) - return - - del_group = service_groups.pop(object_id) - yield from del_group.async_stop() + yield from component.async_remove_entity(entity_id) hass.services.async_register( DOMAIN, SERVICE_SET, groups_service_handler, @@ -395,7 +394,7 @@ class Group(Entity): """Track a group of entity ids.""" def __init__(self, hass, name, order=None, visible=True, icon=None, - view=False, control=None, user_defined=True): + view=False, control=None, user_defined=True, entity_ids=None): """Initialize a group. This Object has factory function for creation. @@ -405,7 +404,10 @@ class Group(Entity): self._state = STATE_UNKNOWN self._icon = icon self.view = view - self.tracking = [] + if entity_ids: + self.tracking = tuple(ent_id.lower() for ent_id in entity_ids) + else: + self.tracking = tuple() self.group_on = None self.group_off = None self.visible = visible @@ -439,23 +441,21 @@ class Group(Entity): hass, name, order=len(hass.states.async_entity_ids(DOMAIN)), visible=visible, icon=icon, view=view, control=control, - user_defined=user_defined + user_defined=user_defined, entity_ids=entity_ids ) group.entity_id = async_generate_entity_id( ENTITY_ID_FORMAT, object_id or name, hass=hass) - # run other async stuff - if entity_ids is not None: - yield from group.async_update_tracked_entity_ids(entity_ids) - else: - yield from group.async_update_ha_state(True) - # If called before the platform async_setup is called (test cases) - if DATA_ALL_GROUPS not in hass.data: - hass.data[DATA_ALL_GROUPS] = {} + component = hass.data.get(DOMAIN) + + if component is None: + component = hass.data[DOMAIN] = \ + EntityComponent(_LOGGER, DOMAIN, hass) + + yield from component.async_add_entities([group], True) - hass.data[DATA_ALL_GROUPS][object_id] = group return group @property @@ -534,10 +534,6 @@ class Group(Entity): yield from self.async_update_ha_state(True) self.async_start() - def start(self): - """Start tracking members.""" - self.hass.add_job(self.async_start) - @callback def async_start(self): """Start tracking members. @@ -549,17 +545,15 @@ class Group(Entity): self.hass, self.tracking, self._async_state_changed_listener ) - def stop(self): - """Unregister the group from Home Assistant.""" - run_coroutine_threadsafe(self.async_stop(), self.hass.loop).result() - @asyncio.coroutine def async_stop(self): """Unregister the group from Home Assistant. This method must be run in the event loop. """ - yield from self.async_remove() + if self._async_unsub_state_changed: + self._async_unsub_state_changed() + self._async_unsub_state_changed = None @asyncio.coroutine def async_update(self): @@ -567,17 +561,19 @@ class Group(Entity): self._state = STATE_UNKNOWN self._async_update_group_state() - def async_remove(self): - """Remove group from HASS. + @asyncio.coroutine + def async_added_to_hass(self): + """Callback when added to HASS.""" + if self.tracking: + self.async_start() - This method must be run in the event loop and returns a coroutine. - """ + @asyncio.coroutine + def async_will_remove_from_hass(self): + """Callback when removed from HASS.""" if self._async_unsub_state_changed: self._async_unsub_state_changed() self._async_unsub_state_changed = None - return super().async_remove() - @asyncio.coroutine def _async_state_changed_listener(self, entity_id, old_state, new_state): """Respond to a member state changing. diff --git a/homeassistant/components/mailbox/__init__.py b/homeassistant/components/mailbox/__init__.py index 4f999649a44..a1e68555649 100644 --- a/homeassistant/components/mailbox/__init__.py +++ b/homeassistant/components/mailbox/__init__.py @@ -82,7 +82,7 @@ def async_setup(hass, config): mailbox_entity = MailboxEntity(hass, mailbox) component = EntityComponent( logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL) - yield from component.async_add_entity(mailbox_entity) + yield from component.async_add_entities([mailbox_entity]) setup_tasks = [async_setup_platform(p_type, p_config) for p_type, p_config in config_per_platform(config, DOMAIN)] diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 44e6810fd5d..91bcb4d8af0 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -366,7 +366,7 @@ def async_setup(hass, config): component = EntityComponent( logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL) - hass.http.register_view(MediaPlayerImageView(component.entities)) + hass.http.register_view(MediaPlayerImageView(component)) yield from component.async_setup(config) @@ -929,14 +929,14 @@ class MediaPlayerImageView(HomeAssistantView): url = '/api/media_player_proxy/{entity_id}' name = 'api:media_player:image' - def __init__(self, entities): + def __init__(self, component): """Initialize a media player view.""" - self.entities = entities + self.component = component @asyncio.coroutine def get(self, request, entity_id): """Start a get request.""" - player = self.entities.get(entity_id) + player = self.component.get_entity(entity_id) if player is None: status = 404 if request[KEY_AUTHENTICATED] else 401 return web.Response(status=status) diff --git a/homeassistant/components/microsoft_face.py b/homeassistant/components/microsoft_face.py index 829c1124363..e61ed05ce10 100644 --- a/homeassistant/components/microsoft_face.py +++ b/homeassistant/components/microsoft_face.py @@ -161,7 +161,7 @@ def async_setup(hass, config): face.store.pop(g_id) entity = entities.pop(g_id) - yield from entity.async_remove() + hass.states.async_remove(entity.entity_id) except HomeAssistantError as err: _LOGGER.error("Can't delete group '%s' with error: %s", g_id, err) diff --git a/homeassistant/components/remember_the_milk/__init__.py b/homeassistant/components/remember_the_milk/__init__.py index 581380e3667..98cd937de3c 100644 --- a/homeassistant/components/remember_the_milk/__init__.py +++ b/homeassistant/components/remember_the_milk/__init__.py @@ -86,7 +86,7 @@ def _create_instance(hass, account_name, api_key, shared_secret, token, stored_rtm_config, component): entity = RememberTheMilk(account_name, api_key, shared_secret, token, stored_rtm_config) - component.add_entity(entity) + component.add_entities([entity]) hass.services.register( DOMAIN, '{}_create_task'.format(account_name), entity.create_task, schema=SERVICE_SCHEMA_CREATE_TASK) diff --git a/homeassistant/components/script.py b/homeassistant/components/script.py index 7be8bd8175e..a45f8ba8930 100644 --- a/homeassistant/components/script.py +++ b/homeassistant/components/script.py @@ -156,7 +156,7 @@ def _async_process_config(hass, config, component): def service_handler(service): """Execute a service call to script.