Add Xiaomi Miio EU gateway support (#47955)
* Add EU gateway support * add options flow translations * fix options flow * fix missing import * try to fix async_add_executor_job * try to fix async_add_executor_job * fix unload * check for login succes * fix not reloading * use cloud option * fix styling * Return after if Co-authored-by: Nathan Tilley <nathan@tilley.xyz> * cleanup * add options flow tests * fix new tests * fix typo in docstring * add missing blank line * Use async_on_unload Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Use async_on_unload Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Use async_setup_platforms Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Use async_unload_platforms Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/xiaomi_miio/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/xiaomi_miio/const.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * default use_cloud False * add options flow checks * fix styling * fix issort * add MiCloud check tests * fix indent * fix styling * fix tests * fix tests * fix black * re-write config flow * add explicit return type * update strings.json * black formatting * fix config flow Tested the config flow and it is now fully working * fix styling * Fix current tests * Add missing tests * fix styling * add re-auth flow * fix styling * fix reauth flow * Add reauth flow test * use ConfigEntryAuthFailed * also trigger reauth @ login error * fix styling * remove unused import * fix spelling Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Fix spelling Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * fix spelling Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * remove unessesary .keys() Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * combine async_add_executor_job calls * remove async_step_model * fix wrong indent * fix gatway.py * fix tests Co-authored-by: Nathan Tilley <nathan@tilley.xyz> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
8705168fe6
commit
3a2d50fe23
10 changed files with 1050 additions and 198 deletions
|
@ -51,6 +51,30 @@ async def async_setup_entry(
|
|||
)
|
||||
|
||||
|
||||
def get_platforms(config_entry):
|
||||
"""Return the platforms belonging to a config_entry."""
|
||||
model = config_entry.data[CONF_MODEL]
|
||||
flow_type = config_entry.data[CONF_FLOW_TYPE]
|
||||
|
||||
if flow_type == CONF_GATEWAY:
|
||||
return GATEWAY_PLATFORMS
|
||||
if flow_type == CONF_DEVICE:
|
||||
if model in MODELS_SWITCH:
|
||||
return SWITCH_PLATFORMS
|
||||
if model in MODELS_FAN:
|
||||
return FAN_PLATFORMS
|
||||
if model in MODELS_LIGHT:
|
||||
return LIGHT_PLATFORMS
|
||||
for vacuum_model in MODELS_VACUUM:
|
||||
if model.startswith(vacuum_model):
|
||||
return VACUUM_PLATFORMS
|
||||
for air_monitor_model in MODELS_AIR_MONITOR:
|
||||
if model.startswith(air_monitor_model):
|
||||
return AIR_MONITOR_PLATFORMS
|
||||
|
||||
return []
|
||||
|
||||
|
||||
async def async_setup_gateway_entry(
|
||||
hass: core.HomeAssistant, entry: config_entries.ConfigEntry
|
||||
):
|
||||
|
@ -64,8 +88,10 @@ async def async_setup_gateway_entry(
|
|||
if entry.unique_id.endswith("-gateway"):
|
||||
hass.config_entries.async_update_entry(entry, unique_id=entry.data["mac"])
|
||||
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
|
||||
# Connect to gateway
|
||||
gateway = ConnectXiaomiGateway(hass)
|
||||
gateway = ConnectXiaomiGateway(hass, entry)
|
||||
if not await gateway.async_connect_gateway(host, token):
|
||||
return False
|
||||
gateway_info = gateway.gateway_info
|
||||
|
@ -128,29 +154,36 @@ async def async_setup_device_entry(
|
|||
hass: core.HomeAssistant, entry: config_entries.ConfigEntry
|
||||
):
|
||||
"""Set up the Xiaomi Miio device component from a config entry."""
|
||||
model = entry.data[CONF_MODEL]
|
||||
|
||||
# Identify platforms to setup
|
||||
platforms = []
|
||||
if model in MODELS_SWITCH:
|
||||
platforms = SWITCH_PLATFORMS
|
||||
elif model in MODELS_FAN:
|
||||
platforms = FAN_PLATFORMS
|
||||
elif model in MODELS_LIGHT:
|
||||
platforms = LIGHT_PLATFORMS
|
||||
for vacuum_model in MODELS_VACUUM:
|
||||
if model.startswith(vacuum_model):
|
||||
platforms = VACUUM_PLATFORMS
|
||||
for air_monitor_model in MODELS_AIR_MONITOR:
|
||||
if model.startswith(air_monitor_model):
|
||||
platforms = AIR_MONITOR_PLATFORMS
|
||||
platforms = get_platforms(entry)
|
||||
|
||||
if not platforms:
|
||||
return False
|
||||
|
||||
for platform in platforms:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, platform)
|
||||
)
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, platforms)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(
|
||||
hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry
|
||||
):
|
||||
"""Unload a config entry."""
|
||||
platforms = get_platforms(config_entry)
|
||||
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(
|
||||
config_entry, platforms
|
||||
)
|
||||
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(config_entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
async def update_listener(
|
||||
hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry
|
||||
):
|
||||
"""Handle options update."""
|
||||
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
|
|
|
@ -2,34 +2,98 @@
|
|||
import logging
|
||||
from re import search
|
||||
|
||||
from micloud import MiCloud
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.config_entries import SOURCE_REAUTH
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
|
||||
from .const import (
|
||||
CONF_CLOUD_COUNTRY,
|
||||
CONF_CLOUD_PASSWORD,
|
||||
CONF_CLOUD_SUBDEVICES,
|
||||
CONF_CLOUD_USERNAME,
|
||||
CONF_DEVICE,
|
||||
CONF_FLOW_TYPE,
|
||||
CONF_GATEWAY,
|
||||
CONF_MAC,
|
||||
CONF_MANUAL,
|
||||
CONF_MODEL,
|
||||
DEFAULT_CLOUD_COUNTRY,
|
||||
DOMAIN,
|
||||
MODELS_ALL,
|
||||
MODELS_ALL_DEVICES,
|
||||
MODELS_GATEWAY,
|
||||
SERVER_COUNTRY_CODES,
|
||||
)
|
||||
from .device import ConnectXiaomiDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_GATEWAY_NAME = "Xiaomi Gateway"
|
||||
|
||||
DEVICE_SETTINGS = {
|
||||
vol.Required(CONF_TOKEN): vol.All(str, vol.Length(min=32, max=32)),
|
||||
}
|
||||
DEVICE_CONFIG = vol.Schema({vol.Required(CONF_HOST): str}).extend(DEVICE_SETTINGS)
|
||||
DEVICE_MODEL_CONFIG = {vol.Optional(CONF_MODEL): vol.In(MODELS_ALL)}
|
||||
DEVICE_MODEL_CONFIG = vol.Schema({vol.Required(CONF_MODEL): vol.In(MODELS_ALL)})
|
||||
DEVICE_CLOUD_CONFIG = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_CLOUD_USERNAME): str,
|
||||
vol.Optional(CONF_CLOUD_PASSWORD): str,
|
||||
vol.Optional(CONF_CLOUD_COUNTRY, default=DEFAULT_CLOUD_COUNTRY): vol.In(
|
||||
SERVER_COUNTRY_CODES
|
||||
),
|
||||
vol.Optional(CONF_MANUAL, default=False): bool,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""Options for the component."""
|
||||
|
||||
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
|
||||
"""Init object."""
|
||||
self.config_entry = config_entry
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
"""Manage the options."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
use_cloud = user_input.get(CONF_CLOUD_SUBDEVICES, False)
|
||||
cloud_username = self.config_entry.data.get(CONF_CLOUD_USERNAME)
|
||||
cloud_password = self.config_entry.data.get(CONF_CLOUD_PASSWORD)
|
||||
cloud_country = self.config_entry.data.get(CONF_CLOUD_COUNTRY)
|
||||
|
||||
if use_cloud and (
|
||||
not cloud_username or not cloud_password or not cloud_country
|
||||
):
|
||||
errors["base"] = "cloud_credentials_incomplete"
|
||||
# trigger re-auth flow
|
||||
self.hass.async_create_task(
|
||||
self.hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_REAUTH},
|
||||
data=self.config_entry.data,
|
||||
)
|
||||
)
|
||||
|
||||
if not errors:
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
||||
settings_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_CLOUD_SUBDEVICES,
|
||||
default=self.config_entry.options.get(CONF_CLOUD_SUBDEVICES, False),
|
||||
): bool
|
||||
}
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="init", data_schema=settings_schema, errors=errors
|
||||
)
|
||||
|
||||
|
||||
class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
@ -41,16 +105,51 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
"""Initialize."""
|
||||
self.host = None
|
||||
self.mac = None
|
||||
self.token = None
|
||||
self.model = None
|
||||
self.name = None
|
||||
self.cloud_username = None
|
||||
self.cloud_password = None
|
||||
self.cloud_country = None
|
||||
self.cloud_devices = {}
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry) -> OptionsFlowHandler:
|
||||
"""Get the options flow."""
|
||||
return OptionsFlowHandler(config_entry)
|
||||
|
||||
async def async_step_reauth(self, user_input=None):
|
||||
"""Perform reauth upon an authentication error or missing cloud credentials."""
|
||||
self.host = user_input[CONF_HOST]
|
||||
self.token = user_input[CONF_TOKEN]
|
||||
self.mac = user_input[CONF_MAC]
|
||||
self.model = user_input.get(CONF_MODEL)
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(self, user_input=None):
|
||||
"""Dialog that informs the user that reauth is required."""
|
||||
if user_input is not None:
|
||||
return await self.async_step_cloud()
|
||||
return self.async_show_form(
|
||||
step_id="reauth_confirm", data_schema=vol.Schema({})
|
||||
)
|
||||
|
||||
async def async_step_import(self, conf: dict):
|
||||
"""Import a configuration from config.yaml."""
|
||||
host = conf[CONF_HOST]
|
||||
self.context.update({"title_placeholders": {"name": f"YAML import {host}"}})
|
||||
return await self.async_step_device(user_input=conf)
|
||||
self.host = conf[CONF_HOST]
|
||||
self.token = conf[CONF_TOKEN]
|
||||
self.name = conf.get(CONF_NAME)
|
||||
self.model = conf.get(CONF_MODEL)
|
||||
|
||||
self.context.update(
|
||||
{"title_placeholders": {"name": f"YAML import {self.host}"}}
|
||||
)
|
||||
return await self.async_step_connect()
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a flow initialized by the user."""
|
||||
return await self.async_step_device()
|
||||
return await self.async_step_cloud()
|
||||
|
||||
async def async_step_zeroconf(self, discovery_info):
|
||||
"""Handle zeroconf discovery."""
|
||||
|
@ -79,7 +178,7 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
{"title_placeholders": {"name": f"Gateway {self.host}"}}
|
||||
)
|
||||
|
||||
return await self.async_step_device()
|
||||
return await self.async_step_cloud()
|
||||
|
||||
for device_model in MODELS_ALL_DEVICES:
|
||||
if name.startswith(device_model.replace(".", "-")):
|
||||
|
@ -91,7 +190,7 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
{"title_placeholders": {"name": f"{device_model} {self.host}"}}
|
||||
)
|
||||
|
||||
return await self.async_step_device()
|
||||
return await self.async_step_cloud()
|
||||
|
||||
# Discovered device is not yet supported
|
||||
_LOGGER.debug(
|
||||
|
@ -101,76 +200,190 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
)
|
||||
return self.async_abort(reason="not_xiaomi_miio")
|
||||
|
||||
async def async_step_device(self, user_input=None):
|
||||
"""Handle a flow initialized by the user to configure a xiaomi miio device."""
|
||||
def extract_cloud_info(self, cloud_device_info):
|
||||
"""Extract the cloud info."""
|
||||
if self.host is None:
|
||||
self.host = cloud_device_info["localip"]
|
||||
if self.mac is None:
|
||||
self.mac = format_mac(cloud_device_info["mac"])
|
||||
if self.model is None:
|
||||
self.model = cloud_device_info["model"]
|
||||
if self.name is None:
|
||||
self.name = cloud_device_info["name"]
|
||||
self.token = cloud_device_info["token"]
|
||||
|
||||
async def async_step_cloud(self, user_input=None):
|
||||
"""Configure a xiaomi miio device through the Miio Cloud."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
token = user_input[CONF_TOKEN]
|
||||
model = user_input.get(CONF_MODEL)
|
||||
if user_input[CONF_MANUAL]:
|
||||
return await self.async_step_manual()
|
||||
|
||||
cloud_username = user_input.get(CONF_CLOUD_USERNAME)
|
||||
cloud_password = user_input.get(CONF_CLOUD_PASSWORD)
|
||||
cloud_country = user_input.get(CONF_CLOUD_COUNTRY)
|
||||
|
||||
if not cloud_username or not cloud_password or not cloud_country:
|
||||
errors["base"] = "cloud_credentials_incomplete"
|
||||
return self.async_show_form(
|
||||
step_id="cloud", data_schema=DEVICE_CLOUD_CONFIG, errors=errors
|
||||
)
|
||||
|
||||
miio_cloud = MiCloud(cloud_username, cloud_password)
|
||||
if not await self.hass.async_add_executor_job(miio_cloud.login):
|
||||
errors["base"] = "cloud_login_error"
|
||||
return self.async_show_form(
|
||||
step_id="cloud", data_schema=DEVICE_CLOUD_CONFIG, errors=errors
|
||||
)
|
||||
|
||||
devices_raw = await self.hass.async_add_executor_job(
|
||||
miio_cloud.get_devices, cloud_country
|
||||
)
|
||||
|
||||
if not devices_raw:
|
||||
errors["base"] = "cloud_no_devices"
|
||||
return self.async_show_form(
|
||||
step_id="cloud", data_schema=DEVICE_CLOUD_CONFIG, errors=errors
|
||||
)
|
||||
|
||||
self.cloud_devices = {}
|
||||
for device in devices_raw:
|
||||
parent_id = device.get("parent_id")
|
||||
if not parent_id:
|
||||
name = device["name"]
|
||||
model = device["model"]
|
||||
list_name = f"{name} - {model}"
|
||||
self.cloud_devices[list_name] = device
|
||||
|
||||
self.cloud_username = cloud_username
|
||||
self.cloud_password = cloud_password
|
||||
self.cloud_country = cloud_country
|
||||
|
||||
if self.host is not None:
|
||||
for device in self.cloud_devices.values():
|
||||
cloud_host = device.get("localip")
|
||||
if cloud_host == self.host:
|
||||
self.extract_cloud_info(device)
|
||||
return await self.async_step_connect()
|
||||
|
||||
if len(self.cloud_devices) == 1:
|
||||
self.extract_cloud_info(list(self.cloud_devices.values())[0])
|
||||
return await self.async_step_connect()
|
||||
|
||||
return await self.async_step_select()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="cloud", data_schema=DEVICE_CLOUD_CONFIG, errors=errors
|
||||
)
|
||||
|
||||
async def async_step_select(self, user_input=None):
|
||||
"""Handle multiple cloud devices found."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
cloud_device = self.cloud_devices[user_input["select_device"]]
|
||||
self.extract_cloud_info(cloud_device)
|
||||
return await self.async_step_connect()
|
||||
|
||||
select_schema = vol.Schema(
|
||||
{vol.Required("select_device"): vol.In(list(self.cloud_devices))}
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="select", data_schema=select_schema, errors=errors
|
||||
)
|
||||
|
||||
async def async_step_manual(self, user_input=None):
|
||||
"""Configure a xiaomi miio device Manually."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
self.token = user_input[CONF_TOKEN]
|
||||
if user_input.get(CONF_HOST):
|
||||
self.host = user_input[CONF_HOST]
|
||||
|
||||
# Try to connect to a Xiaomi Device.
|
||||
connect_device_class = ConnectXiaomiDevice(self.hass)
|
||||
await connect_device_class.async_connect_device(self.host, token)
|
||||
device_info = connect_device_class.device_info
|
||||
|
||||
if model is None and device_info is not None:
|
||||
model = device_info.model
|
||||
|
||||
if model is not None:
|
||||
if self.mac is None and device_info is not None:
|
||||
self.mac = format_mac(device_info.mac_address)
|
||||
|
||||
# Setup Gateways
|
||||
for gateway_model in MODELS_GATEWAY:
|
||||
if model.startswith(gateway_model):
|
||||
unique_id = self.mac
|
||||
await self.async_set_unique_id(
|
||||
unique_id, raise_on_progress=False
|
||||
)
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(
|
||||
title=DEFAULT_GATEWAY_NAME,
|
||||
data={
|
||||
CONF_FLOW_TYPE: CONF_GATEWAY,
|
||||
CONF_HOST: self.host,
|
||||
CONF_TOKEN: token,
|
||||
CONF_MODEL: model,
|
||||
CONF_MAC: self.mac,
|
||||
},
|
||||
)
|
||||
|
||||
# Setup all other Miio Devices
|
||||
name = user_input.get(CONF_NAME, model)
|
||||
|
||||
for device_model in MODELS_ALL_DEVICES:
|
||||
if model.startswith(device_model):
|
||||
unique_id = self.mac
|
||||
await self.async_set_unique_id(
|
||||
unique_id, raise_on_progress=False
|
||||
)
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(
|
||||
title=name,
|
||||
data={
|
||||
CONF_FLOW_TYPE: CONF_DEVICE,
|
||||
CONF_HOST: self.host,
|
||||
CONF_TOKEN: token,
|
||||
CONF_MODEL: model,
|
||||
CONF_MAC: self.mac,
|
||||
},
|
||||
)
|
||||
errors["base"] = "unknown_device"
|
||||
else:
|
||||
errors["base"] = "cannot_connect"
|
||||
return await self.async_step_connect()
|
||||
|
||||
if self.host:
|
||||
schema = vol.Schema(DEVICE_SETTINGS)
|
||||
else:
|
||||
schema = DEVICE_CONFIG
|
||||
|
||||
if errors:
|
||||
schema = schema.extend(DEVICE_MODEL_CONFIG)
|
||||
return self.async_show_form(step_id="manual", data_schema=schema, errors=errors)
|
||||
|
||||
return self.async_show_form(step_id="device", data_schema=schema, errors=errors)
|
||||
async def async_step_connect(self, user_input=None):
|
||||
"""Connect to a xiaomi miio device."""
|
||||
errors = {}
|
||||
if self.host is None or self.token is None:
|
||||
return self.async_abort(reason="incomplete_info")
|
||||
|
||||
if user_input is not None:
|
||||
self.model = user_input[CONF_MODEL]
|
||||
|
||||
# Try to connect to a Xiaomi Device.
|
||||
connect_device_class = ConnectXiaomiDevice(self.hass)
|
||||
await connect_device_class.async_connect_device(self.host, self.token)
|
||||
device_info = connect_device_class.device_info
|
||||
|
||||
if self.model is None and device_info is not None:
|
||||
self.model = device_info.model
|
||||
|
||||
if self.model is None:
|
||||
errors["base"] = "cannot_connect"
|
||||
return self.async_show_form(
|
||||
step_id="connect", data_schema=DEVICE_MODEL_CONFIG, errors=errors
|
||||
)
|
||||
|
||||
if self.mac is None and device_info is not None:
|
||||
self.mac = format_mac(device_info.mac_address)
|
||||
|
||||
unique_id = self.mac
|
||||
existing_entry = await self.async_set_unique_id(
|
||||
unique_id, raise_on_progress=False
|
||||
)
|
||||
if existing_entry:
|
||||
data = existing_entry.data.copy()
|
||||
data[CONF_HOST] = self.host
|
||||
data[CONF_TOKEN] = self.token
|
||||
if (
|
||||
self.cloud_username is not None
|
||||
and self.cloud_password is not None
|
||||
and self.cloud_country is not None
|
||||
):
|
||||
data[CONF_CLOUD_USERNAME] = self.cloud_username
|
||||
data[CONF_CLOUD_PASSWORD] = self.cloud_password
|
||||
data[CONF_CLOUD_COUNTRY] = self.cloud_country
|
||||
self.hass.config_entries.async_update_entry(existing_entry, data=data)
|
||||
await self.hass.config_entries.async_reload(existing_entry.entry_id)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
|
||||
if self.name is None:
|
||||
self.name = self.model
|
||||
|
||||
flow_type = None
|
||||
for gateway_model in MODELS_GATEWAY:
|
||||
if self.model.startswith(gateway_model):
|
||||
flow_type = CONF_GATEWAY
|
||||
|
||||
if flow_type is None:
|
||||
for device_model in MODELS_ALL_DEVICES:
|
||||
if self.model.startswith(device_model):
|
||||
flow_type = CONF_DEVICE
|
||||
|
||||
if flow_type is not None:
|
||||
return self.async_create_entry(
|
||||
title=self.name,
|
||||
data={
|
||||
CONF_FLOW_TYPE: flow_type,
|
||||
CONF_HOST: self.host,
|
||||
CONF_TOKEN: self.token,
|
||||
CONF_MODEL: self.model,
|
||||
CONF_MAC: self.mac,
|
||||
CONF_CLOUD_USERNAME: self.cloud_username,
|
||||
CONF_CLOUD_PASSWORD: self.cloud_password,
|
||||
CONF_CLOUD_COUNTRY: self.cloud_country,
|
||||
},
|
||||
)
|
||||
|
||||
errors["base"] = "unknown_device"
|
||||
return self.async_show_form(
|
||||
step_id="connect", data_schema=DEVICE_MODEL_CONFIG, errors=errors
|
||||
)
|
||||
|
|
|
@ -1,16 +1,28 @@
|
|||
"""Constants for the Xiaomi Miio component."""
|
||||
DOMAIN = "xiaomi_miio"
|
||||
|
||||
# Config flow
|
||||
CONF_FLOW_TYPE = "config_flow_device"
|
||||
CONF_GATEWAY = "gateway"
|
||||
CONF_DEVICE = "device"
|
||||
CONF_MODEL = "model"
|
||||
CONF_MAC = "mac"
|
||||
CONF_CLOUD_USERNAME = "cloud_username"
|
||||
CONF_CLOUD_PASSWORD = "cloud_password"
|
||||
CONF_CLOUD_COUNTRY = "cloud_country"
|
||||
CONF_MANUAL = "manual"
|
||||
|
||||
# Options flow
|
||||
CONF_CLOUD_SUBDEVICES = "cloud_subdevices"
|
||||
|
||||
KEY_COORDINATOR = "coordinator"
|
||||
|
||||
ATTR_AVAILABLE = "available"
|
||||
|
||||
# Cloud
|
||||
SERVER_COUNTRY_CODES = ["cn", "de", "i2", "ru", "sg", "us"]
|
||||
DEFAULT_CLOUD_COUNTRY = "cn"
|
||||
|
||||
# Fan Models
|
||||
MODEL_AIRPURIFIER_V1 = "zhimi.airpurifier.v1"
|
||||
MODEL_AIRPURIFIER_V2 = "zhimi.airpurifier.v2"
|
||||
|
|
|
@ -1,12 +1,22 @@
|
|||
"""Code to handle a Xiaomi Gateway."""
|
||||
import logging
|
||||
|
||||
from micloud import MiCloud
|
||||
from miio import DeviceException, gateway
|
||||
from miio.gateway.gateway import GATEWAY_MODEL_EU
|
||||
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import ATTR_AVAILABLE, DOMAIN
|
||||
from .const import (
|
||||
ATTR_AVAILABLE,
|
||||
CONF_CLOUD_COUNTRY,
|
||||
CONF_CLOUD_PASSWORD,
|
||||
CONF_CLOUD_SUBDEVICES,
|
||||
CONF_CLOUD_USERNAME,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -14,11 +24,18 @@ _LOGGER = logging.getLogger(__name__)
|
|||
class ConnectXiaomiGateway:
|
||||
"""Class to async connect to a Xiaomi Gateway."""
|
||||
|
||||
def __init__(self, hass):
|
||||
def __init__(self, hass, config_entry):
|
||||
"""Initialize the entity."""
|
||||
self._hass = hass
|
||||
self._config_entry = config_entry
|
||||
self._gateway_device = None
|
||||
self._gateway_info = None
|
||||
self._use_cloud = None
|
||||
self._cloud_username = None
|
||||
self._cloud_password = None
|
||||
self._cloud_country = None
|
||||
self._host = None
|
||||
self._token = None
|
||||
|
||||
@property
|
||||
def gateway_device(self):
|
||||
|
@ -33,21 +50,17 @@ class ConnectXiaomiGateway:
|
|||
async def async_connect_gateway(self, host, token):
|
||||
"""Connect to the Xiaomi Gateway."""
|
||||
_LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5])
|
||||
try:
|
||||
self._gateway_device = gateway.Gateway(host, token)
|
||||
# get the gateway info
|
||||
self._gateway_info = await self._hass.async_add_executor_job(
|
||||
self._gateway_device.info
|
||||
)
|
||||
# get the connected sub devices
|
||||
await self._hass.async_add_executor_job(
|
||||
self._gateway_device.discover_devices
|
||||
)
|
||||
except DeviceException:
|
||||
_LOGGER.error(
|
||||
"DeviceException during setup of xiaomi gateway with host %s", host
|
||||
)
|
||||
|
||||
self._host = host
|
||||
self._token = token
|
||||
self._use_cloud = self._config_entry.options.get(CONF_CLOUD_SUBDEVICES, False)
|
||||
self._cloud_username = self._config_entry.data.get(CONF_CLOUD_USERNAME)
|
||||
self._cloud_password = self._config_entry.data.get(CONF_CLOUD_PASSWORD)
|
||||
self._cloud_country = self._config_entry.data.get(CONF_CLOUD_COUNTRY)
|
||||
|
||||
if not await self._hass.async_add_executor_job(self.connect_gateway):
|
||||
return False
|
||||
|
||||
_LOGGER.debug(
|
||||
"%s %s %s detected",
|
||||
self._gateway_info.model,
|
||||
|
@ -56,6 +69,45 @@ class ConnectXiaomiGateway:
|
|||
)
|
||||
return True
|
||||
|
||||
def connect_gateway(self):
|
||||
"""Connect the gateway in a way that can called by async_add_executor_job."""
|
||||
try:
|
||||
self._gateway_device = gateway.Gateway(self._host, self._token)
|
||||
# get the gateway info
|
||||
self._gateway_device.info()
|
||||
|
||||
# get the connected sub devices
|
||||
if self._use_cloud or self._gateway_info.model == GATEWAY_MODEL_EU:
|
||||
if (
|
||||
self._cloud_username is None
|
||||
or self._cloud_password is None
|
||||
or self._cloud_country is None
|
||||
):
|
||||
raise ConfigEntryAuthFailed(
|
||||
"Missing cloud credentials in Xiaomi Miio configuration"
|
||||
)
|
||||
|
||||
# use miio-cloud
|
||||
miio_cloud = MiCloud(self._cloud_username, self._cloud_password)
|
||||
if not miio_cloud.login():
|
||||
raise ConfigEntryAuthFailed(
|
||||
"Could not login to Xioami Miio Cloud, check the credentials"
|
||||
)
|
||||
devices_raw = miio_cloud.get_devices(self._cloud_country)
|
||||
self._gateway_device.get_devices_from_dict(devices_raw)
|
||||
else:
|
||||
# use local query (not supported by all gateway types)
|
||||
self._gateway_device.discover_devices()
|
||||
|
||||
except DeviceException:
|
||||
_LOGGER.error(
|
||||
"DeviceException during setup of xiaomi gateway with host %s",
|
||||
self._host,
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class XiaomiGatewayDevice(CoordinatorEntity, Entity):
|
||||
"""Representation of a base Xiaomi Gateway Device."""
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Xiaomi Miio",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/xiaomi_miio",
|
||||
"requirements": ["construct==2.10.56", "python-miio==0.5.6"],
|
||||
"requirements": ["construct==2.10.56", "micloud==0.3", "python-miio==0.5.6"],
|
||||
"codeowners": ["@rytilahti", "@syssi", "@starkillerOG"],
|
||||
"zeroconf": ["_miio._udp.local."],
|
||||
"iot_class": "local_polling"
|
||||
|
|
|
@ -1,23 +1,70 @@
|
|||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]"
|
||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||
"incomplete_info": "Incomplete information to setup device, no host or token supplied.",
|
||||
"not_xiaomi_miio": "Device is not (yet) supported by Xiaomi Miio."
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"unknown_device": "The device model is not known, not able to setup the device using config flow."
|
||||
"unknown_device": "The device model is not known, not able to setup the device using config flow.",
|
||||
"cloud_no_devices": "No devices found in this Xiaomi Miio cloud account.",
|
||||
"cloud_credentials_incomplete": "Cloud credentials incomplete, please fill in username, password and country",
|
||||
"cloud_login_error": "Could not login to Xioami Miio Cloud, check the credentials."
|
||||
},
|
||||
"flow_title": "{name}",
|
||||
"step": {
|
||||
"device": {
|
||||
"reauth_confirm": {
|
||||
"description": "The Xiaomi Miio integration needs to re-authenticate your account in order to update the tokens or add missing cloud credentials.",
|
||||
"title": "[%key:common::config_flow::title::reauth%]"
|
||||
},
|
||||
"cloud": {
|
||||
"data": {
|
||||
"cloud_username": "Cloud username",
|
||||
"cloud_password": "Cloud password",
|
||||
"cloud_country": "Cloud server country",
|
||||
"manual": "Configure manually (not recommended)"
|
||||
},
|
||||
"description": "Log in to the Xiaomi Miio cloud, see https://www.openhab.org/addons/bindings/miio/#country-servers for the cloud server to use.",
|
||||
"title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway"
|
||||
},
|
||||
"select": {
|
||||
"data": {
|
||||
"select_device": "Miio device"
|
||||
},
|
||||
"description": "Select the Xiaomi Miio device to setup.",
|
||||
"title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway"
|
||||
},
|
||||
"manual": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::ip%]",
|
||||
"model": "Device model (Optional)",
|
||||
"token": "[%key:common::config_flow::data::api_token%]"
|
||||
},
|
||||
"description": "You will need the 32 character [%key:common::config_flow::data::api_token%], see https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instructions. Please note, that this [%key:common::config_flow::data::api_token%] is different from the key used by the Xiaomi Aqara integration.",
|
||||
"title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway"
|
||||
},
|
||||
"connect": {
|
||||
"data": {
|
||||
"model": "Device model"
|
||||
},
|
||||
"description": "Manually select the device model from the supported models.",
|
||||
"title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"error": {
|
||||
"cloud_credentials_incomplete": "Cloud credentials incomplete, please fill in username, password and country"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Xiaomi Miio",
|
||||
"description": "Specify optional settings",
|
||||
"data": {
|
||||
"cloud_subdevices": "Use cloud to get connected subdevices"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,42 +1,71 @@
|
|||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Device is already configured",
|
||||
"already_in_progress": "Configuration flow is already in progress"
|
||||
"config": {
|
||||
"abort": {
|
||||
"reauth_successful": "Re-authentication was successful",
|
||||
"already_configured": "Device is already configured",
|
||||
"already_in_progress": "Configuration flow is already in progress",
|
||||
"incomplete_info": "Incomplete information to setup device, no host or token supplied.",
|
||||
"not_xiaomi_miio": "Device is not (yet) supported by Xiaomi Miio."
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Failed to connect",
|
||||
"unknown_device": "The device model is not known, not able to setup the device using config flow.",
|
||||
"cloud_no_devices": "No devices found in this Xiaomi Miio cloud account.",
|
||||
"cloud_credentials_incomplete": "Cloud credentials incomplete, please fill in username, password and country",
|
||||
"cloud_login_error": "Could not login to Xioami Miio Cloud, check the credentials."
|
||||
},
|
||||
"flow_title": "{name}",
|
||||
"step": {
|
||||
"reauth_confirm": {
|
||||
"description": "The Xiaomi Miio integration needs to re-authenticate your acount in order to update the tokens or add missing cloud credentials.",
|
||||
"title": "Reauthenticate Integration"
|
||||
},
|
||||
"cloud": {
|
||||
"data": {
|
||||
"cloud_username": "Cloud username",
|
||||
"cloud_password": "Cloud password",
|
||||
"cloud_country": "Cloud server country",
|
||||
"manual": "Configure manually (not recommended)"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Failed to connect",
|
||||
"no_device_selected": "No device selected, please select one device.",
|
||||
"unknown_device": "The device model is not known, not able to setup the device using config flow."
|
||||
"description": "Log in to the Xiaomi Miio cloud, see https://www.openhab.org/addons/bindings/miio/#country-servers for the cloud server to use.",
|
||||
"title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway"
|
||||
},
|
||||
"select": {
|
||||
"data": {
|
||||
"select_device": "Miio device"
|
||||
},
|
||||
"flow_title": "{name}",
|
||||
"step": {
|
||||
"device": {
|
||||
"data": {
|
||||
"host": "IP Address",
|
||||
"model": "Device model (Optional)",
|
||||
"name": "Name of the device",
|
||||
"token": "API Token"
|
||||
},
|
||||
"description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.",
|
||||
"title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway"
|
||||
},
|
||||
"gateway": {
|
||||
"data": {
|
||||
"host": "IP Address",
|
||||
"name": "Name of the Gateway",
|
||||
"token": "API Token"
|
||||
},
|
||||
"description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.",
|
||||
"title": "Connect to a Xiaomi Gateway"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"gateway": "Connect to a Xiaomi Gateway"
|
||||
},
|
||||
"description": "Select to which device you want to connect.",
|
||||
"title": "Xiaomi Miio"
|
||||
}
|
||||
}
|
||||
"description": "Select the Xiaomi Miio device to setup.",
|
||||
"title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway"
|
||||
},
|
||||
"manual": {
|
||||
"data": {
|
||||
"host": "IP Address",
|
||||
"token": "API Token"
|
||||
},
|
||||
"description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.",
|
||||
"title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway"
|
||||
},
|
||||
"connect": {
|
||||
"data": {
|
||||
"model": "Device model"
|
||||
},
|
||||
"description": "Manually select the device model from the supported models.",
|
||||
"title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"error": {
|
||||
"cloud_credentials_incomplete": "Cloud credentials incomplete, please fill in username, password and country"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Xiaomi Miio",
|
||||
"description": "Specify optional settings",
|
||||
"data": {
|
||||
"cloud_subdevices": "Use cloud to get connected subdevices"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -956,6 +956,9 @@ meteofrance-api==1.0.2
|
|||
# homeassistant.components.mfi
|
||||
mficlient==0.3.0
|
||||
|
||||
# homeassistant.components.xiaomi_miio
|
||||
micloud==0.3
|
||||
|
||||
# homeassistant.components.miflora
|
||||
miflora==0.7.0
|
||||
|
||||
|
|
|
@ -528,6 +528,9 @@ meteofrance-api==1.0.2
|
|||
# homeassistant.components.mfi
|
||||
mficlient==0.3.0
|
||||
|
||||
# homeassistant.components.xiaomi_miio
|
||||
micloud==0.3
|
||||
|
||||
# homeassistant.components.mill
|
||||
millheater==0.4.1
|
||||
|
||||
|
|
|
@ -2,27 +2,86 @@
|
|||
from unittest.mock import Mock, patch
|
||||
|
||||
from miio import DeviceException
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant import config_entries, data_entry_flow
|
||||
from homeassistant.components.xiaomi_miio import const
|
||||
from homeassistant.components.xiaomi_miio.config_flow import DEFAULT_GATEWAY_NAME
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
ZEROCONF_NAME = "name"
|
||||
ZEROCONF_PROP = "properties"
|
||||
ZEROCONF_MAC = "mac"
|
||||
|
||||
TEST_HOST = "1.2.3.4"
|
||||
TEST_HOST2 = "5.6.7.8"
|
||||
TEST_CLOUD_USER = "username"
|
||||
TEST_CLOUD_PASS = "password"
|
||||
TEST_CLOUD_COUNTRY = "cn"
|
||||
TEST_TOKEN = "12345678901234567890123456789012"
|
||||
TEST_NAME = "Test_Gateway"
|
||||
TEST_NAME2 = "Test_Gateway_2"
|
||||
TEST_MODEL = const.MODELS_GATEWAY[0]
|
||||
TEST_MAC = "ab:cd:ef:gh:ij:kl"
|
||||
TEST_MAC2 = "mn:op:qr:st:uv:wx"
|
||||
TEST_MAC_DEVICE = "abcdefghijkl"
|
||||
TEST_MAC_DEVICE2 = "mnopqrstuvwx"
|
||||
TEST_GATEWAY_ID = TEST_MAC
|
||||
TEST_HARDWARE_VERSION = "AB123"
|
||||
TEST_FIRMWARE_VERSION = "1.2.3_456"
|
||||
TEST_ZEROCONF_NAME = "lumi-gateway-v3_miio12345678._miio._udp.local."
|
||||
TEST_SUB_DEVICE_LIST = []
|
||||
TEST_CLOUD_DEVICES_1 = [
|
||||
{
|
||||
"parent_id": None,
|
||||
"name": TEST_NAME,
|
||||
"model": TEST_MODEL,
|
||||
"localip": TEST_HOST,
|
||||
"mac": TEST_MAC_DEVICE,
|
||||
"token": TEST_TOKEN,
|
||||
}
|
||||
]
|
||||
TEST_CLOUD_DEVICES_2 = [
|
||||
{
|
||||
"parent_id": None,
|
||||
"name": TEST_NAME,
|
||||
"model": TEST_MODEL,
|
||||
"localip": TEST_HOST,
|
||||
"mac": TEST_MAC_DEVICE,
|
||||
"token": TEST_TOKEN,
|
||||
},
|
||||
{
|
||||
"parent_id": None,
|
||||
"name": TEST_NAME2,
|
||||
"model": TEST_MODEL,
|
||||
"localip": TEST_HOST2,
|
||||
"mac": TEST_MAC_DEVICE2,
|
||||
"token": TEST_TOKEN,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(name="xiaomi_miio_connect", autouse=True)
|
||||
def xiaomi_miio_connect_fixture():
|
||||
"""Mock denonavr connection and entry setup."""
|
||||
mock_info = get_mock_info()
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.xiaomi_miio.device.Device.info",
|
||||
return_value=mock_info,
|
||||
), patch(
|
||||
"homeassistant.components.xiaomi_miio.config_flow.MiCloud.login",
|
||||
return_value=True,
|
||||
), patch(
|
||||
"homeassistant.components.xiaomi_miio.config_flow.MiCloud.get_devices",
|
||||
return_value=TEST_CLOUD_DEVICES_1,
|
||||
), patch(
|
||||
"homeassistant.components.xiaomi_miio.async_setup_entry", return_value=True
|
||||
), patch(
|
||||
"homeassistant.components.xiaomi_miio.async_unload_entry", return_value=True
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
def get_mock_info(
|
||||
|
@ -48,7 +107,16 @@ async def test_config_flow_step_gateway_connect_error(hass):
|
|||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{const.CONF_MANUAL: True},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "manual"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
|
@ -61,7 +129,7 @@ async def test_config_flow_step_gateway_connect_error(hass):
|
|||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["step_id"] == "connect"
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
|
||||
|
@ -72,26 +140,30 @@ async def test_config_flow_gateway_success(hass):
|
|||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {}
|
||||
|
||||
mock_info = get_mock_info()
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{const.CONF_MANUAL: True},
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.xiaomi_miio.device.Device.info",
|
||||
return_value=mock_info,
|
||||
), patch(
|
||||
"homeassistant.components.xiaomi_miio.async_setup_entry", return_value=True
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_TOKEN},
|
||||
)
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "manual"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_TOKEN},
|
||||
)
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == DEFAULT_GATEWAY_NAME
|
||||
assert result["title"] == TEST_MODEL
|
||||
assert result["data"] == {
|
||||
const.CONF_FLOW_TYPE: const.CONF_GATEWAY,
|
||||
const.CONF_CLOUD_USERNAME: None,
|
||||
const.CONF_CLOUD_PASSWORD: None,
|
||||
const.CONF_CLOUD_COUNTRY: None,
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
const.CONF_MODEL: TEST_MODEL,
|
||||
|
@ -99,6 +171,202 @@ async def test_config_flow_gateway_success(hass):
|
|||
}
|
||||
|
||||
|
||||
async def test_config_flow_gateway_cloud_success(hass):
|
||||
"""Test a successful config flow using cloud."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
const.CONF_CLOUD_USERNAME: TEST_CLOUD_USER,
|
||||
const.CONF_CLOUD_PASSWORD: TEST_CLOUD_PASS,
|
||||
const.CONF_CLOUD_COUNTRY: TEST_CLOUD_COUNTRY,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == TEST_NAME
|
||||
assert result["data"] == {
|
||||
const.CONF_FLOW_TYPE: const.CONF_GATEWAY,
|
||||
const.CONF_CLOUD_USERNAME: TEST_CLOUD_USER,
|
||||
const.CONF_CLOUD_PASSWORD: TEST_CLOUD_PASS,
|
||||
const.CONF_CLOUD_COUNTRY: TEST_CLOUD_COUNTRY,
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
const.CONF_MODEL: TEST_MODEL,
|
||||
const.CONF_MAC: TEST_MAC,
|
||||
}
|
||||
|
||||
|
||||
async def test_config_flow_gateway_cloud_multiple_success(hass):
|
||||
"""Test a successful config flow using cloud with multiple devices."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.xiaomi_miio.config_flow.MiCloud.get_devices",
|
||||
return_value=TEST_CLOUD_DEVICES_2,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
const.CONF_CLOUD_USERNAME: TEST_CLOUD_USER,
|
||||
const.CONF_CLOUD_PASSWORD: TEST_CLOUD_PASS,
|
||||
const.CONF_CLOUD_COUNTRY: TEST_CLOUD_COUNTRY,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "select"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"select_device": f"{TEST_NAME2} - {TEST_MODEL}"},
|
||||
)
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == TEST_NAME2
|
||||
assert result["data"] == {
|
||||
const.CONF_FLOW_TYPE: const.CONF_GATEWAY,
|
||||
const.CONF_CLOUD_USERNAME: TEST_CLOUD_USER,
|
||||
const.CONF_CLOUD_PASSWORD: TEST_CLOUD_PASS,
|
||||
const.CONF_CLOUD_COUNTRY: TEST_CLOUD_COUNTRY,
|
||||
CONF_HOST: TEST_HOST2,
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
const.CONF_MODEL: TEST_MODEL,
|
||||
const.CONF_MAC: TEST_MAC2,
|
||||
}
|
||||
|
||||
|
||||
async def test_config_flow_gateway_cloud_incomplete(hass):
|
||||
"""Test a failed config flow using incomplete cloud credentials."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
const.CONF_CLOUD_USERNAME: TEST_CLOUD_USER,
|
||||
const.CONF_CLOUD_COUNTRY: TEST_CLOUD_COUNTRY,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {"base": "cloud_credentials_incomplete"}
|
||||
|
||||
|
||||
async def test_config_flow_gateway_cloud_login_error(hass):
|
||||
"""Test a failed config flow using cloud login error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.xiaomi_miio.config_flow.MiCloud.login",
|
||||
return_value=False,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
const.CONF_CLOUD_USERNAME: TEST_CLOUD_USER,
|
||||
const.CONF_CLOUD_PASSWORD: TEST_CLOUD_PASS,
|
||||
const.CONF_CLOUD_COUNTRY: TEST_CLOUD_COUNTRY,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {"base": "cloud_login_error"}
|
||||
|
||||
|
||||
async def test_config_flow_gateway_cloud_no_devices(hass):
|
||||
"""Test a failed config flow using cloud with no devices."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.xiaomi_miio.config_flow.MiCloud.get_devices",
|
||||
return_value=[],
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
const.CONF_CLOUD_USERNAME: TEST_CLOUD_USER,
|
||||
const.CONF_CLOUD_PASSWORD: TEST_CLOUD_PASS,
|
||||
const.CONF_CLOUD_COUNTRY: TEST_CLOUD_COUNTRY,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {"base": "cloud_no_devices"}
|
||||
|
||||
|
||||
async def test_config_flow_gateway_cloud_missing_token(hass):
|
||||
"""Test a failed config flow using cloud with a missing token."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {}
|
||||
|
||||
cloud_device = [
|
||||
{
|
||||
"parent_id": None,
|
||||
"name": TEST_NAME,
|
||||
"model": TEST_MODEL,
|
||||
"localip": TEST_HOST,
|
||||
"mac": TEST_MAC_DEVICE,
|
||||
"token": None,
|
||||
}
|
||||
]
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.xiaomi_miio.config_flow.MiCloud.get_devices",
|
||||
return_value=cloud_device,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
const.CONF_CLOUD_USERNAME: TEST_CLOUD_USER,
|
||||
const.CONF_CLOUD_PASSWORD: TEST_CLOUD_PASS,
|
||||
const.CONF_CLOUD_COUNTRY: TEST_CLOUD_COUNTRY,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "incomplete_info"
|
||||
|
||||
|
||||
async def test_zeroconf_gateway_success(hass):
|
||||
"""Test a successful zeroconf discovery of a gateway."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
|
@ -112,26 +380,25 @@ async def test_zeroconf_gateway_success(hass):
|
|||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {}
|
||||
|
||||
mock_info = get_mock_info()
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.xiaomi_miio.device.Device.info",
|
||||
return_value=mock_info,
|
||||
), patch(
|
||||
"homeassistant.components.xiaomi_miio.async_setup_entry", return_value=True
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_TOKEN: TEST_TOKEN},
|
||||
)
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
const.CONF_CLOUD_USERNAME: TEST_CLOUD_USER,
|
||||
const.CONF_CLOUD_PASSWORD: TEST_CLOUD_PASS,
|
||||
const.CONF_CLOUD_COUNTRY: TEST_CLOUD_COUNTRY,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == DEFAULT_GATEWAY_NAME
|
||||
assert result["title"] == TEST_NAME
|
||||
assert result["data"] == {
|
||||
const.CONF_FLOW_TYPE: const.CONF_GATEWAY,
|
||||
const.CONF_CLOUD_USERNAME: TEST_CLOUD_USER,
|
||||
const.CONF_CLOUD_PASSWORD: TEST_CLOUD_PASS,
|
||||
const.CONF_CLOUD_COUNTRY: TEST_CLOUD_COUNTRY,
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
const.CONF_MODEL: TEST_MODEL,
|
||||
|
@ -184,7 +451,16 @@ async def test_config_flow_step_device_connect_error(hass):
|
|||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{const.CONF_MANUAL: True},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "manual"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
|
@ -197,7 +473,7 @@ async def test_config_flow_step_device_connect_error(hass):
|
|||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["step_id"] == "connect"
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
|
||||
|
@ -208,7 +484,16 @@ async def test_config_flow_step_unknown_device(hass):
|
|||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{const.CONF_MANUAL: True},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "manual"
|
||||
assert result["errors"] == {}
|
||||
|
||||
mock_info = get_mock_info(model="UNKNOWN")
|
||||
|
@ -223,7 +508,7 @@ async def test_config_flow_step_unknown_device(hass):
|
|||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["step_id"] == "connect"
|
||||
assert result["errors"] == {"base": "unknown_device"}
|
||||
|
||||
|
||||
|
@ -234,8 +519,6 @@ async def test_import_flow_success(hass):
|
|||
with patch(
|
||||
"homeassistant.components.xiaomi_miio.device.Device.info",
|
||||
return_value=mock_info,
|
||||
), patch(
|
||||
"homeassistant.components.xiaomi_miio.async_setup_entry", return_value=True
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN,
|
||||
|
@ -247,6 +530,9 @@ async def test_import_flow_success(hass):
|
|||
assert result["title"] == TEST_NAME
|
||||
assert result["data"] == {
|
||||
const.CONF_FLOW_TYPE: const.CONF_DEVICE,
|
||||
const.CONF_CLOUD_USERNAME: None,
|
||||
const.CONF_CLOUD_PASSWORD: None,
|
||||
const.CONF_CLOUD_COUNTRY: None,
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
const.CONF_MODEL: const.MODELS_SWITCH[0],
|
||||
|
@ -261,7 +547,16 @@ async def test_config_flow_step_device_manual_model_succes(hass):
|
|||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{const.CONF_MANUAL: True},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "manual"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
|
@ -274,7 +569,7 @@ async def test_config_flow_step_device_manual_model_succes(hass):
|
|||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["step_id"] == "connect"
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
overwrite_model = const.MODELS_VACUUM[0]
|
||||
|
@ -282,18 +577,19 @@ async def test_config_flow_step_device_manual_model_succes(hass):
|
|||
with patch(
|
||||
"homeassistant.components.xiaomi_miio.device.Device.info",
|
||||
side_effect=DeviceException({}),
|
||||
), patch(
|
||||
"homeassistant.components.xiaomi_miio.async_setup_entry", return_value=True
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_TOKEN: TEST_TOKEN, const.CONF_MODEL: overwrite_model},
|
||||
{const.CONF_MODEL: overwrite_model},
|
||||
)
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == overwrite_model
|
||||
assert result["data"] == {
|
||||
const.CONF_FLOW_TYPE: const.CONF_DEVICE,
|
||||
const.CONF_CLOUD_USERNAME: None,
|
||||
const.CONF_CLOUD_PASSWORD: None,
|
||||
const.CONF_CLOUD_COUNTRY: None,
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
const.CONF_MODEL: overwrite_model,
|
||||
|
@ -308,7 +604,16 @@ async def config_flow_device_success(hass, model_to_test):
|
|||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{const.CONF_MANUAL: True},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "manual"
|
||||
assert result["errors"] == {}
|
||||
|
||||
mock_info = get_mock_info(model=model_to_test)
|
||||
|
@ -316,8 +621,6 @@ async def config_flow_device_success(hass, model_to_test):
|
|||
with patch(
|
||||
"homeassistant.components.xiaomi_miio.device.Device.info",
|
||||
return_value=mock_info,
|
||||
), patch(
|
||||
"homeassistant.components.xiaomi_miio.async_setup_entry", return_value=True
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
|
@ -328,6 +631,9 @@ async def config_flow_device_success(hass, model_to_test):
|
|||
assert result["title"] == model_to_test
|
||||
assert result["data"] == {
|
||||
const.CONF_FLOW_TYPE: const.CONF_DEVICE,
|
||||
const.CONF_CLOUD_USERNAME: None,
|
||||
const.CONF_CLOUD_PASSWORD: None,
|
||||
const.CONF_CLOUD_COUNTRY: None,
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
const.CONF_MODEL: model_to_test,
|
||||
|
@ -348,7 +654,16 @@ async def zeroconf_device_success(hass, zeroconf_name_to_test, model_to_test):
|
|||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{const.CONF_MANUAL: True},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "manual"
|
||||
assert result["errors"] == {}
|
||||
|
||||
mock_info = get_mock_info(model=model_to_test)
|
||||
|
@ -356,8 +671,6 @@ async def zeroconf_device_success(hass, zeroconf_name_to_test, model_to_test):
|
|||
with patch(
|
||||
"homeassistant.components.xiaomi_miio.device.Device.info",
|
||||
return_value=mock_info,
|
||||
), patch(
|
||||
"homeassistant.components.xiaomi_miio.async_setup_entry", return_value=True
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
|
@ -368,6 +681,9 @@ async def zeroconf_device_success(hass, zeroconf_name_to_test, model_to_test):
|
|||
assert result["title"] == model_to_test
|
||||
assert result["data"] == {
|
||||
const.CONF_FLOW_TYPE: const.CONF_DEVICE,
|
||||
const.CONF_CLOUD_USERNAME: None,
|
||||
const.CONF_CLOUD_PASSWORD: None,
|
||||
const.CONF_CLOUD_COUNTRY: None,
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
const.CONF_MODEL: model_to_test,
|
||||
|
@ -399,3 +715,147 @@ async def test_zeroconf_vacuum_success(hass):
|
|||
test_vacuum_model = const.MODELS_VACUUM[0]
|
||||
test_zeroconf_name = const.MODELS_VACUUM[0].replace(".", "-")
|
||||
await zeroconf_device_success(hass, test_zeroconf_name, test_vacuum_model)
|
||||
|
||||
|
||||
async def test_options_flow(hass):
|
||||
"""Test specifying non default settings using options flow."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=const.DOMAIN,
|
||||
unique_id=TEST_GATEWAY_ID,
|
||||
data={
|
||||
const.CONF_CLOUD_USERNAME: TEST_CLOUD_USER,
|
||||
const.CONF_CLOUD_PASSWORD: TEST_CLOUD_PASS,
|
||||
const.CONF_CLOUD_COUNTRY: TEST_CLOUD_COUNTRY,
|
||||
const.CONF_FLOW_TYPE: const.CONF_GATEWAY,
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
const.CONF_MODEL: TEST_MODEL,
|
||||
const.CONF_MAC: TEST_MAC,
|
||||
},
|
||||
title=TEST_NAME,
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "init"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
const.CONF_CLOUD_SUBDEVICES: True,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert config_entry.options == {
|
||||
const.CONF_CLOUD_SUBDEVICES: True,
|
||||
}
|
||||
|
||||
|
||||
async def test_options_flow_incomplete(hass):
|
||||
"""Test specifying incomplete settings using options flow."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=const.DOMAIN,
|
||||
unique_id=TEST_GATEWAY_ID,
|
||||
data={
|
||||
const.CONF_CLOUD_USERNAME: None,
|
||||
const.CONF_CLOUD_PASSWORD: None,
|
||||
const.CONF_CLOUD_COUNTRY: None,
|
||||
const.CONF_FLOW_TYPE: const.CONF_GATEWAY,
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
const.CONF_MODEL: TEST_MODEL,
|
||||
const.CONF_MAC: TEST_MAC,
|
||||
},
|
||||
title=TEST_NAME,
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "init"
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
const.CONF_CLOUD_SUBDEVICES: True,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "init"
|
||||
assert result["errors"] == {"base": "cloud_credentials_incomplete"}
|
||||
|
||||
|
||||
async def test_reauth(hass):
|
||||
"""Test a reauth flow."""
|
||||
# await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
config_entry = MockConfigEntry(
|
||||
domain=const.DOMAIN,
|
||||
unique_id=TEST_GATEWAY_ID,
|
||||
data={
|
||||
const.CONF_CLOUD_USERNAME: None,
|
||||
const.CONF_CLOUD_PASSWORD: None,
|
||||
const.CONF_CLOUD_COUNTRY: None,
|
||||
const.CONF_FLOW_TYPE: const.CONF_GATEWAY,
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
const.CONF_MODEL: TEST_MODEL,
|
||||
const.CONF_MAC: TEST_MAC,
|
||||
},
|
||||
title=TEST_NAME,
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_REAUTH},
|
||||
data=config_entry.data,
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "reauth_confirm"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "cloud"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
const.CONF_CLOUD_USERNAME: TEST_CLOUD_USER,
|
||||
const.CONF_CLOUD_PASSWORD: TEST_CLOUD_PASS,
|
||||
const.CONF_CLOUD_COUNTRY: TEST_CLOUD_COUNTRY,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "reauth_successful"
|
||||
|
||||
config_data = config_entry.data.copy()
|
||||
assert config_data == {
|
||||
const.CONF_FLOW_TYPE: const.CONF_GATEWAY,
|
||||
const.CONF_CLOUD_USERNAME: TEST_CLOUD_USER,
|
||||
const.CONF_CLOUD_PASSWORD: TEST_CLOUD_PASS,
|
||||
const.CONF_CLOUD_COUNTRY: TEST_CLOUD_COUNTRY,
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
const.CONF_MODEL: TEST_MODEL,
|
||||
const.CONF_MAC: TEST_MAC,
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue