From 670e9ca1fdd0660d153eea585d1cb49a182bccf6 Mon Sep 17 00:00:00 2001 From: Cyr-ius Date: Wed, 25 Mar 2020 13:51:29 +0100 Subject: [PATCH 001/108] Add config_flow for roomba --- .../components/roomba/.translations/en.json | 45 ++++++ .../components/roomba/.translations/fr.json | 45 ++++++ homeassistant/components/roomba/__init__.py | 75 +++++++++ .../components/roomba/config_flow.py | 153 ++++++++++++++++++ homeassistant/components/roomba/const.py | 9 ++ homeassistant/components/roomba/manifest.json | 1 + homeassistant/components/roomba/strings.json | 45 ++++++ homeassistant/components/roomba/vacuum.py | 83 +++------- homeassistant/generated/config_flows.py | 1 + requirements_test_all.txt | 3 + tests/components/roomba/__init__.py | 1 + tests/components/roomba/test_config_flow.py | 90 +++++++++++ 12 files changed, 488 insertions(+), 63 deletions(-) create mode 100644 homeassistant/components/roomba/.translations/en.json create mode 100644 homeassistant/components/roomba/.translations/fr.json create mode 100644 homeassistant/components/roomba/config_flow.py create mode 100644 homeassistant/components/roomba/const.py create mode 100644 homeassistant/components/roomba/strings.json create mode 100644 tests/components/roomba/__init__.py create mode 100644 tests/components/roomba/test_config_flow.py diff --git a/homeassistant/components/roomba/.translations/en.json b/homeassistant/components/roomba/.translations/en.json new file mode 100644 index 00000000000000..99152734c34dfa --- /dev/null +++ b/homeassistant/components/roomba/.translations/en.json @@ -0,0 +1,45 @@ +{ + "config": { + "title": "iRobot Roomba", + "step": { + "user": { + "title": "Connect to the device", + "data": { + "host": "Hostname or IP Address", + "username": "Username", + "password": "Password", + "name": "Friendly Name", + "certificate": "Certificate", + "continuous": "Continuous", + "delay": "Delay" + } + } + }, + "error": { + "cannot_connect": "Failed to connect, please try again", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "Device is already configured" + } + }, + "options": { + "step": { + "init": { + "data": { + "certificate": "Certificate", + "continuous": "Continuous", + "delay": "Delay" + } + }, + "options": { + "data": { + "certificate": "Certificate", + "continuous": "Continuous", + "delay": "Delay" + } + } + } + } +} diff --git a/homeassistant/components/roomba/.translations/fr.json b/homeassistant/components/roomba/.translations/fr.json new file mode 100644 index 00000000000000..fe488be88d01ed --- /dev/null +++ b/homeassistant/components/roomba/.translations/fr.json @@ -0,0 +1,45 @@ +{ + "config": { + "title": "iRobot Roomba", + "step": { + "user": { + "title": "Connect to the device", + "data": { + "host": "Nom ou Addresse IP", + "username": "Utilisateur", + "password": "Mot de passe", + "name": "Nom", + "certificate": "Certificat", + "continuous": "Continue", + "delay": "Delais" + } + } + }, + "error": { + "cannot_connect": "Failed to connect, please try again", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "Device is already configured" + } + }, + "options": { + "step": { + "init": { + "data": { + "certificate": "Certificat", + "continuous": "Continue", + "delay": "Delais" + } + }, + "options": { + "data": { + "certificate": "Certificate", + "continuous": "Continuous", + "delay": "Delay" + } + } + } + } +} diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index c0e5f68483e9eb..04d705e9178336 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -1 +1,76 @@ """The roomba component.""" +import logging + +import async_timeout +from roomba import Roomba, RoombaConnectionError + +from homeassistant import config_entries +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME + +from .const import CONF_CERT, CONF_CONTINUOUS, CONF_DELAY, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass, config): + """Set up the roomba environment.""" + if DOMAIN not in config: + return True + + if not hass.config_entries.async_entries(DOMAIN): + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={} + ) + ) + + +async def async_setup_entry(hass, config_entry): + """Set the config entry up.""" + # Set up roomba platforms with config entry + if config_entry.data is None: + return False + + if not config_entry.options: + hass.config_entries.async_update_entry( + config_entry, + options={ + "certificate": config_entry.data[CONF_CERT], + "continuous": config_entry.data[CONF_CONTINUOUS], + "delay": config_entry.data[CONF_DELAY], + }, + ) + + if DOMAIN not in hass.data: + hass.data[DOMAIN] = {} + + if "roomba" not in hass.data[DOMAIN]: + roomba = Roomba( + address=config_entry.data[CONF_HOST], + blid=config_entry.data[CONF_USERNAME], + password=config_entry.data[CONF_PASSWORD], + cert_name=config_entry.data[CONF_CERT], + continuous=config_entry.data[CONF_CONTINUOUS], + delay=config_entry.data[CONF_DELAY], + ) + hass.data[DOMAIN]["roomba"] = roomba + try: + with async_timeout.timeout(9): + await hass.async_add_job(roomba.connect) + except RoombaConnectionError: + _LOGGER.error("Error to connect to {}".format(config_entry.data[CONF_HOST])) + return False + + hass.data[DOMAIN]["name"] = config_entry.data[CONF_NAME] + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, "vacuum") + ) + return True + + +async def async_unload_entry(hass, config_entry): + """Unload a config entry.""" + await hass.config_entries.async_forward_entry_unload(config_entry, "vacuum") + roomba = hass.data[DOMAIN]["roomba"] + await hass.async_add_job(roomba.disconnect) + return True diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py new file mode 100644 index 00000000000000..e30598cf7384b3 --- /dev/null +++ b/homeassistant/components/roomba/config_flow.py @@ -0,0 +1,153 @@ +"""Config flow to configure demo component.""" +import logging +import time + +from roomba import Roomba, RoombaConnectionError +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import callback + +from .const import ( + CONF_CERT, + CONF_CONTINUOUS, + CONF_DELAY, + DEFAULT_CERT, + DEFAULT_CONTINUOUS, + DEFAULT_DELAY, + DEFAULT_NAME, + DOMAIN, +) + +DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_HOST): str, + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): str, + vol.Optional(CONF_CERT, default=DEFAULT_CERT): str, + vol.Optional(CONF_CONTINUOUS, default=DEFAULT_CONTINUOUS): bool, + vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): int, + } +) + +_LOGGER = logging.getLogger(__name__) + + +class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Demo configuration flow.""" + + VERSION = 1 + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return OptionsFlowHandler(config_entry) + + async def async_step_import(self, import_info): + """Set the config entry up from yaml.""" + return self.async_create_entry(title="Roomba", data={}) + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + errors = {} + + if DOMAIN not in self.hass.data: + self.hass.data[DOMAIN] = {} + if user_input is not None: + self.host = user_input[CONF_HOST] + self.username = user_input[CONF_USERNAME] + self.password = user_input[CONF_PASSWORD] + self.name = user_input[CONF_NAME] + self.certificate = user_input[CONF_CERT] + self.continuous = user_input[CONF_CONTINUOUS] + self.delay = user_input[CONF_DELAY] + + roomba = Roomba( + address=self.host, + blid=self.username, + password=self.password, + cert_name=self.certificate, + continuous=self.continuous, + delay=self.delay, + ) + _LOGGER.debug("Initializing communication with host %s", self.host) + + try: + await self.hass.async_add_job(roomba.connect) + except RoombaConnectionError: + errors = {"base": "cannot_connect"} + + timeout = time.time() + 1 + while not roomba.roomba_connected and not errors: + if time.time() > timeout: + errors = {"base": "invalid_auth"} + await self.hass.async_add_job(roomba.disconnect) + time.sleep(0.2) + + if roomba.roomba_connected: + self.hass.data[DOMAIN]["roomba"] = roomba + self.hass.data[DOMAIN]["name"] = self.name + return self.async_create_entry( + title=self.name, + data={ + "host": self.host, + "username": self.username, + "password": self.password, + "name": self.name, + "certificate": self.certificate, + "continuous": self.continuous, + "delay": self.delay, + }, + ) + + # If there was no user input, do not show the errors. + if user_input is None: + errors = {} + + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) + + +class OptionsFlowHandler(config_entries.OptionsFlow): + """Handle options.""" + + def __init__(self, config_entry): + """Initialize options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Manage the options.""" + return await self.async_step_options() + + async def async_step_options(self, user_input=None): + """Manage the options.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + return self.async_show_form( + step_id="options", + data_schema=vol.Schema( + { + vol.Optional( + CONF_CERT, + default=self.config_entry.options.get(CONF_CERT, DEFAULT_CERT), + ): str, + vol.Optional( + CONF_CONTINUOUS, + default=self.config_entry.options.get( + CONF_CONTINUOUS, DEFAULT_CONTINUOUS + ), + ): bool, + vol.Optional( + CONF_DELAY, + default=self.config_entry.options.get( + CONF_DELAY, DEFAULT_DELAY + ), + ): int, + } + ), + ) diff --git a/homeassistant/components/roomba/const.py b/homeassistant/components/roomba/const.py new file mode 100644 index 00000000000000..f5c1a1a3d3e913 --- /dev/null +++ b/homeassistant/components/roomba/const.py @@ -0,0 +1,9 @@ +"""The roomba constants.""" +DOMAIN = "roomba" +CONF_CERT = "certificate" +CONF_CONTINUOUS = "continuous" +CONF_DELAY = "delay" +DEFAULT_CERT = "/etc/ssl/certs/ca-certificates.crt" +DEFAULT_CONTINUOUS = True +DEFAULT_DELAY = 1 +DEFAULT_NAME = "Roomba" diff --git a/homeassistant/components/roomba/manifest.json b/homeassistant/components/roomba/manifest.json index bf048cadc8f10e..5c840cc38770ef 100644 --- a/homeassistant/components/roomba/manifest.json +++ b/homeassistant/components/roomba/manifest.json @@ -1,6 +1,7 @@ { "domain": "roomba", "name": "iRobot Roomba", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/roomba", "requirements": [ "roombapy==1.4.3" diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json new file mode 100644 index 00000000000000..99152734c34dfa --- /dev/null +++ b/homeassistant/components/roomba/strings.json @@ -0,0 +1,45 @@ +{ + "config": { + "title": "iRobot Roomba", + "step": { + "user": { + "title": "Connect to the device", + "data": { + "host": "Hostname or IP Address", + "username": "Username", + "password": "Password", + "name": "Friendly Name", + "certificate": "Certificate", + "continuous": "Continuous", + "delay": "Delay" + } + } + }, + "error": { + "cannot_connect": "Failed to connect, please try again", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "Device is already configured" + } + }, + "options": { + "step": { + "init": { + "data": { + "certificate": "Certificate", + "continuous": "Continuous", + "delay": "Delay" + } + }, + "options": { + "data": { + "certificate": "Certificate", + "continuous": "Continuous", + "delay": "Delay" + } + } + } + } +} diff --git a/homeassistant/components/roomba/vacuum.py b/homeassistant/components/roomba/vacuum.py index 172a494b602eaf..5539fc7135cf74 100644 --- a/homeassistant/components/roomba/vacuum.py +++ b/homeassistant/components/roomba/vacuum.py @@ -1,13 +1,7 @@ """Support for Wi-Fi enabled iRobot Roombas.""" -import asyncio import logging -import async_timeout -from roomba import Roomba -import voluptuous as vol - from homeassistant.components.vacuum import ( - PLATFORM_SCHEMA, SUPPORT_BATTERY, SUPPORT_FAN_SPEED, SUPPORT_LOCATE, @@ -20,9 +14,8 @@ SUPPORT_TURN_ON, VacuumDevice, ) -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME -from homeassistant.exceptions import PlatformNotReady -import homeassistant.helpers.config_validation as cv + +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -37,34 +30,11 @@ CAP_POSITION = "position" CAP_CARPET_BOOST = "carpet_boost" -CONF_CERT = "certificate" -CONF_CONTINUOUS = "continuous" -CONF_DELAY = "delay" - -DEFAULT_CERT = "/etc/ssl/certs/ca-certificates.crt" -DEFAULT_CONTINUOUS = True -DEFAULT_DELAY = 1 -DEFAULT_NAME = "Roomba" - -PLATFORM = "roomba" - FAN_SPEED_AUTOMATIC = "Automatic" FAN_SPEED_ECO = "Eco" FAN_SPEED_PERFORMANCE = "Performance" FAN_SPEEDS = [FAN_SPEED_AUTOMATIC, FAN_SPEED_ECO, FAN_SPEED_PERFORMANCE] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_CERT, default=DEFAULT_CERT): cv.string, - vol.Optional(CONF_CONTINUOUS, default=DEFAULT_CONTINUOUS): cv.boolean, - vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): cv.positive_int, - }, - extra=vol.ALLOW_EXTRA, -) # Commonly supported features SUPPORT_ROOMBA = ( @@ -83,39 +53,12 @@ SUPPORT_ROOMBA_CARPET_BOOST = SUPPORT_ROOMBA | SUPPORT_FAN_SPEED -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the iRobot Roomba vacuum cleaner platform.""" - - if PLATFORM not in hass.data: - hass.data[PLATFORM] = {} - - host = config.get(CONF_HOST) - name = config.get(CONF_NAME) - username = config.get(CONF_USERNAME) - password = config.get(CONF_PASSWORD) - certificate = config.get(CONF_CERT) - continuous = config.get(CONF_CONTINUOUS) - delay = config.get(CONF_DELAY) - - roomba = Roomba( - address=host, - blid=username, - password=password, - cert_name=certificate, - continuous=continuous, - delay=delay, - ) - _LOGGER.debug("Initializing communication with host %s", host) - - try: - with async_timeout.timeout(9): - await hass.async_add_job(roomba.connect) - except asyncio.TimeoutError: - raise PlatformNotReady +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the iRobot Roomba vacuum cleaner.""" + name = hass.data[DOMAIN]["name"] + roomba = hass.data[DOMAIN]["roomba"] roomba_vac = RoombaVacuum(name, roomba) - hass.data[PLATFORM][host] = roomba_vac - async_add_entities([roomba_vac], True) @@ -135,6 +78,20 @@ def __init__(self, name, roomba): self.vacuum = roomba self.vacuum_state = None + @property + def unique_id(self): + """Return the uniqueid of the vacuum cleaner.""" + return self._name + + @property + def device_info(self): + """Return the device info of the vacuum cleaner.""" + return { + "identifiers": {(DOMAIN, self.unique_id)}, + "manufacturer": "iRobots", + "name": str(self._name), + } + @property def supported_features(self): """Flag vacuum cleaner robot features that are supported.""" diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 8c03702e8f95a2..6c6d84339d66b9 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -92,6 +92,7 @@ "rainmachine", "ring", "roku", + "roomba", "samsungtv", "sense", "sentry", diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 48441639880de1..6175c77a3b1664 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -658,6 +658,9 @@ ring_doorbell==0.6.0 # homeassistant.components.roku roku==4.0.0 +# homeassistant.components.roomba +roombapy==1.4.3 + # homeassistant.components.yamaha rxv==0.6.0 diff --git a/tests/components/roomba/__init__.py b/tests/components/roomba/__init__.py new file mode 100644 index 00000000000000..a255e21c709c64 --- /dev/null +++ b/tests/components/roomba/__init__.py @@ -0,0 +1 @@ +"""Tests for the iRobot Roomba integration.""" diff --git a/tests/components/roomba/test_config_flow.py b/tests/components/roomba/test_config_flow.py new file mode 100644 index 00000000000000..5ac8ea6149a81b --- /dev/null +++ b/tests/components/roomba/test_config_flow.py @@ -0,0 +1,90 @@ +"""Test the iRobot Roomba config flow.""" +from asynctest import patch + +from homeassistant import config_entries, setup +from homeassistant.components.roomba.config_flow import CannotConnect, InvalidAuth +from homeassistant.components.roomba.const import DOMAIN + + +async def test_form(hass): + """Test we get the form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch( + "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", + return_value=True, + ), patch( + "homeassistant.components.roomba.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.roomba.async_setup_entry", return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "create_entry" + assert result2["title"] == "Name of the device" + assert result2["data"] == { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_invalid_auth(hass): + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", + side_effect=InvalidAuth, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "invalid_auth"} + + +async def test_form_cannot_connect(hass): + """Test we handle cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", + side_effect=CannotConnect, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "cannot_connect"} From a33330515baa09114db49c9d728897a0cc31a09c Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Fri, 27 Mar 2020 07:42:57 +0100 Subject: [PATCH 002/108] Get options to connect --- homeassistant/components/roomba/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 04d705e9178336..d7513b1febbce1 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -49,9 +49,9 @@ async def async_setup_entry(hass, config_entry): address=config_entry.data[CONF_HOST], blid=config_entry.data[CONF_USERNAME], password=config_entry.data[CONF_PASSWORD], - cert_name=config_entry.data[CONF_CERT], - continuous=config_entry.data[CONF_CONTINUOUS], - delay=config_entry.data[CONF_DELAY], + cert_name=config_entry.options[CONF_CERT], + continuous=config_entry.options[CONF_CONTINUOUS], + delay=config_entry.options[CONF_DELAY], ) hass.data[DOMAIN]["roomba"] = roomba try: From bc6d357af931428274d7438f1d9d4cd3771bdcb9 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Fri, 27 Mar 2020 08:23:09 +0100 Subject: [PATCH 003/108] Fix options in config flow --- .../components/roomba/.translations/en.json | 13 +------------ .../components/roomba/.translations/fr.json | 13 +------------ homeassistant/components/roomba/config_flow.py | 6 +----- homeassistant/components/roomba/strings.json | 13 +------------ 4 files changed, 4 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/roomba/.translations/en.json b/homeassistant/components/roomba/.translations/en.json index 99152734c34dfa..b27dfdf8a5245c 100644 --- a/homeassistant/components/roomba/.translations/en.json +++ b/homeassistant/components/roomba/.translations/en.json @@ -17,11 +17,7 @@ }, "error": { "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected error" - }, - "abort": { - "already_configured": "Device is already configured" + "invalid_auth": "Invalid authentication" } }, "options": { @@ -32,13 +28,6 @@ "continuous": "Continuous", "delay": "Delay" } - }, - "options": { - "data": { - "certificate": "Certificate", - "continuous": "Continuous", - "delay": "Delay" - } } } } diff --git a/homeassistant/components/roomba/.translations/fr.json b/homeassistant/components/roomba/.translations/fr.json index fe488be88d01ed..5f2a4176230d0f 100644 --- a/homeassistant/components/roomba/.translations/fr.json +++ b/homeassistant/components/roomba/.translations/fr.json @@ -17,11 +17,7 @@ }, "error": { "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected error" - }, - "abort": { - "already_configured": "Device is already configured" + "invalid_auth": "Invalid authentication" } }, "options": { @@ -32,13 +28,6 @@ "continuous": "Continue", "delay": "Delais" } - }, - "options": { - "data": { - "certificate": "Certificate", - "continuous": "Continuous", - "delay": "Delay" - } } } } diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index e30598cf7384b3..418ec46227ce02 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -120,16 +120,12 @@ def __init__(self, config_entry): self.config_entry = config_entry async def async_step_init(self, user_input=None): - """Manage the options.""" - return await self.async_step_options() - - async def async_step_options(self, user_input=None): """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) return self.async_show_form( - step_id="options", + step_id="init", data_schema=vol.Schema( { vol.Optional( diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json index 99152734c34dfa..b27dfdf8a5245c 100644 --- a/homeassistant/components/roomba/strings.json +++ b/homeassistant/components/roomba/strings.json @@ -17,11 +17,7 @@ }, "error": { "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected error" - }, - "abort": { - "already_configured": "Device is already configured" + "invalid_auth": "Invalid authentication" } }, "options": { @@ -32,13 +28,6 @@ "continuous": "Continuous", "delay": "Delay" } - }, - "options": { - "data": { - "certificate": "Certificate", - "continuous": "Continuous", - "delay": "Delay" - } } } } From b80cb1071af90da1f291e98a0b6e198d6d5bed49 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Fri, 27 Mar 2020 09:29:48 +0100 Subject: [PATCH 004/108] Fix syntax in config_flow --- homeassistant/components/roomba/__init__.py | 2 +- homeassistant/components/roomba/config_flow.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index d7513b1febbce1..2efc16df425d2a 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -58,7 +58,7 @@ async def async_setup_entry(hass, config_entry): with async_timeout.timeout(9): await hass.async_add_job(roomba.connect) except RoombaConnectionError: - _LOGGER.error("Error to connect to {}".format(config_entry.data[CONF_HOST])) + _LOGGER.error("Error to connect to %s", config_entry.data[CONF_HOST]) return False hass.data[DOMAIN]["name"] = config_entry.data[CONF_NAME] diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 418ec46227ce02..c3dad7cb67d3d9 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -57,13 +57,13 @@ async def async_step_user(self, user_input=None): if DOMAIN not in self.hass.data: self.hass.data[DOMAIN] = {} if user_input is not None: - self.host = user_input[CONF_HOST] - self.username = user_input[CONF_USERNAME] - self.password = user_input[CONF_PASSWORD] - self.name = user_input[CONF_NAME] - self.certificate = user_input[CONF_CERT] - self.continuous = user_input[CONF_CONTINUOUS] - self.delay = user_input[CONF_DELAY] + self.host = user_input["host"] + self.username = user_input["username"] + self.password = user_input["password"] + self.name = user_input["name"] + self.certificate = user_input["certificate"] + self.continuous = user_input["continuous"] + self.delay = user_input["delay"] roomba = Roomba( address=self.host, From cf737d1c502cf97266b009844e68b0b00ac46a03 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 28 Mar 2020 17:54:19 +0100 Subject: [PATCH 005/108] Remove name (not necessary) --- .../components/roomba/.translations/en.json | 1 - .../components/roomba/.translations/fr.json | 1 - .../components/roomba/config_flow.py | 32 +++++++++---------- homeassistant/components/roomba/strings.json | 1 - 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/roomba/.translations/en.json b/homeassistant/components/roomba/.translations/en.json index b27dfdf8a5245c..7ebffc47c0e71b 100644 --- a/homeassistant/components/roomba/.translations/en.json +++ b/homeassistant/components/roomba/.translations/en.json @@ -8,7 +8,6 @@ "host": "Hostname or IP Address", "username": "Username", "password": "Password", - "name": "Friendly Name", "certificate": "Certificate", "continuous": "Continuous", "delay": "Delay" diff --git a/homeassistant/components/roomba/.translations/fr.json b/homeassistant/components/roomba/.translations/fr.json index 5f2a4176230d0f..e31daab2a51eb5 100644 --- a/homeassistant/components/roomba/.translations/fr.json +++ b/homeassistant/components/roomba/.translations/fr.json @@ -8,7 +8,6 @@ "host": "Nom ou Addresse IP", "username": "Utilisateur", "password": "Mot de passe", - "name": "Nom", "certificate": "Certificat", "continuous": "Continue", "delay": "Delais" diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index c3dad7cb67d3d9..c6d30cc3d49390 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -1,12 +1,13 @@ """Config flow to configure demo component.""" +import asyncio import logging -import time +import async_timeout from roomba import Roomba, RoombaConnectionError import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback from .const import ( @@ -16,7 +17,6 @@ DEFAULT_CERT, DEFAULT_CONTINUOUS, DEFAULT_DELAY, - DEFAULT_NAME, DOMAIN, ) @@ -25,7 +25,6 @@ vol.Required(CONF_HOST): str, vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): str, vol.Optional(CONF_CERT, default=DEFAULT_CERT): str, vol.Optional(CONF_CONTINUOUS, default=DEFAULT_CONTINUOUS): bool, vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): int, @@ -56,11 +55,12 @@ async def async_step_user(self, user_input=None): if DOMAIN not in self.hass.data: self.hass.data[DOMAIN] = {} + if user_input is not None: + self.name = None self.host = user_input["host"] self.username = user_input["username"] self.password = user_input["password"] - self.name = user_input["name"] self.certificate = user_input["certificate"] self.continuous = user_input["continuous"] self.delay = user_input["delay"] @@ -76,27 +76,27 @@ async def async_step_user(self, user_input=None): _LOGGER.debug("Initializing communication with host %s", self.host) try: - await self.hass.async_add_job(roomba.connect) - except RoombaConnectionError: + with async_timeout.timeout(10): + await self.hass.async_add_job(roomba.connect) + while not roomba.roomba_connected: + await asyncio.sleep(0.5) + except RoombaConnectionError as exc: + _LOGGER.error(f"Error: {exc}") errors = {"base": "cannot_connect"} - - timeout = time.time() + 1 - while not roomba.roomba_connected and not errors: - if time.time() > timeout: - errors = {"base": "invalid_auth"} - await self.hass.async_add_job(roomba.disconnect) - time.sleep(0.2) + except asyncio.TimeoutError: + _LOGGER.error("Error: Timeout exceeded, user or password incorrect") + # Api looping if user or password incorrect and roomba exist + await self.hass.async_add_job(roomba.disconnect) + errors = {"base": "invalid_auth"} if roomba.roomba_connected: self.hass.data[DOMAIN]["roomba"] = roomba - self.hass.data[DOMAIN]["name"] = self.name return self.async_create_entry( title=self.name, data={ "host": self.host, "username": self.username, "password": self.password, - "name": self.name, "certificate": self.certificate, "continuous": self.continuous, "delay": self.delay, diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json index b27dfdf8a5245c..7ebffc47c0e71b 100644 --- a/homeassistant/components/roomba/strings.json +++ b/homeassistant/components/roomba/strings.json @@ -8,7 +8,6 @@ "host": "Hostname or IP Address", "username": "Username", "password": "Password", - "name": "Friendly Name", "certificate": "Certificate", "continuous": "Continuous", "delay": "Delay" From 52f901b6205b903d06f1dcc95307cbe45f4c1cb6 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 28 Mar 2020 18:02:41 +0100 Subject: [PATCH 006/108] Add bin sensor --- .../components/roomba/binary_sensor.py | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 homeassistant/components/roomba/binary_sensor.py diff --git a/homeassistant/components/roomba/binary_sensor.py b/homeassistant/components/roomba/binary_sensor.py new file mode 100644 index 00000000000000..2135310721e2a7 --- /dev/null +++ b/homeassistant/components/roomba/binary_sensor.py @@ -0,0 +1,76 @@ +"""Roomba binary sensor entities.""" +import logging + +from homeassistant.components.binary_sensor import BinarySensorDevice + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the iRobot Roomba vacuum cleaner.""" + roomba = hass.data[DOMAIN]["roomba"] + status = roomba.master_state.get("state", {}).get("reported", {}).get("bin", {}) + if "full" in status: + roomba_vac = RoombaBinStatus(roomba) + async_add_entities([roomba_vac], True) + + +class RoombaBinStatus(BinarySensorDevice): + """Class to hold Roomba Sensor basic info.""" + + ICON = "mdi:delete-variant" + + def __init__(self, roomba): + """Initialize the sensor object.""" + self.vacuum = roomba + self.vacuum_state = self.vacuum.master_state.get("state", {}).get( + "reported", {} + ) + self._mac = self.vacuum_state.get("mac") + self._name = self.vacuum_state.get("name") + self._identifier = f"roomba_{self._mac}" + self._bin_status = None + + @property + def name(self): + """Return the name of the sensor.""" + return f"{self._name} bin full" + + @property + def unique_id(self): + """Return the ID of this sensor.""" + return f"bin_{self._mac}" + + @property + def icon(self): + """Return the icon of this sensor.""" + return self.ICON + + @property + def state(self): + """Return the state of the sensor.""" + return self._bin_status == "Full" + + @property + def device_info(self): + """Return the device info of the vacuum cleaner.""" + return { + "identifiers": {(DOMAIN, self._identifier)}, + "name": str(self._name), + } + + async def async_update(self): + """Return the update info of the vacuum cleaner.""" + # No data, no update + if not self.vacuum.master_state: + _LOGGER.debug("Roomba %s has no data yet. Skip update", self.name) + return + self._bin_status = ( + self.vacuum.master_state.get("state", {}) + .get("reported", {}) + .get("bin", {}) + .get("full", False) + ) + _LOGGER.debug("Update Full Bin status from the vacuum: %s", self._bin_status) From bd8a95bcc7648f029d5996aff724ceb78e727083 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 28 Mar 2020 18:03:44 +0100 Subject: [PATCH 007/108] Add Battery sensor --- homeassistant/components/roomba/sensor.py | 77 +++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 homeassistant/components/roomba/sensor.py diff --git a/homeassistant/components/roomba/sensor.py b/homeassistant/components/roomba/sensor.py new file mode 100644 index 00000000000000..72d990172231a3 --- /dev/null +++ b/homeassistant/components/roomba/sensor.py @@ -0,0 +1,77 @@ +"""Sensor for checking the battery level of Roomba.""" +import logging + +from homeassistant.const import DEVICE_CLASS_BATTERY +from homeassistant.helpers.entity import Entity + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the iRobot Roomba vacuum cleaner.""" + roomba = hass.data[DOMAIN]["roomba"] + roomba_vac = RoombaBattery(roomba) + async_add_entities([roomba_vac], True) + + +class RoombaBattery(Entity): + """Class to hold Roomba Sensor basic info.""" + + def __init__(self, roomba): + """Initialize the sensor object.""" + self.vacuum = roomba + self.vacuum_state = self.vacuum.master_state.get("state", {}).get( + "reported", {} + ) + self._mac = self.vacuum_state.get("mac") + self._name = self.vacuum_state.get("name") + self._identifier = f"roomba_{self._mac}" + self._battery_level = None + + @property + def name(self): + """Return the name of the sensor.""" + return f"{self._name} battery level" + + @property + def unique_id(self): + """Return the ID of this sensor.""" + return f"battery_{self._mac}" + + @property + def device_class(self): + """Return the device class of the sensor.""" + return DEVICE_CLASS_BATTERY + + @property + def unit_of_measurement(self): + """Return the unit_of_measurement of the device.""" + return "%" + + @property + def state(self): + """Return the state of the sensor.""" + return self._battery_level + + @property + def device_info(self): + """Return the device info of the vacuum cleaner.""" + return { + "identifiers": {(DOMAIN, self._identifier)}, + "name": str(self._name), + } + + async def async_update(self): + """Return the update info of the vacuum cleaner.""" + # No data, no update + if not self.vacuum.master_state: + _LOGGER.debug("Roomba %s has no data yet. Skip update", self.name) + return + self._battery_level = ( + self.vacuum.master_state.get("state", {}).get("reported", {}).get("batPct") + ) + _LOGGER.debug( + "Update battery level status from the vacuum: %s", self._battery_level + ) From 0937fd5c3311febc2f88ca6bb20c4c47c30529fd Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 28 Mar 2020 18:06:08 +0100 Subject: [PATCH 008/108] Add async_connect --- homeassistant/components/roomba/__init__.py | 79 ++++++++++++++------- homeassistant/components/roomba/const.py | 2 +- 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 2efc16df425d2a..c95115d5707c2e 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -1,13 +1,14 @@ """The roomba component.""" +import asyncio import logging import async_timeout -from roomba import Roomba, RoombaConnectionError +from roomba import Roomba from homeassistant import config_entries -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from .const import CONF_CERT, CONF_CONTINUOUS, CONF_DELAY, DOMAIN +from .const import COMPONENTS, CONF_CERT, CONF_CONTINUOUS, CONF_DELAY, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -44,33 +45,63 @@ async def async_setup_entry(hass, config_entry): if DOMAIN not in hass.data: hass.data[DOMAIN] = {} - if "roomba" not in hass.data[DOMAIN]: - roomba = Roomba( - address=config_entry.data[CONF_HOST], - blid=config_entry.data[CONF_USERNAME], - password=config_entry.data[CONF_PASSWORD], - cert_name=config_entry.options[CONF_CERT], - continuous=config_entry.options[CONF_CONTINUOUS], - delay=config_entry.options[CONF_DELAY], + config_entry.add_update_listener(async_connect) + if not await async_connect(hass, config_entry): + return False + + for component in COMPONENTS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, component) ) - hass.data[DOMAIN]["roomba"] = roomba - try: - with async_timeout.timeout(9): - await hass.async_add_job(roomba.connect) - except RoombaConnectionError: - _LOGGER.error("Error to connect to %s", config_entry.data[CONF_HOST]) - return False - - hass.data[DOMAIN]["name"] = config_entry.data[CONF_NAME] - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, "vacuum") - ) + return True async def async_unload_entry(hass, config_entry): """Unload a config entry.""" - await hass.config_entries.async_forward_entry_unload(config_entry, "vacuum") + for component in COMPONENTS: + hass.async_create_task( + hass.config_entries.async_forward_entry_unload(config_entry, component) + ) roomba = hass.data[DOMAIN]["roomba"] await hass.async_add_job(roomba.disconnect) return True + + +async def async_connect(hass, config_entry): + """Connect to vacuum.""" + # Check if triggred listerner + if "roomba" in hass.data[DOMAIN]: + await hass.async_add_job(hass.data[DOMAIN]["roomba"].disconnect) + await asyncio.sleep(1) + + roomba = Roomba( + address=config_entry.data[CONF_HOST], + blid=config_entry.data[CONF_USERNAME], + password=config_entry.data[CONF_PASSWORD], + cert_name=config_entry.options[CONF_CERT], + continuous=config_entry.options[CONF_CONTINUOUS], + delay=config_entry.options[CONF_DELAY], + ) + + hass.data[DOMAIN]["roomba"] = roomba + + try: + name = None + with async_timeout.timeout(9): + await hass.async_add_job(roomba.connect) + while not roomba.roomba_connected or name is None: + # Waiting for connection and check datas ready + name = ( + roomba.master_state.get("state", {}) + .get("reported", {}) + .get("name", None) + ) + await asyncio.sleep(0.5) + except asyncio.TimeoutError: + # api looping if user or password incorrect and roomba exist + _LOGGER.error("Timeout exceeded") + await hass.async_add_job(roomba.disconnect) + return False + + return True diff --git a/homeassistant/components/roomba/const.py b/homeassistant/components/roomba/const.py index f5c1a1a3d3e913..803a1ad87ff272 100644 --- a/homeassistant/components/roomba/const.py +++ b/homeassistant/components/roomba/const.py @@ -1,9 +1,9 @@ """The roomba constants.""" DOMAIN = "roomba" +COMPONENTS = ["sensor", "binary_sensor", "vacuum"] CONF_CERT = "certificate" CONF_CONTINUOUS = "continuous" CONF_DELAY = "delay" DEFAULT_CERT = "/etc/ssl/certs/ca-certificates.crt" DEFAULT_CONTINUOUS = True DEFAULT_DELAY = 1 -DEFAULT_NAME = "Roomba" From 2d9e51d69da9f30eade0dc6b60579939fb5ef5b2 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 28 Mar 2020 18:07:17 +0100 Subject: [PATCH 009/108] Fix typo --- homeassistant/components/roomba/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index c95115d5707c2e..cca6e7648de1c4 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -70,7 +70,7 @@ async def async_unload_entry(hass, config_entry): async def async_connect(hass, config_entry): """Connect to vacuum.""" - # Check if triggred listerner + # Check if triggered listener if "roomba" in hass.data[DOMAIN]: await hass.async_add_job(hass.data[DOMAIN]["roomba"].disconnect) await asyncio.sleep(1) From 501e3e2eaf7022012d9c48571c83fd8b20bbb916 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 28 Mar 2020 18:08:34 +0100 Subject: [PATCH 010/108] Add Model and Software version --- homeassistant/components/roomba/vacuum.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/roomba/vacuum.py b/homeassistant/components/roomba/vacuum.py index 5539fc7135cf74..84b77a1c669aa4 100644 --- a/homeassistant/components/roomba/vacuum.py +++ b/homeassistant/components/roomba/vacuum.py @@ -14,6 +14,7 @@ SUPPORT_TURN_ON, VacuumDevice, ) +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from .const import DOMAIN @@ -55,41 +56,49 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the iRobot Roomba vacuum cleaner.""" - - name = hass.data[DOMAIN]["name"] roomba = hass.data[DOMAIN]["roomba"] - roomba_vac = RoombaVacuum(name, roomba) + roomba_vac = RoombaVacuum(roomba) async_add_entities([roomba_vac], True) class RoombaVacuum(VacuumDevice): """Representation of a Roomba Vacuum cleaner robot.""" - def __init__(self, name, roomba): + def __init__(self, roomba): """Initialize the Roomba handler.""" self._available = False self._battery_level = None self._capabilities = {} self._fan_speed = None self._is_on = False - self._name = name self._state_attrs = {} self._status = None self.vacuum = roomba - self.vacuum_state = None + self.vacuum_state = self.vacuum.master_state.get("state", {}).get( + "reported", {} + ) + self._mac = self.vacuum_state.get("mac") + self._name = self.vacuum_state.get("name") @property def unique_id(self): """Return the uniqueid of the vacuum cleaner.""" - return self._name + return "roomba_{}".format(self._mac) @property def device_info(self): """Return the device info of the vacuum cleaner.""" return { "identifiers": {(DOMAIN, self.unique_id)}, + "connections": {(CONNECTION_NETWORK_MAC, self._mac)}, "manufacturer": "iRobots", "name": str(self._name), + "sw_version": self.vacuum.master_state.get("state", {}) + .get("reported", {}) + .get("softwareVer"), + "model": self.vacuum.master_state.get("state", {}) + .get("reported", {}) + .get("sku"), } @property From e2284ba6a4abac4a7f07584322ee76ca406e8ad9 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 28 Mar 2020 18:18:21 +0100 Subject: [PATCH 011/108] Update Roombapy 1.5.0 --- homeassistant/components/roomba/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roomba/manifest.json b/homeassistant/components/roomba/manifest.json index 5c840cc38770ef..b61a4651cc3321 100644 --- a/homeassistant/components/roomba/manifest.json +++ b/homeassistant/components/roomba/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/roomba", "requirements": [ - "roombapy==1.4.3" + "roombapy==1.5.0" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index 5e0c9f762aa943..7e3946f609c042 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1804,7 +1804,7 @@ rocketchat-API==0.6.1 roku==4.0.0 # homeassistant.components.roomba -roombapy==1.4.3 +roombapy==1.5.0 # homeassistant.components.rova rova==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6175c77a3b1664..fd6287a2226981 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -659,7 +659,7 @@ ring_doorbell==0.6.0 roku==4.0.0 # homeassistant.components.roomba -roombapy==1.4.3 +roombapy==1.5.0 # homeassistant.components.yamaha rxv==0.6.0 From 43616ac8ca7ab9327ff051022b309d41428a96c3 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 28 Mar 2020 20:13:05 +0100 Subject: [PATCH 012/108] Add validate_input --- .../components/roomba/.translations/en.json | 1 + .../components/roomba/.translations/fr.json | 5 +- homeassistant/components/roomba/__init__.py | 3 +- .../components/roomba/config_flow.py | 94 ++++++++++--------- homeassistant/components/roomba/strings.json | 1 + 5 files changed, 58 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/roomba/.translations/en.json b/homeassistant/components/roomba/.translations/en.json index 7ebffc47c0e71b..aaf4518d5fd4ae 100644 --- a/homeassistant/components/roomba/.translations/en.json +++ b/homeassistant/components/roomba/.translations/en.json @@ -15,6 +15,7 @@ } }, "error": { + "unknown" : "Unexpected error", "cannot_connect": "Failed to connect, please try again", "invalid_auth": "Invalid authentication" } diff --git a/homeassistant/components/roomba/.translations/fr.json b/homeassistant/components/roomba/.translations/fr.json index e31daab2a51eb5..4ac792d9cd5b73 100644 --- a/homeassistant/components/roomba/.translations/fr.json +++ b/homeassistant/components/roomba/.translations/fr.json @@ -15,8 +15,9 @@ } }, "error": { - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication" + "unknown" : "Erreur imprévu", + "cannot_connect": "Impossible de se connecter", + "invalid_auth": "Authentification invalide" } }, "options": { diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index cca6e7648de1c4..a933b99bceca3e 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -84,8 +84,6 @@ async def async_connect(hass, config_entry): delay=config_entry.options[CONF_DELAY], ) - hass.data[DOMAIN]["roomba"] = roomba - try: name = None with async_timeout.timeout(9): @@ -98,6 +96,7 @@ async def async_connect(hass, config_entry): .get("name", None) ) await asyncio.sleep(0.5) + hass.data[DOMAIN]["roomba"] = roomba except asyncio.TimeoutError: # api looping if user or password incorrect and roomba exist _LOGGER.error("Timeout exceeded") diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index c6d30cc3d49390..9fde62e3c3b5f1 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -6,7 +6,7 @@ from roomba import Roomba, RoombaConnectionError import voluptuous as vol -from homeassistant import config_entries +from homeassistant import config_entries, core, exceptions from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback @@ -34,6 +34,41 @@ _LOGGER = logging.getLogger(__name__) +async def validate_input(hass: core.HomeAssistant, data): + """Validate the user input allows us to connect. + + Data has the keys from DATA_SCHEMA with values provided by the user. + """ + api = Roomba( + address=data[CONF_HOST], + blid=data[CONF_USERNAME], + password=data[CONF_PASSWORD], + cert_name=data[CONF_CERT], + continuous=data[CONF_CONTINUOUS], + delay=data[CONF_DELAY], + ) + try: + name = None + with async_timeout.timeout(10): + await hass.async_add_job(api.connect) + while not api.roomba_connected or name is None: + name = ( + api.master_state.get("state", {}) + .get("reported", {}) + .get("name", None) + ) + await asyncio.sleep(0.5) + hass.data[DOMAIN]["roomba"] = api + except RoombaConnectionError: + raise CannotConnect + except asyncio.TimeoutError: + # Api looping if user or password incorrect and roomba exist + await hass.async_add_job(api.disconnect) + raise InvalidAuth + + return {"title": name} + + class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Demo configuration flow.""" @@ -57,51 +92,18 @@ async def async_step_user(self, user_input=None): self.hass.data[DOMAIN] = {} if user_input is not None: - self.name = None - self.host = user_input["host"] - self.username = user_input["username"] - self.password = user_input["password"] - self.certificate = user_input["certificate"] - self.continuous = user_input["continuous"] - self.delay = user_input["delay"] - - roomba = Roomba( - address=self.host, - blid=self.username, - password=self.password, - cert_name=self.certificate, - continuous=self.continuous, - delay=self.delay, - ) - _LOGGER.debug("Initializing communication with host %s", self.host) - try: - with async_timeout.timeout(10): - await self.hass.async_add_job(roomba.connect) - while not roomba.roomba_connected: - await asyncio.sleep(0.5) - except RoombaConnectionError as exc: - _LOGGER.error(f"Error: {exc}") + info = await validate_input(self.hass, user_input) + except CannotConnect: errors = {"base": "cannot_connect"} - except asyncio.TimeoutError: - _LOGGER.error("Error: Timeout exceeded, user or password incorrect") - # Api looping if user or password incorrect and roomba exist - await self.hass.async_add_job(roomba.disconnect) + except InvalidAuth: errors = {"base": "invalid_auth"} + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" - if roomba.roomba_connected: - self.hass.data[DOMAIN]["roomba"] = roomba - return self.async_create_entry( - title=self.name, - data={ - "host": self.host, - "username": self.username, - "password": self.password, - "certificate": self.certificate, - "continuous": self.continuous, - "delay": self.delay, - }, - ) + if "base" not in errors: + return self.async_create_entry(title=info["title"], data=user_input) # If there was no user input, do not show the errors. if user_input is None: @@ -147,3 +149,11 @@ async def async_step_init(self, user_input=None): } ), ) + + +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" + + +class InvalidAuth(exceptions.HomeAssistantError): + """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json index 7ebffc47c0e71b..f34f1214c2bb1a 100644 --- a/homeassistant/components/roomba/strings.json +++ b/homeassistant/components/roomba/strings.json @@ -15,6 +15,7 @@ } }, "error": { + "unknown" : "Unexpected error", "cannot_connect": "Failed to connect, please try again", "invalid_auth": "Invalid authentication" } From 82016b708bed9ff657014fee13aa09749a96ee91 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 29 Mar 2020 15:57:24 +0200 Subject: [PATCH 013/108] Add connect and disconnect functions --- homeassistant/components/roomba/__init__.py | 95 ++++++++++++------- .../components/roomba/config_flow.py | 46 ++------- 2 files changed, 69 insertions(+), 72 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index a933b99bceca3e..10eb24a4f1f1ec 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -3,9 +3,9 @@ import logging import async_timeout -from roomba import Roomba +from roomba import Roomba, RoombaConnectionError -from homeassistant import config_entries +from homeassistant import config_entries, exceptions from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from .const import COMPONENTS, CONF_CERT, CONF_CONTINUOUS, CONF_DELAY, DOMAIN @@ -45,48 +45,35 @@ async def async_setup_entry(hass, config_entry): if DOMAIN not in hass.data: hass.data[DOMAIN] = {} - config_entry.add_update_listener(async_connect) - if not await async_connect(hass, config_entry): - return False + if "roomba" not in hass.data[DOMAIN]: - for component in COMPONENTS: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + roomba = Roomba( + address=config_entry.data[CONF_HOST], + blid=config_entry.data[CONF_USERNAME], + password=config_entry.data[CONF_PASSWORD], + cert_name=config_entry.options[CONF_CERT], + continuous=config_entry.options[CONF_CONTINUOUS], + delay=config_entry.options[CONF_DELAY], ) - return True + if not await async_connect_or_timeout(hass, roomba): + return False - -async def async_unload_entry(hass, config_entry): - """Unload a config entry.""" for component in COMPONENTS: hass.async_create_task( - hass.config_entries.async_forward_entry_unload(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, component) ) - roomba = hass.data[DOMAIN]["roomba"] - await hass.async_add_job(roomba.disconnect) + + config_entry.add_update_listener(async_update_options) + return True -async def async_connect(hass, config_entry): +async def async_connect_or_timeout(hass, roomba): """Connect to vacuum.""" - # Check if triggered listener - if "roomba" in hass.data[DOMAIN]: - await hass.async_add_job(hass.data[DOMAIN]["roomba"].disconnect) - await asyncio.sleep(1) - - roomba = Roomba( - address=config_entry.data[CONF_HOST], - blid=config_entry.data[CONF_USERNAME], - password=config_entry.data[CONF_PASSWORD], - cert_name=config_entry.options[CONF_CERT], - continuous=config_entry.options[CONF_CONTINUOUS], - delay=config_entry.options[CONF_DELAY], - ) - try: - name = None - with async_timeout.timeout(9): + hass.data[DOMAIN]["name"] = name = None + with async_timeout.timeout(10): await hass.async_add_job(roomba.connect) while not roomba.roomba_connected or name is None: # Waiting for connection and check datas ready @@ -96,11 +83,47 @@ async def async_connect(hass, config_entry): .get("name", None) ) await asyncio.sleep(0.5) - hass.data[DOMAIN]["roomba"] = roomba + hass.data[DOMAIN]["roomba"] = roomba + hass.data[DOMAIN]["name"] = name + except RoombaConnectionError: + _LOGGER.error("Error to connect to vacuum") + raise CannotConnect except asyncio.TimeoutError: # api looping if user or password incorrect and roomba exist - _LOGGER.error("Timeout exceeded") - await hass.async_add_job(roomba.disconnect) - return False + await async_disconnect_or_timeout(hass, roomba) + _LOGGER.exception("Timeout expired, user or password incorrect") + raise InvalidAuth + + return True + +async def async_disconnect_or_timeout(hass, roomba): + """Disconnect to vacuum.""" + await hass.async_add_job(roomba.disconnect) + await asyncio.sleep(1) return True + + +async def async_update_options(hass, config_entry): + """Update options.""" + roomba = hass.data[DOMAIN]["roomba"] + await async_disconnect_or_timeout(hass, roomba) + await async_connect_or_timeout(hass, roomba) + + +async def async_unload_entry(hass, config_entry): + """Unload a config entry.""" + for component in COMPONENTS: + hass.async_create_task( + hass.config_entries.async_forward_entry_unload(config_entry, component) + ) + roomba = hass.data[DOMAIN]["roomba"] + return await async_disconnect_or_timeout(hass, roomba) + + +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" + + +class InvalidAuth(exceptions.HomeAssistantError): + """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 9fde62e3c3b5f1..4324e21dba4c8d 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -1,15 +1,14 @@ """Config flow to configure demo component.""" -import asyncio import logging -import async_timeout -from roomba import Roomba, RoombaConnectionError +from roomba import Roomba import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant import config_entries, core from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback +from . import CannotConnect, InvalidAuth, async_connect_or_timeout from .const import ( CONF_CERT, CONF_CONTINUOUS, @@ -39,7 +38,7 @@ async def validate_input(hass: core.HomeAssistant, data): Data has the keys from DATA_SCHEMA with values provided by the user. """ - api = Roomba( + roomba = Roomba( address=data[CONF_HOST], blid=data[CONF_USERNAME], password=data[CONF_PASSWORD], @@ -47,26 +46,8 @@ async def validate_input(hass: core.HomeAssistant, data): continuous=data[CONF_CONTINUOUS], delay=data[CONF_DELAY], ) - try: - name = None - with async_timeout.timeout(10): - await hass.async_add_job(api.connect) - while not api.roomba_connected or name is None: - name = ( - api.master_state.get("state", {}) - .get("reported", {}) - .get("name", None) - ) - await asyncio.sleep(0.5) - hass.data[DOMAIN]["roomba"] = api - except RoombaConnectionError: - raise CannotConnect - except asyncio.TimeoutError: - # Api looping if user or password incorrect and roomba exist - await hass.async_add_job(api.disconnect) - raise InvalidAuth - return {"title": name} + await async_connect_or_timeout(hass, roomba) class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -93,17 +74,18 @@ async def async_step_user(self, user_input=None): if user_input is not None: try: - info = await validate_input(self.hass, user_input) + await validate_input(self.hass, user_input) except CannotConnect: errors = {"base": "cannot_connect"} except InvalidAuth: errors = {"base": "invalid_auth"} except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" + errors = {"base": "unknown"} if "base" not in errors: - return self.async_create_entry(title=info["title"], data=user_input) + return self.async_create_entry( + title=self.hass.data[DOMAIN]["name"], data=user_input + ) # If there was no user input, do not show the errors. if user_input is None: @@ -149,11 +131,3 @@ async def async_step_init(self, user_input=None): } ), ) - - -class CannotConnect(exceptions.HomeAssistantError): - """Error to indicate we cannot connect.""" - - -class InvalidAuth(exceptions.HomeAssistantError): - """Error to indicate there is invalid auth.""" From 098621e049269f79c77275e73ce8c09ebb83a01a Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 29 Mar 2020 19:23:00 +0200 Subject: [PATCH 014/108] Remove test config flow --- requirements_test_all.txt | 3 - tests/components/roomba/__init__.py | 1 - tests/components/roomba/test_config_flow.py | 90 --------------------- 3 files changed, 94 deletions(-) delete mode 100644 tests/components/roomba/__init__.py delete mode 100644 tests/components/roomba/test_config_flow.py diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fd6287a2226981..48441639880de1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -658,9 +658,6 @@ ring_doorbell==0.6.0 # homeassistant.components.roku roku==4.0.0 -# homeassistant.components.roomba -roombapy==1.5.0 - # homeassistant.components.yamaha rxv==0.6.0 diff --git a/tests/components/roomba/__init__.py b/tests/components/roomba/__init__.py deleted file mode 100644 index a255e21c709c64..00000000000000 --- a/tests/components/roomba/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the iRobot Roomba integration.""" diff --git a/tests/components/roomba/test_config_flow.py b/tests/components/roomba/test_config_flow.py deleted file mode 100644 index 5ac8ea6149a81b..00000000000000 --- a/tests/components/roomba/test_config_flow.py +++ /dev/null @@ -1,90 +0,0 @@ -"""Test the iRobot Roomba config flow.""" -from asynctest import patch - -from homeassistant import config_entries, setup -from homeassistant.components.roomba.config_flow import CannotConnect, InvalidAuth -from homeassistant.components.roomba.const import DOMAIN - - -async def test_form(hass): - """Test we get the form.""" - await setup.async_setup_component(hass, "persistent_notification", {}) - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - assert result["type"] == "form" - assert result["errors"] == {} - - with patch( - "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", - return_value=True, - ), patch( - "homeassistant.components.roomba.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.roomba.async_setup_entry", return_value=True, - ) as mock_setup_entry: - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "host": "1.1.1.1", - "username": "test-username", - "password": "test-password", - }, - ) - - assert result2["type"] == "create_entry" - assert result2["title"] == "Name of the device" - assert result2["data"] == { - "host": "1.1.1.1", - "username": "test-username", - "password": "test-password", - } - await hass.async_block_till_done() - assert len(mock_setup.mock_calls) == 1 - assert len(mock_setup_entry.mock_calls) == 1 - - -async def test_form_invalid_auth(hass): - """Test we handle invalid auth.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - with patch( - "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", - side_effect=InvalidAuth, - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "host": "1.1.1.1", - "username": "test-username", - "password": "test-password", - }, - ) - - assert result2["type"] == "form" - assert result2["errors"] == {"base": "invalid_auth"} - - -async def test_form_cannot_connect(hass): - """Test we handle cannot connect error.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - with patch( - "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", - side_effect=CannotConnect, - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "host": "1.1.1.1", - "username": "test-username", - "password": "test-password", - }, - ) - - assert result2["type"] == "form" - assert result2["errors"] == {"base": "cannot_connect"} From a2de1d01f565b7a5044db01763c37af2f85d762a Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 29 Mar 2020 19:43:43 +0200 Subject: [PATCH 015/108] Add variables after loop --- homeassistant/components/roomba/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 10eb24a4f1f1ec..67eeead28044e3 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -83,8 +83,8 @@ async def async_connect_or_timeout(hass, roomba): .get("name", None) ) await asyncio.sleep(0.5) - hass.data[DOMAIN]["roomba"] = roomba - hass.data[DOMAIN]["name"] = name + hass.data[DOMAIN]["roomba"] = roomba + hass.data[DOMAIN]["name"] = name except RoombaConnectionError: _LOGGER.error("Error to connect to vacuum") raise CannotConnect From 29b51a60b8482392633980ecf987b9d270e27431 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 29 Mar 2020 19:46:42 +0200 Subject: [PATCH 016/108] Fix translate --- homeassistant/components/roomba/.translations/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/.translations/fr.json b/homeassistant/components/roomba/.translations/fr.json index 4ac792d9cd5b73..17ee2455f4e056 100644 --- a/homeassistant/components/roomba/.translations/fr.json +++ b/homeassistant/components/roomba/.translations/fr.json @@ -3,7 +3,7 @@ "title": "iRobot Roomba", "step": { "user": { - "title": "Connect to the device", + "title": "Connexion au périphérique", "data": { "host": "Nom ou Addresse IP", "username": "Utilisateur", From 2ce5b5fe3a2a5f8966920158ce09f406a4a13c35 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 29 Mar 2020 19:50:08 +0200 Subject: [PATCH 017/108] Fix typo --- homeassistant/components/roomba/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 4324e21dba4c8d..295e3e25e19cbc 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -1,4 +1,4 @@ -"""Config flow to configure demo component.""" +"""Config flow to configure roomba component.""" import logging from roomba import Roomba From 3d86cbcd2228a1d2f47f33537758d36830159a34 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 29 Mar 2020 19:58:25 +0200 Subject: [PATCH 018/108] Fix state of bin --- homeassistant/components/roomba/binary_sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/binary_sensor.py b/homeassistant/components/roomba/binary_sensor.py index 2135310721e2a7..0766c2b1c71d21 100644 --- a/homeassistant/components/roomba/binary_sensor.py +++ b/homeassistant/components/roomba/binary_sensor.py @@ -51,7 +51,7 @@ def icon(self): @property def state(self): """Return the state of the sensor.""" - return self._bin_status == "Full" + return self._bin_status @property def device_info(self): From 32c9d646ed1b23d068fc92bc265de976cc200260 Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:09:50 +0200 Subject: [PATCH 019/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 67eeead28044e3..2d7d44250d88ad 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -106,7 +106,7 @@ async def async_disconnect_or_timeout(hass, roomba): async def async_update_options(hass, config_entry): """Update options.""" - roomba = hass.data[DOMAIN]["roomba"] + await hass.config_entries.async_reload(config_entry.entry_id) await async_disconnect_or_timeout(hass, roomba) await async_connect_or_timeout(hass, roomba) From ad869dca16528374ab26ccf56d2073ecd8f8d0df Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:10:34 +0200 Subject: [PATCH 020/108] Update homeassistant/components/roomba/config_flow.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 295e3e25e19cbc..522f048ad450e9 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -63,7 +63,7 @@ def async_get_options_flow(config_entry): async def async_step_import(self, import_info): """Set the config entry up from yaml.""" - return self.async_create_entry(title="Roomba", data={}) + return await self.async_step_user(import_info) async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" From c14e03bf85af397228fbf53bdb93fd3b78d8cf25 Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:10:51 +0200 Subject: [PATCH 021/108] Update homeassistant/components/roomba/config_flow.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 522f048ad450e9..274959b2aa8909 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -51,7 +51,7 @@ async def validate_input(hass: core.HomeAssistant, data): class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - """Demo configuration flow.""" + """Roomba configuration flow.""" VERSION = 1 From 4b502a747776f39903db875fdf46bad5de93bfd8 Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:11:30 +0200 Subject: [PATCH 022/108] Update homeassistant/components/roomba/config_flow.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 274959b2aa8909..df2e380b896952 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -84,7 +84,7 @@ async def async_step_user(self, user_input=None): if "base" not in errors: return self.async_create_entry( - title=self.hass.data[DOMAIN]["name"], data=user_input + title=validated_input["title"], data=user_input ) # If there was no user input, do not show the errors. From 96bffd8ba246207e8fd6bc15821d70d97b97842e Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:11:59 +0200 Subject: [PATCH 023/108] Update homeassistant/components/roomba/config_flow.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/config_flow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index df2e380b896952..2e58649ed0631b 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -78,7 +78,6 @@ async def async_step_user(self, user_input=None): except CannotConnect: errors = {"base": "cannot_connect"} except InvalidAuth: - errors = {"base": "invalid_auth"} except Exception: # pylint: disable=broad-except errors = {"base": "unknown"} From 239864b48d0268448c0bfd0edf926821c386da38 Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:12:12 +0200 Subject: [PATCH 024/108] Update homeassistant/components/roomba/config_flow.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/config_flow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 2e58649ed0631b..1ace6996ac09c2 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -77,7 +77,6 @@ async def async_step_user(self, user_input=None): await validate_input(self.hass, user_input) except CannotConnect: errors = {"base": "cannot_connect"} - except InvalidAuth: except Exception: # pylint: disable=broad-except errors = {"base": "unknown"} From 9f0a94ae3d2e5a19691432df93c69c733d97872d Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:12:26 +0200 Subject: [PATCH 025/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 2d7d44250d88ad..6492e51a0e742c 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -92,7 +92,7 @@ async def async_connect_or_timeout(hass, roomba): # api looping if user or password incorrect and roomba exist await async_disconnect_or_timeout(hass, roomba) _LOGGER.exception("Timeout expired, user or password incorrect") - raise InvalidAuth + raise CannotConnect return True From 2e6ad58f6c4145ea8fae4e24c55b599382eeaffe Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:13:35 +0200 Subject: [PATCH 026/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 6492e51a0e742c..dd022ba28f0f89 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -36,7 +36,6 @@ async def async_setup_entry(hass, config_entry): hass.config_entries.async_update_entry( config_entry, options={ - "certificate": config_entry.data[CONF_CERT], "continuous": config_entry.data[CONF_CONTINUOUS], "delay": config_entry.data[CONF_DELAY], }, From 24820874966b9b9ae714420bf90eef395e3a84d1 Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:13:47 +0200 Subject: [PATCH 027/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index dd022ba28f0f89..79b5e481626fc9 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -21,7 +21,7 @@ async def async_setup(hass, config): if not hass.config_entries.async_entries(DOMAIN): hass.async_create_task( hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={} + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=config ) ) From eeee9679b9df0da15c3b478c0acdb00ffb875a1b Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:14:07 +0200 Subject: [PATCH 028/108] Update homeassistant/components/roomba/config_flow.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 1ace6996ac09c2..918923a87dd6e6 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -54,7 +54,7 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Roomba configuration flow.""" VERSION = 1 - + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH @staticmethod @callback def async_get_options_flow(config_entry): From 96666c853fe0d7b683a46760f342fa1aab85b96c Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Fri, 3 Apr 2020 07:47:21 +0200 Subject: [PATCH 029/108] Remove invalid auth --- homeassistant/components/roomba/.translations/en.json | 4 +--- homeassistant/components/roomba/.translations/fr.json | 4 +--- homeassistant/components/roomba/strings.json | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/roomba/.translations/en.json b/homeassistant/components/roomba/.translations/en.json index aaf4518d5fd4ae..b40c8bdaebea80 100644 --- a/homeassistant/components/roomba/.translations/en.json +++ b/homeassistant/components/roomba/.translations/en.json @@ -16,15 +16,13 @@ }, "error": { "unknown" : "Unexpected error", - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication" + "cannot_connect": "Failed to connect, please try again" } }, "options": { "step": { "init": { "data": { - "certificate": "Certificate", "continuous": "Continuous", "delay": "Delay" } diff --git a/homeassistant/components/roomba/.translations/fr.json b/homeassistant/components/roomba/.translations/fr.json index 17ee2455f4e056..e99411aa2dba9e 100644 --- a/homeassistant/components/roomba/.translations/fr.json +++ b/homeassistant/components/roomba/.translations/fr.json @@ -16,15 +16,13 @@ }, "error": { "unknown" : "Erreur imprévu", - "cannot_connect": "Impossible de se connecter", - "invalid_auth": "Authentification invalide" + "cannot_connect": "Impossible de se connecter" } }, "options": { "step": { "init": { "data": { - "certificate": "Certificat", "continuous": "Continue", "delay": "Delais" } diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json index f34f1214c2bb1a..05538cb40c8ac2 100644 --- a/homeassistant/components/roomba/strings.json +++ b/homeassistant/components/roomba/strings.json @@ -16,15 +16,13 @@ }, "error": { "unknown" : "Unexpected error", - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication" + "cannot_connect": "Failed to connect, please try again" } }, "options": { "step": { "init": { "data": { - "certificate": "Certificate", "continuous": "Continuous", "delay": "Delay" } From 432c32669b7b4a237d49180774377843f5c6eade Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Fri, 3 Apr 2020 07:48:14 +0200 Subject: [PATCH 030/108] Add call function reported_state --- homeassistant/components/roomba/binary_sensor.py | 12 ++++-------- homeassistant/components/roomba/sensor.py | 9 +++------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/roomba/binary_sensor.py b/homeassistant/components/roomba/binary_sensor.py index 0766c2b1c71d21..02cf4163cf853a 100644 --- a/homeassistant/components/roomba/binary_sensor.py +++ b/homeassistant/components/roomba/binary_sensor.py @@ -3,6 +3,7 @@ from homeassistant.components.binary_sensor import BinarySensorDevice +from . import roomba_reported_state from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -11,7 +12,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the iRobot Roomba vacuum cleaner.""" roomba = hass.data[DOMAIN]["roomba"] - status = roomba.master_state.get("state", {}).get("reported", {}).get("bin", {}) + status = roomba_reported_state(roomba).get("bin", {}) if "full" in status: roomba_vac = RoombaBinStatus(roomba) async_add_entities([roomba_vac], True) @@ -25,9 +26,7 @@ class RoombaBinStatus(BinarySensorDevice): def __init__(self, roomba): """Initialize the sensor object.""" self.vacuum = roomba - self.vacuum_state = self.vacuum.master_state.get("state", {}).get( - "reported", {} - ) + self.vacuum_state = roomba_reported_state(roomba) self._mac = self.vacuum_state.get("mac") self._name = self.vacuum_state.get("name") self._identifier = f"roomba_{self._mac}" @@ -68,9 +67,6 @@ async def async_update(self): _LOGGER.debug("Roomba %s has no data yet. Skip update", self.name) return self._bin_status = ( - self.vacuum.master_state.get("state", {}) - .get("reported", {}) - .get("bin", {}) - .get("full", False) + roomba_reported_state(self.vacuum).get("bin", {}).get("full", False) ) _LOGGER.debug("Update Full Bin status from the vacuum: %s", self._bin_status) diff --git a/homeassistant/components/roomba/sensor.py b/homeassistant/components/roomba/sensor.py index 72d990172231a3..b54fd168bfabea 100644 --- a/homeassistant/components/roomba/sensor.py +++ b/homeassistant/components/roomba/sensor.py @@ -4,6 +4,7 @@ from homeassistant.const import DEVICE_CLASS_BATTERY from homeassistant.helpers.entity import Entity +from . import roomba_reported_state from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -22,9 +23,7 @@ class RoombaBattery(Entity): def __init__(self, roomba): """Initialize the sensor object.""" self.vacuum = roomba - self.vacuum_state = self.vacuum.master_state.get("state", {}).get( - "reported", {} - ) + self.vacuum_state = roomba_reported_state(roomba) self._mac = self.vacuum_state.get("mac") self._name = self.vacuum_state.get("name") self._identifier = f"roomba_{self._mac}" @@ -69,9 +68,7 @@ async def async_update(self): if not self.vacuum.master_state: _LOGGER.debug("Roomba %s has no data yet. Skip update", self.name) return - self._battery_level = ( - self.vacuum.master_state.get("state", {}).get("reported", {}).get("batPct") - ) + self._battery_level = roomba_reported_state(self.vacuum).get("batPct") _LOGGER.debug( "Update battery level status from the vacuum: %s", self._battery_level ) From 779948fcd1a8720285cbff06d57b18c16c8b8abb Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 4 Apr 2020 09:18:37 +0200 Subject: [PATCH 031/108] Add options reload --- homeassistant/components/roomba/__init__.py | 58 ++++++++++--------- .../components/roomba/config_flow.py | 14 ++--- 2 files changed, 34 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 79b5e481626fc9..b45759fe0d459b 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -15,6 +15,8 @@ async def async_setup(hass, config): """Set up the roomba environment.""" + hass.data.setdefault(DOMAIN, {}) + if DOMAIN not in config: return True @@ -29,6 +31,7 @@ async def async_setup(hass, config): async def async_setup_entry(hass, config_entry): """Set the config entry up.""" # Set up roomba platforms with config entry + _LOGGER.debug(config_entry.entry_id) if config_entry.data is None: return False @@ -41,22 +44,21 @@ async def async_setup_entry(hass, config_entry): }, ) - if DOMAIN not in hass.data: - hass.data[DOMAIN] = {} - if "roomba" not in hass.data[DOMAIN]: - roomba = Roomba( address=config_entry.data[CONF_HOST], blid=config_entry.data[CONF_USERNAME], password=config_entry.data[CONF_PASSWORD], - cert_name=config_entry.options[CONF_CERT], + cert_name=config_entry.data[CONF_CERT], continuous=config_entry.options[CONF_CONTINUOUS], delay=config_entry.options[CONF_DELAY], ) - if not await async_connect_or_timeout(hass, roomba): - return False + try: + if not await async_connect_or_timeout(hass, roomba): + return False + except CannotConnect: + raise exceptions.ConfigEntryNotReady for component in COMPONENTS: hass.async_create_task( @@ -74,23 +76,20 @@ async def async_connect_or_timeout(hass, roomba): hass.data[DOMAIN]["name"] = name = None with async_timeout.timeout(10): await hass.async_add_job(roomba.connect) + _LOGGER.debug("Initialize to connect to vacuum") while not roomba.roomba_connected or name is None: # Waiting for connection and check datas ready - name = ( - roomba.master_state.get("state", {}) - .get("reported", {}) - .get("name", None) - ) - await asyncio.sleep(0.5) - hass.data[DOMAIN]["roomba"] = roomba - hass.data[DOMAIN]["name"] = name + name = roomba_reported_state(roomba).get("name", None) + hass.data[DOMAIN]["roomba"] = roomba + hass.data[DOMAIN]["name"] = name + await asyncio.sleep(1) except RoombaConnectionError: _LOGGER.error("Error to connect to vacuum") raise CannotConnect except asyncio.TimeoutError: # api looping if user or password incorrect and roomba exist await async_disconnect_or_timeout(hass, roomba) - _LOGGER.exception("Timeout expired, user or password incorrect") + _LOGGER.exception("Timeout expired") raise CannotConnect return True @@ -98,7 +97,9 @@ async def async_connect_or_timeout(hass, roomba): async def async_disconnect_or_timeout(hass, roomba): """Disconnect to vacuum.""" - await hass.async_add_job(roomba.disconnect) + with async_timeout.timeout(3): + await hass.async_add_job(roomba.disconnect) + hass.data[DOMAIN].pop("roomba", None) await asyncio.sleep(1) return True @@ -106,23 +107,24 @@ async def async_disconnect_or_timeout(hass, roomba): async def async_update_options(hass, config_entry): """Update options.""" await hass.config_entries.async_reload(config_entry.entry_id) - await async_disconnect_or_timeout(hass, roomba) - await async_connect_or_timeout(hass, roomba) async def async_unload_entry(hass, config_entry): """Unload a config entry.""" - for component in COMPONENTS: - hass.async_create_task( + _LOGGER.debug("Unload Entries") + await asyncio.gather( + *[ hass.config_entries.async_forward_entry_unload(config_entry, component) - ) - roomba = hass.data[DOMAIN]["roomba"] - return await async_disconnect_or_timeout(hass, roomba) + for component in COMPONENTS + ] + ) + return await async_disconnect_or_timeout(hass, roomba=hass.data[DOMAIN]["roomba"]) -class CannotConnect(exceptions.HomeAssistantError): - """Error to indicate we cannot connect.""" +def roomba_reported_state(roomba): + """Roomba report.""" + return roomba.master_state.get("state", {}).get("reported", {}) -class InvalidAuth(exceptions.HomeAssistantError): - """Error to indicate there is invalid auth.""" +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 918923a87dd6e6..88d31f894fee82 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -8,7 +8,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback -from . import CannotConnect, InvalidAuth, async_connect_or_timeout +from . import CannotConnect, async_connect_or_timeout from .const import ( CONF_CERT, CONF_CONTINUOUS, @@ -46,7 +46,6 @@ async def validate_input(hass: core.HomeAssistant, data): continuous=data[CONF_CONTINUOUS], delay=data[CONF_DELAY], ) - await async_connect_or_timeout(hass, roomba) @@ -55,6 +54,7 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + @staticmethod @callback def async_get_options_flow(config_entry): @@ -69,11 +69,9 @@ async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" errors = {} - if DOMAIN not in self.hass.data: - self.hass.data[DOMAIN] = {} - if user_input is not None: try: + self.hass.data.setdefault(DOMAIN, {}) await validate_input(self.hass, user_input) except CannotConnect: errors = {"base": "cannot_connect"} @@ -82,7 +80,7 @@ async def async_step_user(self, user_input=None): if "base" not in errors: return self.async_create_entry( - title=validated_input["title"], data=user_input + title=self.hass.data[DOMAIN]["name"], data=user_input ) # If there was no user input, do not show the errors. @@ -110,10 +108,6 @@ async def async_step_init(self, user_input=None): step_id="init", data_schema=vol.Schema( { - vol.Optional( - CONF_CERT, - default=self.config_entry.options.get(CONF_CERT, DEFAULT_CERT), - ): str, vol.Optional( CONF_CONTINUOUS, default=self.config_entry.options.get( From 2e9d26819f16661523019fe98909fa3ac44ab188 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 4 Apr 2020 09:21:01 +0200 Subject: [PATCH 032/108] Fix tracelog --- homeassistant/components/roomba/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index b45759fe0d459b..df57b2e6a30958 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -31,7 +31,6 @@ async def async_setup(hass, config): async def async_setup_entry(hass, config_entry): """Set the config entry up.""" # Set up roomba platforms with config entry - _LOGGER.debug(config_entry.entry_id) if config_entry.data is None: return False @@ -89,7 +88,7 @@ async def async_connect_or_timeout(hass, roomba): except asyncio.TimeoutError: # api looping if user or password incorrect and roomba exist await async_disconnect_or_timeout(hass, roomba) - _LOGGER.exception("Timeout expired") + _LOGGER.error("Timeout expired") raise CannotConnect return True From 8a437a7eea011796a7fa7cc4a47c31d79ff935f2 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 4 Apr 2020 11:22:36 +0200 Subject: [PATCH 033/108] Set entry_id for config_entry --- homeassistant/components/roomba/__init__.py | 58 ++++++++++--------- .../components/roomba/binary_sensor.py | 2 +- .../components/roomba/config_flow.py | 16 ++--- homeassistant/components/roomba/sensor.py | 2 +- homeassistant/components/roomba/vacuum.py | 2 +- 5 files changed, 43 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index df57b2e6a30958..20e9fdada55a18 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -43,21 +43,22 @@ async def async_setup_entry(hass, config_entry): }, ) - if "roomba" not in hass.data[DOMAIN]: - roomba = Roomba( - address=config_entry.data[CONF_HOST], - blid=config_entry.data[CONF_USERNAME], - password=config_entry.data[CONF_PASSWORD], - cert_name=config_entry.data[CONF_CERT], - continuous=config_entry.options[CONF_CONTINUOUS], - delay=config_entry.options[CONF_DELAY], - ) + roomba = Roomba( + address=config_entry.data[CONF_HOST], + blid=config_entry.data[CONF_USERNAME], + password=config_entry.data[CONF_PASSWORD], + cert_name=config_entry.data[CONF_CERT], + continuous=config_entry.options[CONF_CONTINUOUS], + delay=config_entry.options[CONF_DELAY], + ) + + try: + if not await async_connect_or_timeout(hass, roomba): + return False + except CannotConnect: + raise exceptions.ConfigEntryNotReady - try: - if not await async_connect_or_timeout(hass, roomba): - return False - except CannotConnect: - raise exceptions.ConfigEntryNotReady + hass.data[DOMAIN][config_entry.entry_id] = roomba for component in COMPONENTS: hass.async_create_task( @@ -72,15 +73,12 @@ async def async_setup_entry(hass, config_entry): async def async_connect_or_timeout(hass, roomba): """Connect to vacuum.""" try: - hass.data[DOMAIN]["name"] = name = None + name = None with async_timeout.timeout(10): await hass.async_add_job(roomba.connect) - _LOGGER.debug("Initialize to connect to vacuum") while not roomba.roomba_connected or name is None: # Waiting for connection and check datas ready name = roomba_reported_state(roomba).get("name", None) - hass.data[DOMAIN]["roomba"] = roomba - hass.data[DOMAIN]["name"] = name await asyncio.sleep(1) except RoombaConnectionError: _LOGGER.error("Error to connect to vacuum") @@ -91,14 +89,13 @@ async def async_connect_or_timeout(hass, roomba): _LOGGER.error("Timeout expired") raise CannotConnect - return True + return {"roomba": roomba, "name": name} async def async_disconnect_or_timeout(hass, roomba): """Disconnect to vacuum.""" with async_timeout.timeout(3): await hass.async_add_job(roomba.disconnect) - hass.data[DOMAIN].pop("roomba", None) await asyncio.sleep(1) return True @@ -110,14 +107,21 @@ async def async_update_options(hass, config_entry): async def async_unload_entry(hass, config_entry): """Unload a config entry.""" - _LOGGER.debug("Unload Entries") - await asyncio.gather( - *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in COMPONENTS - ] + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(config_entry, component) + for component in COMPONENTS + ] + ) ) - return await async_disconnect_or_timeout(hass, roomba=hass.data[DOMAIN]["roomba"]) + if unload_ok: + await async_disconnect_or_timeout( + hass, roomba=hass.data[DOMAIN][config_entry.entry_id] + ) + hass.data[DOMAIN].pop(config_entry.entry_id) + + return unload_ok def roomba_reported_state(roomba): diff --git a/homeassistant/components/roomba/binary_sensor.py b/homeassistant/components/roomba/binary_sensor.py index 02cf4163cf853a..8b03aaba99ddca 100644 --- a/homeassistant/components/roomba/binary_sensor.py +++ b/homeassistant/components/roomba/binary_sensor.py @@ -11,7 +11,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the iRobot Roomba vacuum cleaner.""" - roomba = hass.data[DOMAIN]["roomba"] + roomba = hass.data[DOMAIN][config_entry.entry_id] status = roomba_reported_state(roomba).get("bin", {}) if "full" in status: roomba_vac = RoombaBinStatus(roomba) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 88d31f894fee82..a529a1a23d9b73 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -8,7 +8,11 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback -from . import CannotConnect, async_connect_or_timeout +from . import ( + CannotConnect, + async_connect_or_timeout as act, + async_disconnect_or_timeout as adt, +) from .const import ( CONF_CERT, CONF_CONTINUOUS, @@ -46,7 +50,7 @@ async def validate_input(hass: core.HomeAssistant, data): continuous=data[CONF_CONTINUOUS], delay=data[CONF_DELAY], ) - await async_connect_or_timeout(hass, roomba) + return await act(hass, roomba) class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -71,17 +75,15 @@ async def async_step_user(self, user_input=None): if user_input is not None: try: - self.hass.data.setdefault(DOMAIN, {}) - await validate_input(self.hass, user_input) + info = await validate_input(self.hass, user_input) except CannotConnect: errors = {"base": "cannot_connect"} except Exception: # pylint: disable=broad-except errors = {"base": "unknown"} if "base" not in errors: - return self.async_create_entry( - title=self.hass.data[DOMAIN]["name"], data=user_input - ) + await adt(self.hass, info["roomba"]) + return self.async_create_entry(title=info["name"], data=user_input) # If there was no user input, do not show the errors. if user_input is None: diff --git a/homeassistant/components/roomba/sensor.py b/homeassistant/components/roomba/sensor.py index b54fd168bfabea..c0dbf0b97197e3 100644 --- a/homeassistant/components/roomba/sensor.py +++ b/homeassistant/components/roomba/sensor.py @@ -12,7 +12,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the iRobot Roomba vacuum cleaner.""" - roomba = hass.data[DOMAIN]["roomba"] + roomba = hass.data[DOMAIN][config_entry.entry_id] roomba_vac = RoombaBattery(roomba) async_add_entities([roomba_vac], True) diff --git a/homeassistant/components/roomba/vacuum.py b/homeassistant/components/roomba/vacuum.py index 84b77a1c669aa4..fa81ff5094da87 100644 --- a/homeassistant/components/roomba/vacuum.py +++ b/homeassistant/components/roomba/vacuum.py @@ -56,7 +56,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the iRobot Roomba vacuum cleaner.""" - roomba = hass.data[DOMAIN]["roomba"] + roomba = hass.data[DOMAIN][config_entry.entry_id] roomba_vac = RoombaVacuum(roomba) async_add_entities([roomba_vac], True) From 54d1914d618876d9a0213314c5915c65ee2df346 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 4 Apr 2020 11:52:57 +0200 Subject: [PATCH 034/108] Fix DOMAIN unsed-import --- homeassistant/components/roomba/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index a529a1a23d9b73..5c8f5742a3acfd 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -20,8 +20,8 @@ DEFAULT_CERT, DEFAULT_CONTINUOUS, DEFAULT_DELAY, - DOMAIN, ) +from .const import DOMAIN # pylint:disable=unused-import DATA_SCHEMA = vol.Schema( { From 9e7aed4cf24310810048a043a78a6cec4157846f Mon Sep 17 00:00:00 2001 From: Save me Date: Sun, 5 Apr 2020 13:25:19 +0200 Subject: [PATCH 035/108] Update homeassistant/components/roomba/config_flow.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 5c8f5742a3acfd..36916a90028229 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -50,7 +50,7 @@ async def validate_input(hass: core.HomeAssistant, data): continuous=data[CONF_CONTINUOUS], delay=data[CONF_DELAY], ) - return await act(hass, roomba) + return await async_connect_or_timeout(hass, roomba) class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): From 5882a7643cfa257e1deb31820cf988725eba4ce0 Mon Sep 17 00:00:00 2001 From: Save me Date: Sun, 5 Apr 2020 13:25:30 +0200 Subject: [PATCH 036/108] Update homeassistant/components/roomba/config_flow.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 36916a90028229..96b81e25eeac61 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -82,7 +82,7 @@ async def async_step_user(self, user_input=None): errors = {"base": "unknown"} if "base" not in errors: - await adt(self.hass, info["roomba"]) + await async_disconnect_or_timeout(self.hass, info["roomba"]) return self.async_create_entry(title=info["name"], data=user_input) # If there was no user input, do not show the errors. From fd5bdcd04d03c8468ef24e30e09d5b62d6a3c5ec Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 5 Apr 2020 18:41:36 +0200 Subject: [PATCH 037/108] Add unique_id for entry --- homeassistant/components/roomba/__init__.py | 45 +++++++++++++++++-- .../components/roomba/config_flow.py | 25 +++++++---- homeassistant/components/roomba/const.py | 3 ++ 3 files changed, 61 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 20e9fdada55a18..b9d7fd75fa528f 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -4,14 +4,43 @@ import async_timeout from roomba import Roomba, RoombaConnectionError +import voluptuous as vol from homeassistant import config_entries, exceptions from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from .const import COMPONENTS, CONF_CERT, CONF_CONTINUOUS, CONF_DELAY, DOMAIN +from .const import ( + COMPONENTS, + CONF_CERT, + CONF_CONTINUOUS, + CONF_DELAY, + CONF_NAME, + DEFAULT_CERT, + DEFAULT_CONTINUOUS, + DEFAULT_DELAY, + DOMAIN, + MAC_ADDRESS, + ROOMBA_SESSION, +) _LOGGER = logging.getLogger(__name__) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_HOST): str, + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + vol.Optional(CONF_CERT, default=DEFAULT_CERT): str, + vol.Optional(CONF_CONTINUOUS, default=DEFAULT_CONTINUOUS): bool, + vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): int, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) + async def async_setup(hass, config): """Set up the roomba environment.""" @@ -23,10 +52,14 @@ async def async_setup(hass, config): if not hass.config_entries.async_entries(DOMAIN): hass.async_create_task( hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=config + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=config[DOMAIN], ) ) + return True + async def async_setup_entry(hass, config_entry): """Set the config entry up.""" @@ -65,7 +98,8 @@ async def async_setup_entry(hass, config_entry): hass.config_entries.async_forward_entry_setup(config_entry, component) ) - config_entry.add_update_listener(async_update_options) + if not config_entry.update_listeners: + config_entry.add_update_listener(async_update_options) return True @@ -75,10 +109,12 @@ async def async_connect_or_timeout(hass, roomba): try: name = None with async_timeout.timeout(10): + _LOGGER.debug("Initialize connection to vacuum") await hass.async_add_job(roomba.connect) while not roomba.roomba_connected or name is None: # Waiting for connection and check datas ready name = roomba_reported_state(roomba).get("name", None) + mac = roomba_reported_state(roomba).get("mac", None) await asyncio.sleep(1) except RoombaConnectionError: _LOGGER.error("Error to connect to vacuum") @@ -89,11 +125,12 @@ async def async_connect_or_timeout(hass, roomba): _LOGGER.error("Timeout expired") raise CannotConnect - return {"roomba": roomba, "name": name} + return {ROOMBA_SESSION: roomba, CONF_NAME: name, MAC_ADDRESS: mac} async def async_disconnect_or_timeout(hass, roomba): """Disconnect to vacuum.""" + _LOGGER.debug("Disconnect vacuum") with async_timeout.timeout(3): await hass.async_add_job(roomba.disconnect) await asyncio.sleep(1) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 96b81e25eeac61..2d1072c5542bb3 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -8,18 +8,17 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback -from . import ( - CannotConnect, - async_connect_or_timeout as act, - async_disconnect_or_timeout as adt, -) +from . import CannotConnect, async_connect_or_timeout, async_disconnect_or_timeout from .const import ( CONF_CERT, CONF_CONTINUOUS, CONF_DELAY, + CONF_NAME, DEFAULT_CERT, DEFAULT_CONTINUOUS, DEFAULT_DELAY, + MAC_ADDRESS, + ROOMBA_SESSION, ) from .const import DOMAIN # pylint:disable=unused-import @@ -50,7 +49,15 @@ async def validate_input(hass: core.HomeAssistant, data): continuous=data[CONF_CONTINUOUS], delay=data[CONF_DELAY], ) - return await async_connect_or_timeout(hass, roomba) + + info = await async_connect_or_timeout(hass, roomba) + + return { + ROOMBA_SESSION: info[ROOMBA_SESSION], + CONF_NAME: info[CONF_NAME], + CONF_HOST: data[CONF_HOST], + MAC_ADDRESS: info[MAC_ADDRESS], + } class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -82,8 +89,10 @@ async def async_step_user(self, user_input=None): errors = {"base": "unknown"} if "base" not in errors: - await async_disconnect_or_timeout(self.hass, info["roomba"]) - return self.async_create_entry(title=info["name"], data=user_input) + await async_disconnect_or_timeout(self.hass, info[ROOMBA_SESSION]) + await self.async_set_unique_id(info[MAC_ADDRESS]) + self._abort_if_unique_id_configured() + return self.async_create_entry(title=info[CONF_NAME], data=user_input) # If there was no user input, do not show the errors. if user_input is None: diff --git a/homeassistant/components/roomba/const.py b/homeassistant/components/roomba/const.py index 803a1ad87ff272..25b29aeda1ef78 100644 --- a/homeassistant/components/roomba/const.py +++ b/homeassistant/components/roomba/const.py @@ -4,6 +4,9 @@ CONF_CERT = "certificate" CONF_CONTINUOUS = "continuous" CONF_DELAY = "delay" +CONF_NAME = "name" DEFAULT_CERT = "/etc/ssl/certs/ca-certificates.crt" DEFAULT_CONTINUOUS = True DEFAULT_DELAY = 1 +MAC_ADDRESS = "mac" +ROOMBA_SESSION = "roomba_session" From 0a9a09d9152a5530d017f6866bff0528067b9446 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 5 Apr 2020 19:02:58 +0200 Subject: [PATCH 038/108] Fix device info --- homeassistant/components/roomba/vacuum.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/roomba/vacuum.py b/homeassistant/components/roomba/vacuum.py index fa81ff5094da87..f73c33e59c3040 100644 --- a/homeassistant/components/roomba/vacuum.py +++ b/homeassistant/components/roomba/vacuum.py @@ -16,6 +16,7 @@ ) from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from . import roomba_reported_state from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -74,11 +75,11 @@ def __init__(self, roomba): self._state_attrs = {} self._status = None self.vacuum = roomba - self.vacuum_state = self.vacuum.master_state.get("state", {}).get( - "reported", {} - ) + self.vacuum_state = roomba_reported_state(roomba) self._mac = self.vacuum_state.get("mac") self._name = self.vacuum_state.get("name") + self._version = self.vacuum_state.get("softwareVer") + self._sku = self.vacuum_state.get("sku") @property def unique_id(self): @@ -91,14 +92,10 @@ def device_info(self): return { "identifiers": {(DOMAIN, self.unique_id)}, "connections": {(CONNECTION_NETWORK_MAC, self._mac)}, - "manufacturer": "iRobots", + "manufacturer": "iRobot", "name": str(self._name), - "sw_version": self.vacuum.master_state.get("state", {}) - .get("reported", {}) - .get("softwareVer"), - "model": self.vacuum.master_state.get("state", {}) - .get("reported", {}) - .get("sku"), + "sw_version": self._version, + "model": self._sku, } @property From d539dfffe38d5b3ec805de55c7933a74724941e2 Mon Sep 17 00:00:00 2001 From: Cyr-ius Date: Wed, 25 Mar 2020 13:51:29 +0100 Subject: [PATCH 039/108] Add config_flow for roomba --- .../components/roomba/.translations/en.json | 45 ++++++ .../components/roomba/.translations/fr.json | 45 ++++++ homeassistant/components/roomba/__init__.py | 75 +++++++++ .../components/roomba/config_flow.py | 153 ++++++++++++++++++ homeassistant/components/roomba/const.py | 9 ++ homeassistant/components/roomba/manifest.json | 1 + homeassistant/components/roomba/strings.json | 45 ++++++ homeassistant/components/roomba/vacuum.py | 83 +++------- homeassistant/generated/config_flows.py | 1 + requirements_test_all.txt | 3 + tests/components/roomba/__init__.py | 1 + tests/components/roomba/test_config_flow.py | 90 +++++++++++ 12 files changed, 488 insertions(+), 63 deletions(-) create mode 100644 homeassistant/components/roomba/.translations/en.json create mode 100644 homeassistant/components/roomba/.translations/fr.json create mode 100644 homeassistant/components/roomba/config_flow.py create mode 100644 homeassistant/components/roomba/const.py create mode 100644 homeassistant/components/roomba/strings.json create mode 100644 tests/components/roomba/__init__.py create mode 100644 tests/components/roomba/test_config_flow.py diff --git a/homeassistant/components/roomba/.translations/en.json b/homeassistant/components/roomba/.translations/en.json new file mode 100644 index 00000000000000..99152734c34dfa --- /dev/null +++ b/homeassistant/components/roomba/.translations/en.json @@ -0,0 +1,45 @@ +{ + "config": { + "title": "iRobot Roomba", + "step": { + "user": { + "title": "Connect to the device", + "data": { + "host": "Hostname or IP Address", + "username": "Username", + "password": "Password", + "name": "Friendly Name", + "certificate": "Certificate", + "continuous": "Continuous", + "delay": "Delay" + } + } + }, + "error": { + "cannot_connect": "Failed to connect, please try again", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "Device is already configured" + } + }, + "options": { + "step": { + "init": { + "data": { + "certificate": "Certificate", + "continuous": "Continuous", + "delay": "Delay" + } + }, + "options": { + "data": { + "certificate": "Certificate", + "continuous": "Continuous", + "delay": "Delay" + } + } + } + } +} diff --git a/homeassistant/components/roomba/.translations/fr.json b/homeassistant/components/roomba/.translations/fr.json new file mode 100644 index 00000000000000..fe488be88d01ed --- /dev/null +++ b/homeassistant/components/roomba/.translations/fr.json @@ -0,0 +1,45 @@ +{ + "config": { + "title": "iRobot Roomba", + "step": { + "user": { + "title": "Connect to the device", + "data": { + "host": "Nom ou Addresse IP", + "username": "Utilisateur", + "password": "Mot de passe", + "name": "Nom", + "certificate": "Certificat", + "continuous": "Continue", + "delay": "Delais" + } + } + }, + "error": { + "cannot_connect": "Failed to connect, please try again", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "Device is already configured" + } + }, + "options": { + "step": { + "init": { + "data": { + "certificate": "Certificat", + "continuous": "Continue", + "delay": "Delais" + } + }, + "options": { + "data": { + "certificate": "Certificate", + "continuous": "Continuous", + "delay": "Delay" + } + } + } + } +} diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index c0e5f68483e9eb..04d705e9178336 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -1 +1,76 @@ """The roomba component.""" +import logging + +import async_timeout +from roomba import Roomba, RoombaConnectionError + +from homeassistant import config_entries +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME + +from .const import CONF_CERT, CONF_CONTINUOUS, CONF_DELAY, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass, config): + """Set up the roomba environment.""" + if DOMAIN not in config: + return True + + if not hass.config_entries.async_entries(DOMAIN): + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={} + ) + ) + + +async def async_setup_entry(hass, config_entry): + """Set the config entry up.""" + # Set up roomba platforms with config entry + if config_entry.data is None: + return False + + if not config_entry.options: + hass.config_entries.async_update_entry( + config_entry, + options={ + "certificate": config_entry.data[CONF_CERT], + "continuous": config_entry.data[CONF_CONTINUOUS], + "delay": config_entry.data[CONF_DELAY], + }, + ) + + if DOMAIN not in hass.data: + hass.data[DOMAIN] = {} + + if "roomba" not in hass.data[DOMAIN]: + roomba = Roomba( + address=config_entry.data[CONF_HOST], + blid=config_entry.data[CONF_USERNAME], + password=config_entry.data[CONF_PASSWORD], + cert_name=config_entry.data[CONF_CERT], + continuous=config_entry.data[CONF_CONTINUOUS], + delay=config_entry.data[CONF_DELAY], + ) + hass.data[DOMAIN]["roomba"] = roomba + try: + with async_timeout.timeout(9): + await hass.async_add_job(roomba.connect) + except RoombaConnectionError: + _LOGGER.error("Error to connect to {}".format(config_entry.data[CONF_HOST])) + return False + + hass.data[DOMAIN]["name"] = config_entry.data[CONF_NAME] + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, "vacuum") + ) + return True + + +async def async_unload_entry(hass, config_entry): + """Unload a config entry.""" + await hass.config_entries.async_forward_entry_unload(config_entry, "vacuum") + roomba = hass.data[DOMAIN]["roomba"] + await hass.async_add_job(roomba.disconnect) + return True diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py new file mode 100644 index 00000000000000..e30598cf7384b3 --- /dev/null +++ b/homeassistant/components/roomba/config_flow.py @@ -0,0 +1,153 @@ +"""Config flow to configure demo component.""" +import logging +import time + +from roomba import Roomba, RoombaConnectionError +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import callback + +from .const import ( + CONF_CERT, + CONF_CONTINUOUS, + CONF_DELAY, + DEFAULT_CERT, + DEFAULT_CONTINUOUS, + DEFAULT_DELAY, + DEFAULT_NAME, + DOMAIN, +) + +DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_HOST): str, + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): str, + vol.Optional(CONF_CERT, default=DEFAULT_CERT): str, + vol.Optional(CONF_CONTINUOUS, default=DEFAULT_CONTINUOUS): bool, + vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): int, + } +) + +_LOGGER = logging.getLogger(__name__) + + +class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Demo configuration flow.""" + + VERSION = 1 + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return OptionsFlowHandler(config_entry) + + async def async_step_import(self, import_info): + """Set the config entry up from yaml.""" + return self.async_create_entry(title="Roomba", data={}) + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + errors = {} + + if DOMAIN not in self.hass.data: + self.hass.data[DOMAIN] = {} + if user_input is not None: + self.host = user_input[CONF_HOST] + self.username = user_input[CONF_USERNAME] + self.password = user_input[CONF_PASSWORD] + self.name = user_input[CONF_NAME] + self.certificate = user_input[CONF_CERT] + self.continuous = user_input[CONF_CONTINUOUS] + self.delay = user_input[CONF_DELAY] + + roomba = Roomba( + address=self.host, + blid=self.username, + password=self.password, + cert_name=self.certificate, + continuous=self.continuous, + delay=self.delay, + ) + _LOGGER.debug("Initializing communication with host %s", self.host) + + try: + await self.hass.async_add_job(roomba.connect) + except RoombaConnectionError: + errors = {"base": "cannot_connect"} + + timeout = time.time() + 1 + while not roomba.roomba_connected and not errors: + if time.time() > timeout: + errors = {"base": "invalid_auth"} + await self.hass.async_add_job(roomba.disconnect) + time.sleep(0.2) + + if roomba.roomba_connected: + self.hass.data[DOMAIN]["roomba"] = roomba + self.hass.data[DOMAIN]["name"] = self.name + return self.async_create_entry( + title=self.name, + data={ + "host": self.host, + "username": self.username, + "password": self.password, + "name": self.name, + "certificate": self.certificate, + "continuous": self.continuous, + "delay": self.delay, + }, + ) + + # If there was no user input, do not show the errors. + if user_input is None: + errors = {} + + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) + + +class OptionsFlowHandler(config_entries.OptionsFlow): + """Handle options.""" + + def __init__(self, config_entry): + """Initialize options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Manage the options.""" + return await self.async_step_options() + + async def async_step_options(self, user_input=None): + """Manage the options.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + return self.async_show_form( + step_id="options", + data_schema=vol.Schema( + { + vol.Optional( + CONF_CERT, + default=self.config_entry.options.get(CONF_CERT, DEFAULT_CERT), + ): str, + vol.Optional( + CONF_CONTINUOUS, + default=self.config_entry.options.get( + CONF_CONTINUOUS, DEFAULT_CONTINUOUS + ), + ): bool, + vol.Optional( + CONF_DELAY, + default=self.config_entry.options.get( + CONF_DELAY, DEFAULT_DELAY + ), + ): int, + } + ), + ) diff --git a/homeassistant/components/roomba/const.py b/homeassistant/components/roomba/const.py new file mode 100644 index 00000000000000..f5c1a1a3d3e913 --- /dev/null +++ b/homeassistant/components/roomba/const.py @@ -0,0 +1,9 @@ +"""The roomba constants.""" +DOMAIN = "roomba" +CONF_CERT = "certificate" +CONF_CONTINUOUS = "continuous" +CONF_DELAY = "delay" +DEFAULT_CERT = "/etc/ssl/certs/ca-certificates.crt" +DEFAULT_CONTINUOUS = True +DEFAULT_DELAY = 1 +DEFAULT_NAME = "Roomba" diff --git a/homeassistant/components/roomba/manifest.json b/homeassistant/components/roomba/manifest.json index 942ebd08426265..30e975bbb0a996 100644 --- a/homeassistant/components/roomba/manifest.json +++ b/homeassistant/components/roomba/manifest.json @@ -1,6 +1,7 @@ { "domain": "roomba", "name": "iRobot Roomba", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/roomba", "requirements": ["roombapy==1.4.3"], "codeowners": ["@pschmitt"] diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json new file mode 100644 index 00000000000000..99152734c34dfa --- /dev/null +++ b/homeassistant/components/roomba/strings.json @@ -0,0 +1,45 @@ +{ + "config": { + "title": "iRobot Roomba", + "step": { + "user": { + "title": "Connect to the device", + "data": { + "host": "Hostname or IP Address", + "username": "Username", + "password": "Password", + "name": "Friendly Name", + "certificate": "Certificate", + "continuous": "Continuous", + "delay": "Delay" + } + } + }, + "error": { + "cannot_connect": "Failed to connect, please try again", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "Device is already configured" + } + }, + "options": { + "step": { + "init": { + "data": { + "certificate": "Certificate", + "continuous": "Continuous", + "delay": "Delay" + } + }, + "options": { + "data": { + "certificate": "Certificate", + "continuous": "Continuous", + "delay": "Delay" + } + } + } + } +} diff --git a/homeassistant/components/roomba/vacuum.py b/homeassistant/components/roomba/vacuum.py index 172a494b602eaf..5539fc7135cf74 100644 --- a/homeassistant/components/roomba/vacuum.py +++ b/homeassistant/components/roomba/vacuum.py @@ -1,13 +1,7 @@ """Support for Wi-Fi enabled iRobot Roombas.""" -import asyncio import logging -import async_timeout -from roomba import Roomba -import voluptuous as vol - from homeassistant.components.vacuum import ( - PLATFORM_SCHEMA, SUPPORT_BATTERY, SUPPORT_FAN_SPEED, SUPPORT_LOCATE, @@ -20,9 +14,8 @@ SUPPORT_TURN_ON, VacuumDevice, ) -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME -from homeassistant.exceptions import PlatformNotReady -import homeassistant.helpers.config_validation as cv + +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -37,34 +30,11 @@ CAP_POSITION = "position" CAP_CARPET_BOOST = "carpet_boost" -CONF_CERT = "certificate" -CONF_CONTINUOUS = "continuous" -CONF_DELAY = "delay" - -DEFAULT_CERT = "/etc/ssl/certs/ca-certificates.crt" -DEFAULT_CONTINUOUS = True -DEFAULT_DELAY = 1 -DEFAULT_NAME = "Roomba" - -PLATFORM = "roomba" - FAN_SPEED_AUTOMATIC = "Automatic" FAN_SPEED_ECO = "Eco" FAN_SPEED_PERFORMANCE = "Performance" FAN_SPEEDS = [FAN_SPEED_AUTOMATIC, FAN_SPEED_ECO, FAN_SPEED_PERFORMANCE] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_CERT, default=DEFAULT_CERT): cv.string, - vol.Optional(CONF_CONTINUOUS, default=DEFAULT_CONTINUOUS): cv.boolean, - vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): cv.positive_int, - }, - extra=vol.ALLOW_EXTRA, -) # Commonly supported features SUPPORT_ROOMBA = ( @@ -83,39 +53,12 @@ SUPPORT_ROOMBA_CARPET_BOOST = SUPPORT_ROOMBA | SUPPORT_FAN_SPEED -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the iRobot Roomba vacuum cleaner platform.""" - - if PLATFORM not in hass.data: - hass.data[PLATFORM] = {} - - host = config.get(CONF_HOST) - name = config.get(CONF_NAME) - username = config.get(CONF_USERNAME) - password = config.get(CONF_PASSWORD) - certificate = config.get(CONF_CERT) - continuous = config.get(CONF_CONTINUOUS) - delay = config.get(CONF_DELAY) - - roomba = Roomba( - address=host, - blid=username, - password=password, - cert_name=certificate, - continuous=continuous, - delay=delay, - ) - _LOGGER.debug("Initializing communication with host %s", host) - - try: - with async_timeout.timeout(9): - await hass.async_add_job(roomba.connect) - except asyncio.TimeoutError: - raise PlatformNotReady +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the iRobot Roomba vacuum cleaner.""" + name = hass.data[DOMAIN]["name"] + roomba = hass.data[DOMAIN]["roomba"] roomba_vac = RoombaVacuum(name, roomba) - hass.data[PLATFORM][host] = roomba_vac - async_add_entities([roomba_vac], True) @@ -135,6 +78,20 @@ def __init__(self, name, roomba): self.vacuum = roomba self.vacuum_state = None + @property + def unique_id(self): + """Return the uniqueid of the vacuum cleaner.""" + return self._name + + @property + def device_info(self): + """Return the device info of the vacuum cleaner.""" + return { + "identifiers": {(DOMAIN, self.unique_id)}, + "manufacturer": "iRobots", + "name": str(self._name), + } + @property def supported_features(self): """Flag vacuum cleaner robot features that are supported.""" diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index e00cd1b5936b34..015dc4b7431154 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -96,6 +96,7 @@ "rainmachine", "ring", "roku", + "roomba", "samsungtv", "sense", "sentry", diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b064ca1b411718..e7e587ef93a668 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -678,6 +678,9 @@ ring_doorbell==0.6.0 # homeassistant.components.roku roku==4.1.0 +# homeassistant.components.roomba +roombapy==1.4.3 + # homeassistant.components.yamaha rxv==0.6.0 diff --git a/tests/components/roomba/__init__.py b/tests/components/roomba/__init__.py new file mode 100644 index 00000000000000..a255e21c709c64 --- /dev/null +++ b/tests/components/roomba/__init__.py @@ -0,0 +1 @@ +"""Tests for the iRobot Roomba integration.""" diff --git a/tests/components/roomba/test_config_flow.py b/tests/components/roomba/test_config_flow.py new file mode 100644 index 00000000000000..5ac8ea6149a81b --- /dev/null +++ b/tests/components/roomba/test_config_flow.py @@ -0,0 +1,90 @@ +"""Test the iRobot Roomba config flow.""" +from asynctest import patch + +from homeassistant import config_entries, setup +from homeassistant.components.roomba.config_flow import CannotConnect, InvalidAuth +from homeassistant.components.roomba.const import DOMAIN + + +async def test_form(hass): + """Test we get the form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch( + "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", + return_value=True, + ), patch( + "homeassistant.components.roomba.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.roomba.async_setup_entry", return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "create_entry" + assert result2["title"] == "Name of the device" + assert result2["data"] == { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_invalid_auth(hass): + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", + side_effect=InvalidAuth, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "invalid_auth"} + + +async def test_form_cannot_connect(hass): + """Test we handle cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", + side_effect=CannotConnect, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "cannot_connect"} From 2dac24119a6e7fae869bf8160225849168a80c77 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Fri, 27 Mar 2020 07:42:57 +0100 Subject: [PATCH 040/108] Get options to connect --- homeassistant/components/roomba/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 04d705e9178336..d7513b1febbce1 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -49,9 +49,9 @@ async def async_setup_entry(hass, config_entry): address=config_entry.data[CONF_HOST], blid=config_entry.data[CONF_USERNAME], password=config_entry.data[CONF_PASSWORD], - cert_name=config_entry.data[CONF_CERT], - continuous=config_entry.data[CONF_CONTINUOUS], - delay=config_entry.data[CONF_DELAY], + cert_name=config_entry.options[CONF_CERT], + continuous=config_entry.options[CONF_CONTINUOUS], + delay=config_entry.options[CONF_DELAY], ) hass.data[DOMAIN]["roomba"] = roomba try: From 54bd240d3ae02bb35aa543c834d6d286123bc69f Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Fri, 27 Mar 2020 08:23:09 +0100 Subject: [PATCH 041/108] Fix options in config flow --- .../components/roomba/.translations/en.json | 13 +------------ .../components/roomba/.translations/fr.json | 13 +------------ homeassistant/components/roomba/config_flow.py | 6 +----- homeassistant/components/roomba/strings.json | 13 +------------ 4 files changed, 4 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/roomba/.translations/en.json b/homeassistant/components/roomba/.translations/en.json index 99152734c34dfa..b27dfdf8a5245c 100644 --- a/homeassistant/components/roomba/.translations/en.json +++ b/homeassistant/components/roomba/.translations/en.json @@ -17,11 +17,7 @@ }, "error": { "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected error" - }, - "abort": { - "already_configured": "Device is already configured" + "invalid_auth": "Invalid authentication" } }, "options": { @@ -32,13 +28,6 @@ "continuous": "Continuous", "delay": "Delay" } - }, - "options": { - "data": { - "certificate": "Certificate", - "continuous": "Continuous", - "delay": "Delay" - } } } } diff --git a/homeassistant/components/roomba/.translations/fr.json b/homeassistant/components/roomba/.translations/fr.json index fe488be88d01ed..5f2a4176230d0f 100644 --- a/homeassistant/components/roomba/.translations/fr.json +++ b/homeassistant/components/roomba/.translations/fr.json @@ -17,11 +17,7 @@ }, "error": { "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected error" - }, - "abort": { - "already_configured": "Device is already configured" + "invalid_auth": "Invalid authentication" } }, "options": { @@ -32,13 +28,6 @@ "continuous": "Continue", "delay": "Delais" } - }, - "options": { - "data": { - "certificate": "Certificate", - "continuous": "Continuous", - "delay": "Delay" - } } } } diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index e30598cf7384b3..418ec46227ce02 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -120,16 +120,12 @@ def __init__(self, config_entry): self.config_entry = config_entry async def async_step_init(self, user_input=None): - """Manage the options.""" - return await self.async_step_options() - - async def async_step_options(self, user_input=None): """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) return self.async_show_form( - step_id="options", + step_id="init", data_schema=vol.Schema( { vol.Optional( diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json index 99152734c34dfa..b27dfdf8a5245c 100644 --- a/homeassistant/components/roomba/strings.json +++ b/homeassistant/components/roomba/strings.json @@ -17,11 +17,7 @@ }, "error": { "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected error" - }, - "abort": { - "already_configured": "Device is already configured" + "invalid_auth": "Invalid authentication" } }, "options": { @@ -32,13 +28,6 @@ "continuous": "Continuous", "delay": "Delay" } - }, - "options": { - "data": { - "certificate": "Certificate", - "continuous": "Continuous", - "delay": "Delay" - } } } } From f1884cedf937cd27d1dc9511c83d72547ae46945 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Fri, 27 Mar 2020 09:29:48 +0100 Subject: [PATCH 042/108] Fix syntax in config_flow --- homeassistant/components/roomba/__init__.py | 2 +- homeassistant/components/roomba/config_flow.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index d7513b1febbce1..2efc16df425d2a 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -58,7 +58,7 @@ async def async_setup_entry(hass, config_entry): with async_timeout.timeout(9): await hass.async_add_job(roomba.connect) except RoombaConnectionError: - _LOGGER.error("Error to connect to {}".format(config_entry.data[CONF_HOST])) + _LOGGER.error("Error to connect to %s", config_entry.data[CONF_HOST]) return False hass.data[DOMAIN]["name"] = config_entry.data[CONF_NAME] diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 418ec46227ce02..c3dad7cb67d3d9 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -57,13 +57,13 @@ async def async_step_user(self, user_input=None): if DOMAIN not in self.hass.data: self.hass.data[DOMAIN] = {} if user_input is not None: - self.host = user_input[CONF_HOST] - self.username = user_input[CONF_USERNAME] - self.password = user_input[CONF_PASSWORD] - self.name = user_input[CONF_NAME] - self.certificate = user_input[CONF_CERT] - self.continuous = user_input[CONF_CONTINUOUS] - self.delay = user_input[CONF_DELAY] + self.host = user_input["host"] + self.username = user_input["username"] + self.password = user_input["password"] + self.name = user_input["name"] + self.certificate = user_input["certificate"] + self.continuous = user_input["continuous"] + self.delay = user_input["delay"] roomba = Roomba( address=self.host, From c1e3e5c4e2ea3e5b509e52d8a481f49bc0ee6971 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 28 Mar 2020 17:54:19 +0100 Subject: [PATCH 043/108] Remove name (not necessary) --- .../components/roomba/.translations/en.json | 1 - .../components/roomba/.translations/fr.json | 1 - .../components/roomba/config_flow.py | 32 +++++++++---------- homeassistant/components/roomba/strings.json | 1 - 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/roomba/.translations/en.json b/homeassistant/components/roomba/.translations/en.json index b27dfdf8a5245c..7ebffc47c0e71b 100644 --- a/homeassistant/components/roomba/.translations/en.json +++ b/homeassistant/components/roomba/.translations/en.json @@ -8,7 +8,6 @@ "host": "Hostname or IP Address", "username": "Username", "password": "Password", - "name": "Friendly Name", "certificate": "Certificate", "continuous": "Continuous", "delay": "Delay" diff --git a/homeassistant/components/roomba/.translations/fr.json b/homeassistant/components/roomba/.translations/fr.json index 5f2a4176230d0f..e31daab2a51eb5 100644 --- a/homeassistant/components/roomba/.translations/fr.json +++ b/homeassistant/components/roomba/.translations/fr.json @@ -8,7 +8,6 @@ "host": "Nom ou Addresse IP", "username": "Utilisateur", "password": "Mot de passe", - "name": "Nom", "certificate": "Certificat", "continuous": "Continue", "delay": "Delais" diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index c3dad7cb67d3d9..c6d30cc3d49390 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -1,12 +1,13 @@ """Config flow to configure demo component.""" +import asyncio import logging -import time +import async_timeout from roomba import Roomba, RoombaConnectionError import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback from .const import ( @@ -16,7 +17,6 @@ DEFAULT_CERT, DEFAULT_CONTINUOUS, DEFAULT_DELAY, - DEFAULT_NAME, DOMAIN, ) @@ -25,7 +25,6 @@ vol.Required(CONF_HOST): str, vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): str, vol.Optional(CONF_CERT, default=DEFAULT_CERT): str, vol.Optional(CONF_CONTINUOUS, default=DEFAULT_CONTINUOUS): bool, vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): int, @@ -56,11 +55,12 @@ async def async_step_user(self, user_input=None): if DOMAIN not in self.hass.data: self.hass.data[DOMAIN] = {} + if user_input is not None: + self.name = None self.host = user_input["host"] self.username = user_input["username"] self.password = user_input["password"] - self.name = user_input["name"] self.certificate = user_input["certificate"] self.continuous = user_input["continuous"] self.delay = user_input["delay"] @@ -76,27 +76,27 @@ async def async_step_user(self, user_input=None): _LOGGER.debug("Initializing communication with host %s", self.host) try: - await self.hass.async_add_job(roomba.connect) - except RoombaConnectionError: + with async_timeout.timeout(10): + await self.hass.async_add_job(roomba.connect) + while not roomba.roomba_connected: + await asyncio.sleep(0.5) + except RoombaConnectionError as exc: + _LOGGER.error(f"Error: {exc}") errors = {"base": "cannot_connect"} - - timeout = time.time() + 1 - while not roomba.roomba_connected and not errors: - if time.time() > timeout: - errors = {"base": "invalid_auth"} - await self.hass.async_add_job(roomba.disconnect) - time.sleep(0.2) + except asyncio.TimeoutError: + _LOGGER.error("Error: Timeout exceeded, user or password incorrect") + # Api looping if user or password incorrect and roomba exist + await self.hass.async_add_job(roomba.disconnect) + errors = {"base": "invalid_auth"} if roomba.roomba_connected: self.hass.data[DOMAIN]["roomba"] = roomba - self.hass.data[DOMAIN]["name"] = self.name return self.async_create_entry( title=self.name, data={ "host": self.host, "username": self.username, "password": self.password, - "name": self.name, "certificate": self.certificate, "continuous": self.continuous, "delay": self.delay, diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json index b27dfdf8a5245c..7ebffc47c0e71b 100644 --- a/homeassistant/components/roomba/strings.json +++ b/homeassistant/components/roomba/strings.json @@ -8,7 +8,6 @@ "host": "Hostname or IP Address", "username": "Username", "password": "Password", - "name": "Friendly Name", "certificate": "Certificate", "continuous": "Continuous", "delay": "Delay" From 20f2e8347857cb62817c07848ad182cd037d7b3c Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 28 Mar 2020 18:02:41 +0100 Subject: [PATCH 044/108] Add bin sensor --- .../components/roomba/binary_sensor.py | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 homeassistant/components/roomba/binary_sensor.py diff --git a/homeassistant/components/roomba/binary_sensor.py b/homeassistant/components/roomba/binary_sensor.py new file mode 100644 index 00000000000000..2135310721e2a7 --- /dev/null +++ b/homeassistant/components/roomba/binary_sensor.py @@ -0,0 +1,76 @@ +"""Roomba binary sensor entities.""" +import logging + +from homeassistant.components.binary_sensor import BinarySensorDevice + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the iRobot Roomba vacuum cleaner.""" + roomba = hass.data[DOMAIN]["roomba"] + status = roomba.master_state.get("state", {}).get("reported", {}).get("bin", {}) + if "full" in status: + roomba_vac = RoombaBinStatus(roomba) + async_add_entities([roomba_vac], True) + + +class RoombaBinStatus(BinarySensorDevice): + """Class to hold Roomba Sensor basic info.""" + + ICON = "mdi:delete-variant" + + def __init__(self, roomba): + """Initialize the sensor object.""" + self.vacuum = roomba + self.vacuum_state = self.vacuum.master_state.get("state", {}).get( + "reported", {} + ) + self._mac = self.vacuum_state.get("mac") + self._name = self.vacuum_state.get("name") + self._identifier = f"roomba_{self._mac}" + self._bin_status = None + + @property + def name(self): + """Return the name of the sensor.""" + return f"{self._name} bin full" + + @property + def unique_id(self): + """Return the ID of this sensor.""" + return f"bin_{self._mac}" + + @property + def icon(self): + """Return the icon of this sensor.""" + return self.ICON + + @property + def state(self): + """Return the state of the sensor.""" + return self._bin_status == "Full" + + @property + def device_info(self): + """Return the device info of the vacuum cleaner.""" + return { + "identifiers": {(DOMAIN, self._identifier)}, + "name": str(self._name), + } + + async def async_update(self): + """Return the update info of the vacuum cleaner.""" + # No data, no update + if not self.vacuum.master_state: + _LOGGER.debug("Roomba %s has no data yet. Skip update", self.name) + return + self._bin_status = ( + self.vacuum.master_state.get("state", {}) + .get("reported", {}) + .get("bin", {}) + .get("full", False) + ) + _LOGGER.debug("Update Full Bin status from the vacuum: %s", self._bin_status) From 557ac4a8567492e86b091862857ce2ba47f19060 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 28 Mar 2020 18:03:44 +0100 Subject: [PATCH 045/108] Add Battery sensor --- homeassistant/components/roomba/sensor.py | 77 +++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 homeassistant/components/roomba/sensor.py diff --git a/homeassistant/components/roomba/sensor.py b/homeassistant/components/roomba/sensor.py new file mode 100644 index 00000000000000..72d990172231a3 --- /dev/null +++ b/homeassistant/components/roomba/sensor.py @@ -0,0 +1,77 @@ +"""Sensor for checking the battery level of Roomba.""" +import logging + +from homeassistant.const import DEVICE_CLASS_BATTERY +from homeassistant.helpers.entity import Entity + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the iRobot Roomba vacuum cleaner.""" + roomba = hass.data[DOMAIN]["roomba"] + roomba_vac = RoombaBattery(roomba) + async_add_entities([roomba_vac], True) + + +class RoombaBattery(Entity): + """Class to hold Roomba Sensor basic info.""" + + def __init__(self, roomba): + """Initialize the sensor object.""" + self.vacuum = roomba + self.vacuum_state = self.vacuum.master_state.get("state", {}).get( + "reported", {} + ) + self._mac = self.vacuum_state.get("mac") + self._name = self.vacuum_state.get("name") + self._identifier = f"roomba_{self._mac}" + self._battery_level = None + + @property + def name(self): + """Return the name of the sensor.""" + return f"{self._name} battery level" + + @property + def unique_id(self): + """Return the ID of this sensor.""" + return f"battery_{self._mac}" + + @property + def device_class(self): + """Return the device class of the sensor.""" + return DEVICE_CLASS_BATTERY + + @property + def unit_of_measurement(self): + """Return the unit_of_measurement of the device.""" + return "%" + + @property + def state(self): + """Return the state of the sensor.""" + return self._battery_level + + @property + def device_info(self): + """Return the device info of the vacuum cleaner.""" + return { + "identifiers": {(DOMAIN, self._identifier)}, + "name": str(self._name), + } + + async def async_update(self): + """Return the update info of the vacuum cleaner.""" + # No data, no update + if not self.vacuum.master_state: + _LOGGER.debug("Roomba %s has no data yet. Skip update", self.name) + return + self._battery_level = ( + self.vacuum.master_state.get("state", {}).get("reported", {}).get("batPct") + ) + _LOGGER.debug( + "Update battery level status from the vacuum: %s", self._battery_level + ) From 3978d0a1d0515b3c6cca12f71569f6b2f828ddc4 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 28 Mar 2020 18:06:08 +0100 Subject: [PATCH 046/108] Add async_connect --- homeassistant/components/roomba/__init__.py | 79 ++++++++++++++------- homeassistant/components/roomba/const.py | 2 +- 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 2efc16df425d2a..c95115d5707c2e 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -1,13 +1,14 @@ """The roomba component.""" +import asyncio import logging import async_timeout -from roomba import Roomba, RoombaConnectionError +from roomba import Roomba from homeassistant import config_entries -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from .const import CONF_CERT, CONF_CONTINUOUS, CONF_DELAY, DOMAIN +from .const import COMPONENTS, CONF_CERT, CONF_CONTINUOUS, CONF_DELAY, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -44,33 +45,63 @@ async def async_setup_entry(hass, config_entry): if DOMAIN not in hass.data: hass.data[DOMAIN] = {} - if "roomba" not in hass.data[DOMAIN]: - roomba = Roomba( - address=config_entry.data[CONF_HOST], - blid=config_entry.data[CONF_USERNAME], - password=config_entry.data[CONF_PASSWORD], - cert_name=config_entry.options[CONF_CERT], - continuous=config_entry.options[CONF_CONTINUOUS], - delay=config_entry.options[CONF_DELAY], + config_entry.add_update_listener(async_connect) + if not await async_connect(hass, config_entry): + return False + + for component in COMPONENTS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, component) ) - hass.data[DOMAIN]["roomba"] = roomba - try: - with async_timeout.timeout(9): - await hass.async_add_job(roomba.connect) - except RoombaConnectionError: - _LOGGER.error("Error to connect to %s", config_entry.data[CONF_HOST]) - return False - - hass.data[DOMAIN]["name"] = config_entry.data[CONF_NAME] - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, "vacuum") - ) + return True async def async_unload_entry(hass, config_entry): """Unload a config entry.""" - await hass.config_entries.async_forward_entry_unload(config_entry, "vacuum") + for component in COMPONENTS: + hass.async_create_task( + hass.config_entries.async_forward_entry_unload(config_entry, component) + ) roomba = hass.data[DOMAIN]["roomba"] await hass.async_add_job(roomba.disconnect) return True + + +async def async_connect(hass, config_entry): + """Connect to vacuum.""" + # Check if triggred listerner + if "roomba" in hass.data[DOMAIN]: + await hass.async_add_job(hass.data[DOMAIN]["roomba"].disconnect) + await asyncio.sleep(1) + + roomba = Roomba( + address=config_entry.data[CONF_HOST], + blid=config_entry.data[CONF_USERNAME], + password=config_entry.data[CONF_PASSWORD], + cert_name=config_entry.options[CONF_CERT], + continuous=config_entry.options[CONF_CONTINUOUS], + delay=config_entry.options[CONF_DELAY], + ) + + hass.data[DOMAIN]["roomba"] = roomba + + try: + name = None + with async_timeout.timeout(9): + await hass.async_add_job(roomba.connect) + while not roomba.roomba_connected or name is None: + # Waiting for connection and check datas ready + name = ( + roomba.master_state.get("state", {}) + .get("reported", {}) + .get("name", None) + ) + await asyncio.sleep(0.5) + except asyncio.TimeoutError: + # api looping if user or password incorrect and roomba exist + _LOGGER.error("Timeout exceeded") + await hass.async_add_job(roomba.disconnect) + return False + + return True diff --git a/homeassistant/components/roomba/const.py b/homeassistant/components/roomba/const.py index f5c1a1a3d3e913..803a1ad87ff272 100644 --- a/homeassistant/components/roomba/const.py +++ b/homeassistant/components/roomba/const.py @@ -1,9 +1,9 @@ """The roomba constants.""" DOMAIN = "roomba" +COMPONENTS = ["sensor", "binary_sensor", "vacuum"] CONF_CERT = "certificate" CONF_CONTINUOUS = "continuous" CONF_DELAY = "delay" DEFAULT_CERT = "/etc/ssl/certs/ca-certificates.crt" DEFAULT_CONTINUOUS = True DEFAULT_DELAY = 1 -DEFAULT_NAME = "Roomba" From a0c939211e2b3b057e4d3b1c63747c57e376e3f7 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 28 Mar 2020 18:07:17 +0100 Subject: [PATCH 047/108] Fix typo --- homeassistant/components/roomba/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index c95115d5707c2e..cca6e7648de1c4 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -70,7 +70,7 @@ async def async_unload_entry(hass, config_entry): async def async_connect(hass, config_entry): """Connect to vacuum.""" - # Check if triggred listerner + # Check if triggered listener if "roomba" in hass.data[DOMAIN]: await hass.async_add_job(hass.data[DOMAIN]["roomba"].disconnect) await asyncio.sleep(1) From 4fdf7cc9dadf34c41d56bac326d8f84afa87fb65 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 28 Mar 2020 18:08:34 +0100 Subject: [PATCH 048/108] Add Model and Software version --- homeassistant/components/roomba/vacuum.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/roomba/vacuum.py b/homeassistant/components/roomba/vacuum.py index 5539fc7135cf74..84b77a1c669aa4 100644 --- a/homeassistant/components/roomba/vacuum.py +++ b/homeassistant/components/roomba/vacuum.py @@ -14,6 +14,7 @@ SUPPORT_TURN_ON, VacuumDevice, ) +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from .const import DOMAIN @@ -55,41 +56,49 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the iRobot Roomba vacuum cleaner.""" - - name = hass.data[DOMAIN]["name"] roomba = hass.data[DOMAIN]["roomba"] - roomba_vac = RoombaVacuum(name, roomba) + roomba_vac = RoombaVacuum(roomba) async_add_entities([roomba_vac], True) class RoombaVacuum(VacuumDevice): """Representation of a Roomba Vacuum cleaner robot.""" - def __init__(self, name, roomba): + def __init__(self, roomba): """Initialize the Roomba handler.""" self._available = False self._battery_level = None self._capabilities = {} self._fan_speed = None self._is_on = False - self._name = name self._state_attrs = {} self._status = None self.vacuum = roomba - self.vacuum_state = None + self.vacuum_state = self.vacuum.master_state.get("state", {}).get( + "reported", {} + ) + self._mac = self.vacuum_state.get("mac") + self._name = self.vacuum_state.get("name") @property def unique_id(self): """Return the uniqueid of the vacuum cleaner.""" - return self._name + return "roomba_{}".format(self._mac) @property def device_info(self): """Return the device info of the vacuum cleaner.""" return { "identifiers": {(DOMAIN, self.unique_id)}, + "connections": {(CONNECTION_NETWORK_MAC, self._mac)}, "manufacturer": "iRobots", "name": str(self._name), + "sw_version": self.vacuum.master_state.get("state", {}) + .get("reported", {}) + .get("softwareVer"), + "model": self.vacuum.master_state.get("state", {}) + .get("reported", {}) + .get("sku"), } @property From 6c32598c6e9207de5edb554ea939050c26890af3 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 28 Mar 2020 18:18:21 +0100 Subject: [PATCH 049/108] Update Roombapy 1.5.0 --- homeassistant/components/roomba/manifest.json | 9 +++++++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/roomba/manifest.json b/homeassistant/components/roomba/manifest.json index 30e975bbb0a996..b61a4651cc3321 100644 --- a/homeassistant/components/roomba/manifest.json +++ b/homeassistant/components/roomba/manifest.json @@ -3,6 +3,11 @@ "name": "iRobot Roomba", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/roomba", - "requirements": ["roombapy==1.4.3"], - "codeowners": ["@pschmitt"] + "requirements": [ + "roombapy==1.5.0" + ], + "dependencies": [], + "codeowners": [ + "@pschmitt" + ] } diff --git a/requirements_all.txt b/requirements_all.txt index 48322760753abb..f8df2c5475e0c7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1812,7 +1812,7 @@ rocketchat-API==0.6.1 roku==4.1.0 # homeassistant.components.roomba -roombapy==1.4.3 +roombapy==1.5.0 # homeassistant.components.rova rova==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e7e587ef93a668..fa50cc3837d2fd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -679,7 +679,7 @@ ring_doorbell==0.6.0 roku==4.1.0 # homeassistant.components.roomba -roombapy==1.4.3 +roombapy==1.5.0 # homeassistant.components.yamaha rxv==0.6.0 From 5114707300d0d97630175a7a24cba967668454eb Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 28 Mar 2020 20:13:05 +0100 Subject: [PATCH 050/108] Add validate_input --- .../components/roomba/.translations/en.json | 1 + .../components/roomba/.translations/fr.json | 5 +- homeassistant/components/roomba/__init__.py | 3 +- .../components/roomba/config_flow.py | 94 ++++++++++--------- homeassistant/components/roomba/strings.json | 1 + 5 files changed, 58 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/roomba/.translations/en.json b/homeassistant/components/roomba/.translations/en.json index 7ebffc47c0e71b..aaf4518d5fd4ae 100644 --- a/homeassistant/components/roomba/.translations/en.json +++ b/homeassistant/components/roomba/.translations/en.json @@ -15,6 +15,7 @@ } }, "error": { + "unknown" : "Unexpected error", "cannot_connect": "Failed to connect, please try again", "invalid_auth": "Invalid authentication" } diff --git a/homeassistant/components/roomba/.translations/fr.json b/homeassistant/components/roomba/.translations/fr.json index e31daab2a51eb5..4ac792d9cd5b73 100644 --- a/homeassistant/components/roomba/.translations/fr.json +++ b/homeassistant/components/roomba/.translations/fr.json @@ -15,8 +15,9 @@ } }, "error": { - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication" + "unknown" : "Erreur imprévu", + "cannot_connect": "Impossible de se connecter", + "invalid_auth": "Authentification invalide" } }, "options": { diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index cca6e7648de1c4..a933b99bceca3e 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -84,8 +84,6 @@ async def async_connect(hass, config_entry): delay=config_entry.options[CONF_DELAY], ) - hass.data[DOMAIN]["roomba"] = roomba - try: name = None with async_timeout.timeout(9): @@ -98,6 +96,7 @@ async def async_connect(hass, config_entry): .get("name", None) ) await asyncio.sleep(0.5) + hass.data[DOMAIN]["roomba"] = roomba except asyncio.TimeoutError: # api looping if user or password incorrect and roomba exist _LOGGER.error("Timeout exceeded") diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index c6d30cc3d49390..9fde62e3c3b5f1 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -6,7 +6,7 @@ from roomba import Roomba, RoombaConnectionError import voluptuous as vol -from homeassistant import config_entries +from homeassistant import config_entries, core, exceptions from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback @@ -34,6 +34,41 @@ _LOGGER = logging.getLogger(__name__) +async def validate_input(hass: core.HomeAssistant, data): + """Validate the user input allows us to connect. + + Data has the keys from DATA_SCHEMA with values provided by the user. + """ + api = Roomba( + address=data[CONF_HOST], + blid=data[CONF_USERNAME], + password=data[CONF_PASSWORD], + cert_name=data[CONF_CERT], + continuous=data[CONF_CONTINUOUS], + delay=data[CONF_DELAY], + ) + try: + name = None + with async_timeout.timeout(10): + await hass.async_add_job(api.connect) + while not api.roomba_connected or name is None: + name = ( + api.master_state.get("state", {}) + .get("reported", {}) + .get("name", None) + ) + await asyncio.sleep(0.5) + hass.data[DOMAIN]["roomba"] = api + except RoombaConnectionError: + raise CannotConnect + except asyncio.TimeoutError: + # Api looping if user or password incorrect and roomba exist + await hass.async_add_job(api.disconnect) + raise InvalidAuth + + return {"title": name} + + class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Demo configuration flow.""" @@ -57,51 +92,18 @@ async def async_step_user(self, user_input=None): self.hass.data[DOMAIN] = {} if user_input is not None: - self.name = None - self.host = user_input["host"] - self.username = user_input["username"] - self.password = user_input["password"] - self.certificate = user_input["certificate"] - self.continuous = user_input["continuous"] - self.delay = user_input["delay"] - - roomba = Roomba( - address=self.host, - blid=self.username, - password=self.password, - cert_name=self.certificate, - continuous=self.continuous, - delay=self.delay, - ) - _LOGGER.debug("Initializing communication with host %s", self.host) - try: - with async_timeout.timeout(10): - await self.hass.async_add_job(roomba.connect) - while not roomba.roomba_connected: - await asyncio.sleep(0.5) - except RoombaConnectionError as exc: - _LOGGER.error(f"Error: {exc}") + info = await validate_input(self.hass, user_input) + except CannotConnect: errors = {"base": "cannot_connect"} - except asyncio.TimeoutError: - _LOGGER.error("Error: Timeout exceeded, user or password incorrect") - # Api looping if user or password incorrect and roomba exist - await self.hass.async_add_job(roomba.disconnect) + except InvalidAuth: errors = {"base": "invalid_auth"} + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" - if roomba.roomba_connected: - self.hass.data[DOMAIN]["roomba"] = roomba - return self.async_create_entry( - title=self.name, - data={ - "host": self.host, - "username": self.username, - "password": self.password, - "certificate": self.certificate, - "continuous": self.continuous, - "delay": self.delay, - }, - ) + if "base" not in errors: + return self.async_create_entry(title=info["title"], data=user_input) # If there was no user input, do not show the errors. if user_input is None: @@ -147,3 +149,11 @@ async def async_step_init(self, user_input=None): } ), ) + + +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" + + +class InvalidAuth(exceptions.HomeAssistantError): + """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json index 7ebffc47c0e71b..f34f1214c2bb1a 100644 --- a/homeassistant/components/roomba/strings.json +++ b/homeassistant/components/roomba/strings.json @@ -15,6 +15,7 @@ } }, "error": { + "unknown" : "Unexpected error", "cannot_connect": "Failed to connect, please try again", "invalid_auth": "Invalid authentication" } From ff4d19d6d93a761f0ced34eb4f2b365bd07bddfd Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 29 Mar 2020 15:57:24 +0200 Subject: [PATCH 051/108] Add connect and disconnect functions --- homeassistant/components/roomba/__init__.py | 95 ++++++++++++------- .../components/roomba/config_flow.py | 46 ++------- 2 files changed, 69 insertions(+), 72 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index a933b99bceca3e..10eb24a4f1f1ec 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -3,9 +3,9 @@ import logging import async_timeout -from roomba import Roomba +from roomba import Roomba, RoombaConnectionError -from homeassistant import config_entries +from homeassistant import config_entries, exceptions from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from .const import COMPONENTS, CONF_CERT, CONF_CONTINUOUS, CONF_DELAY, DOMAIN @@ -45,48 +45,35 @@ async def async_setup_entry(hass, config_entry): if DOMAIN not in hass.data: hass.data[DOMAIN] = {} - config_entry.add_update_listener(async_connect) - if not await async_connect(hass, config_entry): - return False + if "roomba" not in hass.data[DOMAIN]: - for component in COMPONENTS: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + roomba = Roomba( + address=config_entry.data[CONF_HOST], + blid=config_entry.data[CONF_USERNAME], + password=config_entry.data[CONF_PASSWORD], + cert_name=config_entry.options[CONF_CERT], + continuous=config_entry.options[CONF_CONTINUOUS], + delay=config_entry.options[CONF_DELAY], ) - return True + if not await async_connect_or_timeout(hass, roomba): + return False - -async def async_unload_entry(hass, config_entry): - """Unload a config entry.""" for component in COMPONENTS: hass.async_create_task( - hass.config_entries.async_forward_entry_unload(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, component) ) - roomba = hass.data[DOMAIN]["roomba"] - await hass.async_add_job(roomba.disconnect) + + config_entry.add_update_listener(async_update_options) + return True -async def async_connect(hass, config_entry): +async def async_connect_or_timeout(hass, roomba): """Connect to vacuum.""" - # Check if triggered listener - if "roomba" in hass.data[DOMAIN]: - await hass.async_add_job(hass.data[DOMAIN]["roomba"].disconnect) - await asyncio.sleep(1) - - roomba = Roomba( - address=config_entry.data[CONF_HOST], - blid=config_entry.data[CONF_USERNAME], - password=config_entry.data[CONF_PASSWORD], - cert_name=config_entry.options[CONF_CERT], - continuous=config_entry.options[CONF_CONTINUOUS], - delay=config_entry.options[CONF_DELAY], - ) - try: - name = None - with async_timeout.timeout(9): + hass.data[DOMAIN]["name"] = name = None + with async_timeout.timeout(10): await hass.async_add_job(roomba.connect) while not roomba.roomba_connected or name is None: # Waiting for connection and check datas ready @@ -96,11 +83,47 @@ async def async_connect(hass, config_entry): .get("name", None) ) await asyncio.sleep(0.5) - hass.data[DOMAIN]["roomba"] = roomba + hass.data[DOMAIN]["roomba"] = roomba + hass.data[DOMAIN]["name"] = name + except RoombaConnectionError: + _LOGGER.error("Error to connect to vacuum") + raise CannotConnect except asyncio.TimeoutError: # api looping if user or password incorrect and roomba exist - _LOGGER.error("Timeout exceeded") - await hass.async_add_job(roomba.disconnect) - return False + await async_disconnect_or_timeout(hass, roomba) + _LOGGER.exception("Timeout expired, user or password incorrect") + raise InvalidAuth + + return True + +async def async_disconnect_or_timeout(hass, roomba): + """Disconnect to vacuum.""" + await hass.async_add_job(roomba.disconnect) + await asyncio.sleep(1) return True + + +async def async_update_options(hass, config_entry): + """Update options.""" + roomba = hass.data[DOMAIN]["roomba"] + await async_disconnect_or_timeout(hass, roomba) + await async_connect_or_timeout(hass, roomba) + + +async def async_unload_entry(hass, config_entry): + """Unload a config entry.""" + for component in COMPONENTS: + hass.async_create_task( + hass.config_entries.async_forward_entry_unload(config_entry, component) + ) + roomba = hass.data[DOMAIN]["roomba"] + return await async_disconnect_or_timeout(hass, roomba) + + +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" + + +class InvalidAuth(exceptions.HomeAssistantError): + """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 9fde62e3c3b5f1..4324e21dba4c8d 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -1,15 +1,14 @@ """Config flow to configure demo component.""" -import asyncio import logging -import async_timeout -from roomba import Roomba, RoombaConnectionError +from roomba import Roomba import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant import config_entries, core from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback +from . import CannotConnect, InvalidAuth, async_connect_or_timeout from .const import ( CONF_CERT, CONF_CONTINUOUS, @@ -39,7 +38,7 @@ async def validate_input(hass: core.HomeAssistant, data): Data has the keys from DATA_SCHEMA with values provided by the user. """ - api = Roomba( + roomba = Roomba( address=data[CONF_HOST], blid=data[CONF_USERNAME], password=data[CONF_PASSWORD], @@ -47,26 +46,8 @@ async def validate_input(hass: core.HomeAssistant, data): continuous=data[CONF_CONTINUOUS], delay=data[CONF_DELAY], ) - try: - name = None - with async_timeout.timeout(10): - await hass.async_add_job(api.connect) - while not api.roomba_connected or name is None: - name = ( - api.master_state.get("state", {}) - .get("reported", {}) - .get("name", None) - ) - await asyncio.sleep(0.5) - hass.data[DOMAIN]["roomba"] = api - except RoombaConnectionError: - raise CannotConnect - except asyncio.TimeoutError: - # Api looping if user or password incorrect and roomba exist - await hass.async_add_job(api.disconnect) - raise InvalidAuth - return {"title": name} + await async_connect_or_timeout(hass, roomba) class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -93,17 +74,18 @@ async def async_step_user(self, user_input=None): if user_input is not None: try: - info = await validate_input(self.hass, user_input) + await validate_input(self.hass, user_input) except CannotConnect: errors = {"base": "cannot_connect"} except InvalidAuth: errors = {"base": "invalid_auth"} except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" + errors = {"base": "unknown"} if "base" not in errors: - return self.async_create_entry(title=info["title"], data=user_input) + return self.async_create_entry( + title=self.hass.data[DOMAIN]["name"], data=user_input + ) # If there was no user input, do not show the errors. if user_input is None: @@ -149,11 +131,3 @@ async def async_step_init(self, user_input=None): } ), ) - - -class CannotConnect(exceptions.HomeAssistantError): - """Error to indicate we cannot connect.""" - - -class InvalidAuth(exceptions.HomeAssistantError): - """Error to indicate there is invalid auth.""" From 26a89422e89d7c88dd3c0ec3066e607afdc99f09 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 29 Mar 2020 19:23:00 +0200 Subject: [PATCH 052/108] Remove test config flow --- requirements_test_all.txt | 3 - tests/components/roomba/__init__.py | 1 - tests/components/roomba/test_config_flow.py | 90 --------------------- 3 files changed, 94 deletions(-) delete mode 100644 tests/components/roomba/__init__.py delete mode 100644 tests/components/roomba/test_config_flow.py diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fa50cc3837d2fd..b064ca1b411718 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -678,9 +678,6 @@ ring_doorbell==0.6.0 # homeassistant.components.roku roku==4.1.0 -# homeassistant.components.roomba -roombapy==1.5.0 - # homeassistant.components.yamaha rxv==0.6.0 diff --git a/tests/components/roomba/__init__.py b/tests/components/roomba/__init__.py deleted file mode 100644 index a255e21c709c64..00000000000000 --- a/tests/components/roomba/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the iRobot Roomba integration.""" diff --git a/tests/components/roomba/test_config_flow.py b/tests/components/roomba/test_config_flow.py deleted file mode 100644 index 5ac8ea6149a81b..00000000000000 --- a/tests/components/roomba/test_config_flow.py +++ /dev/null @@ -1,90 +0,0 @@ -"""Test the iRobot Roomba config flow.""" -from asynctest import patch - -from homeassistant import config_entries, setup -from homeassistant.components.roomba.config_flow import CannotConnect, InvalidAuth -from homeassistant.components.roomba.const import DOMAIN - - -async def test_form(hass): - """Test we get the form.""" - await setup.async_setup_component(hass, "persistent_notification", {}) - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - assert result["type"] == "form" - assert result["errors"] == {} - - with patch( - "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", - return_value=True, - ), patch( - "homeassistant.components.roomba.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.roomba.async_setup_entry", return_value=True, - ) as mock_setup_entry: - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "host": "1.1.1.1", - "username": "test-username", - "password": "test-password", - }, - ) - - assert result2["type"] == "create_entry" - assert result2["title"] == "Name of the device" - assert result2["data"] == { - "host": "1.1.1.1", - "username": "test-username", - "password": "test-password", - } - await hass.async_block_till_done() - assert len(mock_setup.mock_calls) == 1 - assert len(mock_setup_entry.mock_calls) == 1 - - -async def test_form_invalid_auth(hass): - """Test we handle invalid auth.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - with patch( - "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", - side_effect=InvalidAuth, - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "host": "1.1.1.1", - "username": "test-username", - "password": "test-password", - }, - ) - - assert result2["type"] == "form" - assert result2["errors"] == {"base": "invalid_auth"} - - -async def test_form_cannot_connect(hass): - """Test we handle cannot connect error.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - with patch( - "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", - side_effect=CannotConnect, - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "host": "1.1.1.1", - "username": "test-username", - "password": "test-password", - }, - ) - - assert result2["type"] == "form" - assert result2["errors"] == {"base": "cannot_connect"} From f85d48a62c6f741176086da064ac7d09b678c8f8 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 29 Mar 2020 19:43:43 +0200 Subject: [PATCH 053/108] Add variables after loop --- homeassistant/components/roomba/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 10eb24a4f1f1ec..67eeead28044e3 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -83,8 +83,8 @@ async def async_connect_or_timeout(hass, roomba): .get("name", None) ) await asyncio.sleep(0.5) - hass.data[DOMAIN]["roomba"] = roomba - hass.data[DOMAIN]["name"] = name + hass.data[DOMAIN]["roomba"] = roomba + hass.data[DOMAIN]["name"] = name except RoombaConnectionError: _LOGGER.error("Error to connect to vacuum") raise CannotConnect From d0a0be2ffb98f15ad8ba3b733a3db519b07330f8 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 29 Mar 2020 19:46:42 +0200 Subject: [PATCH 054/108] Fix translate --- homeassistant/components/roomba/.translations/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/.translations/fr.json b/homeassistant/components/roomba/.translations/fr.json index 4ac792d9cd5b73..17ee2455f4e056 100644 --- a/homeassistant/components/roomba/.translations/fr.json +++ b/homeassistant/components/roomba/.translations/fr.json @@ -3,7 +3,7 @@ "title": "iRobot Roomba", "step": { "user": { - "title": "Connect to the device", + "title": "Connexion au périphérique", "data": { "host": "Nom ou Addresse IP", "username": "Utilisateur", From 0203110343e291634729c5fff0c2ee7363b6b70f Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 29 Mar 2020 19:50:08 +0200 Subject: [PATCH 055/108] Fix typo --- homeassistant/components/roomba/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 4324e21dba4c8d..295e3e25e19cbc 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -1,4 +1,4 @@ -"""Config flow to configure demo component.""" +"""Config flow to configure roomba component.""" import logging from roomba import Roomba From 15f1bd2c2afb42ea8d8da42d2c43800c8a5f380a Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 29 Mar 2020 19:58:25 +0200 Subject: [PATCH 056/108] Fix state of bin --- homeassistant/components/roomba/binary_sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/binary_sensor.py b/homeassistant/components/roomba/binary_sensor.py index 2135310721e2a7..0766c2b1c71d21 100644 --- a/homeassistant/components/roomba/binary_sensor.py +++ b/homeassistant/components/roomba/binary_sensor.py @@ -51,7 +51,7 @@ def icon(self): @property def state(self): """Return the state of the sensor.""" - return self._bin_status == "Full" + return self._bin_status @property def device_info(self): From 4cc82e6cd254f14883d554a824484387ede6e706 Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:09:50 +0200 Subject: [PATCH 057/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 67eeead28044e3..2d7d44250d88ad 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -106,7 +106,7 @@ async def async_disconnect_or_timeout(hass, roomba): async def async_update_options(hass, config_entry): """Update options.""" - roomba = hass.data[DOMAIN]["roomba"] + await hass.config_entries.async_reload(config_entry.entry_id) await async_disconnect_or_timeout(hass, roomba) await async_connect_or_timeout(hass, roomba) From 81f071f9adf261a84b77145af5619abdd075d033 Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:10:34 +0200 Subject: [PATCH 058/108] Update homeassistant/components/roomba/config_flow.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 295e3e25e19cbc..522f048ad450e9 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -63,7 +63,7 @@ def async_get_options_flow(config_entry): async def async_step_import(self, import_info): """Set the config entry up from yaml.""" - return self.async_create_entry(title="Roomba", data={}) + return await self.async_step_user(import_info) async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" From 569febea12f15e7f855cecb6e4f775eddb03335e Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:10:51 +0200 Subject: [PATCH 059/108] Update homeassistant/components/roomba/config_flow.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 522f048ad450e9..274959b2aa8909 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -51,7 +51,7 @@ async def validate_input(hass: core.HomeAssistant, data): class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - """Demo configuration flow.""" + """Roomba configuration flow.""" VERSION = 1 From d998f29722f121ddda7d35ebf6c0595ec2488576 Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:11:30 +0200 Subject: [PATCH 060/108] Update homeassistant/components/roomba/config_flow.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 274959b2aa8909..df2e380b896952 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -84,7 +84,7 @@ async def async_step_user(self, user_input=None): if "base" not in errors: return self.async_create_entry( - title=self.hass.data[DOMAIN]["name"], data=user_input + title=validated_input["title"], data=user_input ) # If there was no user input, do not show the errors. From f46360641030610b5c59ed38b8df8d546fb7a2be Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:11:59 +0200 Subject: [PATCH 061/108] Update homeassistant/components/roomba/config_flow.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/config_flow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index df2e380b896952..2e58649ed0631b 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -78,7 +78,6 @@ async def async_step_user(self, user_input=None): except CannotConnect: errors = {"base": "cannot_connect"} except InvalidAuth: - errors = {"base": "invalid_auth"} except Exception: # pylint: disable=broad-except errors = {"base": "unknown"} From 75217112d36b2c1e7a3e8d169fe188b9b4321124 Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:12:12 +0200 Subject: [PATCH 062/108] Update homeassistant/components/roomba/config_flow.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/config_flow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 2e58649ed0631b..1ace6996ac09c2 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -77,7 +77,6 @@ async def async_step_user(self, user_input=None): await validate_input(self.hass, user_input) except CannotConnect: errors = {"base": "cannot_connect"} - except InvalidAuth: except Exception: # pylint: disable=broad-except errors = {"base": "unknown"} From 0c49bd553ab70431654527ab34cadbf653610fe8 Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:12:26 +0200 Subject: [PATCH 063/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 2d7d44250d88ad..6492e51a0e742c 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -92,7 +92,7 @@ async def async_connect_or_timeout(hass, roomba): # api looping if user or password incorrect and roomba exist await async_disconnect_or_timeout(hass, roomba) _LOGGER.exception("Timeout expired, user or password incorrect") - raise InvalidAuth + raise CannotConnect return True From 331b470fc8393a1087b312f8489538aab7a6b40f Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:13:35 +0200 Subject: [PATCH 064/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 6492e51a0e742c..dd022ba28f0f89 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -36,7 +36,6 @@ async def async_setup_entry(hass, config_entry): hass.config_entries.async_update_entry( config_entry, options={ - "certificate": config_entry.data[CONF_CERT], "continuous": config_entry.data[CONF_CONTINUOUS], "delay": config_entry.data[CONF_DELAY], }, From 0a535b6890b240b2ef8bdb49125076eae98c34eb Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:13:47 +0200 Subject: [PATCH 065/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index dd022ba28f0f89..79b5e481626fc9 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -21,7 +21,7 @@ async def async_setup(hass, config): if not hass.config_entries.async_entries(DOMAIN): hass.async_create_task( hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={} + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=config ) ) From 9f34793c9243ff5d2f3ce6aaad64a4370d5512d8 Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 2 Apr 2020 14:14:07 +0200 Subject: [PATCH 066/108] Update homeassistant/components/roomba/config_flow.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 1ace6996ac09c2..918923a87dd6e6 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -54,7 +54,7 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Roomba configuration flow.""" VERSION = 1 - + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH @staticmethod @callback def async_get_options_flow(config_entry): From 71230a34452d9f17363d7baafe3c8cf34de93d64 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Fri, 3 Apr 2020 07:47:21 +0200 Subject: [PATCH 067/108] Remove invalid auth --- homeassistant/components/roomba/.translations/en.json | 4 +--- homeassistant/components/roomba/.translations/fr.json | 4 +--- homeassistant/components/roomba/strings.json | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/roomba/.translations/en.json b/homeassistant/components/roomba/.translations/en.json index aaf4518d5fd4ae..b40c8bdaebea80 100644 --- a/homeassistant/components/roomba/.translations/en.json +++ b/homeassistant/components/roomba/.translations/en.json @@ -16,15 +16,13 @@ }, "error": { "unknown" : "Unexpected error", - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication" + "cannot_connect": "Failed to connect, please try again" } }, "options": { "step": { "init": { "data": { - "certificate": "Certificate", "continuous": "Continuous", "delay": "Delay" } diff --git a/homeassistant/components/roomba/.translations/fr.json b/homeassistant/components/roomba/.translations/fr.json index 17ee2455f4e056..e99411aa2dba9e 100644 --- a/homeassistant/components/roomba/.translations/fr.json +++ b/homeassistant/components/roomba/.translations/fr.json @@ -16,15 +16,13 @@ }, "error": { "unknown" : "Erreur imprévu", - "cannot_connect": "Impossible de se connecter", - "invalid_auth": "Authentification invalide" + "cannot_connect": "Impossible de se connecter" } }, "options": { "step": { "init": { "data": { - "certificate": "Certificat", "continuous": "Continue", "delay": "Delais" } diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json index f34f1214c2bb1a..05538cb40c8ac2 100644 --- a/homeassistant/components/roomba/strings.json +++ b/homeassistant/components/roomba/strings.json @@ -16,15 +16,13 @@ }, "error": { "unknown" : "Unexpected error", - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication" + "cannot_connect": "Failed to connect, please try again" } }, "options": { "step": { "init": { "data": { - "certificate": "Certificate", "continuous": "Continuous", "delay": "Delay" } From 9ea758f67210070e76e009016ee9608f8b079eba Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Fri, 3 Apr 2020 07:48:14 +0200 Subject: [PATCH 068/108] Add call function reported_state --- homeassistant/components/roomba/binary_sensor.py | 12 ++++-------- homeassistant/components/roomba/sensor.py | 9 +++------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/roomba/binary_sensor.py b/homeassistant/components/roomba/binary_sensor.py index 0766c2b1c71d21..02cf4163cf853a 100644 --- a/homeassistant/components/roomba/binary_sensor.py +++ b/homeassistant/components/roomba/binary_sensor.py @@ -3,6 +3,7 @@ from homeassistant.components.binary_sensor import BinarySensorDevice +from . import roomba_reported_state from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -11,7 +12,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the iRobot Roomba vacuum cleaner.""" roomba = hass.data[DOMAIN]["roomba"] - status = roomba.master_state.get("state", {}).get("reported", {}).get("bin", {}) + status = roomba_reported_state(roomba).get("bin", {}) if "full" in status: roomba_vac = RoombaBinStatus(roomba) async_add_entities([roomba_vac], True) @@ -25,9 +26,7 @@ class RoombaBinStatus(BinarySensorDevice): def __init__(self, roomba): """Initialize the sensor object.""" self.vacuum = roomba - self.vacuum_state = self.vacuum.master_state.get("state", {}).get( - "reported", {} - ) + self.vacuum_state = roomba_reported_state(roomba) self._mac = self.vacuum_state.get("mac") self._name = self.vacuum_state.get("name") self._identifier = f"roomba_{self._mac}" @@ -68,9 +67,6 @@ async def async_update(self): _LOGGER.debug("Roomba %s has no data yet. Skip update", self.name) return self._bin_status = ( - self.vacuum.master_state.get("state", {}) - .get("reported", {}) - .get("bin", {}) - .get("full", False) + roomba_reported_state(self.vacuum).get("bin", {}).get("full", False) ) _LOGGER.debug("Update Full Bin status from the vacuum: %s", self._bin_status) diff --git a/homeassistant/components/roomba/sensor.py b/homeassistant/components/roomba/sensor.py index 72d990172231a3..b54fd168bfabea 100644 --- a/homeassistant/components/roomba/sensor.py +++ b/homeassistant/components/roomba/sensor.py @@ -4,6 +4,7 @@ from homeassistant.const import DEVICE_CLASS_BATTERY from homeassistant.helpers.entity import Entity +from . import roomba_reported_state from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -22,9 +23,7 @@ class RoombaBattery(Entity): def __init__(self, roomba): """Initialize the sensor object.""" self.vacuum = roomba - self.vacuum_state = self.vacuum.master_state.get("state", {}).get( - "reported", {} - ) + self.vacuum_state = roomba_reported_state(roomba) self._mac = self.vacuum_state.get("mac") self._name = self.vacuum_state.get("name") self._identifier = f"roomba_{self._mac}" @@ -69,9 +68,7 @@ async def async_update(self): if not self.vacuum.master_state: _LOGGER.debug("Roomba %s has no data yet. Skip update", self.name) return - self._battery_level = ( - self.vacuum.master_state.get("state", {}).get("reported", {}).get("batPct") - ) + self._battery_level = roomba_reported_state(self.vacuum).get("batPct") _LOGGER.debug( "Update battery level status from the vacuum: %s", self._battery_level ) From c0e1bae718dafda7d03744c87cb7b4926394f0fb Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 4 Apr 2020 09:18:37 +0200 Subject: [PATCH 069/108] Add options reload --- homeassistant/components/roomba/__init__.py | 58 ++++++++++--------- .../components/roomba/config_flow.py | 14 ++--- 2 files changed, 34 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 79b5e481626fc9..b45759fe0d459b 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -15,6 +15,8 @@ async def async_setup(hass, config): """Set up the roomba environment.""" + hass.data.setdefault(DOMAIN, {}) + if DOMAIN not in config: return True @@ -29,6 +31,7 @@ async def async_setup(hass, config): async def async_setup_entry(hass, config_entry): """Set the config entry up.""" # Set up roomba platforms with config entry + _LOGGER.debug(config_entry.entry_id) if config_entry.data is None: return False @@ -41,22 +44,21 @@ async def async_setup_entry(hass, config_entry): }, ) - if DOMAIN not in hass.data: - hass.data[DOMAIN] = {} - if "roomba" not in hass.data[DOMAIN]: - roomba = Roomba( address=config_entry.data[CONF_HOST], blid=config_entry.data[CONF_USERNAME], password=config_entry.data[CONF_PASSWORD], - cert_name=config_entry.options[CONF_CERT], + cert_name=config_entry.data[CONF_CERT], continuous=config_entry.options[CONF_CONTINUOUS], delay=config_entry.options[CONF_DELAY], ) - if not await async_connect_or_timeout(hass, roomba): - return False + try: + if not await async_connect_or_timeout(hass, roomba): + return False + except CannotConnect: + raise exceptions.ConfigEntryNotReady for component in COMPONENTS: hass.async_create_task( @@ -74,23 +76,20 @@ async def async_connect_or_timeout(hass, roomba): hass.data[DOMAIN]["name"] = name = None with async_timeout.timeout(10): await hass.async_add_job(roomba.connect) + _LOGGER.debug("Initialize to connect to vacuum") while not roomba.roomba_connected or name is None: # Waiting for connection and check datas ready - name = ( - roomba.master_state.get("state", {}) - .get("reported", {}) - .get("name", None) - ) - await asyncio.sleep(0.5) - hass.data[DOMAIN]["roomba"] = roomba - hass.data[DOMAIN]["name"] = name + name = roomba_reported_state(roomba).get("name", None) + hass.data[DOMAIN]["roomba"] = roomba + hass.data[DOMAIN]["name"] = name + await asyncio.sleep(1) except RoombaConnectionError: _LOGGER.error("Error to connect to vacuum") raise CannotConnect except asyncio.TimeoutError: # api looping if user or password incorrect and roomba exist await async_disconnect_or_timeout(hass, roomba) - _LOGGER.exception("Timeout expired, user or password incorrect") + _LOGGER.exception("Timeout expired") raise CannotConnect return True @@ -98,7 +97,9 @@ async def async_connect_or_timeout(hass, roomba): async def async_disconnect_or_timeout(hass, roomba): """Disconnect to vacuum.""" - await hass.async_add_job(roomba.disconnect) + with async_timeout.timeout(3): + await hass.async_add_job(roomba.disconnect) + hass.data[DOMAIN].pop("roomba", None) await asyncio.sleep(1) return True @@ -106,23 +107,24 @@ async def async_disconnect_or_timeout(hass, roomba): async def async_update_options(hass, config_entry): """Update options.""" await hass.config_entries.async_reload(config_entry.entry_id) - await async_disconnect_or_timeout(hass, roomba) - await async_connect_or_timeout(hass, roomba) async def async_unload_entry(hass, config_entry): """Unload a config entry.""" - for component in COMPONENTS: - hass.async_create_task( + _LOGGER.debug("Unload Entries") + await asyncio.gather( + *[ hass.config_entries.async_forward_entry_unload(config_entry, component) - ) - roomba = hass.data[DOMAIN]["roomba"] - return await async_disconnect_or_timeout(hass, roomba) + for component in COMPONENTS + ] + ) + return await async_disconnect_or_timeout(hass, roomba=hass.data[DOMAIN]["roomba"]) -class CannotConnect(exceptions.HomeAssistantError): - """Error to indicate we cannot connect.""" +def roomba_reported_state(roomba): + """Roomba report.""" + return roomba.master_state.get("state", {}).get("reported", {}) -class InvalidAuth(exceptions.HomeAssistantError): - """Error to indicate there is invalid auth.""" +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 918923a87dd6e6..88d31f894fee82 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -8,7 +8,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback -from . import CannotConnect, InvalidAuth, async_connect_or_timeout +from . import CannotConnect, async_connect_or_timeout from .const import ( CONF_CERT, CONF_CONTINUOUS, @@ -46,7 +46,6 @@ async def validate_input(hass: core.HomeAssistant, data): continuous=data[CONF_CONTINUOUS], delay=data[CONF_DELAY], ) - await async_connect_or_timeout(hass, roomba) @@ -55,6 +54,7 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + @staticmethod @callback def async_get_options_flow(config_entry): @@ -69,11 +69,9 @@ async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" errors = {} - if DOMAIN not in self.hass.data: - self.hass.data[DOMAIN] = {} - if user_input is not None: try: + self.hass.data.setdefault(DOMAIN, {}) await validate_input(self.hass, user_input) except CannotConnect: errors = {"base": "cannot_connect"} @@ -82,7 +80,7 @@ async def async_step_user(self, user_input=None): if "base" not in errors: return self.async_create_entry( - title=validated_input["title"], data=user_input + title=self.hass.data[DOMAIN]["name"], data=user_input ) # If there was no user input, do not show the errors. @@ -110,10 +108,6 @@ async def async_step_init(self, user_input=None): step_id="init", data_schema=vol.Schema( { - vol.Optional( - CONF_CERT, - default=self.config_entry.options.get(CONF_CERT, DEFAULT_CERT), - ): str, vol.Optional( CONF_CONTINUOUS, default=self.config_entry.options.get( From 3354e2611a0f8c541c26929c3432ba102f908db8 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 4 Apr 2020 09:21:01 +0200 Subject: [PATCH 070/108] Fix tracelog --- homeassistant/components/roomba/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index b45759fe0d459b..df57b2e6a30958 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -31,7 +31,6 @@ async def async_setup(hass, config): async def async_setup_entry(hass, config_entry): """Set the config entry up.""" # Set up roomba platforms with config entry - _LOGGER.debug(config_entry.entry_id) if config_entry.data is None: return False @@ -89,7 +88,7 @@ async def async_connect_or_timeout(hass, roomba): except asyncio.TimeoutError: # api looping if user or password incorrect and roomba exist await async_disconnect_or_timeout(hass, roomba) - _LOGGER.exception("Timeout expired") + _LOGGER.error("Timeout expired") raise CannotConnect return True From 4eec0e0b5146fd5646e8abfeba7b79f3eb072047 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 4 Apr 2020 11:22:36 +0200 Subject: [PATCH 071/108] Set entry_id for config_entry --- homeassistant/components/roomba/__init__.py | 58 ++++++++++--------- .../components/roomba/binary_sensor.py | 2 +- .../components/roomba/config_flow.py | 16 ++--- homeassistant/components/roomba/sensor.py | 2 +- homeassistant/components/roomba/vacuum.py | 2 +- 5 files changed, 43 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index df57b2e6a30958..20e9fdada55a18 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -43,21 +43,22 @@ async def async_setup_entry(hass, config_entry): }, ) - if "roomba" not in hass.data[DOMAIN]: - roomba = Roomba( - address=config_entry.data[CONF_HOST], - blid=config_entry.data[CONF_USERNAME], - password=config_entry.data[CONF_PASSWORD], - cert_name=config_entry.data[CONF_CERT], - continuous=config_entry.options[CONF_CONTINUOUS], - delay=config_entry.options[CONF_DELAY], - ) + roomba = Roomba( + address=config_entry.data[CONF_HOST], + blid=config_entry.data[CONF_USERNAME], + password=config_entry.data[CONF_PASSWORD], + cert_name=config_entry.data[CONF_CERT], + continuous=config_entry.options[CONF_CONTINUOUS], + delay=config_entry.options[CONF_DELAY], + ) + + try: + if not await async_connect_or_timeout(hass, roomba): + return False + except CannotConnect: + raise exceptions.ConfigEntryNotReady - try: - if not await async_connect_or_timeout(hass, roomba): - return False - except CannotConnect: - raise exceptions.ConfigEntryNotReady + hass.data[DOMAIN][config_entry.entry_id] = roomba for component in COMPONENTS: hass.async_create_task( @@ -72,15 +73,12 @@ async def async_setup_entry(hass, config_entry): async def async_connect_or_timeout(hass, roomba): """Connect to vacuum.""" try: - hass.data[DOMAIN]["name"] = name = None + name = None with async_timeout.timeout(10): await hass.async_add_job(roomba.connect) - _LOGGER.debug("Initialize to connect to vacuum") while not roomba.roomba_connected or name is None: # Waiting for connection and check datas ready name = roomba_reported_state(roomba).get("name", None) - hass.data[DOMAIN]["roomba"] = roomba - hass.data[DOMAIN]["name"] = name await asyncio.sleep(1) except RoombaConnectionError: _LOGGER.error("Error to connect to vacuum") @@ -91,14 +89,13 @@ async def async_connect_or_timeout(hass, roomba): _LOGGER.error("Timeout expired") raise CannotConnect - return True + return {"roomba": roomba, "name": name} async def async_disconnect_or_timeout(hass, roomba): """Disconnect to vacuum.""" with async_timeout.timeout(3): await hass.async_add_job(roomba.disconnect) - hass.data[DOMAIN].pop("roomba", None) await asyncio.sleep(1) return True @@ -110,14 +107,21 @@ async def async_update_options(hass, config_entry): async def async_unload_entry(hass, config_entry): """Unload a config entry.""" - _LOGGER.debug("Unload Entries") - await asyncio.gather( - *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in COMPONENTS - ] + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(config_entry, component) + for component in COMPONENTS + ] + ) ) - return await async_disconnect_or_timeout(hass, roomba=hass.data[DOMAIN]["roomba"]) + if unload_ok: + await async_disconnect_or_timeout( + hass, roomba=hass.data[DOMAIN][config_entry.entry_id] + ) + hass.data[DOMAIN].pop(config_entry.entry_id) + + return unload_ok def roomba_reported_state(roomba): diff --git a/homeassistant/components/roomba/binary_sensor.py b/homeassistant/components/roomba/binary_sensor.py index 02cf4163cf853a..8b03aaba99ddca 100644 --- a/homeassistant/components/roomba/binary_sensor.py +++ b/homeassistant/components/roomba/binary_sensor.py @@ -11,7 +11,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the iRobot Roomba vacuum cleaner.""" - roomba = hass.data[DOMAIN]["roomba"] + roomba = hass.data[DOMAIN][config_entry.entry_id] status = roomba_reported_state(roomba).get("bin", {}) if "full" in status: roomba_vac = RoombaBinStatus(roomba) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 88d31f894fee82..a529a1a23d9b73 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -8,7 +8,11 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback -from . import CannotConnect, async_connect_or_timeout +from . import ( + CannotConnect, + async_connect_or_timeout as act, + async_disconnect_or_timeout as adt, +) from .const import ( CONF_CERT, CONF_CONTINUOUS, @@ -46,7 +50,7 @@ async def validate_input(hass: core.HomeAssistant, data): continuous=data[CONF_CONTINUOUS], delay=data[CONF_DELAY], ) - await async_connect_or_timeout(hass, roomba) + return await act(hass, roomba) class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -71,17 +75,15 @@ async def async_step_user(self, user_input=None): if user_input is not None: try: - self.hass.data.setdefault(DOMAIN, {}) - await validate_input(self.hass, user_input) + info = await validate_input(self.hass, user_input) except CannotConnect: errors = {"base": "cannot_connect"} except Exception: # pylint: disable=broad-except errors = {"base": "unknown"} if "base" not in errors: - return self.async_create_entry( - title=self.hass.data[DOMAIN]["name"], data=user_input - ) + await adt(self.hass, info["roomba"]) + return self.async_create_entry(title=info["name"], data=user_input) # If there was no user input, do not show the errors. if user_input is None: diff --git a/homeassistant/components/roomba/sensor.py b/homeassistant/components/roomba/sensor.py index b54fd168bfabea..c0dbf0b97197e3 100644 --- a/homeassistant/components/roomba/sensor.py +++ b/homeassistant/components/roomba/sensor.py @@ -12,7 +12,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the iRobot Roomba vacuum cleaner.""" - roomba = hass.data[DOMAIN]["roomba"] + roomba = hass.data[DOMAIN][config_entry.entry_id] roomba_vac = RoombaBattery(roomba) async_add_entities([roomba_vac], True) diff --git a/homeassistant/components/roomba/vacuum.py b/homeassistant/components/roomba/vacuum.py index 84b77a1c669aa4..fa81ff5094da87 100644 --- a/homeassistant/components/roomba/vacuum.py +++ b/homeassistant/components/roomba/vacuum.py @@ -56,7 +56,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the iRobot Roomba vacuum cleaner.""" - roomba = hass.data[DOMAIN]["roomba"] + roomba = hass.data[DOMAIN][config_entry.entry_id] roomba_vac = RoombaVacuum(roomba) async_add_entities([roomba_vac], True) From abce6fbc2871e93dc27565e642d8e3240af88ca7 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 4 Apr 2020 11:52:57 +0200 Subject: [PATCH 072/108] Fix DOMAIN unsed-import --- homeassistant/components/roomba/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index a529a1a23d9b73..5c8f5742a3acfd 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -20,8 +20,8 @@ DEFAULT_CERT, DEFAULT_CONTINUOUS, DEFAULT_DELAY, - DOMAIN, ) +from .const import DOMAIN # pylint:disable=unused-import DATA_SCHEMA = vol.Schema( { From 369d5dae7ba76ab69d77c0ce1738c728e35c688e Mon Sep 17 00:00:00 2001 From: Save me Date: Sun, 5 Apr 2020 13:25:19 +0200 Subject: [PATCH 073/108] Update homeassistant/components/roomba/config_flow.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 5c8f5742a3acfd..36916a90028229 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -50,7 +50,7 @@ async def validate_input(hass: core.HomeAssistant, data): continuous=data[CONF_CONTINUOUS], delay=data[CONF_DELAY], ) - return await act(hass, roomba) + return await async_connect_or_timeout(hass, roomba) class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): From fa2e47430bc3379282c769bc08d8fd7635c7445c Mon Sep 17 00:00:00 2001 From: Save me Date: Sun, 5 Apr 2020 13:25:30 +0200 Subject: [PATCH 074/108] Update homeassistant/components/roomba/config_flow.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 36916a90028229..96b81e25eeac61 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -82,7 +82,7 @@ async def async_step_user(self, user_input=None): errors = {"base": "unknown"} if "base" not in errors: - await adt(self.hass, info["roomba"]) + await async_disconnect_or_timeout(self.hass, info["roomba"]) return self.async_create_entry(title=info["name"], data=user_input) # If there was no user input, do not show the errors. From 999d66d93515a75d8a6738a78a240d508f42f214 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 5 Apr 2020 18:41:36 +0200 Subject: [PATCH 075/108] Add unique_id for entry --- homeassistant/components/roomba/__init__.py | 45 +++++++++++++++++-- .../components/roomba/config_flow.py | 25 +++++++---- homeassistant/components/roomba/const.py | 3 ++ 3 files changed, 61 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 20e9fdada55a18..b9d7fd75fa528f 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -4,14 +4,43 @@ import async_timeout from roomba import Roomba, RoombaConnectionError +import voluptuous as vol from homeassistant import config_entries, exceptions from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from .const import COMPONENTS, CONF_CERT, CONF_CONTINUOUS, CONF_DELAY, DOMAIN +from .const import ( + COMPONENTS, + CONF_CERT, + CONF_CONTINUOUS, + CONF_DELAY, + CONF_NAME, + DEFAULT_CERT, + DEFAULT_CONTINUOUS, + DEFAULT_DELAY, + DOMAIN, + MAC_ADDRESS, + ROOMBA_SESSION, +) _LOGGER = logging.getLogger(__name__) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_HOST): str, + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + vol.Optional(CONF_CERT, default=DEFAULT_CERT): str, + vol.Optional(CONF_CONTINUOUS, default=DEFAULT_CONTINUOUS): bool, + vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): int, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) + async def async_setup(hass, config): """Set up the roomba environment.""" @@ -23,10 +52,14 @@ async def async_setup(hass, config): if not hass.config_entries.async_entries(DOMAIN): hass.async_create_task( hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=config + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=config[DOMAIN], ) ) + return True + async def async_setup_entry(hass, config_entry): """Set the config entry up.""" @@ -65,7 +98,8 @@ async def async_setup_entry(hass, config_entry): hass.config_entries.async_forward_entry_setup(config_entry, component) ) - config_entry.add_update_listener(async_update_options) + if not config_entry.update_listeners: + config_entry.add_update_listener(async_update_options) return True @@ -75,10 +109,12 @@ async def async_connect_or_timeout(hass, roomba): try: name = None with async_timeout.timeout(10): + _LOGGER.debug("Initialize connection to vacuum") await hass.async_add_job(roomba.connect) while not roomba.roomba_connected or name is None: # Waiting for connection and check datas ready name = roomba_reported_state(roomba).get("name", None) + mac = roomba_reported_state(roomba).get("mac", None) await asyncio.sleep(1) except RoombaConnectionError: _LOGGER.error("Error to connect to vacuum") @@ -89,11 +125,12 @@ async def async_connect_or_timeout(hass, roomba): _LOGGER.error("Timeout expired") raise CannotConnect - return {"roomba": roomba, "name": name} + return {ROOMBA_SESSION: roomba, CONF_NAME: name, MAC_ADDRESS: mac} async def async_disconnect_or_timeout(hass, roomba): """Disconnect to vacuum.""" + _LOGGER.debug("Disconnect vacuum") with async_timeout.timeout(3): await hass.async_add_job(roomba.disconnect) await asyncio.sleep(1) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 96b81e25eeac61..2d1072c5542bb3 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -8,18 +8,17 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback -from . import ( - CannotConnect, - async_connect_or_timeout as act, - async_disconnect_or_timeout as adt, -) +from . import CannotConnect, async_connect_or_timeout, async_disconnect_or_timeout from .const import ( CONF_CERT, CONF_CONTINUOUS, CONF_DELAY, + CONF_NAME, DEFAULT_CERT, DEFAULT_CONTINUOUS, DEFAULT_DELAY, + MAC_ADDRESS, + ROOMBA_SESSION, ) from .const import DOMAIN # pylint:disable=unused-import @@ -50,7 +49,15 @@ async def validate_input(hass: core.HomeAssistant, data): continuous=data[CONF_CONTINUOUS], delay=data[CONF_DELAY], ) - return await async_connect_or_timeout(hass, roomba) + + info = await async_connect_or_timeout(hass, roomba) + + return { + ROOMBA_SESSION: info[ROOMBA_SESSION], + CONF_NAME: info[CONF_NAME], + CONF_HOST: data[CONF_HOST], + MAC_ADDRESS: info[MAC_ADDRESS], + } class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -82,8 +89,10 @@ async def async_step_user(self, user_input=None): errors = {"base": "unknown"} if "base" not in errors: - await async_disconnect_or_timeout(self.hass, info["roomba"]) - return self.async_create_entry(title=info["name"], data=user_input) + await async_disconnect_or_timeout(self.hass, info[ROOMBA_SESSION]) + await self.async_set_unique_id(info[MAC_ADDRESS]) + self._abort_if_unique_id_configured() + return self.async_create_entry(title=info[CONF_NAME], data=user_input) # If there was no user input, do not show the errors. if user_input is None: diff --git a/homeassistant/components/roomba/const.py b/homeassistant/components/roomba/const.py index 803a1ad87ff272..25b29aeda1ef78 100644 --- a/homeassistant/components/roomba/const.py +++ b/homeassistant/components/roomba/const.py @@ -4,6 +4,9 @@ CONF_CERT = "certificate" CONF_CONTINUOUS = "continuous" CONF_DELAY = "delay" +CONF_NAME = "name" DEFAULT_CERT = "/etc/ssl/certs/ca-certificates.crt" DEFAULT_CONTINUOUS = True DEFAULT_DELAY = 1 +MAC_ADDRESS = "mac" +ROOMBA_SESSION = "roomba_session" From 991a16c3728bbe83a79c47fb958e8f175eb5b018 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 5 Apr 2020 19:02:58 +0200 Subject: [PATCH 076/108] Fix device info --- homeassistant/components/roomba/vacuum.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/roomba/vacuum.py b/homeassistant/components/roomba/vacuum.py index fa81ff5094da87..f73c33e59c3040 100644 --- a/homeassistant/components/roomba/vacuum.py +++ b/homeassistant/components/roomba/vacuum.py @@ -16,6 +16,7 @@ ) from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from . import roomba_reported_state from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -74,11 +75,11 @@ def __init__(self, roomba): self._state_attrs = {} self._status = None self.vacuum = roomba - self.vacuum_state = self.vacuum.master_state.get("state", {}).get( - "reported", {} - ) + self.vacuum_state = roomba_reported_state(roomba) self._mac = self.vacuum_state.get("mac") self._name = self.vacuum_state.get("name") + self._version = self.vacuum_state.get("softwareVer") + self._sku = self.vacuum_state.get("sku") @property def unique_id(self): @@ -91,14 +92,10 @@ def device_info(self): return { "identifiers": {(DOMAIN, self.unique_id)}, "connections": {(CONNECTION_NETWORK_MAC, self._mac)}, - "manufacturer": "iRobots", + "manufacturer": "iRobot", "name": str(self._name), - "sw_version": self.vacuum.master_state.get("state", {}) - .get("reported", {}) - .get("softwareVer"), - "model": self.vacuum.master_state.get("state", {}) - .get("reported", {}) - .get("sku"), + "sw_version": self._version, + "model": self._sku, } @property From 9e7b153e06d181bbcc8bdbfe837f8ab48942f9c3 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 5 Apr 2020 19:27:16 +0200 Subject: [PATCH 077/108] syntax for mac (pyupgrade) --- homeassistant/components/roomba/vacuum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/vacuum.py b/homeassistant/components/roomba/vacuum.py index f73c33e59c3040..e4476a4c3337c3 100644 --- a/homeassistant/components/roomba/vacuum.py +++ b/homeassistant/components/roomba/vacuum.py @@ -84,7 +84,7 @@ def __init__(self, roomba): @property def unique_id(self): """Return the uniqueid of the vacuum cleaner.""" - return "roomba_{}".format(self._mac) + return f"roomba_{self._mac}" @property def device_info(self): From 304f601eb97a3309308c5a08427d7284cd794413 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sun, 5 Apr 2020 20:07:41 +0200 Subject: [PATCH 078/108] Change single key to BLID --- .../components/roomba/.translations/en.json | 2 +- .../components/roomba/.translations/fr.json | 2 +- homeassistant/components/roomba/__init__.py | 8 ++++---- homeassistant/components/roomba/binary_sensor.py | 13 +++++++------ homeassistant/components/roomba/config_flow.py | 10 +++++----- homeassistant/components/roomba/const.py | 2 +- homeassistant/components/roomba/sensor.py | 13 +++++++------ homeassistant/components/roomba/strings.json | 4 ++-- homeassistant/components/roomba/vacuum.py | 13 ++++++------- 9 files changed, 34 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/roomba/.translations/en.json b/homeassistant/components/roomba/.translations/en.json index b40c8bdaebea80..0a0944df6c520d 100644 --- a/homeassistant/components/roomba/.translations/en.json +++ b/homeassistant/components/roomba/.translations/en.json @@ -6,7 +6,7 @@ "title": "Connect to the device", "data": { "host": "Hostname or IP Address", - "username": "Username", + "blid": "Username", "password": "Password", "certificate": "Certificate", "continuous": "Continuous", diff --git a/homeassistant/components/roomba/.translations/fr.json b/homeassistant/components/roomba/.translations/fr.json index e99411aa2dba9e..2ac450608558b8 100644 --- a/homeassistant/components/roomba/.translations/fr.json +++ b/homeassistant/components/roomba/.translations/fr.json @@ -6,7 +6,7 @@ "title": "Connexion au périphérique", "data": { "host": "Nom ou Addresse IP", - "username": "Utilisateur", + "blid": "Utilisateur", "password": "Mot de passe", "certificate": "Certificat", "continuous": "Continue", diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index b9d7fd75fa528f..2d39e0023f36d7 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -11,6 +11,7 @@ from .const import ( COMPONENTS, + CONF_BLID, CONF_CERT, CONF_CONTINUOUS, CONF_DELAY, @@ -19,7 +20,6 @@ DEFAULT_CONTINUOUS, DEFAULT_DELAY, DOMAIN, - MAC_ADDRESS, ROOMBA_SESSION, ) @@ -78,7 +78,7 @@ async def async_setup_entry(hass, config_entry): roomba = Roomba( address=config_entry.data[CONF_HOST], - blid=config_entry.data[CONF_USERNAME], + blid=config_entry.data[CONF_BLID], password=config_entry.data[CONF_PASSWORD], cert_name=config_entry.data[CONF_CERT], continuous=config_entry.options[CONF_CONTINUOUS], @@ -92,6 +92,7 @@ async def async_setup_entry(hass, config_entry): raise exceptions.ConfigEntryNotReady hass.data[DOMAIN][config_entry.entry_id] = roomba + hass.data[DOMAIN][CONF_BLID] = config_entry.data[CONF_BLID] for component in COMPONENTS: hass.async_create_task( @@ -114,7 +115,6 @@ async def async_connect_or_timeout(hass, roomba): while not roomba.roomba_connected or name is None: # Waiting for connection and check datas ready name = roomba_reported_state(roomba).get("name", None) - mac = roomba_reported_state(roomba).get("mac", None) await asyncio.sleep(1) except RoombaConnectionError: _LOGGER.error("Error to connect to vacuum") @@ -125,7 +125,7 @@ async def async_connect_or_timeout(hass, roomba): _LOGGER.error("Timeout expired") raise CannotConnect - return {ROOMBA_SESSION: roomba, CONF_NAME: name, MAC_ADDRESS: mac} + return {ROOMBA_SESSION: roomba, CONF_NAME: name} async def async_disconnect_or_timeout(hass, roomba): diff --git a/homeassistant/components/roomba/binary_sensor.py b/homeassistant/components/roomba/binary_sensor.py index 8b03aaba99ddca..66d3ea9bbee49e 100644 --- a/homeassistant/components/roomba/binary_sensor.py +++ b/homeassistant/components/roomba/binary_sensor.py @@ -4,7 +4,7 @@ from homeassistant.components.binary_sensor import BinarySensorDevice from . import roomba_reported_state -from .const import DOMAIN +from .const import CONF_BLID, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -12,9 +12,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the iRobot Roomba vacuum cleaner.""" roomba = hass.data[DOMAIN][config_entry.entry_id] + blid = hass.data[DOMAIN][CONF_BLID] status = roomba_reported_state(roomba).get("bin", {}) if "full" in status: - roomba_vac = RoombaBinStatus(roomba) + roomba_vac = RoombaBinStatus(roomba, blid) async_add_entities([roomba_vac], True) @@ -23,13 +24,13 @@ class RoombaBinStatus(BinarySensorDevice): ICON = "mdi:delete-variant" - def __init__(self, roomba): + def __init__(self, roomba, blid): """Initialize the sensor object.""" self.vacuum = roomba self.vacuum_state = roomba_reported_state(roomba) - self._mac = self.vacuum_state.get("mac") + self._blid = blid self._name = self.vacuum_state.get("name") - self._identifier = f"roomba_{self._mac}" + self._identifier = f"roomba_{self._blid}" self._bin_status = None @property @@ -40,7 +41,7 @@ def name(self): @property def unique_id(self): """Return the ID of this sensor.""" - return f"bin_{self._mac}" + return f"bin_{self._blid}" @property def icon(self): diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 2d1072c5542bb3..f43e965861cad6 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -10,6 +10,7 @@ from . import CannotConnect, async_connect_or_timeout, async_disconnect_or_timeout from .const import ( + CONF_BLID, CONF_CERT, CONF_CONTINUOUS, CONF_DELAY, @@ -17,7 +18,6 @@ DEFAULT_CERT, DEFAULT_CONTINUOUS, DEFAULT_DELAY, - MAC_ADDRESS, ROOMBA_SESSION, ) from .const import DOMAIN # pylint:disable=unused-import @@ -25,7 +25,7 @@ DATA_SCHEMA = vol.Schema( { vol.Required(CONF_HOST): str, - vol.Required(CONF_USERNAME): str, + vol.Required(CONF_BLID): str, vol.Required(CONF_PASSWORD): str, vol.Optional(CONF_CERT, default=DEFAULT_CERT): str, vol.Optional(CONF_CONTINUOUS, default=DEFAULT_CONTINUOUS): bool, @@ -43,7 +43,7 @@ async def validate_input(hass: core.HomeAssistant, data): """ roomba = Roomba( address=data[CONF_HOST], - blid=data[CONF_USERNAME], + blid=data[CONF_BLID], password=data[CONF_PASSWORD], cert_name=data[CONF_CERT], continuous=data[CONF_CONTINUOUS], @@ -56,7 +56,6 @@ async def validate_input(hass: core.HomeAssistant, data): ROOMBA_SESSION: info[ROOMBA_SESSION], CONF_NAME: info[CONF_NAME], CONF_HOST: data[CONF_HOST], - MAC_ADDRESS: info[MAC_ADDRESS], } @@ -74,6 +73,7 @@ def async_get_options_flow(config_entry): async def async_step_import(self, import_info): """Set the config entry up from yaml.""" + import_info[CONF_BLID] = import_info.pop(CONF_USERNAME) return await self.async_step_user(import_info) async def async_step_user(self, user_input=None): @@ -90,7 +90,7 @@ async def async_step_user(self, user_input=None): if "base" not in errors: await async_disconnect_or_timeout(self.hass, info[ROOMBA_SESSION]) - await self.async_set_unique_id(info[MAC_ADDRESS]) + await self.async_set_unique_id(user_input[CONF_BLID]) self._abort_if_unique_id_configured() return self.async_create_entry(title=info[CONF_NAME], data=user_input) diff --git a/homeassistant/components/roomba/const.py b/homeassistant/components/roomba/const.py index 25b29aeda1ef78..cb09ac3a8380a2 100644 --- a/homeassistant/components/roomba/const.py +++ b/homeassistant/components/roomba/const.py @@ -5,8 +5,8 @@ CONF_CONTINUOUS = "continuous" CONF_DELAY = "delay" CONF_NAME = "name" +CONF_BLID = "blid" DEFAULT_CERT = "/etc/ssl/certs/ca-certificates.crt" DEFAULT_CONTINUOUS = True DEFAULT_DELAY = 1 -MAC_ADDRESS = "mac" ROOMBA_SESSION = "roomba_session" diff --git a/homeassistant/components/roomba/sensor.py b/homeassistant/components/roomba/sensor.py index c0dbf0b97197e3..ac3ec8a71701da 100644 --- a/homeassistant/components/roomba/sensor.py +++ b/homeassistant/components/roomba/sensor.py @@ -5,7 +5,7 @@ from homeassistant.helpers.entity import Entity from . import roomba_reported_state -from .const import DOMAIN +from .const import CONF_BLID, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -13,20 +13,21 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the iRobot Roomba vacuum cleaner.""" roomba = hass.data[DOMAIN][config_entry.entry_id] - roomba_vac = RoombaBattery(roomba) + blid = hass.data[DOMAIN][CONF_BLID] + roomba_vac = RoombaBattery(roomba, blid) async_add_entities([roomba_vac], True) class RoombaBattery(Entity): """Class to hold Roomba Sensor basic info.""" - def __init__(self, roomba): + def __init__(self, roomba, blid): """Initialize the sensor object.""" self.vacuum = roomba self.vacuum_state = roomba_reported_state(roomba) - self._mac = self.vacuum_state.get("mac") + self._blid = blid self._name = self.vacuum_state.get("name") - self._identifier = f"roomba_{self._mac}" + self._identifier = f"roomba_{self._blid}" self._battery_level = None @property @@ -37,7 +38,7 @@ def name(self): @property def unique_id(self): """Return the ID of this sensor.""" - return f"battery_{self._mac}" + return f"battery_{self._blid}" @property def device_class(self): diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json index 05538cb40c8ac2..dc305388ab630f 100644 --- a/homeassistant/components/roomba/strings.json +++ b/homeassistant/components/roomba/strings.json @@ -6,7 +6,7 @@ "title": "Connect to the device", "data": { "host": "Hostname or IP Address", - "username": "Username", + "blid": "Username", "password": "Password", "certificate": "Certificate", "continuous": "Continuous", @@ -15,7 +15,7 @@ } }, "error": { - "unknown" : "Unexpected error", + "unknown": "Unexpected error", "cannot_connect": "Failed to connect, please try again" } }, diff --git a/homeassistant/components/roomba/vacuum.py b/homeassistant/components/roomba/vacuum.py index e4476a4c3337c3..6baa0832c19cb7 100644 --- a/homeassistant/components/roomba/vacuum.py +++ b/homeassistant/components/roomba/vacuum.py @@ -14,10 +14,9 @@ SUPPORT_TURN_ON, VacuumDevice, ) -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from . import roomba_reported_state -from .const import DOMAIN +from .const import CONF_BLID, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -58,14 +57,15 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the iRobot Roomba vacuum cleaner.""" roomba = hass.data[DOMAIN][config_entry.entry_id] - roomba_vac = RoombaVacuum(roomba) + blid = hass.data[DOMAIN][CONF_BLID] + roomba_vac = RoombaVacuum(roomba, blid) async_add_entities([roomba_vac], True) class RoombaVacuum(VacuumDevice): """Representation of a Roomba Vacuum cleaner robot.""" - def __init__(self, roomba): + def __init__(self, roomba, blid): """Initialize the Roomba handler.""" self._available = False self._battery_level = None @@ -76,7 +76,7 @@ def __init__(self, roomba): self._status = None self.vacuum = roomba self.vacuum_state = roomba_reported_state(roomba) - self._mac = self.vacuum_state.get("mac") + self._blid = blid self._name = self.vacuum_state.get("name") self._version = self.vacuum_state.get("softwareVer") self._sku = self.vacuum_state.get("sku") @@ -84,14 +84,13 @@ def __init__(self, roomba): @property def unique_id(self): """Return the uniqueid of the vacuum cleaner.""" - return f"roomba_{self._mac}" + return f"roomba_{self._blid}" @property def device_info(self): """Return the device info of the vacuum cleaner.""" return { "identifiers": {(DOMAIN, self.unique_id)}, - "connections": {(CONNECTION_NETWORK_MAC, self._mac)}, "manufacturer": "iRobot", "name": str(self._name), "sw_version": self._version, From 6fa5e941a67a26341ef5f709231e7c1338dadd37 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Mon, 6 Apr 2020 09:00:59 +0200 Subject: [PATCH 079/108] Resolve dict conflict --- homeassistant/components/roomba/__init__.py | 12 +++++++----- homeassistant/components/roomba/binary_sensor.py | 7 ++++--- homeassistant/components/roomba/const.py | 1 + homeassistant/components/roomba/sensor.py | 7 ++++--- homeassistant/components/roomba/vacuum.py | 7 ++++--- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 2d39e0023f36d7..eebe8a4cd7b066 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -10,6 +10,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from .const import ( + BLID, COMPONENTS, CONF_BLID, CONF_CERT, @@ -91,8 +92,10 @@ async def async_setup_entry(hass, config_entry): except CannotConnect: raise exceptions.ConfigEntryNotReady - hass.data[DOMAIN][config_entry.entry_id] = roomba - hass.data[DOMAIN][CONF_BLID] = config_entry.data[CONF_BLID] + hass.data[DOMAIN][config_entry.entry_id] = { + ROOMBA_SESSION: roomba, + BLID: config_entry.data[CONF_BLID], + } for component in COMPONENTS: hass.async_create_task( @@ -153,9 +156,8 @@ async def async_unload_entry(hass, config_entry): ) ) if unload_ok: - await async_disconnect_or_timeout( - hass, roomba=hass.data[DOMAIN][config_entry.entry_id] - ) + domain_data = hass.data[DOMAIN][config_entry.entry_id] + await async_disconnect_or_timeout(hass, roomba=domain_data[ROOMBA_SESSION]) hass.data[DOMAIN].pop(config_entry.entry_id) return unload_ok diff --git a/homeassistant/components/roomba/binary_sensor.py b/homeassistant/components/roomba/binary_sensor.py index 66d3ea9bbee49e..d931cf2129da09 100644 --- a/homeassistant/components/roomba/binary_sensor.py +++ b/homeassistant/components/roomba/binary_sensor.py @@ -4,15 +4,16 @@ from homeassistant.components.binary_sensor import BinarySensorDevice from . import roomba_reported_state -from .const import CONF_BLID, DOMAIN +from .const import BLID, DOMAIN, ROOMBA_SESSION _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the iRobot Roomba vacuum cleaner.""" - roomba = hass.data[DOMAIN][config_entry.entry_id] - blid = hass.data[DOMAIN][CONF_BLID] + domain_data = hass.data[DOMAIN][config_entry.entry_id] + roomba = domain_data[ROOMBA_SESSION] + blid = domain_data[BLID] status = roomba_reported_state(roomba).get("bin", {}) if "full" in status: roomba_vac = RoombaBinStatus(roomba, blid) diff --git a/homeassistant/components/roomba/const.py b/homeassistant/components/roomba/const.py index cb09ac3a8380a2..06684e63bdc238 100644 --- a/homeassistant/components/roomba/const.py +++ b/homeassistant/components/roomba/const.py @@ -10,3 +10,4 @@ DEFAULT_CONTINUOUS = True DEFAULT_DELAY = 1 ROOMBA_SESSION = "roomba_session" +BLID = "blid_key" diff --git a/homeassistant/components/roomba/sensor.py b/homeassistant/components/roomba/sensor.py index ac3ec8a71701da..4582b9212e9220 100644 --- a/homeassistant/components/roomba/sensor.py +++ b/homeassistant/components/roomba/sensor.py @@ -5,15 +5,16 @@ from homeassistant.helpers.entity import Entity from . import roomba_reported_state -from .const import CONF_BLID, DOMAIN +from .const import BLID, DOMAIN, ROOMBA_SESSION _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the iRobot Roomba vacuum cleaner.""" - roomba = hass.data[DOMAIN][config_entry.entry_id] - blid = hass.data[DOMAIN][CONF_BLID] + domain_data = hass.data[DOMAIN][config_entry.entry_id] + roomba = domain_data[ROOMBA_SESSION] + blid = domain_data[BLID] roomba_vac = RoombaBattery(roomba, blid) async_add_entities([roomba_vac], True) diff --git a/homeassistant/components/roomba/vacuum.py b/homeassistant/components/roomba/vacuum.py index 6baa0832c19cb7..80dbbc312ea15a 100644 --- a/homeassistant/components/roomba/vacuum.py +++ b/homeassistant/components/roomba/vacuum.py @@ -16,7 +16,7 @@ ) from . import roomba_reported_state -from .const import CONF_BLID, DOMAIN +from .const import BLID, DOMAIN, ROOMBA_SESSION _LOGGER = logging.getLogger(__name__) @@ -56,8 +56,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the iRobot Roomba vacuum cleaner.""" - roomba = hass.data[DOMAIN][config_entry.entry_id] - blid = hass.data[DOMAIN][CONF_BLID] + domain_data = hass.data[DOMAIN][config_entry.entry_id] + roomba = domain_data[ROOMBA_SESSION] + blid = domain_data[BLID] roomba_vac = RoombaVacuum(roomba, blid) async_add_entities([roomba_vac], True) From 9646e168e14e05d9c2c5523684a322a442ffa7db Mon Sep 17 00:00:00 2001 From: Save me Date: Wed, 8 Apr 2020 08:23:13 +0200 Subject: [PATCH 080/108] Update homeassistant/components/roomba/binary_sensor.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/binary_sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/binary_sensor.py b/homeassistant/components/roomba/binary_sensor.py index d931cf2129da09..4ed3ab02418a55 100644 --- a/homeassistant/components/roomba/binary_sensor.py +++ b/homeassistant/components/roomba/binary_sensor.py @@ -37,7 +37,7 @@ def __init__(self, roomba, blid): @property def name(self): """Return the name of the sensor.""" - return f"{self._name} bin full" + return f"{self._name} Bin Full" @property def unique_id(self): From 6aa29e1503a6cb134a897b269310ddf29cafb868 Mon Sep 17 00:00:00 2001 From: Save me Date: Wed, 8 Apr 2020 08:23:26 +0200 Subject: [PATCH 081/108] Update homeassistant/components/roomba/sensor.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/sensor.py b/homeassistant/components/roomba/sensor.py index 4582b9212e9220..522ba18390f49c 100644 --- a/homeassistant/components/roomba/sensor.py +++ b/homeassistant/components/roomba/sensor.py @@ -34,7 +34,7 @@ def __init__(self, roomba, blid): @property def name(self): """Return the name of the sensor.""" - return f"{self._name} battery level" + return f"{self._name} Battery Level" @property def unique_id(self): From 3cad764e08146b5550f9d6e5dfc45771ea3d9869 Mon Sep 17 00:00:00 2001 From: Save me Date: Wed, 8 Apr 2020 08:23:49 +0200 Subject: [PATCH 082/108] Update homeassistant/components/roomba/.translations/en.json Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/.translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/.translations/en.json b/homeassistant/components/roomba/.translations/en.json index 0a0944df6c520d..64c631c8e18e3b 100644 --- a/homeassistant/components/roomba/.translations/en.json +++ b/homeassistant/components/roomba/.translations/en.json @@ -6,7 +6,7 @@ "title": "Connect to the device", "data": { "host": "Hostname or IP Address", - "blid": "Username", + "blid": "BLID", "password": "Password", "certificate": "Certificate", "continuous": "Continuous", From 62b15e3244704f45a5eaada6e074b631f4c06c28 Mon Sep 17 00:00:00 2001 From: Save me Date: Wed, 8 Apr 2020 08:24:07 +0200 Subject: [PATCH 083/108] Update homeassistant/components/roomba/strings.json Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json index dc305388ab630f..4514ea96c3525e 100644 --- a/homeassistant/components/roomba/strings.json +++ b/homeassistant/components/roomba/strings.json @@ -6,7 +6,7 @@ "title": "Connect to the device", "data": { "host": "Hostname or IP Address", - "blid": "Username", + "blid": "BLID", "password": "Password", "certificate": "Certificate", "continuous": "Continuous", From 35a285b59c098224c64efe2a741495bbbce3c1e3 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Wed, 8 Apr 2020 08:34:49 +0200 Subject: [PATCH 084/108] Add description --- homeassistant/components/roomba/.translations/en.json | 1 + homeassistant/components/roomba/.translations/fr.json | 3 ++- homeassistant/components/roomba/strings.json | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/.translations/en.json b/homeassistant/components/roomba/.translations/en.json index 64c631c8e18e3b..69e4c0c57609d5 100644 --- a/homeassistant/components/roomba/.translations/en.json +++ b/homeassistant/components/roomba/.translations/en.json @@ -4,6 +4,7 @@ "step": { "user": { "title": "Connect to the device", + "description": "Currently retrieving the BLID and password is a manual process. Please follow the steps outlined in the documentation at: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", "data": { "host": "Hostname or IP Address", "blid": "BLID", diff --git a/homeassistant/components/roomba/.translations/fr.json b/homeassistant/components/roomba/.translations/fr.json index 2ac450608558b8..5f59144727f57f 100644 --- a/homeassistant/components/roomba/.translations/fr.json +++ b/homeassistant/components/roomba/.translations/fr.json @@ -4,9 +4,10 @@ "step": { "user": { "title": "Connexion au périphérique", + "description": "Actuellement la récupération du BLID et du mot de passe nécessite une procédure manuelle. Veuillez suivre les étapes décrites dans la documentation sur: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", "data": { "host": "Nom ou Addresse IP", - "blid": "Utilisateur", + "blid": "BLID", "password": "Mot de passe", "certificate": "Certificat", "continuous": "Continue", diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json index 4514ea96c3525e..403d1980b94814 100644 --- a/homeassistant/components/roomba/strings.json +++ b/homeassistant/components/roomba/strings.json @@ -4,6 +4,7 @@ "step": { "user": { "title": "Connect to the device", + "description": "Currently retrieving the BLID and password is a manual process. Please follow the steps outlined in the documentation at: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", "data": { "host": "Hostname or IP Address", "blid": "BLID", From 7cd6420f5657c04dd1b38035ebb86a47c95ed130 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 8 Apr 2020 16:03:11 +0000 Subject: [PATCH 085/108] Revert "Remove test config flow" This reverts commit 26a89422e89d7c88dd3c0ec3066e607afdc99f09. --- requirements_test_all.txt | 3 + tests/components/roomba/__init__.py | 1 + tests/components/roomba/test_config_flow.py | 90 +++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 tests/components/roomba/__init__.py create mode 100644 tests/components/roomba/test_config_flow.py diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b064ca1b411718..fa50cc3837d2fd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -678,6 +678,9 @@ ring_doorbell==0.6.0 # homeassistant.components.roku roku==4.1.0 +# homeassistant.components.roomba +roombapy==1.5.0 + # homeassistant.components.yamaha rxv==0.6.0 diff --git a/tests/components/roomba/__init__.py b/tests/components/roomba/__init__.py new file mode 100644 index 00000000000000..a255e21c709c64 --- /dev/null +++ b/tests/components/roomba/__init__.py @@ -0,0 +1 @@ +"""Tests for the iRobot Roomba integration.""" diff --git a/tests/components/roomba/test_config_flow.py b/tests/components/roomba/test_config_flow.py new file mode 100644 index 00000000000000..5ac8ea6149a81b --- /dev/null +++ b/tests/components/roomba/test_config_flow.py @@ -0,0 +1,90 @@ +"""Test the iRobot Roomba config flow.""" +from asynctest import patch + +from homeassistant import config_entries, setup +from homeassistant.components.roomba.config_flow import CannotConnect, InvalidAuth +from homeassistant.components.roomba.const import DOMAIN + + +async def test_form(hass): + """Test we get the form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch( + "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", + return_value=True, + ), patch( + "homeassistant.components.roomba.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.roomba.async_setup_entry", return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "create_entry" + assert result2["title"] == "Name of the device" + assert result2["data"] == { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_invalid_auth(hass): + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", + side_effect=InvalidAuth, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "invalid_auth"} + + +async def test_form_cannot_connect(hass): + """Test we handle cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", + side_effect=CannotConnect, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "cannot_connect"} From 7fc9dd258c5a38ec0336f0103399274963ede43a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 8 Apr 2020 16:41:30 +0000 Subject: [PATCH 086/108] Add tests --- homeassistant/components/roomba/__init__.py | 3 +- .../components/roomba/config_flow.py | 4 +- tests/components/roomba/test_config_flow.py | 161 +++++++++++++----- 3 files changed, 119 insertions(+), 49 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index eebe8a4cd7b066..041da4e50dd161 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -118,6 +118,8 @@ async def async_connect_or_timeout(hass, roomba): while not roomba.roomba_connected or name is None: # Waiting for connection and check datas ready name = roomba_reported_state(roomba).get("name", None) + if name: + break await asyncio.sleep(1) except RoombaConnectionError: _LOGGER.error("Error to connect to vacuum") @@ -136,7 +138,6 @@ async def async_disconnect_or_timeout(hass, roomba): _LOGGER.debug("Disconnect vacuum") with async_timeout.timeout(3): await hass.async_add_job(roomba.disconnect) - await asyncio.sleep(1) return True diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index f43e965861cad6..d0d47ed9012808 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -81,6 +81,8 @@ async def async_step_user(self, user_input=None): errors = {} if user_input is not None: + await self.async_set_unique_id(user_input[CONF_BLID]) + self._abort_if_unique_id_configured() try: info = await validate_input(self.hass, user_input) except CannotConnect: @@ -90,8 +92,6 @@ async def async_step_user(self, user_input=None): if "base" not in errors: await async_disconnect_or_timeout(self.hass, info[ROOMBA_SESSION]) - await self.async_set_unique_id(user_input[CONF_BLID]) - self._abort_if_unique_id_configured() return self.async_create_entry(title=info[CONF_NAME], data=user_input) # If there was no user input, do not show the errors. diff --git a/tests/components/roomba/test_config_flow.py b/tests/components/roomba/test_config_flow.py index 5ac8ea6149a81b..70ebe87c2e00a4 100644 --- a/tests/components/roomba/test_config_flow.py +++ b/tests/components/roomba/test_config_flow.py @@ -1,9 +1,40 @@ """Test the iRobot Roomba config flow.""" -from asynctest import patch +from asynctest import MagicMock, PropertyMock, patch +from roomba import RoombaConnectionError -from homeassistant import config_entries, setup -from homeassistant.components.roomba.config_flow import CannotConnect, InvalidAuth -from homeassistant.components.roomba.const import DOMAIN +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.roomba.const import ( + CONF_BLID, + CONF_CERT, + CONF_CONTINUOUS, + CONF_DELAY, + DOMAIN, +) +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME + +from tests.common import MockConfigEntry + +VALID_CONFIG = {CONF_HOST: "1.2.3.4", CONF_BLID: "blid", CONF_PASSWORD: "password"} + +VALID_YAML_CONFIG = { + CONF_HOST: "1.2.3.4", + CONF_USERNAME: "blid", + CONF_PASSWORD: "password", + CONF_CERT: "/etc/ssl/certs/ca-certificates.crt", + CONF_CONTINUOUS: True, + CONF_DELAY: 1, +} + + +def _create_mocked_roomba( + roomba_connected=None, master_state=None, connect=None, disconnect=None +): + mocked_roomba = MagicMock() + type(mocked_roomba).roomba_connected = PropertyMock(return_value=roomba_connected) + type(mocked_roomba).master_state = PropertyMock(return_value=master_state) + type(mocked_roomba).connect = MagicMock(side_effect=connect) + type(mocked_roomba).disconnect = MagicMock(side_effect=disconnect) + return mocked_roomba async def test_form(hass): @@ -12,79 +43,117 @@ async def test_form(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - assert result["type"] == "form" + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {} + mocked_roomba = _create_mocked_roomba( + roomba_connected=True, + master_state={"state": {"reported": {"name": "myroomba"}}}, + ) + with patch( - "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", - return_value=True, + "homeassistant.components.roomba.config_flow.Roomba", + return_value=mocked_roomba, ), patch( "homeassistant.components.roomba.async_setup", return_value=True ) as mock_setup, patch( "homeassistant.components.roomba.async_setup_entry", return_value=True, ) as mock_setup_entry: result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "host": "1.1.1.1", - "username": "test-username", - "password": "test-password", - }, + result["flow_id"], VALID_CONFIG, ) - assert result2["type"] == "create_entry" - assert result2["title"] == "Name of the device" + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "myroomba" + + assert result2["result"].unique_id == "blid" assert result2["data"] == { - "host": "1.1.1.1", - "username": "test-username", - "password": "test-password", + CONF_BLID: "blid", + CONF_CERT: "/etc/ssl/certs/ca-certificates.crt", + CONF_CONTINUOUS: True, + CONF_DELAY: 1, + CONF_HOST: "1.2.3.4", + CONF_PASSWORD: "password", } await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 -async def test_form_invalid_auth(hass): - """Test we handle invalid auth.""" +async def test_form_cannot_connect(hass): + """Test we handle cannot connect error.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) + mocked_roomba = _create_mocked_roomba( + connect=RoombaConnectionError, + roomba_connected=True, + master_state={"state": {"reported": {"name": "myroomba"}}}, + ) + with patch( - "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", - side_effect=InvalidAuth, + "homeassistant.components.roomba.config_flow.Roomba", + return_value=mocked_roomba, ): result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "host": "1.1.1.1", - "username": "test-username", - "password": "test-password", - }, + result["flow_id"], VALID_CONFIG, ) - assert result2["type"] == "form" - assert result2["errors"] == {"base": "invalid_auth"} + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["errors"] == {"base": "cannot_connect"} -async def test_form_cannot_connect(hass): - """Test we handle cannot connect error.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} +async def test_form_import(hass): + """Test we can import yaml config.""" + + mocked_roomba = _create_mocked_roomba( + roomba_connected=True, + master_state={"state": {"reported": {"name": "imported_roomba"}}}, ) with patch( - "homeassistant.components.roomba.config_flow.PlaceholderHub.authenticate", - side_effect=CannotConnect, - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "host": "1.1.1.1", - "username": "test-username", - "password": "test-password", - }, + "homeassistant.components.roomba.config_flow.Roomba", + return_value=mocked_roomba, + ), patch( + "homeassistant.components.roomba.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.roomba.async_setup_entry", return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=VALID_YAML_CONFIG.copy(), ) - assert result2["type"] == "form" - assert result2["errors"] == {"base": "cannot_connect"} + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["result"].unique_id == "blid" + assert result["title"] == "imported_roomba" + assert result["data"] == { + CONF_BLID: "blid", + CONF_CERT: "/etc/ssl/certs/ca-certificates.crt", + CONF_CONTINUOUS: True, + CONF_DELAY: 1, + CONF_HOST: "1.2.3.4", + CONF_PASSWORD: "password", + } + + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_import_dupe(hass): + """Test we get abort on duplicate import.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + entry = MockConfigEntry(domain=DOMAIN, data=VALID_CONFIG, unique_id="blid") + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=VALID_YAML_CONFIG.copy(), + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" From 3c899675ad04f6afa287d170985da8b5a507f96c Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Thu, 9 Apr 2020 09:22:00 +0200 Subject: [PATCH 087/108] Remove check if user none --- homeassistant/components/roomba/config_flow.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index d0d47ed9012808..3453b7de46f635 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -94,10 +94,6 @@ async def async_step_user(self, user_input=None): await async_disconnect_or_timeout(self.hass, info[ROOMBA_SESSION]) return self.async_create_entry(title=info[CONF_NAME], data=user_input) - # If there was no user input, do not show the errors. - if user_input is None: - errors = {} - return self.async_show_form( step_id="user", data_schema=DATA_SCHEMA, errors=errors ) From 82ce71c1665b85223aa46c8c4ed4ac7d141b79a1 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Thu, 9 Apr 2020 09:27:04 +0200 Subject: [PATCH 088/108] Replace CONF_USERNAME to CONF_BLID (breaking change) --- homeassistant/components/roomba/__init__.py | 4 ++-- homeassistant/components/roomba/config_flow.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 041da4e50dd161..e3fb2ca2c6ed55 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant import config_entries, exceptions -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_HOST, CONF_PASSWORD from .const import ( BLID, @@ -31,7 +31,7 @@ DOMAIN: vol.Schema( { vol.Required(CONF_HOST): str, - vol.Required(CONF_USERNAME): str, + vol.Required(CONF_BLID): str, vol.Required(CONF_PASSWORD): str, vol.Optional(CONF_CERT, default=DEFAULT_CERT): str, vol.Optional(CONF_CONTINUOUS, default=DEFAULT_CONTINUOUS): bool, diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 3453b7de46f635..3668984a41f26b 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -5,7 +5,7 @@ import voluptuous as vol from homeassistant import config_entries, core -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_HOST, CONF_PASSWORD from homeassistant.core import callback from . import CannotConnect, async_connect_or_timeout, async_disconnect_or_timeout @@ -73,7 +73,6 @@ def async_get_options_flow(config_entry): async def async_step_import(self, import_info): """Set the config entry up from yaml.""" - import_info[CONF_BLID] = import_info.pop(CONF_USERNAME) return await self.async_step_user(import_info) async def async_step_user(self, user_input=None): From 6d690b97687a981d676ade62c96bd88db1cad7f5 Mon Sep 17 00:00:00 2001 From: Save me Date: Thu, 9 Apr 2020 14:10:36 +0200 Subject: [PATCH 089/108] Update test_config_flow.py --- tests/components/roomba/test_config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/roomba/test_config_flow.py b/tests/components/roomba/test_config_flow.py index 70ebe87c2e00a4..f108af9ed98167 100644 --- a/tests/components/roomba/test_config_flow.py +++ b/tests/components/roomba/test_config_flow.py @@ -18,7 +18,7 @@ VALID_YAML_CONFIG = { CONF_HOST: "1.2.3.4", - CONF_USERNAME: "blid", + CONF_BLID: "blid", CONF_PASSWORD: "password", CONF_CERT: "/etc/ssl/certs/ca-certificates.crt", CONF_CONTINUOUS: True, From 4257793699d1bbe422b754f1259b346f2932388b Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Fri, 10 Apr 2020 11:45:14 +0200 Subject: [PATCH 090/108] Add code owners --- CODEOWNERS | 2 +- homeassistant/components/roomba/manifest.json | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 0ece14799a3112..fa0982ad476fe2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -312,7 +312,7 @@ homeassistant/components/rfxtrx/* @danielhiversen homeassistant/components/ring/* @balloob homeassistant/components/rmvtransport/* @cgtobi homeassistant/components/roku/* @ctalkington -homeassistant/components/roomba/* @pschmitt +homeassistant/components/roomba/* @pschmitt @cyr-ius homeassistant/components/safe_mode/* @home-assistant/core homeassistant/components/saj/* @fredericvl homeassistant/components/salt/* @bjornorri diff --git a/homeassistant/components/roomba/manifest.json b/homeassistant/components/roomba/manifest.json index b61a4651cc3321..6ef71bb952445c 100644 --- a/homeassistant/components/roomba/manifest.json +++ b/homeassistant/components/roomba/manifest.json @@ -3,11 +3,7 @@ "name": "iRobot Roomba", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/roomba", - "requirements": [ - "roombapy==1.5.0" - ], + "requirements": ["roombapy==1.5.0"], "dependencies": [], - "codeowners": [ - "@pschmitt" - ] + "codeowners": ["@pschmitt", "@cyr-ius"] } From 6e642bc5f6fb6d9e1d7f36ec142c5914155e5e2f Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Fri, 10 Apr 2020 11:46:54 +0200 Subject: [PATCH 091/108] Remove CONF_USERNAME (unused) --- tests/components/roomba/test_config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/roomba/test_config_flow.py b/tests/components/roomba/test_config_flow.py index f108af9ed98167..c9a0d8fde17c1c 100644 --- a/tests/components/roomba/test_config_flow.py +++ b/tests/components/roomba/test_config_flow.py @@ -10,7 +10,7 @@ CONF_DELAY, DOMAIN, ) -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_HOST, CONF_PASSWORD from tests.common import MockConfigEntry From 9bbd0132b456535e89b5073edde18392dcaab1dd Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Fri, 10 Apr 2020 19:46:30 +0200 Subject: [PATCH 092/108] Add multiple vacuum --- homeassistant/components/roomba/__init__.py | 60 +++++++++++++++------ homeassistant/components/roomba/const.py | 1 + 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index e3fb2ca2c6ed55..ae2b837e8997e2 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -8,6 +8,8 @@ from homeassistant import config_entries, exceptions from homeassistant.const import CONF_HOST, CONF_PASSWORD +from homeassistant.core import callback +from homeassistant.helpers import config_validation as cv from .const import ( BLID, @@ -17,6 +19,7 @@ CONF_CONTINUOUS, CONF_DELAY, CONF_NAME, + CONF_PREFIX, DEFAULT_CERT, DEFAULT_CONTINUOUS, DEFAULT_DELAY, @@ -26,19 +29,33 @@ _LOGGER = logging.getLogger(__name__) -CONFIG_SCHEMA = vol.Schema( + +def _has_all_unique_prefixes(value): + """Validate that each vacuum configured has a unique prefix. + + Uniqueness is determined case-independently. + """ + prefixes = [device[CONF_PREFIX] for device in value] + schema = vol.Schema(vol.Unique()) + schema(prefixes) + return value + + +DEVICE_SCHEMA = vol.Schema( { - DOMAIN: vol.Schema( - { - vol.Required(CONF_HOST): str, - vol.Required(CONF_BLID): str, - vol.Required(CONF_PASSWORD): str, - vol.Optional(CONF_CERT, default=DEFAULT_CERT): str, - vol.Optional(CONF_CONTINUOUS, default=DEFAULT_CONTINUOUS): bool, - vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): int, - } - ) + vol.Required(CONF_HOST): str, + vol.Optional(CONF_PREFIX, default=""): vol.All(cv.string, vol.Lower), + vol.Required(CONF_BLID): str, + vol.Required(CONF_PASSWORD): str, + vol.Optional(CONF_CERT, default=DEFAULT_CERT): str, + vol.Optional(CONF_CONTINUOUS, default=DEFAULT_CONTINUOUS): bool, + vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): int, }, +) + + +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.All(cv.ensure_list, [DEVICE_SCHEMA], _has_all_unique_prefixes)}, extra=vol.ALLOW_EXTRA, ) @@ -49,13 +66,17 @@ async def async_setup(hass, config): if DOMAIN not in config: return True - - if not hass.config_entries.async_entries(DOMAIN): + for index, conf in enumerate(config[DOMAIN]): + _LOGGER.debug("Importing Roomba #%d - %s", index, conf[CONF_HOST]) + current_config_entry = _async_find_matching_config_entry( + hass, conf[CONF_PREFIX] + ) + if current_config_entry: + hass.config_entries.async_update_entry(current_config_entry, data=conf) + continue hass.async_create_task( hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=config[DOMAIN], + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=conf, ) ) @@ -169,5 +190,12 @@ def roomba_reported_state(roomba): return roomba.master_state.get("state", {}).get("reported", {}) +@callback +def _async_find_matching_config_entry(hass, prefix): + for entry in hass.config_entries.async_entries(DOMAIN): + if entry.unique_id == prefix: + return entry + + class CannotConnect(exceptions.HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/roomba/const.py b/homeassistant/components/roomba/const.py index 06684e63bdc238..cb1c37062cc78f 100644 --- a/homeassistant/components/roomba/const.py +++ b/homeassistant/components/roomba/const.py @@ -6,6 +6,7 @@ CONF_DELAY = "delay" CONF_NAME = "name" CONF_BLID = "blid" +CONF_PREFIX = "prefix" DEFAULT_CERT = "/etc/ssl/certs/ca-certificates.crt" DEFAULT_CONTINUOUS = True DEFAULT_DELAY = 1 From 52626164734fc2a94b59167242511f768595eea7 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Fri, 10 Apr 2020 20:07:48 +0200 Subject: [PATCH 093/108] Add multiple vacuum --- homeassistant/components/roomba/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/const.py b/homeassistant/components/roomba/const.py index cb1c37062cc78f..5f033b7aba1961 100644 --- a/homeassistant/components/roomba/const.py +++ b/homeassistant/components/roomba/const.py @@ -6,7 +6,7 @@ CONF_DELAY = "delay" CONF_NAME = "name" CONF_BLID = "blid" -CONF_PREFIX = "prefix" +CONF_PREFIX = "name" DEFAULT_CERT = "/etc/ssl/certs/ca-certificates.crt" DEFAULT_CONTINUOUS = True DEFAULT_DELAY = 1 From 6a0104e2a326ce6c2e3d7fe9a0faac19f1d84610 Mon Sep 17 00:00:00 2001 From: Save me Date: Fri, 10 Apr 2020 20:19:05 +0200 Subject: [PATCH 094/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index ae2b837e8997e2..9e18db63264a01 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -35,7 +35,7 @@ def _has_all_unique_prefixes(value): Uniqueness is determined case-independently. """ - prefixes = [device[CONF_PREFIX] for device in value] + bilds = [device[CONF_BLID] for device in value] schema = vol.Schema(vol.Unique()) schema(prefixes) return value From 2d394d4f04822dc68f52aa4d6354866427901eb9 Mon Sep 17 00:00:00 2001 From: Save me Date: Fri, 10 Apr 2020 20:19:16 +0200 Subject: [PATCH 095/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 9e18db63264a01..785ea10b94a4ff 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -30,7 +30,7 @@ _LOGGER = logging.getLogger(__name__) -def _has_all_unique_prefixes(value): +def _has_all_unique_blids(value): """Validate that each vacuum configured has a unique prefix. Uniqueness is determined case-independently. From 7bb8164d85717a50de0464109cc3884340ce99cb Mon Sep 17 00:00:00 2001 From: Save me Date: Fri, 10 Apr 2020 20:19:29 +0200 Subject: [PATCH 096/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 785ea10b94a4ff..361062f6cff51c 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -31,7 +31,7 @@ def _has_all_unique_blids(value): - """Validate that each vacuum configured has a unique prefix. + """Validate that each vacuum configured has a unique bild. Uniqueness is determined case-independently. """ From e723d0fd734df3f12f9d2887576fb2469743a763 Mon Sep 17 00:00:00 2001 From: Save me Date: Fri, 10 Apr 2020 20:19:39 +0200 Subject: [PATCH 097/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 361062f6cff51c..53df26f2ed6e95 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -37,7 +37,7 @@ def _has_all_unique_blids(value): """ bilds = [device[CONF_BLID] for device in value] schema = vol.Schema(vol.Unique()) - schema(prefixes) + schema(bilds) return value From a3e266b183251fc5353d7b23776c7b915da7488f Mon Sep 17 00:00:00 2001 From: Save me Date: Fri, 10 Apr 2020 20:19:49 +0200 Subject: [PATCH 098/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 53df26f2ed6e95..10d23f9ff43003 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -55,7 +55,7 @@ def _has_all_unique_blids(value): CONFIG_SCHEMA = vol.Schema( - {DOMAIN: vol.All(cv.ensure_list, [DEVICE_SCHEMA], _has_all_unique_prefixes)}, + {DOMAIN: vol.All(cv.ensure_list, [DEVICE_SCHEMA], _has_all_unique_bilds)}, extra=vol.ALLOW_EXTRA, ) From e74c51778301e88b1cece995375d55bca2286814 Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Fri, 10 Apr 2020 20:27:07 +0200 Subject: [PATCH 099/108] Fix syntax name --- homeassistant/components/roomba/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 10d23f9ff43003..bca38ae2bb809f 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -30,7 +30,7 @@ _LOGGER = logging.getLogger(__name__) -def _has_all_unique_blids(value): +def _has_all_unique_bilds(value): """Validate that each vacuum configured has a unique bild. Uniqueness is determined case-independently. @@ -68,9 +68,7 @@ async def async_setup(hass, config): return True for index, conf in enumerate(config[DOMAIN]): _LOGGER.debug("Importing Roomba #%d - %s", index, conf[CONF_HOST]) - current_config_entry = _async_find_matching_config_entry( - hass, conf[CONF_PREFIX] - ) + current_config_entry = _async_find_matching_config_entry(hass, conf[CONF_BLID]) if current_config_entry: hass.config_entries.async_update_entry(current_config_entry, data=conf) continue From 9daeeac1c3163c6411225a7cd7ab1d37e43de794 Mon Sep 17 00:00:00 2001 From: Save me Date: Sat, 11 Apr 2020 14:23:31 +0200 Subject: [PATCH 100/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index bca38ae2bb809f..e504f2dcc05ff3 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -68,7 +68,6 @@ async def async_setup(hass, config): return True for index, conf in enumerate(config[DOMAIN]): _LOGGER.debug("Importing Roomba #%d - %s", index, conf[CONF_HOST]) - current_config_entry = _async_find_matching_config_entry(hass, conf[CONF_BLID]) if current_config_entry: hass.config_entries.async_update_entry(current_config_entry, data=conf) continue From d29d3dc6f0ba9be91a31d625f6917a688590e5af Mon Sep 17 00:00:00 2001 From: Save me Date: Sat, 11 Apr 2020 14:23:45 +0200 Subject: [PATCH 101/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index e504f2dcc05ff3..0f05e507615365 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -70,7 +70,6 @@ async def async_setup(hass, config): _LOGGER.debug("Importing Roomba #%d - %s", index, conf[CONF_HOST]) if current_config_entry: hass.config_entries.async_update_entry(current_config_entry, data=conf) - continue hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=conf, From f83beb95ba2c840d4b56d389adf85f3f6db43328 Mon Sep 17 00:00:00 2001 From: Save me Date: Sat, 11 Apr 2020 14:24:08 +0200 Subject: [PATCH 102/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 0f05e507615365..b42d5008cc1568 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -68,7 +68,6 @@ async def async_setup(hass, config): return True for index, conf in enumerate(config[DOMAIN]): _LOGGER.debug("Importing Roomba #%d - %s", index, conf[CONF_HOST]) - if current_config_entry: hass.config_entries.async_update_entry(current_config_entry, data=conf) hass.async_create_task( hass.config_entries.flow.async_init( From f2a40c9737b37c6401a171973e8a74d3198e0d9f Mon Sep 17 00:00:00 2001 From: Save me Date: Sat, 11 Apr 2020 14:24:27 +0200 Subject: [PATCH 103/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index b42d5008cc1568..c7dc8e1bad18d8 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -68,7 +68,6 @@ async def async_setup(hass, config): return True for index, conf in enumerate(config[DOMAIN]): _LOGGER.debug("Importing Roomba #%d - %s", index, conf[CONF_HOST]) - hass.config_entries.async_update_entry(current_config_entry, data=conf) hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=conf, From 017e3e6695e33d44ffdb21518b773fb1a502ef2f Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 11 Apr 2020 14:31:27 +0200 Subject: [PATCH 104/108] Remove CONF_PREFIX (unused) --- homeassistant/components/roomba/__init__.py | 2 -- homeassistant/components/roomba/const.py | 1 - 2 files changed, 3 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index c7dc8e1bad18d8..8db886b18a622c 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -19,7 +19,6 @@ CONF_CONTINUOUS, CONF_DELAY, CONF_NAME, - CONF_PREFIX, DEFAULT_CERT, DEFAULT_CONTINUOUS, DEFAULT_DELAY, @@ -44,7 +43,6 @@ def _has_all_unique_bilds(value): DEVICE_SCHEMA = vol.Schema( { vol.Required(CONF_HOST): str, - vol.Optional(CONF_PREFIX, default=""): vol.All(cv.string, vol.Lower), vol.Required(CONF_BLID): str, vol.Required(CONF_PASSWORD): str, vol.Optional(CONF_CERT, default=DEFAULT_CERT): str, diff --git a/homeassistant/components/roomba/const.py b/homeassistant/components/roomba/const.py index 5f033b7aba1961..06684e63bdc238 100644 --- a/homeassistant/components/roomba/const.py +++ b/homeassistant/components/roomba/const.py @@ -6,7 +6,6 @@ CONF_DELAY = "delay" CONF_NAME = "name" CONF_BLID = "blid" -CONF_PREFIX = "name" DEFAULT_CERT = "/etc/ssl/certs/ca-certificates.crt" DEFAULT_CONTINUOUS = True DEFAULT_DELAY = 1 From d1db911fe65a68968646faa3e66008c88a52be81 Mon Sep 17 00:00:00 2001 From: Save me Date: Sat, 11 Apr 2020 15:52:56 +0200 Subject: [PATCH 105/108] Update homeassistant/components/roomba/sensor.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/sensor.py b/homeassistant/components/roomba/sensor.py index 522ba18390f49c..638ef94673b679 100644 --- a/homeassistant/components/roomba/sensor.py +++ b/homeassistant/components/roomba/sensor.py @@ -49,7 +49,7 @@ def device_class(self): @property def unit_of_measurement(self): """Return the unit_of_measurement of the device.""" - return "%" + return UNIT_PERCENTAGE @property def state(self): From 0e798b38d9a897ba37e6f3ce9ab317d20702331d Mon Sep 17 00:00:00 2001 From: cyr-ius Date: Sat, 11 Apr 2020 15:54:42 +0200 Subject: [PATCH 106/108] Add import UNIT_PERCENTAGE --- homeassistant/components/roomba/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/sensor.py b/homeassistant/components/roomba/sensor.py index 638ef94673b679..2f3a6c53555b13 100644 --- a/homeassistant/components/roomba/sensor.py +++ b/homeassistant/components/roomba/sensor.py @@ -1,7 +1,7 @@ """Sensor for checking the battery level of Roomba.""" import logging -from homeassistant.const import DEVICE_CLASS_BATTERY +from homeassistant.const import DEVICE_CLASS_BATTERY, UNIT_PERCENTAGE from homeassistant.helpers.entity import Entity from . import roomba_reported_state From c70d2f6494e76bc1b24ff79151cad9a6abc9c41c Mon Sep 17 00:00:00 2001 From: Save me Date: Sat, 11 Apr 2020 16:11:55 +0200 Subject: [PATCH 107/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 8db886b18a622c..fa1fa3e2b959fa 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -78,7 +78,6 @@ async def async_setup(hass, config): async def async_setup_entry(hass, config_entry): """Set the config entry up.""" # Set up roomba platforms with config entry - if config_entry.data is None: return False if not config_entry.options: From 9da03a0ab2668b02b30de7e9750e785539b5622a Mon Sep 17 00:00:00 2001 From: Save me Date: Sat, 11 Apr 2020 16:12:05 +0200 Subject: [PATCH 108/108] Update homeassistant/components/roomba/__init__.py Co-Authored-By: J. Nick Koston --- homeassistant/components/roomba/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index fa1fa3e2b959fa..2b4582610e1c13 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -78,7 +78,6 @@ async def async_setup(hass, config): async def async_setup_entry(hass, config_entry): """Set the config entry up.""" # Set up roomba platforms with config entry - return False if not config_entry.options: hass.config_entries.async_update_entry(