1
0
mirror of https://github.com/home-assistant/core.git synced 2026-02-15 07:36:16 +00:00

Add the ability to select region for Roborock (#160898)

This commit is contained in:
Luke Lashley
2026-02-01 14:50:34 -05:00
committed by GitHub
parent b7c6e4eafc
commit 705eadf8ce
4 changed files with 107 additions and 5 deletions

View File

@@ -29,17 +29,24 @@ from homeassistant.const import CONF_USERNAME
from homeassistant.core import callback
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.selector import (
SelectSelector,
SelectSelectorConfig,
SelectSelectorMode,
)
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from . import RoborockConfigEntry
from .const import (
CONF_BASE_URL,
CONF_ENTRY_CODE,
CONF_REGION,
CONF_SHOW_BACKGROUND,
CONF_USER_DATA,
DEFAULT_DRAWABLES,
DOMAIN,
DRAWABLES,
REGION_OPTIONS,
)
_LOGGER = logging.getLogger(__name__)
@@ -64,17 +71,35 @@ class RoborockFlowHandler(ConfigFlow, domain=DOMAIN):
if user_input is not None:
username = user_input[CONF_USERNAME]
region = user_input[CONF_REGION]
self._username = username
_LOGGER.debug("Requesting code for Roborock account")
base_url = None
if region != "auto":
base_url = f"https://{region}iot.roborock.com"
self._client = RoborockApiClient(
username, session=async_get_clientsession(self.hass)
username,
base_url=base_url,
session=async_get_clientsession(self.hass),
)
errors = await self._request_code()
if not errors:
return await self.async_step_code()
return self.async_show_form(
step_id="user",
data_schema=vol.Schema({vol.Required(CONF_USERNAME): str}),
data_schema=vol.Schema(
{
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_REGION, default="auto"): SelectSelector(
SelectSelectorConfig(
options=REGION_OPTIONS,
mode=SelectSelectorMode.DROPDOWN,
translation_key="region",
)
),
}
),
errors=errors,
)
@@ -114,6 +139,8 @@ class RoborockFlowHandler(ConfigFlow, domain=DOMAIN):
user_data = await self._client.code_login_v4(code)
except RoborockInvalidCode:
errors["base"] = "invalid_code"
except RoborockAccountDoesNotExist:
errors["base"] = "invalid_email_or_region"
except RoborockException:
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown_roborock"

View File

@@ -11,7 +11,8 @@ CONF_ENTRY_CODE = "code"
CONF_BASE_URL = "base_url"
CONF_USER_DATA = "user_data"
CONF_SHOW_BACKGROUND = "show_background"
CONF_REGION = "region"
REGION_OPTIONS = ["auto", "us", "eu", "ru", "cn"]
# Option Flow steps
DRAWABLES = "drawables"

View File

@@ -9,6 +9,7 @@
"invalid_code": "The code you entered was incorrect, please check it and try again.",
"invalid_email": "There is no account associated with the email you entered, please try again.",
"invalid_email_format": "There is an issue with the formatting of your email - please try again.",
"invalid_email_or_region": "Either there is no account associated with the email you entered, or there is no account in the selected region.",
"too_frequent_code_requests": "You have attempted to request too many codes. Try again later.",
"unknown": "[%key:common::config_flow::error::unknown%]",
"unknown_roborock": "There was an unknown Roborock exception - please check your logs.",
@@ -30,9 +31,11 @@
},
"user": {
"data": {
"region": "Roborock server region",
"username": "[%key:common::config_flow::data::email%]"
},
"data_description": {
"region": "The server region your Roborock account is registered in when setting up the app. Auto is recommended unless you are having issues.",
"username": "The email address used to sign in to the Roborock app."
},
"description": "Enter your Roborock email address."
@@ -545,6 +548,17 @@
}
}
},
"selector": {
"region": {
"options": {
"auto": "Auto",
"cn": "CN",
"eu": "EU",
"ru": "RU",
"us": "US"
}
}
},
"services": {
"get_maps": {
"description": "Retrieves the map and room information of your device.",

View File

@@ -1,7 +1,8 @@
"""Test Roborock config flow."""
import asyncio
from copy import deepcopy
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
import pytest
from roborock import RoborockTooFrequentCodeRequests
@@ -15,10 +16,17 @@ from roborock.exceptions import (
from vacuum_map_parser_base.config.drawable import Drawable
from homeassistant import config_entries
from homeassistant.components.roborock.const import CONF_ENTRY_CODE, DOMAIN, DRAWABLES
from homeassistant.components.roborock.const import (
CONF_BASE_URL,
CONF_ENTRY_CODE,
CONF_REGION,
DOMAIN,
DRAWABLES,
)
from homeassistant.const import CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from .mock_data import MOCK_CONFIG, NETWORK_INFO, ROBOROCK_RRUID, USER_DATA, USER_EMAIL
@@ -148,6 +156,7 @@ async def test_config_flow_failures_request_code(
[
(RoborockException(), {"base": "unknown_roborock"}),
(RoborockInvalidCode(), {"base": "invalid_code"}),
(RoborockAccountDoesNotExist(), {"base": "invalid_email_or_region"}),
(Exception(), {"base": "unknown"}),
],
)
@@ -398,3 +407,54 @@ async def test_discovery_already_setup(
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
async def test_config_flow_with_region(
hass: HomeAssistant,
) -> None:
"""Handle the config flow with a specific region."""
with patch(
"homeassistant.components.roborock.async_setup_entry", return_value=True
) as mock_setup:
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
with patch(
"homeassistant.components.roborock.config_flow.RoborockApiClient"
) as mock_client_cls:
mock_client = mock_client_cls.return_value
mock_client.request_code_v4 = AsyncMock(return_value=None)
mock_client.code_login_v4 = AsyncMock(return_value=USER_DATA)
# base_url is awaited in config_flow, so it needs to be an awaitable
future_base_url = asyncio.Future()
future_base_url.set_result("https://usiot.roborock.com")
mock_client.base_url = future_base_url
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_USERNAME: USER_EMAIL, CONF_REGION: "us"}
)
# Check that the client was initialized with the correct base_url
mock_client_cls.assert_called_with(
USER_EMAIL,
base_url="https://usiot.roborock.com",
session=async_get_clientsession(hass),
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "code"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_ENTRY_CODE: "123456"}
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["context"]["unique_id"] == ROBOROCK_RRUID
assert result["title"] == USER_EMAIL
assert result["data"][CONF_BASE_URL] == "https://usiot.roborock.com"
assert result["result"]
assert len(mock_setup.mock_calls) == 1