From 47e76fcd5e9fe2651f7346b4d39f8f0044aa8574 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 1 Sep 2019 21:42:57 -0700 Subject: [PATCH 01/24] Expose current direction properly on state machine (#26298) * Expose current direction properly on state machine * Fix template fan --- homeassistant/components/demo/fan.py | 8 ++++---- homeassistant/components/fan/__init__.py | 2 +- homeassistant/components/template/fan.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/demo/fan.py b/homeassistant/components/demo/fan.py index cdeed5dbfec692..ab8a6f3fae9dc6 100644 --- a/homeassistant/components/demo/fan.py +++ b/homeassistant/components/demo/fan.py @@ -34,13 +34,13 @@ def __init__(self, hass, name: str, supported_features: int) -> None: self._supported_features = supported_features self._speed = STATE_OFF self.oscillating = None - self.direction = None + self._direction = None self._name = name if supported_features & SUPPORT_OSCILLATE: self.oscillating = False if supported_features & SUPPORT_DIRECTION: - self.direction = "forward" + self._direction = "forward" @property def name(self) -> str: @@ -80,7 +80,7 @@ def set_speed(self, speed: str) -> None: def set_direction(self, direction: str) -> None: """Set the direction of the fan.""" - self.direction = direction + self._direction = direction self.schedule_update_ha_state() def oscillate(self, oscillating: bool) -> None: @@ -91,7 +91,7 @@ def oscillate(self, oscillating: bool) -> None: @property def current_direction(self) -> str: """Fan direction.""" - return self.direction + return self._direction @property def supported_features(self) -> int: diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index f5edfe5bb5996e..50d698f733656f 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -54,7 +54,7 @@ "speed": ATTR_SPEED, "speed_list": ATTR_SPEED_LIST, "oscillating": ATTR_OSCILLATING, - "direction": ATTR_DIRECTION, + "current_direction": ATTR_DIRECTION, } # type: dict FAN_SET_SPEED_SCHEMA = ENTITY_SERVICE_SCHEMA.extend( diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index c3d5a4d878fd9e..7fd8c4d9b3cea6 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -243,7 +243,7 @@ def oscillating(self): return self._oscillating @property - def direction(self): + def current_direction(self): """Return the oscillation state.""" return self._direction From 732855e86c88c105151bd88b570c2bf8cd896530 Mon Sep 17 00:00:00 2001 From: Paul Annekov Date: Sat, 31 Aug 2019 03:30:18 +0300 Subject: [PATCH 02/24] bump tuyaha 0.0.4 (#26303) --- homeassistant/components/tuya/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/tuya/manifest.json b/homeassistant/components/tuya/manifest.json index 8d47d8a0173bad..9c83056f6aca81 100644 --- a/homeassistant/components/tuya/manifest.json +++ b/homeassistant/components/tuya/manifest.json @@ -3,7 +3,7 @@ "name": "Tuya", "documentation": "https://www.home-assistant.io/components/tuya", "requirements": [ - "tuyaha==0.0.3" + "tuyaha==0.0.4" ], "dependencies": [], "codeowners": [] diff --git a/requirements_all.txt b/requirements_all.txt index e749340a8cb2c1..2bf7e6a841cc0a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1857,7 +1857,7 @@ tplink==0.2.1 transmissionrpc==0.11 # homeassistant.components.tuya -tuyaha==0.0.3 +tuyaha==0.0.4 # homeassistant.components.twentemilieu twentemilieu==0.1.0 From 795d5405db4bfa50a47f3d9b72dc83683831d3be Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 30 Aug 2019 18:34:40 -0700 Subject: [PATCH 03/24] Fix Alexa Report State (#26305) * Fix Alexa Report State * Forgot to save a file --- homeassistant/components/alexa/auth.py | 5 +++ homeassistant/components/alexa/config.py | 8 ++++ .../components/alexa/smart_home_http.py | 5 +++ .../components/alexa/state_report.py | 27 +++++++++---- .../components/cloud/alexa_config.py | 8 +++- tests/components/cloud/test_alexa_config.py | 38 ++++++++++++++++++- 6 files changed, 81 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/alexa/auth.py b/homeassistant/components/alexa/auth.py index d4633d938ed505..9f87a6d954e95b 100644 --- a/homeassistant/components/alexa/auth.py +++ b/homeassistant/components/alexa/auth.py @@ -56,6 +56,11 @@ async def async_do_auth(self, accept_grant_code): return await self._async_request_new_token(lwa_params) + @callback + def async_invalidate_access_token(self): + """Invalidate access token.""" + self._prefs[STORAGE_ACCESS_TOKEN] = None + async def async_get_access_token(self): """Perform access token or token refresh request.""" async with self._get_token_lock: diff --git a/homeassistant/components/alexa/config.py b/homeassistant/components/alexa/config.py index a22ebbcd30d431..f98337d71c56ae 100644 --- a/homeassistant/components/alexa/config.py +++ b/homeassistant/components/alexa/config.py @@ -1,4 +1,6 @@ """Config helpers for Alexa.""" +from homeassistant.core import callback + from .state_report import async_enable_proactive_mode @@ -55,11 +57,17 @@ async def async_disable_proactive_mode(self): unsub_func() self._unsub_proactive_report = None + @callback def should_expose(self, entity_id): """If an entity should be exposed.""" # pylint: disable=no-self-use return False + @callback + def async_invalidate_access_token(self): + """Invalidate access token.""" + raise NotImplementedError + async def async_get_access_token(self): """Get an access token.""" raise NotImplementedError diff --git a/homeassistant/components/alexa/smart_home_http.py b/homeassistant/components/alexa/smart_home_http.py index 7fdd4e3000a3ea..ada00e8a326820 100644 --- a/homeassistant/components/alexa/smart_home_http.py +++ b/homeassistant/components/alexa/smart_home_http.py @@ -57,6 +57,11 @@ def should_expose(self, entity_id): """If an entity should be exposed.""" return self._config[CONF_FILTER](entity_id) + @core.callback + def async_invalidate_access_token(self): + """Invalidate access token.""" + self._auth.async_invalidate_access_token() + async def async_get_access_token(self): """Get an access token.""" return await self._auth.async_get_access_token() diff --git a/homeassistant/components/alexa/state_report.py b/homeassistant/components/alexa/state_report.py index 7e8428899776e3..1e22d5fc09f23b 100644 --- a/homeassistant/components/alexa/state_report.py +++ b/homeassistant/components/alexa/state_report.py @@ -51,7 +51,9 @@ async def async_entity_state_listener(changed_entity, old_state, new_state): ) -async def async_send_changereport_message(hass, config, alexa_entity): +async def async_send_changereport_message( + hass, config, alexa_entity, *, invalidate_access_token=True +): """Send a ChangeReport message for an Alexa entity. https://developer.amazon.com/docs/smarthome/state-reporting-for-a-smart-home-skill.html#report-state-with-changereport-events @@ -88,21 +90,30 @@ async def async_send_changereport_message(hass, config, alexa_entity): except (asyncio.TimeoutError, aiohttp.ClientError): _LOGGER.error("Timeout sending report to Alexa.") - return None + return response_text = await response.text() _LOGGER.debug("Sent: %s", json.dumps(message_serialized)) _LOGGER.debug("Received (%s): %s", response.status, response_text) - if response.status != 202: - response_json = json.loads(response_text) - _LOGGER.error( - "Error when sending ChangeReport to Alexa: %s: %s", - response_json["payload"]["code"], - response_json["payload"]["description"], + if response.status == 202 and not invalidate_access_token: + return + + response_json = json.loads(response_text) + + if response_json["payload"]["code"] == "INVALID_ACCESS_TOKEN_EXCEPTION": + config.async_invalidate_access_token() + return await async_send_changereport_message( + hass, config, alexa_entity, invalidate_access_token=False ) + _LOGGER.error( + "Error when sending ChangeReport to Alexa: %s: %s", + response_json["payload"]["code"], + response_json["payload"]["description"], + ) + async def async_send_add_or_update_message(hass, config, entity_ids): """Send an AddOrUpdateReport message for entities. diff --git a/homeassistant/components/cloud/alexa_config.py b/homeassistant/components/cloud/alexa_config.py index d31bcfdfc40e61..a1432f196bf5d9 100644 --- a/homeassistant/components/cloud/alexa_config.py +++ b/homeassistant/components/cloud/alexa_config.py @@ -7,6 +7,7 @@ import async_timeout from hass_nabucasa import cloud_api +from homeassistant.core import callback from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES from homeassistant.helpers import entity_registry from homeassistant.helpers.event import async_call_later @@ -95,9 +96,14 @@ def should_expose(self, entity_id): entity_config = entity_configs.get(entity_id, {}) return entity_config.get(PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE) + @callback + def async_invalidate_access_token(self): + """Invalidate access token.""" + self._token_valid = None + async def async_get_access_token(self): """Get an access token.""" - if self._token_valid is not None and self._token_valid < utcnow(): + if self._token_valid is not None and self._token_valid > utcnow(): return self._token resp = await cloud_api.async_alexa_access_token(self._cloud) diff --git a/tests/components/cloud/test_alexa_config.py b/tests/components/cloud/test_alexa_config.py index 22d8c64c3b0f85..c8e84016a28a9f 100644 --- a/tests/components/cloud/test_alexa_config.py +++ b/tests/components/cloud/test_alexa_config.py @@ -1,6 +1,6 @@ """Test Alexa config.""" import contextlib -from unittest.mock import patch +from unittest.mock import patch, Mock from homeassistant.components.cloud import ALEXA_SCHEMA, alexa_config from homeassistant.util.dt import utcnow @@ -43,6 +43,42 @@ async def test_alexa_config_report_state(hass, cloud_prefs): assert conf.is_reporting_states is False +async def test_alexa_config_invalidate_token(hass, cloud_prefs, aioclient_mock): + """Test Alexa config should expose using prefs.""" + aioclient_mock.post( + "http://example/alexa_token", + json={ + "access_token": "mock-token", + "event_endpoint": "http://example.com/alexa_endpoint", + "expires_in": 30, + }, + ) + conf = alexa_config.AlexaConfig( + hass, + ALEXA_SCHEMA({}), + cloud_prefs, + Mock( + alexa_access_token_url="http://example/alexa_token", + run_executor=Mock(side_effect=mock_coro), + websession=hass.helpers.aiohttp_client.async_get_clientsession(), + ), + ) + + token = await conf.async_get_access_token() + assert token == "mock-token" + assert len(aioclient_mock.mock_calls) == 1 + + token = await conf.async_get_access_token() + assert token == "mock-token" + assert len(aioclient_mock.mock_calls) == 1 + assert conf._token_valid is not None + conf.async_invalidate_access_token() + assert conf._token_valid is None + token = await conf.async_get_access_token() + assert token == "mock-token" + assert len(aioclient_mock.mock_calls) == 2 + + @contextlib.contextmanager def patch_sync_helper(): """Patch sync helper. From 309d401e476fe0171085cb9561e24b498d02cbe9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 31 Aug 2019 07:46:26 -0700 Subject: [PATCH 04/24] Fix alexa bad temp sensors (#26307) --- .../components/alexa/capabilities.py | 23 +++++++- tests/components/alexa/__init__.py | 6 +++ tests/components/alexa/test_capabilities.py | 54 ++++++++++++++++++- 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index dfb97cd9db25f4..d769f797da1bfa 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -11,6 +11,7 @@ STATE_ON, STATE_UNAVAILABLE, STATE_UNLOCKED, + STATE_UNKNOWN, ) import homeassistant.components.climate.const as climate from homeassistant.components import light, fan, cover @@ -443,7 +444,17 @@ def get_property(self, name): if self.entity.domain == climate.DOMAIN: unit = self.hass.config.units.temperature_unit temp = self.entity.attributes.get(climate.ATTR_CURRENT_TEMPERATURE) - return {"value": float(temp), "scale": API_TEMP_UNITS[unit]} + + if temp in (STATE_UNAVAILABLE, STATE_UNKNOWN): + return None + + try: + temp = float(temp) + except ValueError: + _LOGGER.warning("Invalid temp value %s for %s", temp, self.entity.entity_id) + return None + + return {"value": temp, "scale": API_TEMP_UNITS[unit]} class AlexaContactSensor(AlexaCapibility): @@ -591,4 +602,12 @@ def get_property(self, name): if temp is None: return None - return {"value": float(temp), "scale": API_TEMP_UNITS[unit]} + try: + temp = float(temp) + except ValueError: + _LOGGER.warning( + "Invalid temp value %s for %s in %s", temp, name, self.entity.entity_id + ) + return None + + return {"value": temp, "scale": API_TEMP_UNITS[unit]} diff --git a/tests/components/alexa/__init__.py b/tests/components/alexa/__init__.py index f853c4ef848cb0..48406a11aef1f2 100644 --- a/tests/components/alexa/__init__.py +++ b/tests/components/alexa/__init__.py @@ -171,6 +171,12 @@ def __init__(self, properties): """Initialize class.""" self.properties = properties + def assert_not_has_property(self, namespace, name): + """Assert a property does not exist.""" + for prop in self.properties: + if prop["namespace"] == namespace and prop["name"] == name: + assert False, "Property %s:%s exists" + def assert_equal(self, namespace, name, value): """Assert a property is equal to a given value.""" for prop in self.properties: diff --git a/tests/components/alexa/test_capabilities.py b/tests/components/alexa/test_capabilities.py index f8ad3f57c420e0..357e0e3026d47c 100644 --- a/tests/components/alexa/test_capabilities.py +++ b/tests/components/alexa/test_capabilities.py @@ -1,7 +1,15 @@ """Test Alexa capabilities.""" import pytest -from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN +from homeassistant.const import ( + ATTR_UNIT_OF_MEASUREMENT, + TEMP_CELSIUS, + STATE_LOCKED, + STATE_UNLOCKED, + STATE_UNKNOWN, + STATE_UNAVAILABLE, +) +from homeassistant.components import climate from homeassistant.components.alexa import smart_home from tests.common import async_mock_service @@ -368,3 +376,47 @@ async def test_report_cover_percentage_state(hass): properties = await reported_properties(hass, "cover.closed") properties.assert_equal("Alexa.PercentageController", "percentage", 0) + + +async def test_temperature_sensor_sensor(hass): + """Test TemperatureSensor reports sensor temperature correctly.""" + for bad_value in (STATE_UNKNOWN, STATE_UNAVAILABLE, "not-number"): + hass.states.async_set( + "sensor.temp_living_room", + bad_value, + {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}, + ) + + properties = await reported_properties(hass, "sensor.temp_living_room") + properties.assert_not_has_property("Alexa.TemperatureSensor", "temperature") + + hass.states.async_set( + "sensor.temp_living_room", "34", {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS} + ) + properties = await reported_properties(hass, "sensor.temp_living_room") + properties.assert_equal( + "Alexa.TemperatureSensor", "temperature", {"value": 34.0, "scale": "CELSIUS"} + ) + + +async def test_temperature_sensor_climate(hass): + """Test TemperatureSensor reports climate temperature correctly.""" + for bad_value in (STATE_UNKNOWN, STATE_UNAVAILABLE, "not-number"): + hass.states.async_set( + "climate.downstairs", + climate.HVAC_MODE_HEAT, + {climate.ATTR_CURRENT_TEMPERATURE: bad_value}, + ) + + properties = await reported_properties(hass, "climate.downstairs") + properties.assert_not_has_property("Alexa.TemperatureSensor", "temperature") + + hass.states.async_set( + "climate.downstairs", + climate.HVAC_MODE_HEAT, + {climate.ATTR_CURRENT_TEMPERATURE: 34}, + ) + properties = await reported_properties(hass, "climate.downstairs") + properties.assert_equal( + "Alexa.TemperatureSensor", "temperature", {"value": 34.0, "scale": "CELSIUS"} + ) From d1e3fbd622ccc0fd94b1e5d5834d166db189d891 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 31 Aug 2019 15:56:43 +0200 Subject: [PATCH 05/24] deCONZ - Dont update entry if data is equal --- homeassistant/components/deconz/config_flow.py | 10 ++++++---- tests/components/deconz/test_config_flow.py | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index 650c02857509da..306a4fbf839243 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -157,8 +157,12 @@ async def _create_entry(self): async def _update_entry(self, entry, host): """Update existing entry.""" + if entry.data[CONF_HOST] == host: + return self.async_abort(reason="already_configured") + entry.data[CONF_HOST] = host self.hass.config_entries.async_update_entry(entry) + return self.async_abort(reason="updated_instance") async def async_step_ssdp(self, discovery_info): """Handle a discovered deCONZ bridge.""" @@ -175,8 +179,7 @@ async def async_step_ssdp(self, discovery_info): if uuid in gateways: entry = gateways[uuid].config_entry - await self._update_entry(entry, discovery_info[CONF_HOST]) - return self.async_abort(reason="updated_instance") + return await self._update_entry(entry, discovery_info[CONF_HOST]) bridgeid = discovery_info[ATTR_SERIAL] if any( @@ -224,8 +227,7 @@ async def async_step_hassio(self, user_input=None): if bridgeid in gateway_entries: entry = gateway_entries[bridgeid] - await self._update_entry(entry, user_input[CONF_HOST]) - return self.async_abort(reason="updated_instance") + return await self._update_entry(entry, user_input[CONF_HOST]) self._hassio_discovery = user_input diff --git a/tests/components/deconz/test_config_flow.py b/tests/components/deconz/test_config_flow.py index 8165c9df080dcf..ea3abead02870e 100644 --- a/tests/components/deconz/test_config_flow.py +++ b/tests/components/deconz/test_config_flow.py @@ -336,6 +336,24 @@ async def test_hassio_update_instance(hass): assert entry.data[config_flow.CONF_HOST] == "mock-deconz" +async def test_hassio_dont_update_instance(hass): + """Test we can update an existing config entry.""" + entry = MockConfigEntry( + domain=config_flow.DOMAIN, + data={config_flow.CONF_BRIDGEID: "id", config_flow.CONF_HOST: "1.2.3.4"}, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, + data={config_flow.CONF_HOST: "1.2.3.4", config_flow.CONF_SERIAL: "id"}, + context={"source": "hassio"}, + ) + + assert result["type"] == "abort" + assert result["reason"] == "already_configured" + + async def test_hassio_confirm(hass): """Test we can finish a config flow.""" result = await hass.config_entries.flow.async_init( From 85a1726e69d4aa55a9d88468969d1995d0425f7e Mon Sep 17 00:00:00 2001 From: tyjtyj Date: Sun, 1 Sep 2019 16:24:54 +0800 Subject: [PATCH 06/24] Fix google_maps scan interval (#26328) Reported on https://github.com/home-assistant/home-assistant/issues/26275 --- homeassistant/components/google_maps/device_tracker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/google_maps/device_tracker.py b/homeassistant/components/google_maps/device_tracker.py index 2149e40e5045f5..2b5550860ee5b5 100644 --- a/homeassistant/components/google_maps/device_tracker.py +++ b/homeassistant/components/google_maps/device_tracker.py @@ -52,7 +52,7 @@ def __init__(self, hass, config: ConfigType, see) -> None: self.see = see self.username = config[CONF_USERNAME] self.max_gps_accuracy = config[CONF_MAX_GPS_ACCURACY] - self.scan_interval = config.get(CONF_SCAN_INTERVAL) or timedelta(60) + self.scan_interval = config.get(CONF_SCAN_INTERVAL) or timedelta(seconds=60) credfile = "{}.{}".format( hass.config.path(CREDENTIALS_FILE), slugify(self.username) From f6cf4c38e741091b00a6b09a432839db517fb5e2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 1 Sep 2019 22:31:00 -0700 Subject: [PATCH 07/24] Bumped version to 0.98.2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 2f2546378dbb5e..6d195da991e905 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 98 -PATCH_VERSION = "1" +PATCH_VERSION = "2" __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) __version__ = "{}.{}".format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 6, 0) From b50ac6f4869081cb930adbbadcec9532d9b1e239 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 3 Sep 2019 10:17:03 +0200 Subject: [PATCH 08/24] Upgrade pyhaversion to 3.1.0 (#26232) --- homeassistant/components/version/manifest.json | 2 +- homeassistant/components/version/sensor.py | 12 ++++++++++-- requirements_all.txt | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/version/manifest.json b/homeassistant/components/version/manifest.json index 2a48f91a6f8887..815e7ff9a25794 100644 --- a/homeassistant/components/version/manifest.json +++ b/homeassistant/components/version/manifest.json @@ -3,7 +3,7 @@ "name": "Version", "documentation": "https://www.home-assistant.io/components/version", "requirements": [ - "pyhaversion==3.0.2" + "pyhaversion==3.1.0" ], "dependencies": [], "codeowners": [ diff --git a/homeassistant/components/version/sensor.py b/homeassistant/components/version/sensor.py index 438ea8f690cda4..3e00b87e9840d2 100644 --- a/homeassistant/components/version/sensor.py +++ b/homeassistant/components/version/sensor.py @@ -28,7 +28,7 @@ "odroid-c2", "odroid-xu", ] -ALL_SOURCES = ["local", "pypi", "hassio", "docker"] +ALL_SOURCES = ["local", "pypi", "hassio", "docker", "haio"] CONF_BETA = "beta" CONF_IMAGE = "image" @@ -54,7 +54,13 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Version sensor platform.""" - from pyhaversion import LocalVersion, DockerVersion, HassioVersion, PyPiVersion + from pyhaversion import ( + LocalVersion, + DockerVersion, + HassioVersion, + PyPiVersion, + HaIoVersion, + ) beta = config.get(CONF_BETA) image = config.get(CONF_IMAGE) @@ -74,6 +80,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= haversion = VersionData(HassioVersion(hass.loop, session, branch, image)) elif source == "docker": haversion = VersionData(DockerVersion(hass.loop, session, branch, image)) + elif source == "haio": + haversion = VersionData(HaIoVersion(hass.loop, session)) else: haversion = VersionData(LocalVersion(hass.loop, session)) diff --git a/requirements_all.txt b/requirements_all.txt index 2bf7e6a841cc0a..42272a0ccbb186 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1186,7 +1186,7 @@ pygtfs==0.1.5 pygtt==1.1.2 # homeassistant.components.version -pyhaversion==3.0.2 +pyhaversion==3.1.0 # homeassistant.components.heos pyheos==0.6.0 From a74bb3fd5e7b66bf6afb24cc4d831a5303fb797f Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 3 Sep 2019 07:12:10 +0200 Subject: [PATCH 09/24] String has nothing to do with class method naming (#26368) --- homeassistant/components/deconz/.translations/en.json | 2 +- homeassistant/components/deconz/strings.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/deconz/.translations/en.json b/homeassistant/components/deconz/.translations/en.json index 57da3c706a035c..3c6656d6ae696b 100644 --- a/homeassistant/components/deconz/.translations/en.json +++ b/homeassistant/components/deconz/.translations/en.json @@ -43,7 +43,7 @@ }, "options": { "step": { - "async_step_deconz_devices": { + "deconz_devices": { "data": { "allow_clip_sensor": "Allow deCONZ CLIP sensors", "allow_deconz_groups": "Allow deCONZ light groups" diff --git a/homeassistant/components/deconz/strings.json b/homeassistant/components/deconz/strings.json index ea9ea2805155c6..7081f816e6ae08 100644 --- a/homeassistant/components/deconz/strings.json +++ b/homeassistant/components/deconz/strings.json @@ -43,7 +43,7 @@ }, "options": { "step": { - "async_step_deconz_devices": { + "deconz_devices": { "description": "Configure visibility of deCONZ device types", "data": { "allow_clip_sensor": "Allow deCONZ CLIP sensors", From a980eedd226321732ff2a6889dc23a49363f217b Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Tue, 3 Sep 2019 14:14:33 +0200 Subject: [PATCH 10/24] Fix race during initial Sonos group construction (#26371) * Fix race during initial Sonos group construction * Update homeassistant/components/sonos/media_player.py --- homeassistant/components/sonos/media_player.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 86e30621334579..70461ad15d2fa6 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -337,8 +337,16 @@ def __init__(self, player): async def async_added_to_hass(self): """Subscribe sonos events.""" await self.async_seen() + self.hass.data[DATA_SONOS].entities.append(self) + def _rebuild_groups(): + """Build the current group topology.""" + for entity in self.hass.data[DATA_SONOS].entities: + entity.update_groups() + + self.hass.async_add_executor_job(_rebuild_groups) + @property def unique_id(self): """Return a unique ID.""" @@ -469,10 +477,6 @@ def _attach_player(self): self.update_volume() self._set_favorites() - # New player available, build the current group topology - for entity in self.hass.data[DATA_SONOS].entities: - entity.update_groups() - player = self.soco def subscribe(service, action): From d4905477b812213da2333103c9fdd789151ae2d8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 3 Sep 2019 23:13:34 -0700 Subject: [PATCH 11/24] Allow core config updated (#26398) --- homeassistant/components/websocket_api/permissions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/websocket_api/permissions.py b/homeassistant/components/websocket_api/permissions.py index 7aa845a298ddb2..ffbb80fa19ec7b 100644 --- a/homeassistant/components/websocket_api/permissions.py +++ b/homeassistant/components/websocket_api/permissions.py @@ -8,6 +8,7 @@ EVENT_SERVICE_REMOVED, EVENT_STATE_CHANGED, EVENT_THEMES_UPDATED, + EVENT_CORE_CONFIG_UPDATE, ) from homeassistant.components.persistent_notification import ( EVENT_PERSISTENT_NOTIFICATIONS_UPDATED, @@ -22,6 +23,7 @@ # Except for state_changed, which is handled accordingly. SUBSCRIBE_WHITELIST = { EVENT_COMPONENT_LOADED, + EVENT_CORE_CONFIG_UPDATE, EVENT_PANELS_UPDATED, EVENT_PERSISTENT_NOTIFICATIONS_UPDATED, EVENT_SERVICE_REGISTERED, From 93e4cd6bb22ab8295d32b53651bb2f21aa76b2fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Wed, 4 Sep 2019 09:13:17 +0300 Subject: [PATCH 12/24] Met, check for existing location (#26400) --- .../components/met/.translations/en.json | 2 +- homeassistant/components/met/config_flow.py | 18 +++++++++++++----- homeassistant/components/met/strings.json | 2 +- tests/components/met/test_config_flow.py | 4 +++- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/met/.translations/en.json b/homeassistant/components/met/.translations/en.json index 21ae7cb78fa4f9..93d028b06261ba 100644 --- a/homeassistant/components/met/.translations/en.json +++ b/homeassistant/components/met/.translations/en.json @@ -1,7 +1,7 @@ { "config": { "error": { - "name_exists": "Name already exists" + "name_exists": "Location already exists" }, "step": { "user": { diff --git a/homeassistant/components/met/config_flow.py b/homeassistant/components/met/config_flow.py index 795ba57d9887b2..c7ff4973c7d2aa 100644 --- a/homeassistant/components/met/config_flow.py +++ b/homeassistant/components/met/config_flow.py @@ -12,9 +12,15 @@ @callback def configured_instances(hass): """Return a set of configured SimpliSafe instances.""" - return set( - entry.data[CONF_NAME] for entry in hass.config_entries.async_entries(DOMAIN) - ) + entites = [] + for entry in hass.config_entries.async_entries(DOMAIN): + if entry.data.get("track_home"): + entites.append("home") + continue + entites.append( + f"{entry.data.get(CONF_LATITUDE)}-{entry.data.get(CONF_LONGITUDE)}" + ) + return set(entites) class MetFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @@ -32,11 +38,13 @@ async def async_step_user(self, user_input=None): self._errors = {} if user_input is not None: - if user_input[CONF_NAME] not in configured_instances(self.hass): + if ( + f"{user_input.get(CONF_LATITUDE)}-{user_input.get(CONF_LONGITUDE)}" + not in configured_instances(self.hass) + ): return self.async_create_entry( title=user_input[CONF_NAME], data=user_input ) - self._errors[CONF_NAME] = "name_exists" return await self._show_config_form( diff --git a/homeassistant/components/met/strings.json b/homeassistant/components/met/strings.json index f5c49bac3c4420..0c52e624418a29 100644 --- a/homeassistant/components/met/strings.json +++ b/homeassistant/components/met/strings.json @@ -14,7 +14,7 @@ } }, "error": { - "name_exists": "Name already exists" + "name_exists": "Location already exists" } } } diff --git a/tests/components/met/test_config_flow.py b/tests/components/met/test_config_flow.py index 22061386b93c56..32f3be676e0514 100644 --- a/tests/components/met/test_config_flow.py +++ b/tests/components/met/test_config_flow.py @@ -102,7 +102,7 @@ async def test_flow_entry_created_from_user_input(): async def test_flow_entry_config_entry_already_exists(): """Test that create data from user input and config_entry already exists. - Test when the form should show when user puts existing name + Test when the form should show when user puts existing location in the config gui. Then the form should show with error """ hass = Mock() @@ -112,6 +112,8 @@ async def test_flow_entry_config_entry_already_exists(): first_entry = MockConfigEntry(domain="met") first_entry.data["name"] = "home" + first_entry.data[CONF_LONGITUDE] = "0" + first_entry.data[CONF_LATITUDE] = "0" first_entry.add_to_hass(hass) test_data = { From 8cf02e0b223572604e07d6e79e05ddac90aee7ab Mon Sep 17 00:00:00 2001 From: ehendrix23 Date: Tue, 3 Sep 2019 20:00:05 -0600 Subject: [PATCH 13/24] Update to 0.1.13 (#26402) Update to 0.1.13 --- homeassistant/components/harmony/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/harmony/manifest.json b/homeassistant/components/harmony/manifest.json index b2f9e69e014627..a957db0675fb94 100644 --- a/homeassistant/components/harmony/manifest.json +++ b/homeassistant/components/harmony/manifest.json @@ -3,7 +3,7 @@ "name": "Harmony", "documentation": "https://www.home-assistant.io/components/harmony", "requirements": [ - "aioharmony==0.1.11" + "aioharmony==0.1.13" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index 42272a0ccbb186..bb6035b01e1e8d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -142,7 +142,7 @@ aiofreepybox==0.0.8 aioftp==0.12.0 # homeassistant.components.harmony -aioharmony==0.1.11 +aioharmony==0.1.13 # homeassistant.components.emulated_hue # homeassistant.components.http From 7bccbcbcc3737cf8076a91d6f68017bb4bfef150 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 3 Sep 2019 18:57:32 -0700 Subject: [PATCH 14/24] Fix state report (#26406) * Fix state report * Update test --- homeassistant/components/alexa/state_report.py | 7 +++++-- tests/components/alexa/test_state_report.py | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/alexa/state_report.py b/homeassistant/components/alexa/state_report.py index 1e22d5fc09f23b..fbf928fd23e9dd 100644 --- a/homeassistant/components/alexa/state_report.py +++ b/homeassistant/components/alexa/state_report.py @@ -97,12 +97,15 @@ async def async_send_changereport_message( _LOGGER.debug("Sent: %s", json.dumps(message_serialized)) _LOGGER.debug("Received (%s): %s", response.status, response_text) - if response.status == 202 and not invalidate_access_token: + if response.status == 202: return response_json = json.loads(response_text) - if response_json["payload"]["code"] == "INVALID_ACCESS_TOKEN_EXCEPTION": + if ( + response_json["payload"]["code"] == "INVALID_ACCESS_TOKEN_EXCEPTION" + and not invalidate_access_token + ): config.async_invalidate_access_token() return await async_send_changereport_message( hass, config, alexa_entity, invalidate_access_token=False diff --git a/tests/components/alexa/test_state_report.py b/tests/components/alexa/test_state_report.py index f6bb4c9cc29f45..2b3f9f34adf57d 100644 --- a/tests/components/alexa/test_state_report.py +++ b/tests/components/alexa/test_state_report.py @@ -5,7 +5,7 @@ async def test_report_state(hass, aioclient_mock): """Test proactive state reports.""" - aioclient_mock.post(TEST_URL, json={"data": "is irrelevant"}, status=202) + aioclient_mock.post(TEST_URL, text="", status=202) hass.states.async_set( "binary_sensor.test_contact", @@ -39,7 +39,7 @@ async def test_report_state(hass, aioclient_mock): async def test_send_add_or_update_message(hass, aioclient_mock): """Test sending an AddOrUpdateReport message.""" - aioclient_mock.post(TEST_URL, json={"data": "is irrelevant"}) + aioclient_mock.post(TEST_URL, text="") hass.states.async_set( "binary_sensor.test_contact", From 860843ada1690dcf7c968834109012f8a23899fa Mon Sep 17 00:00:00 2001 From: Greg Laabs Date: Tue, 3 Sep 2019 23:11:30 -0700 Subject: [PATCH 15/24] Bump ISY994's PyISY dependency to 1.1.2 (#26413) Fixed a major bug that was responsible for ISY events getting seemingly random delays up to 24 seconds --- homeassistant/components/isy994/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/isy994/manifest.json b/homeassistant/components/isy994/manifest.json index 7860c080b2fe05..0dd0f1eae80a0b 100644 --- a/homeassistant/components/isy994/manifest.json +++ b/homeassistant/components/isy994/manifest.json @@ -3,7 +3,7 @@ "name": "Isy994", "documentation": "https://www.home-assistant.io/components/isy994", "requirements": [ - "PyISY==1.1.1" + "PyISY==1.1.2" ], "dependencies": [], "codeowners": [] diff --git a/requirements_all.txt b/requirements_all.txt index bb6035b01e1e8d..3660514e355b66 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -50,7 +50,7 @@ PyEssent==0.13 PyGithub==1.43.5 # homeassistant.components.isy994 -PyISY==1.1.1 +PyISY==1.1.2 # homeassistant.components.mvglive PyMVGLive==1.1.4 From b8f9319cb0cbeee2c4058e5339f1a6a32da2b45c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 3 Sep 2019 23:20:25 -0700 Subject: [PATCH 16/24] Bumped version to 0.98.3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 6d195da991e905..2a20917b3be4d2 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 98 -PATCH_VERSION = "2" +PATCH_VERSION = "3" __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) __version__ = "{}.{}".format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 6, 0) From c922448f4cc31f043e5696bdac77221401ca01ad Mon Sep 17 00:00:00 2001 From: Rami Mosleh Date: Wed, 4 Sep 2019 12:39:57 +0300 Subject: [PATCH 17/24] Add config flow to transmission --- .../transmission/.translations/en.json | 57 +++++ .../components/transmission/__init__.py | 195 +++++++++++------- .../components/transmission/config_flow.py | 164 +++++++++++++++ .../components/transmission/const.py | 21 ++ .../components/transmission/manifest.json | 3 +- .../components/transmission/sensor.py | 33 +-- .../components/transmission/services.yaml | 2 +- .../components/transmission/strings.json | 57 +++++ .../components/transmission/switch.py | 16 +- homeassistant/generated/config_flows.py | 3 +- 10 files changed, 456 insertions(+), 95 deletions(-) create mode 100644 homeassistant/components/transmission/.translations/en.json create mode 100644 homeassistant/components/transmission/config_flow.py create mode 100644 homeassistant/components/transmission/const.py create mode 100644 homeassistant/components/transmission/strings.json diff --git a/homeassistant/components/transmission/.translations/en.json b/homeassistant/components/transmission/.translations/en.json new file mode 100644 index 00000000000000..d1b3b7e65fdf8a --- /dev/null +++ b/homeassistant/components/transmission/.translations/en.json @@ -0,0 +1,57 @@ +{ + "config": { + "title": "Transmission", + "step": { + "user": { + "title": "Set up Transmission Client", + "data": { + "name": "Name", + "host": "Host", + "username": "User name", + "password": "Password", + "port": "Port" + } + }, + "options": { + "title": "Configure Options", + "data": { + "active_torrents": "Active Torrents", + "current_status": "Current Status", + "download_speed": "Download Speed [MB/s]", + "paused_torrents": "Pause Torrents", + "total_torrents": "Total Torrents", + "upload_speed": "Upload Speed [MB/s]", + "completed_torrents": "Completed torrents (seeding)", + "started_torrents": "Started torrents (downloading)", + "turtle_mode": "‘Turtle mode’ switch", + "scan_interval": "Update frequency" + } + } + }, + "error": { + "cannot_connect": "Unable to Connect to Client" + }, + "abort": { + "one_instance_allowed": "Only a single instance is necessary." + } + }, + "options": { + "step": { + "init": { + "description": "Configure options for Transmission", + "data": { + "active_torrents": "Active Torrents", + "current_status": "Current Status", + "download_speed": "Download Speed [MB/s]", + "paused_torrents": "Pause Torrents", + "total_torrents": "Total Torrents", + "upload_speed": "Upload Speed [MB/s]", + "completed_torrents": "Completed torrents (seeding)", + "started_torrents": "Started torrents (downloading)", + "turtle_mode": "‘Turtle mode’ switch", + "scan_interval": "Update frequency" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index e7f9b94046d367..efa061db0cf7d8 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -3,6 +3,8 @@ import logging import voluptuous as vol +import transmissionrpc +from transmissionrpc.error import TransmissionError from homeassistant.const import ( CONF_HOST, @@ -13,36 +15,28 @@ CONF_SCAN_INTERVAL, CONF_USERNAME, ) -from homeassistant.helpers import config_validation as cv, discovery + +from .const import ( + DOMAIN, + CONF_TURTLE_MODE, + CONF_SENSOR_TYPES, + ATTR_TORRENT, + SERVICE_ADD_TORRENT, +) +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import dispatcher_send -from homeassistant.helpers.event import track_time_interval +from homeassistant.config_entries import SOURCE_IMPORT + +from homeassistant.helpers.event import async_track_time_interval _LOGGER = logging.getLogger(__name__) -DOMAIN = "transmission" DATA_UPDATED = "transmission_data_updated" DATA_TRANSMISSION = "data_transmission" DEFAULT_NAME = "Transmission" DEFAULT_PORT = 9091 -TURTLE_MODE = "turtle_mode" - -SENSOR_TYPES = { - "active_torrents": ["Active Torrents", None], - "current_status": ["Status", None], - "download_speed": ["Down Speed", "MB/s"], - "paused_torrents": ["Paused Torrents", None], - "total_torrents": ["Total Torrents", None], - "upload_speed": ["Up Speed", "MB/s"], - "completed_torrents": ["Completed Torrents", None], - "started_torrents": ["Started Torrents", None], -} - -DEFAULT_SCAN_INTERVAL = timedelta(seconds=120) - -ATTR_TORRENT = "torrent" - -SERVICE_ADD_TORRENT = "add_torrent" +DEFAULT_SCAN_INTERVAL = 120 SERVICE_ADD_TORRENT_SCHEMA = vol.Schema({vol.Required(ATTR_TORRENT): cv.string}) @@ -55,13 +49,13 @@ vol.Optional(CONF_USERNAME): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(TURTLE_MODE, default=False): cv.boolean, + vol.Optional(CONF_TURTLE_MODE, default=False): cv.boolean, vol.Optional( CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL ): cv.time_period, vol.Optional( CONF_MONITORED_CONDITIONS, default=["current_status"] - ): vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), + ): vol.All(cv.ensure_list, [vol.In(CONF_SENSOR_TYPES)]), } ) }, @@ -69,63 +63,121 @@ ) -def setup(hass, config): +async def async_setup(hass, config): + """Set up the Transmission Component from config.""" + if not hass.config_entries.async_entries(DOMAIN) and DOMAIN in config: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN] + ) + ) + + return True + + +async def async_setup_entry(hass, config_entry): """Set up the Transmission Component.""" - host = config[DOMAIN][CONF_HOST] - username = config[DOMAIN].get(CONF_USERNAME) - password = config[DOMAIN].get(CONF_PASSWORD) - port = config[DOMAIN][CONF_PORT] - scan_interval = config[DOMAIN][CONF_SCAN_INTERVAL] - - import transmissionrpc - from transmissionrpc.error import TransmissionError - - try: - api = transmissionrpc.Client(host, port=port, user=username, password=password) - api.session_stats() - except TransmissionError as error: - if str(error).find("401: Unauthorized"): - _LOGGER.error("Credentials for" " Transmission client are not valid") + if not config_entry.options: + await async_populate_options(hass, config_entry) + + client = TransmissionClient(hass, config_entry) + if not await client.async_setup(): return False - tm_data = hass.data[DATA_TRANSMISSION] = TransmissionData(hass, config, api) + return True - tm_data.update() - tm_data.init_torrent_list() - def refresh(event_time): - """Get the latest data from Transmission.""" - tm_data.update() +async def async_populate_options(hass, config_entry): + """Populate default options for Transmission Client.""" + options = {} + options[CONF_MONITORED_CONDITIONS] = {} + for sensor in CONF_SENSOR_TYPES: + options[CONF_MONITORED_CONDITIONS][sensor] = config_entry.data["options"].get( + sensor, False + ) + options[CONF_TURTLE_MODE] = config_entry.data["options"].get( + CONF_TURTLE_MODE, False + ) + options[CONF_SCAN_INTERVAL] = config_entry.data["options"].get( + CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL + ) - track_time_interval(hass, refresh, scan_interval) - - def add_torrent(service): - """Add new torrent to download.""" - torrent = service.data[ATTR_TORRENT] - if torrent.startswith( - ("http", "ftp:", "magnet:") - ) or hass.config.is_allowed_path(torrent): - api.add_torrent(torrent) - else: - _LOGGER.warning( - "Could not add torrent: " "unsupported type or no permission" - ) + hass.config_entries.async_update_entry(config_entry, options=options) - hass.services.register( - DOMAIN, SERVICE_ADD_TORRENT, add_torrent, schema=SERVICE_ADD_TORRENT_SCHEMA - ) - sensorconfig = { - "sensors": config[DOMAIN][CONF_MONITORED_CONDITIONS], - "client_name": config[DOMAIN][CONF_NAME], - } +async def async_unload_entry(hass, entry): + """Unload Transmission Entry from config_entry.""" + hass.services.async_remove(DOMAIN, SERVICE_ADD_TORRENT) + return True - discovery.load_platform(hass, "sensor", DOMAIN, sensorconfig, config) - if config[DOMAIN][TURTLE_MODE]: - discovery.load_platform(hass, "switch", DOMAIN, sensorconfig, config) +class TransmissionClient: + """Transmission Client Object.""" - return True + def __init__(self, hass, config_entry): + """Initialize the Transmission RPC API.""" + self.hass = hass + self.config_entry = config_entry + self.host = self.config_entry.data[CONF_HOST] + self.username = self.config_entry.data.get(CONF_USERNAME) + self.password = self.config_entry.data.get(CONF_PASSWORD) + self.port = self.config_entry.data[CONF_PORT] + self.scan_interval = self.config_entry.options.get( + CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL + ) + + async def async_setup(self): + """Set up the Transmission client.""" + hass = self.hass + try: + api = transmissionrpc.Client( + self.host, port=self.port, user=self.username, password=self.password + ) + api.session_stats() + except TransmissionError as error: + if str(error).find("401: Unauthorized"): + _LOGGER.error("Credentials for" " Transmission client are not valid") + return False + tm_data = self.hass.data[DATA_TRANSMISSION] = TransmissionData( + self.hass, self.config_entry, api + ) + + tm_data.update() + await tm_data.async_init_torrent_list() + + def refresh(event_time): + """Get the latest data from Transmission.""" + tm_data.update() + + async_track_time_interval( + self.hass, refresh, timedelta(seconds=self.scan_interval) + ) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(self.config_entry, "sensor") + ) + if self.config_entry.options.get(CONF_TURTLE_MODE): + hass.async_create_task( + hass.config_entries.async_forward_entry_setup( + self.config_entry, "switch" + ) + ) + + def add_torrent(self, service): + """Add new torrent to download.""" + torrent = service.data[ATTR_TORRENT] + if torrent.startswith( + ("http", "ftp:", "magnet:") + ) or self.hass.config.is_allowed_path(torrent): + self.api.add_torrent(torrent) + else: + _LOGGER.warning( + "Could not add torrent: " "unsupported type or no permission" + ) + + hass.services.async_register( + DOMAIN, SERVICE_ADD_TORRENT, add_torrent, schema=SERVICE_ADD_TORRENT_SCHEMA + ) + return True class TransmissionData: @@ -141,6 +193,9 @@ def __init__(self, hass, config, api): self.completed_torrents = [] self.started_torrents = [] self.hass = hass + self.scan_interval = timedelta( + seconds=config.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) + ) def update(self): """Get the latest data from Transmission instance.""" @@ -162,7 +217,7 @@ def update(self): self.available = False _LOGGER.error("Unable to connect to Transmission client") - def init_torrent_list(self): + async def async_init_torrent_list(self): """Initialize torrent lists.""" self.torrents = self._api.get_torrents() self.completed_torrents = [ diff --git a/homeassistant/components/transmission/config_flow.py b/homeassistant/components/transmission/config_flow.py new file mode 100644 index 00000000000000..588d49ca657059 --- /dev/null +++ b/homeassistant/components/transmission/config_flow.py @@ -0,0 +1,164 @@ +"""Config flow for Transmission Bittorent Client.""" +import voluptuous as vol + +import transmissionrpc +from transmissionrpc.error import TransmissionError + +from homeassistant import config_entries +from homeassistant.core import callback +from homeassistant.const import ( + CONF_HOST, + CONF_MONITORED_CONDITIONS, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_SCAN_INTERVAL, + CONF_USERNAME, +) + +from .const import DOMAIN, CONF_TURTLE_MODE, CONF_SENSOR_TYPES + +DEFAULT_NAME = "Transmission" +DEFAULT_PORT = 9091 +DEFAULT_SCAN_INTERVAL = 120 +DEFAULT_SENSOR = "current_status" + + +class TransmissionFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a UniFi config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return TransmissionOptionsFlowHandler(config_entry) + + def __init__(self): + """Initialize the Transmission flow.""" + self.config = None + self.errors = {} + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + if self.hass.config_entries.async_entries(DOMAIN): + return self.async_abort(reason="one_instance_allowed") + + if user_input is not None: + valid = await self.is_valid(user_input) + if valid: + self.config = user_input + return await self.async_step_options() + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_NAME, default=DEFAULT_NAME): str, + vol.Required(CONF_HOST): str, + vol.Optional(CONF_USERNAME): str, + vol.Optional(CONF_PASSWORD): str, + vol.Required(CONF_PORT, default=DEFAULT_PORT): int, + } + ), + errors=self.errors, + ) + + async def is_valid(self, user_input): + """Validate connection to the Transmission Client.""" + try: + transmissionrpc.Client( + user_input[CONF_HOST], + port=user_input[CONF_PORT], + user=user_input.get(CONF_USERNAME), + password=user_input.get(CONF_PASSWORD), + ) + return True + + except TransmissionError as error: + if str(error).find("401: Unauthorized"): + self.errors["base"] = "cannot_connect" + + return False + + async def async_step_options(self, user_input=None): + """Set options for the Transmission Client.""" + if user_input is not None: + self.config["options"] = user_input + return self.async_create_entry( + title=self.config[CONF_NAME], data=self.config + ) + + options = { + vol.Optional(CONF_TURTLE_MODE, default=False): bool, + vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): int, + } + for sensor in CONF_SENSOR_TYPES: + options.update( + {vol.Optional(sensor, default=CONF_SENSOR_TYPES[sensor][2]): bool} + ) + + return self.async_show_form(step_id="options", data_schema=vol.Schema(options)) + + async def async_step_import(self, import_config): + """Import from Transmission client config.""" + config = { + CONF_NAME: import_config.get(CONF_NAME, DEFAULT_NAME), + CONF_HOST: import_config[CONF_HOST], + CONF_USERNAME: import_config.get(CONF_USERNAME), + CONF_PASSWORD: import_config.get(CONF_PASSWORD), + CONF_PORT: import_config.get(CONF_PORT, DEFAULT_PORT), + } + + return await self.async_step_user(user_input=config) + + +class TransmissionOptionsFlowHandler(config_entries.OptionsFlow): + """Handle Transmission client options.""" + + def __init__(self, config_entry): + """Initialize Transmission options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Manage the Transmission options.""" + if user_input is not None: + options = {} + options[CONF_MONITORED_CONDITIONS] = {} + for sensor in CONF_SENSOR_TYPES: + options[CONF_MONITORED_CONDITIONS][sensor] = user_input[sensor] + options[CONF_TURTLE_MODE] = user_input[CONF_TURTLE_MODE] + options[CONF_SCAN_INTERVAL] = user_input[CONF_SCAN_INTERVAL] + + return self.async_create_entry(title="", data=options) + + options = { + vol.Optional( + CONF_TURTLE_MODE, + default=self.config_entry.options.get( + CONF_TURTLE_MODE, + self.config_entry.data["options"][CONF_TURTLE_MODE], + ), + ): bool, + vol.Optional( + CONF_SCAN_INTERVAL, + default=self.config_entry.options.get( + CONF_SCAN_INTERVAL, + self.config_entry.data["options"][CONF_SCAN_INTERVAL], + ), + ): int, + } + for sensor in CONF_SENSOR_TYPES: + options.update( + { + vol.Optional( + sensor, + default=self.config_entry.options[ + CONF_MONITORED_CONDITIONS + ].get(sensor, self.config_entry.data["options"][sensor]), + ): bool + } + ) + return self.async_show_form(step_id="init", data_schema=vol.Schema(options)) diff --git a/homeassistant/components/transmission/const.py b/homeassistant/components/transmission/const.py new file mode 100644 index 00000000000000..0199ffc5fb50f7 --- /dev/null +++ b/homeassistant/components/transmission/const.py @@ -0,0 +1,21 @@ +"""Constants for the Transmission Bittorent Client component.""" +DOMAIN = "transmission" +DATA_UPDATED = "transmission_data_updated" +DATA_TRANSMISSION = "data_transmission" + + +CONF_TURTLE_MODE = "turtle_mode" +CONF_SENSOR_TYPES = { + "active_torrents": ["Active Torrents", None, False], + "current_status": ["Status", None, True], + "download_speed": ["Down Speed", "MB/s", False], + "paused_torrents": ["Paused Torrents", None, False], + "total_torrents": ["Total Torrents", None, False], + "upload_speed": ["Up Speed", "MB/s", False], + "completed_torrents": ["Completed Torrents", None, False], + "started_torrents": ["Started Torrents", None, False], +} + +ATTR_TORRENT = "torrent" + +SERVICE_ADD_TORRENT = "add_torrent" diff --git a/homeassistant/components/transmission/manifest.json b/homeassistant/components/transmission/manifest.json index bc5da64fcacd9b..69b46594f20615 100644 --- a/homeassistant/components/transmission/manifest.json +++ b/homeassistant/components/transmission/manifest.json @@ -1,10 +1,11 @@ { "domain": "transmission", "name": "Transmission", + "config_flow": true, "documentation": "https://www.home-assistant.io/components/transmission", "requirements": [ "transmissionrpc==0.11" ], "dependencies": [], "codeowners": [] -} +} \ No newline at end of file diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index ac2e64ce92f390..abd402d856ba39 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -2,12 +2,12 @@ from datetime import timedelta import logging -from homeassistant.const import STATE_IDLE +from homeassistant.const import CONF_NAME, STATE_IDLE, CONF_MONITORED_CONDITIONS from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from . import DATA_TRANSMISSION, DATA_UPDATED, SENSOR_TYPES +from .const import DATA_TRANSMISSION, DATA_UPDATED, CONF_SENSOR_TYPES _LOGGER = logging.getLogger(__name__) @@ -17,25 +17,28 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Import config from configuration.yaml.""" + pass + + +async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Transmission sensors.""" - if discovery_info is None: - return transmission_api = hass.data[DATA_TRANSMISSION] - monitored_variables = discovery_info["sensors"] - name = discovery_info["client_name"] + name = config_entry.data[CONF_NAME] dev = [] - for sensor_type in monitored_variables: - dev.append( - TransmissionSensor( - sensor_type, - transmission_api, - name, - SENSOR_TYPES[sensor_type][0], - SENSOR_TYPES[sensor_type][1], + for sensor_type in CONF_SENSOR_TYPES: + if config_entry.options[CONF_MONITORED_CONDITIONS].get(sensor_type): + dev.append( + TransmissionSensor( + sensor_type, + transmission_api, + name, + CONF_SENSOR_TYPES[sensor_type][0], + CONF_SENSOR_TYPES[sensor_type][1], + ) ) - ) async_add_entities(dev, True) diff --git a/homeassistant/components/transmission/services.yaml b/homeassistant/components/transmission/services.yaml index e049f89b3c6a6d..ab383584e83fdc 100644 --- a/homeassistant/components/transmission/services.yaml +++ b/homeassistant/components/transmission/services.yaml @@ -3,4 +3,4 @@ add_torrent: fields: torrent: description: URL, magnet link or Base64 encoded file. - example: http://releases.ubuntu.com/19.04/ubuntu-19.04-desktop-amd64.iso.torrent} + example: http://releases.ubuntu.com/19.04/ubuntu-19.04-desktop-amd64.iso.torrent diff --git a/homeassistant/components/transmission/strings.json b/homeassistant/components/transmission/strings.json new file mode 100644 index 00000000000000..50926781759d7b --- /dev/null +++ b/homeassistant/components/transmission/strings.json @@ -0,0 +1,57 @@ +{ + "config": { + "title": "Transmission", + "step": { + "user": { + "title": "Setup Transmission Client", + "data": { + "name": "Name", + "host": "Host", + "username": "User name", + "password": "Password", + "port": "Port" + } + }, + "options": { + "title": "Configure Options", + "data": { + "active_torrents": "Active Torrents", + "current_status": "Current Status", + "download_speed": "Download Speed [MB/s]", + "paused_torrents": "Pause Torrents", + "total_torrents": "Total Torrents", + "upload_speed": "Upload Speed [MB/s]", + "completed_torrents": "Completed torrents (seeding)", + "started_torrents": "Started torrents (downloading)", + "turtle_mode": "‘Turtle mode’ switch", + "scan_interval": "Update frequency" + } + } + }, + "error": { + "cannot_connect": "Unable to Connect to Client" + }, + "abort": { + "one_instance_allowed": "Only a single instance is necessary." + } + }, + "options": { + "step": { + "init": { + "description": "Configure options for Transmission", + "data": { + "active_torrents": "Active Torrents", + "current_status": "Current Status", + "download_speed": "Download Speed [MB/s]", + "paused_torrents": "Pause Torrents", + "total_torrents": "Total Torrents", + "upload_speed": "Upload Speed [MB/s]", + "completed_torrents": "Completed torrents (seeding)", + "started_torrents": "Started torrents (downloading)", + "turtle_mode": "‘Turtle mode’ switch", + "scan_interval": "Update frequency" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/transmission/switch.py b/homeassistant/components/transmission/switch.py index df490cdbe47cbe..6163521a2eb050 100644 --- a/homeassistant/components/transmission/switch.py +++ b/homeassistant/components/transmission/switch.py @@ -1,7 +1,7 @@ """Support for setting the Transmission BitTorrent client Turtle Mode.""" import logging -from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.const import CONF_NAME, STATE_OFF, STATE_ON from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import ToggleEntity @@ -14,13 +14,15 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Import config from configuration.yaml.""" + pass + + +async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Transmission switch.""" - if discovery_info is None: - return - component_name = DATA_TRANSMISSION - transmission_api = hass.data[component_name] - name = discovery_info["client_name"] + transmission_api = hass.data[DATA_TRANSMISSION] + name = config_entry.data[CONF_NAME] async_add_entities([TransmissionSwitch(transmission_api, name)], True) @@ -74,7 +76,7 @@ async def async_added_to_hass(self): def _schedule_immediate_update(self): self.async_schedule_update_ha_state(True) - def update(self): + async def async_update(self): """Get the latest data from Transmission and updates the state.""" active = self.transmission_client.get_alt_speed_enabled() diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 32690153221b65..70d4f5dd8f0b30 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -56,6 +56,7 @@ "tplink", "traccar", "tradfri", + "transmission", "twentemilieu", "twilio", "unifi", @@ -67,5 +68,5 @@ "wwlln", "zha", "zone", - "zwave" + "zwave", ] From 1d201efcd894bef46b62a54d23eb9c29fcf550f6 Mon Sep 17 00:00:00 2001 From: Rami Mosleh Date: Wed, 4 Sep 2019 16:31:40 +0300 Subject: [PATCH 18/24] sort imports and constants --- .../components/transmission/__init__.py | 21 +++++++++---------- .../components/transmission/config_flow.py | 19 +++++++++-------- .../components/transmission/const.py | 10 ++++++--- .../components/transmission/manifest.json | 2 +- .../components/transmission/sensor.py | 9 ++------ .../components/transmission/strings.json | 2 +- .../components/transmission/switch.py | 2 -- 7 files changed, 31 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index efa061db0cf7d8..df0126870e03c9 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -2,10 +2,11 @@ from datetime import timedelta import logging -import voluptuous as vol import transmissionrpc from transmissionrpc.error import TransmissionError +import voluptuous as vol +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( CONF_HOST, CONF_MONITORED_CONDITIONS, @@ -15,24 +16,22 @@ CONF_SCAN_INTERVAL, CONF_USERNAME, ) +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.dispatcher import dispatcher_send +from homeassistant.helpers.event import async_track_time_interval from .const import ( - DOMAIN, - CONF_TURTLE_MODE, - CONF_SENSOR_TYPES, ATTR_TORRENT, + CONF_SENSOR_TYPES, + CONF_TURTLE_MODE, + DATA_TRANSMISSION, + DATA_UPDATED, + DOMAIN, SERVICE_ADD_TORRENT, ) -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.dispatcher import dispatcher_send -from homeassistant.config_entries import SOURCE_IMPORT - -from homeassistant.helpers.event import async_track_time_interval _LOGGER = logging.getLogger(__name__) -DATA_UPDATED = "transmission_data_updated" -DATA_TRANSMISSION = "data_transmission" DEFAULT_NAME = "Transmission" DEFAULT_PORT = 9091 diff --git a/homeassistant/components/transmission/config_flow.py b/homeassistant/components/transmission/config_flow.py index 588d49ca657059..384af7715bd503 100644 --- a/homeassistant/components/transmission/config_flow.py +++ b/homeassistant/components/transmission/config_flow.py @@ -1,11 +1,9 @@ """Config flow for Transmission Bittorent Client.""" -import voluptuous as vol - import transmissionrpc from transmissionrpc.error import TransmissionError +import voluptuous as vol from homeassistant import config_entries -from homeassistant.core import callback from homeassistant.const import ( CONF_HOST, CONF_MONITORED_CONDITIONS, @@ -15,13 +13,16 @@ CONF_SCAN_INTERVAL, CONF_USERNAME, ) +from homeassistant.core import callback -from .const import DOMAIN, CONF_TURTLE_MODE, CONF_SENSOR_TYPES - -DEFAULT_NAME = "Transmission" -DEFAULT_PORT = 9091 -DEFAULT_SCAN_INTERVAL = 120 -DEFAULT_SENSOR = "current_status" +from .const import ( + CONF_SENSOR_TYPES, + CONF_TURTLE_MODE, + DEFAULT_NAME, + DEFAULT_PORT, + DEFAULT_SCAN_INTERVAL, + DOMAIN, +) class TransmissionFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/transmission/const.py b/homeassistant/components/transmission/const.py index 0199ffc5fb50f7..4fe5910405098c 100644 --- a/homeassistant/components/transmission/const.py +++ b/homeassistant/components/transmission/const.py @@ -1,8 +1,5 @@ """Constants for the Transmission Bittorent Client component.""" DOMAIN = "transmission" -DATA_UPDATED = "transmission_data_updated" -DATA_TRANSMISSION = "data_transmission" - CONF_TURTLE_MODE = "turtle_mode" CONF_SENSOR_TYPES = { @@ -16,6 +13,13 @@ "started_torrents": ["Started Torrents", None, False], } +DEFAULT_NAME = "Transmission" +DEFAULT_PORT = 9091 +DEFAULT_SCAN_INTERVAL = 120 + ATTR_TORRENT = "torrent" SERVICE_ADD_TORRENT = "add_torrent" + +DATA_UPDATED = "transmission_data_updated" +DATA_TRANSMISSION = "data_transmission" diff --git a/homeassistant/components/transmission/manifest.json b/homeassistant/components/transmission/manifest.json index 69b46594f20615..9451ce90773399 100644 --- a/homeassistant/components/transmission/manifest.json +++ b/homeassistant/components/transmission/manifest.json @@ -8,4 +8,4 @@ ], "dependencies": [], "codeowners": [] -} \ No newline at end of file +} diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index abd402d856ba39..a6617ed60f6cbb 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -1,20 +1,15 @@ """Support for monitoring the Transmission BitTorrent client API.""" -from datetime import timedelta import logging -from homeassistant.const import CONF_NAME, STATE_IDLE, CONF_MONITORED_CONDITIONS +from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_NAME, STATE_IDLE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from .const import DATA_TRANSMISSION, DATA_UPDATED, CONF_SENSOR_TYPES +from .const import CONF_SENSOR_TYPES, DATA_TRANSMISSION, DATA_UPDATED _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = "Transmission" - -SCAN_INTERVAL = timedelta(seconds=120) - async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Import config from configuration.yaml.""" diff --git a/homeassistant/components/transmission/strings.json b/homeassistant/components/transmission/strings.json index 50926781759d7b..f184cf477d2f73 100644 --- a/homeassistant/components/transmission/strings.json +++ b/homeassistant/components/transmission/strings.json @@ -54,4 +54,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/transmission/switch.py b/homeassistant/components/transmission/switch.py index 6163521a2eb050..9a17adc0ff988b 100644 --- a/homeassistant/components/transmission/switch.py +++ b/homeassistant/components/transmission/switch.py @@ -10,8 +10,6 @@ _LOGGING = logging.getLogger(__name__) -DEFAULT_NAME = "Transmission Turtle Mode" - async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Import config from configuration.yaml.""" From 2c65e024910a413f63363af93934da28c83a731b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 4 Sep 2019 15:51:15 +0200 Subject: [PATCH 19/24] Updated frontend to 20190904.0 (#26421) * Updated frontend to 20190904.0 * Updated frontend to 20190904.0 --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 2b17091ba5cb94..3659b40b7b01b7 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/components/frontend", "requirements": [ - "home-assistant-frontend==20190901.0" + "home-assistant-frontend==20190904.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 2fa5a1cd41ae4b..6ec2ed358d5797 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -11,7 +11,7 @@ contextvars==2.4;python_version<"3.7" cryptography==2.7 distro==1.4.0 hass-nabucasa==0.17 -home-assistant-frontend==20190901.0 +home-assistant-frontend==20190904.0 importlib-metadata==0.19 jinja2>=2.10.1 netdisco==2.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index 852788e1be305f..41d474808bef3b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -631,7 +631,7 @@ hole==0.5.0 holidays==0.9.11 # homeassistant.components.frontend -home-assistant-frontend==20190901.0 +home-assistant-frontend==20190904.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a23bc7ce610ee8..5e0e0d2a3ea9d4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -179,7 +179,7 @@ hole==0.5.0 holidays==0.9.11 # homeassistant.components.frontend -home-assistant-frontend==20190901.0 +home-assistant-frontend==20190904.0 # homeassistant.components.homekit_controller homekit[IP]==0.15.0 From c4c21d3e99fa1a84ca3d9a5be1e02a06bcd2e2c7 Mon Sep 17 00:00:00 2001 From: zewelor Date: Wed, 4 Sep 2019 16:15:40 +0200 Subject: [PATCH 20/24] Add device to mqtt camera (#26238) * Add device to mqtt camera * Support discovery device info update and add tests --- homeassistant/components/mqtt/camera.py | 29 +++++++--- tests/components/mqtt/test_camera.py | 77 +++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index 1df635bbde4ab3..f3ae36c5746855 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -7,13 +7,19 @@ from homeassistant.components import camera, mqtt from homeassistant.components.camera import PLATFORM_SCHEMA, Camera -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_NAME, CONF_DEVICE from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import ConfigType, HomeAssistantType -from . import ATTR_DISCOVERY_HASH, CONF_UNIQUE_ID, MqttDiscoveryUpdate, subscription +from . import ( + ATTR_DISCOVERY_HASH, + CONF_UNIQUE_ID, + MqttDiscoveryUpdate, + MqttEntityDeviceInfo, + subscription, +) from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash _LOGGER = logging.getLogger(__name__) @@ -26,6 +32,7 @@ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_UNIQUE_ID): cv.string, + vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, } ) @@ -45,7 +52,9 @@ async def async_discover(discovery_payload): try: discovery_hash = discovery_payload.pop(ATTR_DISCOVERY_HASH) config = PLATFORM_SCHEMA(discovery_payload) - await _async_setup_entity(config, async_add_entities, discovery_hash) + await _async_setup_entity( + config, async_add_entities, config_entry, discovery_hash + ) except Exception: if discovery_hash: clear_discovery_hash(hass, discovery_hash) @@ -56,15 +65,17 @@ async def async_discover(discovery_payload): ) -async def _async_setup_entity(config, async_add_entities, discovery_hash=None): +async def _async_setup_entity( + config, async_add_entities, config_entry=None, discovery_hash=None +): """Set up the MQTT Camera.""" - async_add_entities([MqttCamera(config, discovery_hash)]) + async_add_entities([MqttCamera(config, config_entry, discovery_hash)]) -class MqttCamera(MqttDiscoveryUpdate, Camera): +class MqttCamera(MqttDiscoveryUpdate, MqttEntityDeviceInfo, Camera): """representation of a MQTT camera.""" - def __init__(self, config, discovery_hash): + def __init__(self, config, config_entry, discovery_hash): """Initialize the MQTT Camera.""" self._config = config self._unique_id = config.get(CONF_UNIQUE_ID) @@ -73,8 +84,11 @@ def __init__(self, config, discovery_hash): self._qos = 0 self._last_image = None + device_config = config.get(CONF_DEVICE) + Camera.__init__(self) MqttDiscoveryUpdate.__init__(self, discovery_hash, self.discovery_update) + MqttEntityDeviceInfo.__init__(self, device_config, config_entry) async def async_added_to_hass(self): """Subscribe MQTT events.""" @@ -85,6 +99,7 @@ async def discovery_update(self, discovery_payload): """Handle updated discovery message.""" config = PLATFORM_SCHEMA(discovery_payload) self._config = config + await self.device_info_discovery_update(config) await self._subscribe_topics() self.async_write_ha_state() diff --git a/tests/components/mqtt/test_camera.py b/tests/components/mqtt/test_camera.py index ecc54e0e209b0c..70b5e941fe3868 100644 --- a/tests/components/mqtt/test_camera.py +++ b/tests/components/mqtt/test_camera.py @@ -1,5 +1,6 @@ """The tests for mqtt camera component.""" from unittest.mock import ANY +import json from homeassistant.components import camera, mqtt from homeassistant.components.mqtt.discovery import async_start @@ -167,3 +168,79 @@ async def test_entity_id_update(hass, mqtt_mock): assert state is not None assert mock_mqtt.async_subscribe.call_count == 1 mock_mqtt.async_subscribe.assert_any_call("test-topic", ANY, 0, None) + + +async def test_entity_device_info_with_identifier(hass, mqtt_mock): + """Test MQTT camera device registry integration.""" + entry = MockConfigEntry(domain=mqtt.DOMAIN) + entry.add_to_hass(hass) + await async_start(hass, "homeassistant", {}, entry) + registry = await hass.helpers.device_registry.async_get_registry() + + data = json.dumps( + { + "platform": "mqtt", + "name": "Test 1", + "topic": "test-topic", + "device": { + "identifiers": ["helloworld"], + "connections": [["mac", "02:5b:26:a8:dc:12"]], + "manufacturer": "Whatever", + "name": "Beer", + "model": "Glass", + "sw_version": "0.1-beta", + }, + "unique_id": "veryunique", + } + ) + async_fire_mqtt_message(hass, "homeassistant/camera/bla/config", data) + await hass.async_block_till_done() + + device = registry.async_get_device({("mqtt", "helloworld")}, set()) + assert device is not None + assert device.identifiers == {("mqtt", "helloworld")} + assert device.connections == {("mac", "02:5b:26:a8:dc:12")} + assert device.manufacturer == "Whatever" + assert device.name == "Beer" + assert device.model == "Glass" + assert device.sw_version == "0.1-beta" + + +async def test_entity_device_info_update(hass, mqtt_mock): + """Test device registry update.""" + entry = MockConfigEntry(domain=mqtt.DOMAIN) + entry.add_to_hass(hass) + await async_start(hass, "homeassistant", {}, entry) + registry = await hass.helpers.device_registry.async_get_registry() + + config = { + "platform": "mqtt", + "name": "Test 1", + "topic": "test-topic", + "device": { + "identifiers": ["helloworld"], + "connections": [["mac", "02:5b:26:a8:dc:12"]], + "manufacturer": "Whatever", + "name": "Beer", + "model": "Glass", + "sw_version": "0.1-beta", + }, + "unique_id": "veryunique", + } + + data = json.dumps(config) + async_fire_mqtt_message(hass, "homeassistant/camera/bla/config", data) + await hass.async_block_till_done() + + device = registry.async_get_device({("mqtt", "helloworld")}, set()) + assert device is not None + assert device.name == "Beer" + + config["device"]["name"] = "Milk" + data = json.dumps(config) + async_fire_mqtt_message(hass, "homeassistant/camera/bla/config", data) + await hass.async_block_till_done() + + device = registry.async_get_device({("mqtt", "helloworld")}, set()) + assert device is not None + assert device.name == "Milk" From 4004879ae0b673e77f25e5df99aed069a5d4c1f7 Mon Sep 17 00:00:00 2001 From: Penny Wood Date: Thu, 5 Sep 2019 00:49:22 +0800 Subject: [PATCH 21/24] Entity registry doesn't overwrite with None (#24275) --- homeassistant/helpers/entity_registry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 3be00c859a7a41..4b97aff19a8983 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -154,8 +154,8 @@ def async_get_or_create( if entity_id: return self._async_update_entity( entity_id, - config_entry_id=config_entry_id, - device_id=device_id, + config_entry_id=config_entry_id or _UNDEF, + device_id=device_id or _UNDEF, # When we changed our slugify algorithm, we invalidated some # stored entity IDs with either a __ or ending in _. # Fix introduced in 0.86 (Jan 23, 2019). Next line can be From 0df1b4c7a18d131df758daec3676a36690ffb27f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 4 Sep 2019 19:09:24 +0200 Subject: [PATCH 22/24] Replaces IOError by OSError (#26428) --- homeassistant/__main__.py | 6 +++--- homeassistant/components/bme680/sensor.py | 2 +- homeassistant/components/bmw_connected_drive/__init__.py | 2 +- homeassistant/components/egardia/__init__.py | 4 ++-- homeassistant/components/hangouts/hangouts_bot.py | 2 +- homeassistant/components/keyboard_remote/__init__.py | 2 +- homeassistant/components/liveboxplaytv/media_player.py | 2 +- homeassistant/components/miflora/sensor.py | 2 +- homeassistant/components/mitemp_bt/sensor.py | 2 +- homeassistant/components/pilight/__init__.py | 2 +- homeassistant/components/proxy/camera.py | 2 +- homeassistant/components/remote_rpi_gpio/__init__.py | 2 +- homeassistant/components/remote_rpi_gpio/binary_sensor.py | 2 +- homeassistant/components/remote_rpi_gpio/switch.py | 2 +- homeassistant/components/sky_hub/device_tracker.py | 2 +- homeassistant/components/supla/__init__.py | 2 +- homeassistant/components/telnet/switch.py | 2 +- homeassistant/components/temper/sensor.py | 2 +- homeassistant/config.py | 4 ++-- homeassistant/scripts/macos/__init__.py | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 9fe501078c2a45..f7e24d69884975 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -216,7 +216,7 @@ def check_pid(pid_file: str) -> None: try: with open(pid_file, "r") as file: pid = int(file.readline()) - except IOError: + except OSError: # PID File does not exist return @@ -239,7 +239,7 @@ def write_pid(pid_file: str) -> None: try: with open(pid_file, "w") as file: file.write(str(pid)) - except IOError: + except OSError: print(f"Fatal Error: Unable to write pid file {pid_file}") sys.exit(1) @@ -258,7 +258,7 @@ def closefds_osx(min_fd: int, max_fd: int) -> None: val = fcntl(_fd, F_GETFD) if not val & FD_CLOEXEC: fcntl(_fd, F_SETFD, val | FD_CLOEXEC) - except IOError: + except OSError: pass diff --git a/homeassistant/components/bme680/sensor.py b/homeassistant/components/bme680/sensor.py index 20fdfc9ee79f6f..a36b35ea9d4be2 100644 --- a/homeassistant/components/bme680/sensor.py +++ b/homeassistant/components/bme680/sensor.py @@ -171,7 +171,7 @@ def _setup_bme680(config): sensor.select_gas_heater_profile(0) else: sensor.set_gas_status(bme680.DISABLE_GAS_MEAS) - except (RuntimeError, IOError): + except (RuntimeError, OSError): _LOGGER.error("BME680 sensor not detected at 0x%02x", i2c_address) return None diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index c257470bb2d0e8..160c8a5e4551c9 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -142,7 +142,7 @@ def update(self, *_): self.account.update_vehicle_states() for listener in self._update_listeners: listener() - except IOError as exception: + except OSError as exception: _LOGGER.error( "Could not connect to the BMW Connected Drive portal. " "The vehicle state could not be updated." diff --git a/homeassistant/components/egardia/__init__.py b/homeassistant/components/egardia/__init__.py index e17ea8f065d10a..9e11f522dd53d0 100644 --- a/homeassistant/components/egardia/__init__.py +++ b/homeassistant/components/egardia/__init__.py @@ -110,7 +110,7 @@ def setup(hass, config): server = egardiaserver.EgardiaServer("", rs_port) bound = server.bind() if not bound: - raise IOError( + raise OSError( "Binding error occurred while " + "starting EgardiaServer." ) hass.data[EGARDIA_SERVER] = server @@ -123,7 +123,7 @@ def handle_stop_event(event): # listen to home assistant stop event hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop_event) - except IOError: + except OSError: _LOGGER.error("Binding error occurred while starting EgardiaServer") return False diff --git a/homeassistant/components/hangouts/hangouts_bot.py b/homeassistant/components/hangouts/hangouts_bot.py index 35f866b3d813aa..9fc3e2fa58e8fb 100644 --- a/homeassistant/components/hangouts/hangouts_bot.py +++ b/homeassistant/components/hangouts/hangouts_bot.py @@ -293,7 +293,7 @@ async def _async_send_message(self, message, targets, data): if self.hass.config.is_allowed_path(uri): try: image_file = open(uri, "rb") - except IOError as error: + except OSError as error: _LOGGER.error( "Image file I/O error(%s): %s", error.errno, error.strerror ) diff --git a/homeassistant/components/keyboard_remote/__init__.py b/homeassistant/components/keyboard_remote/__init__.py index 1a3b41f74bd993..8b901dcc61e930 100644 --- a/homeassistant/components/keyboard_remote/__init__.py +++ b/homeassistant/components/keyboard_remote/__init__.py @@ -157,7 +157,7 @@ def run(self): try: event = self.dev.read_one() - except IOError: # Keyboard Disconnected + except OSError: # Keyboard Disconnected self.dev = None self.hass.bus.fire( KEYBOARD_REMOTE_DISCONNECTED, diff --git a/homeassistant/components/liveboxplaytv/media_player.py b/homeassistant/components/liveboxplaytv/media_player.py index 98418d6be81895..c466d71c4c5fd3 100644 --- a/homeassistant/components/liveboxplaytv/media_player.py +++ b/homeassistant/components/liveboxplaytv/media_player.py @@ -70,7 +70,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= try: device = LiveboxPlayTvDevice(host, port, name) livebox_devices.append(device) - except IOError: + except OSError: _LOGGER.error( "Failed to connect to Livebox Play TV at %s:%s. " "Please check your configuration", diff --git a/homeassistant/components/miflora/sensor.py b/homeassistant/components/miflora/sensor.py index 86f1462e2cca55..28020a801750f3 100644 --- a/homeassistant/components/miflora/sensor.py +++ b/homeassistant/components/miflora/sensor.py @@ -157,7 +157,7 @@ def update(self): try: _LOGGER.debug("Polling data for %s", self.name) data = self.poller.parameter_value(self.parameter) - except IOError as ioerr: + except OSError as ioerr: _LOGGER.info("Polling error %s", ioerr) return except BluetoothBackendException as bterror: diff --git a/homeassistant/components/mitemp_bt/sensor.py b/homeassistant/components/mitemp_bt/sensor.py index 9cd1f1cebc29be..adeba48dbc8517 100644 --- a/homeassistant/components/mitemp_bt/sensor.py +++ b/homeassistant/components/mitemp_bt/sensor.py @@ -157,7 +157,7 @@ def update(self): try: _LOGGER.debug("Polling data for %s", self.name) data = self.poller.parameter_value(self.parameter) - except IOError as ioerr: + except OSError as ioerr: _LOGGER.warning("Polling error %s", ioerr) return except BluetoothBackendException as bterror: diff --git a/homeassistant/components/pilight/__init__.py b/homeassistant/components/pilight/__init__.py index 5d4f5dd25b52f8..2688b15e837c1d 100644 --- a/homeassistant/components/pilight/__init__.py +++ b/homeassistant/components/pilight/__init__.py @@ -92,7 +92,7 @@ def send_code(call): try: pilight_client.send_code(message_data) - except IOError: + except OSError: _LOGGER.error("Pilight send failed for %s", str(message_data)) hass.services.register(DOMAIN, SERVICE_NAME, send_code, schema=RF_CODE_SCHEMA) diff --git a/homeassistant/components/proxy/camera.py b/homeassistant/components/proxy/camera.py index 7d145315748417..53a4f620dcc3d4 100644 --- a/homeassistant/components/proxy/camera.py +++ b/homeassistant/components/proxy/camera.py @@ -66,7 +66,7 @@ def _precheck_image(image, opts): raise ValueError() try: img = Image.open(io.BytesIO(image)) - except IOError: + except OSError: _LOGGER.warning("Failed to open image") raise ValueError() imgfmt = str(img.format) diff --git a/homeassistant/components/remote_rpi_gpio/__init__.py b/homeassistant/components/remote_rpi_gpio/__init__.py index ccefd00c723ac3..33356d0e3b82cc 100644 --- a/homeassistant/components/remote_rpi_gpio/__init__.py +++ b/homeassistant/components/remote_rpi_gpio/__init__.py @@ -47,7 +47,7 @@ def setup_input(address, port, pull_mode, bouncetime): bounce_time=bouncetime, pin_factory=PiGPIOFactory(address), ) - except (ValueError, IndexError, KeyError, IOError): + except (ValueError, IndexError, KeyError, OSError): return None diff --git a/homeassistant/components/remote_rpi_gpio/binary_sensor.py b/homeassistant/components/remote_rpi_gpio/binary_sensor.py index 8c7d7b7d023b1d..e12d83324fd756 100644 --- a/homeassistant/components/remote_rpi_gpio/binary_sensor.py +++ b/homeassistant/components/remote_rpi_gpio/binary_sensor.py @@ -51,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): button = remote_rpi_gpio.setup_input( address, port_num, pull_mode, bouncetime ) - except (ValueError, IndexError, KeyError, IOError): + except (ValueError, IndexError, KeyError, OSError): return new_sensor = RemoteRPiGPIOBinarySensor(port_name, button, invert_logic) devices.append(new_sensor) diff --git a/homeassistant/components/remote_rpi_gpio/switch.py b/homeassistant/components/remote_rpi_gpio/switch.py index aa20a2909d2ffd..8240de7951d710 100644 --- a/homeassistant/components/remote_rpi_gpio/switch.py +++ b/homeassistant/components/remote_rpi_gpio/switch.py @@ -36,7 +36,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for port, name in ports.items(): try: led = remote_rpi_gpio.setup_output(address, port, invert_logic) - except (ValueError, IndexError, KeyError, IOError): + except (ValueError, IndexError, KeyError, OSError): return new_switch = RemoteRPiGPIOSwitch(name, led, invert_logic) devices.append(new_switch) diff --git a/homeassistant/components/sky_hub/device_tracker.py b/homeassistant/components/sky_hub/device_tracker.py index c8969add244a19..109c410c16d0b8 100644 --- a/homeassistant/components/sky_hub/device_tracker.py +++ b/homeassistant/components/sky_hub/device_tracker.py @@ -94,7 +94,7 @@ def _parse_skyhub_response(data_str): """Parse the Sky Hub data format.""" pattmatch = re.search("attach_dev = '(.*)'", data_str) if pattmatch is None: - raise IOError( + raise OSError( "Error: Impossible to fetch data from" + " Sky Hub. Try to reboot the router." ) diff --git a/homeassistant/components/supla/__init__.py b/homeassistant/components/supla/__init__.py index 9eef9d989cb093..86e763142e6191 100644 --- a/homeassistant/components/supla/__init__.py +++ b/homeassistant/components/supla/__init__.py @@ -65,7 +65,7 @@ def setup(hass, base_config): srv_info, ) return False - except IOError: + except OSError: _LOGGER.exception( "Server: %s not configured. Error on Supla API access: ", server_address ) diff --git a/homeassistant/components/telnet/switch.py b/homeassistant/components/telnet/switch.py index a4777af54578bb..87fb70bb8886a6 100644 --- a/homeassistant/components/telnet/switch.py +++ b/homeassistant/components/telnet/switch.py @@ -117,7 +117,7 @@ def _telnet_command(self, command): response = telnet.read_until(b"\r", timeout=self._timeout) _LOGGER.debug("telnet response: %s", response.decode("ASCII").strip()) return response.decode("ASCII").strip() - except IOError as error: + except OSError as error: _LOGGER.error( 'Command "%s" failed with exception: %s', command, repr(error) ) diff --git a/homeassistant/components/temper/sensor.py b/homeassistant/components/temper/sensor.py index c5e5c4af978773..a32de3da10fb62 100644 --- a/homeassistant/components/temper/sensor.py +++ b/homeassistant/components/temper/sensor.py @@ -96,7 +96,7 @@ def update(self): ) sensor_value = self.temper_device.get_temperature(format_str) self.current_value = round(sensor_value, 1) - except IOError: + except OSError: _LOGGER.error( "Failed to get temperature. The device address may" "have changed. Attempting to reset device" diff --git a/homeassistant/config.py b/homeassistant/config.py index 4b7efed00e4082..d3bd97dad8f777 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -289,7 +289,7 @@ def _write_default_config(config_dir: str) -> Optional[str]: return config_path - except IOError: + except OSError: print("Unable to create default configuration file", config_path) return None @@ -393,7 +393,7 @@ def process_ha_config_upgrade(hass: HomeAssistant) -> None: try: with open(config_path, "wt", encoding="utf-8") as config_file: config_file.write(config_raw) - except IOError: + except OSError: _LOGGER.exception("Migrating to google_translate tts failed") pass diff --git a/homeassistant/scripts/macos/__init__.py b/homeassistant/scripts/macos/__init__.py index e8d8306c8ce038..ceb3609dbdb1b7 100644 --- a/homeassistant/scripts/macos/__init__.py +++ b/homeassistant/scripts/macos/__init__.py @@ -27,7 +27,7 @@ def install_osx(): try: with open(path, "w", encoding="utf-8") as outp: outp.write(plist) - except IOError as err: + except OSError as err: print("Unable to write to " + path, err) return From 3313a6c4ff95e470433ed58e411700909f377b29 Mon Sep 17 00:00:00 2001 From: Rami Mosleh Date: Wed, 4 Sep 2019 12:39:57 +0300 Subject: [PATCH 23/24] Add config flow to transmission --- .../transmission/.translations/en.json | 57 +++++ .../components/transmission/__init__.py | 195 +++++++++++------- .../components/transmission/config_flow.py | 164 +++++++++++++++ .../components/transmission/const.py | 21 ++ .../components/transmission/manifest.json | 3 +- .../components/transmission/sensor.py | 33 +-- .../components/transmission/services.yaml | 2 +- .../components/transmission/strings.json | 57 +++++ .../components/transmission/switch.py | 16 +- homeassistant/generated/config_flows.py | 3 +- 10 files changed, 456 insertions(+), 95 deletions(-) create mode 100644 homeassistant/components/transmission/.translations/en.json create mode 100644 homeassistant/components/transmission/config_flow.py create mode 100644 homeassistant/components/transmission/const.py create mode 100644 homeassistant/components/transmission/strings.json diff --git a/homeassistant/components/transmission/.translations/en.json b/homeassistant/components/transmission/.translations/en.json new file mode 100644 index 00000000000000..d1b3b7e65fdf8a --- /dev/null +++ b/homeassistant/components/transmission/.translations/en.json @@ -0,0 +1,57 @@ +{ + "config": { + "title": "Transmission", + "step": { + "user": { + "title": "Set up Transmission Client", + "data": { + "name": "Name", + "host": "Host", + "username": "User name", + "password": "Password", + "port": "Port" + } + }, + "options": { + "title": "Configure Options", + "data": { + "active_torrents": "Active Torrents", + "current_status": "Current Status", + "download_speed": "Download Speed [MB/s]", + "paused_torrents": "Pause Torrents", + "total_torrents": "Total Torrents", + "upload_speed": "Upload Speed [MB/s]", + "completed_torrents": "Completed torrents (seeding)", + "started_torrents": "Started torrents (downloading)", + "turtle_mode": "‘Turtle mode’ switch", + "scan_interval": "Update frequency" + } + } + }, + "error": { + "cannot_connect": "Unable to Connect to Client" + }, + "abort": { + "one_instance_allowed": "Only a single instance is necessary." + } + }, + "options": { + "step": { + "init": { + "description": "Configure options for Transmission", + "data": { + "active_torrents": "Active Torrents", + "current_status": "Current Status", + "download_speed": "Download Speed [MB/s]", + "paused_torrents": "Pause Torrents", + "total_torrents": "Total Torrents", + "upload_speed": "Upload Speed [MB/s]", + "completed_torrents": "Completed torrents (seeding)", + "started_torrents": "Started torrents (downloading)", + "turtle_mode": "‘Turtle mode’ switch", + "scan_interval": "Update frequency" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index e7f9b94046d367..efa061db0cf7d8 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -3,6 +3,8 @@ import logging import voluptuous as vol +import transmissionrpc +from transmissionrpc.error import TransmissionError from homeassistant.const import ( CONF_HOST, @@ -13,36 +15,28 @@ CONF_SCAN_INTERVAL, CONF_USERNAME, ) -from homeassistant.helpers import config_validation as cv, discovery + +from .const import ( + DOMAIN, + CONF_TURTLE_MODE, + CONF_SENSOR_TYPES, + ATTR_TORRENT, + SERVICE_ADD_TORRENT, +) +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import dispatcher_send -from homeassistant.helpers.event import track_time_interval +from homeassistant.config_entries import SOURCE_IMPORT + +from homeassistant.helpers.event import async_track_time_interval _LOGGER = logging.getLogger(__name__) -DOMAIN = "transmission" DATA_UPDATED = "transmission_data_updated" DATA_TRANSMISSION = "data_transmission" DEFAULT_NAME = "Transmission" DEFAULT_PORT = 9091 -TURTLE_MODE = "turtle_mode" - -SENSOR_TYPES = { - "active_torrents": ["Active Torrents", None], - "current_status": ["Status", None], - "download_speed": ["Down Speed", "MB/s"], - "paused_torrents": ["Paused Torrents", None], - "total_torrents": ["Total Torrents", None], - "upload_speed": ["Up Speed", "MB/s"], - "completed_torrents": ["Completed Torrents", None], - "started_torrents": ["Started Torrents", None], -} - -DEFAULT_SCAN_INTERVAL = timedelta(seconds=120) - -ATTR_TORRENT = "torrent" - -SERVICE_ADD_TORRENT = "add_torrent" +DEFAULT_SCAN_INTERVAL = 120 SERVICE_ADD_TORRENT_SCHEMA = vol.Schema({vol.Required(ATTR_TORRENT): cv.string}) @@ -55,13 +49,13 @@ vol.Optional(CONF_USERNAME): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(TURTLE_MODE, default=False): cv.boolean, + vol.Optional(CONF_TURTLE_MODE, default=False): cv.boolean, vol.Optional( CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL ): cv.time_period, vol.Optional( CONF_MONITORED_CONDITIONS, default=["current_status"] - ): vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), + ): vol.All(cv.ensure_list, [vol.In(CONF_SENSOR_TYPES)]), } ) }, @@ -69,63 +63,121 @@ ) -def setup(hass, config): +async def async_setup(hass, config): + """Set up the Transmission Component from config.""" + if not hass.config_entries.async_entries(DOMAIN) and DOMAIN in config: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN] + ) + ) + + return True + + +async def async_setup_entry(hass, config_entry): """Set up the Transmission Component.""" - host = config[DOMAIN][CONF_HOST] - username = config[DOMAIN].get(CONF_USERNAME) - password = config[DOMAIN].get(CONF_PASSWORD) - port = config[DOMAIN][CONF_PORT] - scan_interval = config[DOMAIN][CONF_SCAN_INTERVAL] - - import transmissionrpc - from transmissionrpc.error import TransmissionError - - try: - api = transmissionrpc.Client(host, port=port, user=username, password=password) - api.session_stats() - except TransmissionError as error: - if str(error).find("401: Unauthorized"): - _LOGGER.error("Credentials for" " Transmission client are not valid") + if not config_entry.options: + await async_populate_options(hass, config_entry) + + client = TransmissionClient(hass, config_entry) + if not await client.async_setup(): return False - tm_data = hass.data[DATA_TRANSMISSION] = TransmissionData(hass, config, api) + return True - tm_data.update() - tm_data.init_torrent_list() - def refresh(event_time): - """Get the latest data from Transmission.""" - tm_data.update() +async def async_populate_options(hass, config_entry): + """Populate default options for Transmission Client.""" + options = {} + options[CONF_MONITORED_CONDITIONS] = {} + for sensor in CONF_SENSOR_TYPES: + options[CONF_MONITORED_CONDITIONS][sensor] = config_entry.data["options"].get( + sensor, False + ) + options[CONF_TURTLE_MODE] = config_entry.data["options"].get( + CONF_TURTLE_MODE, False + ) + options[CONF_SCAN_INTERVAL] = config_entry.data["options"].get( + CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL + ) - track_time_interval(hass, refresh, scan_interval) - - def add_torrent(service): - """Add new torrent to download.""" - torrent = service.data[ATTR_TORRENT] - if torrent.startswith( - ("http", "ftp:", "magnet:") - ) or hass.config.is_allowed_path(torrent): - api.add_torrent(torrent) - else: - _LOGGER.warning( - "Could not add torrent: " "unsupported type or no permission" - ) + hass.config_entries.async_update_entry(config_entry, options=options) - hass.services.register( - DOMAIN, SERVICE_ADD_TORRENT, add_torrent, schema=SERVICE_ADD_TORRENT_SCHEMA - ) - sensorconfig = { - "sensors": config[DOMAIN][CONF_MONITORED_CONDITIONS], - "client_name": config[DOMAIN][CONF_NAME], - } +async def async_unload_entry(hass, entry): + """Unload Transmission Entry from config_entry.""" + hass.services.async_remove(DOMAIN, SERVICE_ADD_TORRENT) + return True - discovery.load_platform(hass, "sensor", DOMAIN, sensorconfig, config) - if config[DOMAIN][TURTLE_MODE]: - discovery.load_platform(hass, "switch", DOMAIN, sensorconfig, config) +class TransmissionClient: + """Transmission Client Object.""" - return True + def __init__(self, hass, config_entry): + """Initialize the Transmission RPC API.""" + self.hass = hass + self.config_entry = config_entry + self.host = self.config_entry.data[CONF_HOST] + self.username = self.config_entry.data.get(CONF_USERNAME) + self.password = self.config_entry.data.get(CONF_PASSWORD) + self.port = self.config_entry.data[CONF_PORT] + self.scan_interval = self.config_entry.options.get( + CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL + ) + + async def async_setup(self): + """Set up the Transmission client.""" + hass = self.hass + try: + api = transmissionrpc.Client( + self.host, port=self.port, user=self.username, password=self.password + ) + api.session_stats() + except TransmissionError as error: + if str(error).find("401: Unauthorized"): + _LOGGER.error("Credentials for" " Transmission client are not valid") + return False + tm_data = self.hass.data[DATA_TRANSMISSION] = TransmissionData( + self.hass, self.config_entry, api + ) + + tm_data.update() + await tm_data.async_init_torrent_list() + + def refresh(event_time): + """Get the latest data from Transmission.""" + tm_data.update() + + async_track_time_interval( + self.hass, refresh, timedelta(seconds=self.scan_interval) + ) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(self.config_entry, "sensor") + ) + if self.config_entry.options.get(CONF_TURTLE_MODE): + hass.async_create_task( + hass.config_entries.async_forward_entry_setup( + self.config_entry, "switch" + ) + ) + + def add_torrent(self, service): + """Add new torrent to download.""" + torrent = service.data[ATTR_TORRENT] + if torrent.startswith( + ("http", "ftp:", "magnet:") + ) or self.hass.config.is_allowed_path(torrent): + self.api.add_torrent(torrent) + else: + _LOGGER.warning( + "Could not add torrent: " "unsupported type or no permission" + ) + + hass.services.async_register( + DOMAIN, SERVICE_ADD_TORRENT, add_torrent, schema=SERVICE_ADD_TORRENT_SCHEMA + ) + return True class TransmissionData: @@ -141,6 +193,9 @@ def __init__(self, hass, config, api): self.completed_torrents = [] self.started_torrents = [] self.hass = hass + self.scan_interval = timedelta( + seconds=config.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) + ) def update(self): """Get the latest data from Transmission instance.""" @@ -162,7 +217,7 @@ def update(self): self.available = False _LOGGER.error("Unable to connect to Transmission client") - def init_torrent_list(self): + async def async_init_torrent_list(self): """Initialize torrent lists.""" self.torrents = self._api.get_torrents() self.completed_torrents = [ diff --git a/homeassistant/components/transmission/config_flow.py b/homeassistant/components/transmission/config_flow.py new file mode 100644 index 00000000000000..588d49ca657059 --- /dev/null +++ b/homeassistant/components/transmission/config_flow.py @@ -0,0 +1,164 @@ +"""Config flow for Transmission Bittorent Client.""" +import voluptuous as vol + +import transmissionrpc +from transmissionrpc.error import TransmissionError + +from homeassistant import config_entries +from homeassistant.core import callback +from homeassistant.const import ( + CONF_HOST, + CONF_MONITORED_CONDITIONS, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_SCAN_INTERVAL, + CONF_USERNAME, +) + +from .const import DOMAIN, CONF_TURTLE_MODE, CONF_SENSOR_TYPES + +DEFAULT_NAME = "Transmission" +DEFAULT_PORT = 9091 +DEFAULT_SCAN_INTERVAL = 120 +DEFAULT_SENSOR = "current_status" + + +class TransmissionFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a UniFi config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return TransmissionOptionsFlowHandler(config_entry) + + def __init__(self): + """Initialize the Transmission flow.""" + self.config = None + self.errors = {} + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + if self.hass.config_entries.async_entries(DOMAIN): + return self.async_abort(reason="one_instance_allowed") + + if user_input is not None: + valid = await self.is_valid(user_input) + if valid: + self.config = user_input + return await self.async_step_options() + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_NAME, default=DEFAULT_NAME): str, + vol.Required(CONF_HOST): str, + vol.Optional(CONF_USERNAME): str, + vol.Optional(CONF_PASSWORD): str, + vol.Required(CONF_PORT, default=DEFAULT_PORT): int, + } + ), + errors=self.errors, + ) + + async def is_valid(self, user_input): + """Validate connection to the Transmission Client.""" + try: + transmissionrpc.Client( + user_input[CONF_HOST], + port=user_input[CONF_PORT], + user=user_input.get(CONF_USERNAME), + password=user_input.get(CONF_PASSWORD), + ) + return True + + except TransmissionError as error: + if str(error).find("401: Unauthorized"): + self.errors["base"] = "cannot_connect" + + return False + + async def async_step_options(self, user_input=None): + """Set options for the Transmission Client.""" + if user_input is not None: + self.config["options"] = user_input + return self.async_create_entry( + title=self.config[CONF_NAME], data=self.config + ) + + options = { + vol.Optional(CONF_TURTLE_MODE, default=False): bool, + vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): int, + } + for sensor in CONF_SENSOR_TYPES: + options.update( + {vol.Optional(sensor, default=CONF_SENSOR_TYPES[sensor][2]): bool} + ) + + return self.async_show_form(step_id="options", data_schema=vol.Schema(options)) + + async def async_step_import(self, import_config): + """Import from Transmission client config.""" + config = { + CONF_NAME: import_config.get(CONF_NAME, DEFAULT_NAME), + CONF_HOST: import_config[CONF_HOST], + CONF_USERNAME: import_config.get(CONF_USERNAME), + CONF_PASSWORD: import_config.get(CONF_PASSWORD), + CONF_PORT: import_config.get(CONF_PORT, DEFAULT_PORT), + } + + return await self.async_step_user(user_input=config) + + +class TransmissionOptionsFlowHandler(config_entries.OptionsFlow): + """Handle Transmission client options.""" + + def __init__(self, config_entry): + """Initialize Transmission options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Manage the Transmission options.""" + if user_input is not None: + options = {} + options[CONF_MONITORED_CONDITIONS] = {} + for sensor in CONF_SENSOR_TYPES: + options[CONF_MONITORED_CONDITIONS][sensor] = user_input[sensor] + options[CONF_TURTLE_MODE] = user_input[CONF_TURTLE_MODE] + options[CONF_SCAN_INTERVAL] = user_input[CONF_SCAN_INTERVAL] + + return self.async_create_entry(title="", data=options) + + options = { + vol.Optional( + CONF_TURTLE_MODE, + default=self.config_entry.options.get( + CONF_TURTLE_MODE, + self.config_entry.data["options"][CONF_TURTLE_MODE], + ), + ): bool, + vol.Optional( + CONF_SCAN_INTERVAL, + default=self.config_entry.options.get( + CONF_SCAN_INTERVAL, + self.config_entry.data["options"][CONF_SCAN_INTERVAL], + ), + ): int, + } + for sensor in CONF_SENSOR_TYPES: + options.update( + { + vol.Optional( + sensor, + default=self.config_entry.options[ + CONF_MONITORED_CONDITIONS + ].get(sensor, self.config_entry.data["options"][sensor]), + ): bool + } + ) + return self.async_show_form(step_id="init", data_schema=vol.Schema(options)) diff --git a/homeassistant/components/transmission/const.py b/homeassistant/components/transmission/const.py new file mode 100644 index 00000000000000..0199ffc5fb50f7 --- /dev/null +++ b/homeassistant/components/transmission/const.py @@ -0,0 +1,21 @@ +"""Constants for the Transmission Bittorent Client component.""" +DOMAIN = "transmission" +DATA_UPDATED = "transmission_data_updated" +DATA_TRANSMISSION = "data_transmission" + + +CONF_TURTLE_MODE = "turtle_mode" +CONF_SENSOR_TYPES = { + "active_torrents": ["Active Torrents", None, False], + "current_status": ["Status", None, True], + "download_speed": ["Down Speed", "MB/s", False], + "paused_torrents": ["Paused Torrents", None, False], + "total_torrents": ["Total Torrents", None, False], + "upload_speed": ["Up Speed", "MB/s", False], + "completed_torrents": ["Completed Torrents", None, False], + "started_torrents": ["Started Torrents", None, False], +} + +ATTR_TORRENT = "torrent" + +SERVICE_ADD_TORRENT = "add_torrent" diff --git a/homeassistant/components/transmission/manifest.json b/homeassistant/components/transmission/manifest.json index bc5da64fcacd9b..69b46594f20615 100644 --- a/homeassistant/components/transmission/manifest.json +++ b/homeassistant/components/transmission/manifest.json @@ -1,10 +1,11 @@ { "domain": "transmission", "name": "Transmission", + "config_flow": true, "documentation": "https://www.home-assistant.io/components/transmission", "requirements": [ "transmissionrpc==0.11" ], "dependencies": [], "codeowners": [] -} +} \ No newline at end of file diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index ac2e64ce92f390..abd402d856ba39 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -2,12 +2,12 @@ from datetime import timedelta import logging -from homeassistant.const import STATE_IDLE +from homeassistant.const import CONF_NAME, STATE_IDLE, CONF_MONITORED_CONDITIONS from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from . import DATA_TRANSMISSION, DATA_UPDATED, SENSOR_TYPES +from .const import DATA_TRANSMISSION, DATA_UPDATED, CONF_SENSOR_TYPES _LOGGER = logging.getLogger(__name__) @@ -17,25 +17,28 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Import config from configuration.yaml.""" + pass + + +async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Transmission sensors.""" - if discovery_info is None: - return transmission_api = hass.data[DATA_TRANSMISSION] - monitored_variables = discovery_info["sensors"] - name = discovery_info["client_name"] + name = config_entry.data[CONF_NAME] dev = [] - for sensor_type in monitored_variables: - dev.append( - TransmissionSensor( - sensor_type, - transmission_api, - name, - SENSOR_TYPES[sensor_type][0], - SENSOR_TYPES[sensor_type][1], + for sensor_type in CONF_SENSOR_TYPES: + if config_entry.options[CONF_MONITORED_CONDITIONS].get(sensor_type): + dev.append( + TransmissionSensor( + sensor_type, + transmission_api, + name, + CONF_SENSOR_TYPES[sensor_type][0], + CONF_SENSOR_TYPES[sensor_type][1], + ) ) - ) async_add_entities(dev, True) diff --git a/homeassistant/components/transmission/services.yaml b/homeassistant/components/transmission/services.yaml index e049f89b3c6a6d..ab383584e83fdc 100644 --- a/homeassistant/components/transmission/services.yaml +++ b/homeassistant/components/transmission/services.yaml @@ -3,4 +3,4 @@ add_torrent: fields: torrent: description: URL, magnet link or Base64 encoded file. - example: http://releases.ubuntu.com/19.04/ubuntu-19.04-desktop-amd64.iso.torrent} + example: http://releases.ubuntu.com/19.04/ubuntu-19.04-desktop-amd64.iso.torrent diff --git a/homeassistant/components/transmission/strings.json b/homeassistant/components/transmission/strings.json new file mode 100644 index 00000000000000..50926781759d7b --- /dev/null +++ b/homeassistant/components/transmission/strings.json @@ -0,0 +1,57 @@ +{ + "config": { + "title": "Transmission", + "step": { + "user": { + "title": "Setup Transmission Client", + "data": { + "name": "Name", + "host": "Host", + "username": "User name", + "password": "Password", + "port": "Port" + } + }, + "options": { + "title": "Configure Options", + "data": { + "active_torrents": "Active Torrents", + "current_status": "Current Status", + "download_speed": "Download Speed [MB/s]", + "paused_torrents": "Pause Torrents", + "total_torrents": "Total Torrents", + "upload_speed": "Upload Speed [MB/s]", + "completed_torrents": "Completed torrents (seeding)", + "started_torrents": "Started torrents (downloading)", + "turtle_mode": "‘Turtle mode’ switch", + "scan_interval": "Update frequency" + } + } + }, + "error": { + "cannot_connect": "Unable to Connect to Client" + }, + "abort": { + "one_instance_allowed": "Only a single instance is necessary." + } + }, + "options": { + "step": { + "init": { + "description": "Configure options for Transmission", + "data": { + "active_torrents": "Active Torrents", + "current_status": "Current Status", + "download_speed": "Download Speed [MB/s]", + "paused_torrents": "Pause Torrents", + "total_torrents": "Total Torrents", + "upload_speed": "Upload Speed [MB/s]", + "completed_torrents": "Completed torrents (seeding)", + "started_torrents": "Started torrents (downloading)", + "turtle_mode": "‘Turtle mode’ switch", + "scan_interval": "Update frequency" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/transmission/switch.py b/homeassistant/components/transmission/switch.py index df490cdbe47cbe..6163521a2eb050 100644 --- a/homeassistant/components/transmission/switch.py +++ b/homeassistant/components/transmission/switch.py @@ -1,7 +1,7 @@ """Support for setting the Transmission BitTorrent client Turtle Mode.""" import logging -from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.const import CONF_NAME, STATE_OFF, STATE_ON from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import ToggleEntity @@ -14,13 +14,15 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Import config from configuration.yaml.""" + pass + + +async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Transmission switch.""" - if discovery_info is None: - return - component_name = DATA_TRANSMISSION - transmission_api = hass.data[component_name] - name = discovery_info["client_name"] + transmission_api = hass.data[DATA_TRANSMISSION] + name = config_entry.data[CONF_NAME] async_add_entities([TransmissionSwitch(transmission_api, name)], True) @@ -74,7 +76,7 @@ async def async_added_to_hass(self): def _schedule_immediate_update(self): self.async_schedule_update_ha_state(True) - def update(self): + async def async_update(self): """Get the latest data from Transmission and updates the state.""" active = self.transmission_client.get_alt_speed_enabled() diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 32690153221b65..70d4f5dd8f0b30 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -56,6 +56,7 @@ "tplink", "traccar", "tradfri", + "transmission", "twentemilieu", "twilio", "unifi", @@ -67,5 +68,5 @@ "wwlln", "zha", "zone", - "zwave" + "zwave", ] From 2105ec1025943b9353f4a1eb4f18b88b425a9c9e Mon Sep 17 00:00:00 2001 From: Rami Mosleh Date: Wed, 4 Sep 2019 16:31:40 +0300 Subject: [PATCH 24/24] sort imports and constants --- .../components/transmission/__init__.py | 21 +++++++++---------- .../components/transmission/config_flow.py | 19 +++++++++-------- .../components/transmission/const.py | 10 ++++++--- .../components/transmission/manifest.json | 2 +- .../components/transmission/sensor.py | 9 ++------ .../components/transmission/strings.json | 2 +- .../components/transmission/switch.py | 2 -- 7 files changed, 31 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index efa061db0cf7d8..df0126870e03c9 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -2,10 +2,11 @@ from datetime import timedelta import logging -import voluptuous as vol import transmissionrpc from transmissionrpc.error import TransmissionError +import voluptuous as vol +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( CONF_HOST, CONF_MONITORED_CONDITIONS, @@ -15,24 +16,22 @@ CONF_SCAN_INTERVAL, CONF_USERNAME, ) +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.dispatcher import dispatcher_send +from homeassistant.helpers.event import async_track_time_interval from .const import ( - DOMAIN, - CONF_TURTLE_MODE, - CONF_SENSOR_TYPES, ATTR_TORRENT, + CONF_SENSOR_TYPES, + CONF_TURTLE_MODE, + DATA_TRANSMISSION, + DATA_UPDATED, + DOMAIN, SERVICE_ADD_TORRENT, ) -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.dispatcher import dispatcher_send -from homeassistant.config_entries import SOURCE_IMPORT - -from homeassistant.helpers.event import async_track_time_interval _LOGGER = logging.getLogger(__name__) -DATA_UPDATED = "transmission_data_updated" -DATA_TRANSMISSION = "data_transmission" DEFAULT_NAME = "Transmission" DEFAULT_PORT = 9091 diff --git a/homeassistant/components/transmission/config_flow.py b/homeassistant/components/transmission/config_flow.py index 588d49ca657059..384af7715bd503 100644 --- a/homeassistant/components/transmission/config_flow.py +++ b/homeassistant/components/transmission/config_flow.py @@ -1,11 +1,9 @@ """Config flow for Transmission Bittorent Client.""" -import voluptuous as vol - import transmissionrpc from transmissionrpc.error import TransmissionError +import voluptuous as vol from homeassistant import config_entries -from homeassistant.core import callback from homeassistant.const import ( CONF_HOST, CONF_MONITORED_CONDITIONS, @@ -15,13 +13,16 @@ CONF_SCAN_INTERVAL, CONF_USERNAME, ) +from homeassistant.core import callback -from .const import DOMAIN, CONF_TURTLE_MODE, CONF_SENSOR_TYPES - -DEFAULT_NAME = "Transmission" -DEFAULT_PORT = 9091 -DEFAULT_SCAN_INTERVAL = 120 -DEFAULT_SENSOR = "current_status" +from .const import ( + CONF_SENSOR_TYPES, + CONF_TURTLE_MODE, + DEFAULT_NAME, + DEFAULT_PORT, + DEFAULT_SCAN_INTERVAL, + DOMAIN, +) class TransmissionFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/transmission/const.py b/homeassistant/components/transmission/const.py index 0199ffc5fb50f7..4fe5910405098c 100644 --- a/homeassistant/components/transmission/const.py +++ b/homeassistant/components/transmission/const.py @@ -1,8 +1,5 @@ """Constants for the Transmission Bittorent Client component.""" DOMAIN = "transmission" -DATA_UPDATED = "transmission_data_updated" -DATA_TRANSMISSION = "data_transmission" - CONF_TURTLE_MODE = "turtle_mode" CONF_SENSOR_TYPES = { @@ -16,6 +13,13 @@ "started_torrents": ["Started Torrents", None, False], } +DEFAULT_NAME = "Transmission" +DEFAULT_PORT = 9091 +DEFAULT_SCAN_INTERVAL = 120 + ATTR_TORRENT = "torrent" SERVICE_ADD_TORRENT = "add_torrent" + +DATA_UPDATED = "transmission_data_updated" +DATA_TRANSMISSION = "data_transmission" diff --git a/homeassistant/components/transmission/manifest.json b/homeassistant/components/transmission/manifest.json index 69b46594f20615..9451ce90773399 100644 --- a/homeassistant/components/transmission/manifest.json +++ b/homeassistant/components/transmission/manifest.json @@ -8,4 +8,4 @@ ], "dependencies": [], "codeowners": [] -} \ No newline at end of file +} diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index abd402d856ba39..a6617ed60f6cbb 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -1,20 +1,15 @@ """Support for monitoring the Transmission BitTorrent client API.""" -from datetime import timedelta import logging -from homeassistant.const import CONF_NAME, STATE_IDLE, CONF_MONITORED_CONDITIONS +from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_NAME, STATE_IDLE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from .const import DATA_TRANSMISSION, DATA_UPDATED, CONF_SENSOR_TYPES +from .const import CONF_SENSOR_TYPES, DATA_TRANSMISSION, DATA_UPDATED _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = "Transmission" - -SCAN_INTERVAL = timedelta(seconds=120) - async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Import config from configuration.yaml.""" diff --git a/homeassistant/components/transmission/strings.json b/homeassistant/components/transmission/strings.json index 50926781759d7b..f184cf477d2f73 100644 --- a/homeassistant/components/transmission/strings.json +++ b/homeassistant/components/transmission/strings.json @@ -54,4 +54,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/transmission/switch.py b/homeassistant/components/transmission/switch.py index 6163521a2eb050..9a17adc0ff988b 100644 --- a/homeassistant/components/transmission/switch.py +++ b/homeassistant/components/transmission/switch.py @@ -10,8 +10,6 @@ _LOGGING = logging.getLogger(__name__) -DEFAULT_NAME = "Transmission Turtle Mode" - async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Import config from configuration.yaml."""