diff --git a/homeassistant/components/switchbot/__init__.py b/homeassistant/components/switchbot/__init__.py index b79e42ba5b9c..5d4f29b9dfe7 100644 --- a/homeassistant/components/switchbot/__init__.py +++ b/homeassistant/components/switchbot/__init__.py @@ -45,7 +45,11 @@ PLATFORMS_BY_TYPE = { SupportedModels.CONTACT.value: [Platform.BINARY_SENSOR, Platform.SENSOR], SupportedModels.MOTION.value: [Platform.BINARY_SENSOR, Platform.SENSOR], SupportedModels.HUMIDIFIER.value: [Platform.HUMIDIFIER, Platform.SENSOR], - SupportedModels.LOCK.value: [Platform.BINARY_SENSOR, Platform.LOCK], + SupportedModels.LOCK.value: [ + Platform.BINARY_SENSOR, + Platform.LOCK, + Platform.SENSOR, + ], } CLASS_BY_DEVICE = { SupportedModels.CEILING_LIGHT.value: switchbot.SwitchbotCeilingLight, diff --git a/homeassistant/components/switchbot/config_flow.py b/homeassistant/components/switchbot/config_flow.py index a71e30b2f96f..6ba0e4637188 100644 --- a/homeassistant/components/switchbot/config_flow.py +++ b/homeassistant/components/switchbot/config_flow.py @@ -5,7 +5,9 @@ import logging from typing import Any from switchbot import ( + SwitchbotAccountConnectionError, SwitchBotAdvertisement, + SwitchbotAuthenticationError, SwitchbotLock, SwitchbotModel, parse_advertisement_data, @@ -100,7 +102,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): "address": short_address(discovery_info.address), } if model_name == SwitchbotModel.LOCK: - return await self.async_step_lock_chose_method() + return await self.async_step_lock_choose_method() if self._discovered_adv.data["isEncrypted"]: return await self.async_step_password() return await self.async_step_confirm() @@ -172,11 +174,12 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): user_input[CONF_USERNAME], user_input[CONF_PASSWORD], ) + except SwitchbotAccountConnectionError as ex: + raise AbortFlow("cannot_connect") from ex + except SwitchbotAuthenticationError: + errors = {"base": "auth_failed"} + else: return await self.async_step_lock_key(key_details) - except RuntimeError: - errors = { - "base": "auth_failed", - } user_input = user_input or {} return self.async_show_form( @@ -195,14 +198,14 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): }, ) - async def async_step_lock_chose_method( + async def async_step_lock_choose_method( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle the SwitchBot API chose method step.""" assert self._discovered_adv is not None return self.async_show_menu( - step_id="lock_chose_method", + step_id="lock_choose_method", menu_options=["lock_auth", "lock_key"], description_placeholders={ "name": name_from_discovery(self._discovered_adv), @@ -286,7 +289,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): device_adv = self._discovered_advs[user_input[CONF_ADDRESS]] await self._async_set_device(device_adv) if device_adv.data.get("modelName") == SwitchbotModel.LOCK: - return await self.async_step_lock_chose_method() + return await self.async_step_lock_choose_method() if device_adv.data["isEncrypted"]: return await self.async_step_password() return await self._async_create_entry_from_discovery(user_input) @@ -298,7 +301,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): device_adv = list(self._discovered_advs.values())[0] await self._async_set_device(device_adv) if device_adv.data.get("modelName") == SwitchbotModel.LOCK: - return await self.async_step_lock_chose_method() + return await self.async_step_lock_choose_method() if device_adv.data["isEncrypted"]: return await self.async_step_password() return await self.async_step_confirm() diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index b5b3d6332859..f18a80b1b89e 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -2,7 +2,7 @@ "domain": "switchbot", "name": "SwitchBot", "documentation": "https://www.home-assistant.io/integrations/switchbot", - "requirements": ["PySwitchbot==0.34.1"], + "requirements": ["PySwitchbot==0.36.0"], "config_flow": true, "dependencies": ["bluetooth"], "codeowners": [ diff --git a/homeassistant/components/switchbot/strings.json b/homeassistant/components/switchbot/strings.json index 10a623a70d7d..08fd960334af 100644 --- a/homeassistant/components/switchbot/strings.json +++ b/homeassistant/components/switchbot/strings.json @@ -30,11 +30,11 @@ "password": "[%key:common::config_flow::data::password%]" } }, - "lock_chose_method": { - "description": "Choose configuration method, details can be found in the documentation.", + "lock_choose_method": { + "description": "A SwitchBot lock can be set up in Home Assistant in two different ways.\n\nYou can enter the key id and encryption key yourself, or Home Assistant can import them from your SwitchBot account.", "menu_options": { - "lock_auth": "SwitchBot app login and password", - "lock_key": "Lock encryption key" + "lock_auth": "SwitchBot account (recommended)", + "lock_key": "Enter lock encryption key manually" } } }, diff --git a/homeassistant/components/switchbot/translations/en.json b/homeassistant/components/switchbot/translations/en.json index ab2ffa8d6acc..b5658e58d6bf 100644 --- a/homeassistant/components/switchbot/translations/en.json +++ b/homeassistant/components/switchbot/translations/en.json @@ -23,11 +23,11 @@ }, "description": "Please provide your SwitchBot app username and password. This data won't be saved and only used to retrieve your locks encryption key." }, - "lock_chose_method": { - "description": "Choose configuration method, details can be found in the documentation.", + "lock_choose_method": { + "description": "A SwitchBot lock can be set up in Home Assistant in two different ways.\n\nYou can enter the key id and encryption key yourself, or Home Assistant can import them from your SwitchBot account.", "menu_options": { - "lock_auth": "SwitchBot app login and password", - "lock_key": "Lock encryption key" + "lock_auth": "SwitchBot account (recommended)", + "lock_key": "Enter lock encryption key manually" } }, "lock_key": { diff --git a/requirements_all.txt b/requirements_all.txt index 43cc42d95126..756240346cce 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -40,7 +40,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.34.1 +PySwitchbot==0.36.0 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4a9d3801e835..2e1dafe0009a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -36,7 +36,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.34.1 +PySwitchbot==0.36.0 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 diff --git a/tests/components/switchbot/test_config_flow.py b/tests/components/switchbot/test_config_flow.py index 6e1a1a14c6a2..1a3db48f1929 100644 --- a/tests/components/switchbot/test_config_flow.py +++ b/tests/components/switchbot/test_config_flow.py @@ -2,6 +2,8 @@ from unittest.mock import patch +from switchbot import SwitchbotAccountConnectionError, SwitchbotAuthenticationError + from homeassistant.components.switchbot.const import ( CONF_ENCRYPTION_KEY, CONF_KEY_ID, @@ -99,7 +101,7 @@ async def test_bluetooth_discovery_lock_key(hass): data=WOLOCK_SERVICE_INFO, ) assert result["type"] == FlowResultType.MENU - assert result["step_id"] == "lock_chose_method" + assert result["step_id"] == "lock_choose_method" result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={"next_step_id": "lock_key"} @@ -404,7 +406,7 @@ async def test_user_setup_wolock_key(hass): DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] == FlowResultType.MENU - assert result["step_id"] == "lock_chose_method" + assert result["step_id"] == "lock_choose_method" result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={"next_step_id": "lock_key"} @@ -467,7 +469,7 @@ async def test_user_setup_wolock_auth(hass): DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] == FlowResultType.MENU - assert result["step_id"] == "lock_chose_method" + assert result["step_id"] == "lock_choose_method" result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={"next_step_id": "lock_auth"} @@ -479,7 +481,7 @@ async def test_user_setup_wolock_auth(hass): with patch( "homeassistant.components.switchbot.config_flow.SwitchbotLock.retrieve_encryption_key", - side_effect=RuntimeError, + side_effect=SwitchbotAuthenticationError, ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -524,6 +526,43 @@ async def test_user_setup_wolock_auth(hass): assert len(mock_setup_entry.mock_calls) == 1 +async def test_user_setup_wolock_auth_switchbot_api_down(hass): + """Test the user initiated form for a lock when the switchbot api is down.""" + + with patch( + "homeassistant.components.switchbot.config_flow.async_discovered_service_info", + return_value=[WOLOCK_SERVICE_INFO], + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "lock_choose_method" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"next_step_id": "lock_auth"} + ) + await hass.async_block_till_done() + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "lock_auth" + assert result["errors"] == {} + + with patch( + "homeassistant.components.switchbot.config_flow.SwitchbotLock.retrieve_encryption_key", + side_effect=SwitchbotAccountConnectionError, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: "", + CONF_PASSWORD: "", + }, + ) + await hass.async_block_till_done() + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "cannot_connect" + + async def test_user_setup_wolock_or_bot(hass): """Test the user initiated form for a lock.""" @@ -547,7 +586,7 @@ async def test_user_setup_wolock_or_bot(hass): ) await hass.async_block_till_done() assert result["type"] == FlowResultType.MENU - assert result["step_id"] == "lock_chose_method" + assert result["step_id"] == "lock_choose_method" result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={"next_step_id": "lock_key"}