Add Xiaomi Miio vacuum config flow (#46669)
parent
23c2bd4e69
commit
338c07a56b
|
@ -17,6 +17,7 @@ from .const import (
|
|||
DOMAIN,
|
||||
KEY_COORDINATOR,
|
||||
MODELS_SWITCH,
|
||||
MODELS_VACUUM,
|
||||
)
|
||||
from .gateway import ConnectXiaomiGateway
|
||||
|
||||
|
@ -24,6 +25,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||
|
||||
GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "light"]
|
||||
SWITCH_PLATFORMS = ["switch"]
|
||||
VACUUM_PLATFORMS = ["vacuum"]
|
||||
|
||||
|
||||
async def async_setup(hass: core.HomeAssistant, config: dict):
|
||||
|
@ -117,9 +119,14 @@ async def async_setup_device_entry(
|
|||
model = entry.data[CONF_MODEL]
|
||||
|
||||
# Identify platforms to setup
|
||||
platforms = []
|
||||
if model in MODELS_SWITCH:
|
||||
platforms = SWITCH_PLATFORMS
|
||||
else:
|
||||
for vacuum_model in MODELS_VACUUM:
|
||||
if model.startswith(vacuum_model):
|
||||
platforms = VACUUM_PLATFORMS
|
||||
|
||||
if not platforms:
|
||||
return False
|
||||
|
||||
for component in platforms:
|
||||
|
|
|
@ -15,8 +15,9 @@ from .const import (
|
|||
CONF_MAC,
|
||||
CONF_MODEL,
|
||||
DOMAIN,
|
||||
MODELS_ALL,
|
||||
MODELS_ALL_DEVICES,
|
||||
MODELS_GATEWAY,
|
||||
MODELS_SWITCH,
|
||||
)
|
||||
from .device import ConnectXiaomiDevice
|
||||
|
||||
|
@ -29,6 +30,7 @@ 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)}
|
||||
|
||||
|
||||
class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
@ -40,6 +42,7 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
def __init__(self):
|
||||
"""Initialize."""
|
||||
self.host = None
|
||||
self.mac = None
|
||||
|
||||
async def async_step_import(self, conf: dict):
|
||||
"""Import a configuration from config.yaml."""
|
||||
|
@ -53,15 +56,15 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
"""Handle zeroconf discovery."""
|
||||
name = discovery_info.get("name")
|
||||
self.host = discovery_info.get("host")
|
||||
mac_address = discovery_info.get("properties", {}).get("mac")
|
||||
self.mac = discovery_info.get("properties", {}).get("mac")
|
||||
|
||||
if not name or not self.host or not mac_address:
|
||||
if not name or not self.host or not self.mac:
|
||||
return self.async_abort(reason="not_xiaomi_miio")
|
||||
|
||||
# Check which device is discovered.
|
||||
for gateway_model in MODELS_GATEWAY:
|
||||
if name.startswith(gateway_model.replace(".", "-")):
|
||||
unique_id = format_mac(mac_address)
|
||||
unique_id = format_mac(self.mac)
|
||||
await self.async_set_unique_id(unique_id)
|
||||
self._abort_if_unique_id_configured({CONF_HOST: self.host})
|
||||
|
||||
|
@ -70,9 +73,9 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
)
|
||||
|
||||
return await self.async_step_device()
|
||||
for switch_model in MODELS_SWITCH:
|
||||
if name.startswith(switch_model.replace(".", "-")):
|
||||
unique_id = format_mac(mac_address)
|
||||
for device_model in MODELS_ALL_DEVICES:
|
||||
if name.startswith(device_model.replace(".", "-")):
|
||||
unique_id = format_mac(self.mac)
|
||||
await self.async_set_unique_id(unique_id)
|
||||
self._abort_if_unique_id_configured({CONF_HOST: self.host})
|
||||
|
||||
|
@ -95,6 +98,7 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
errors = {}
|
||||
if user_input is not None:
|
||||
token = user_input[CONF_TOKEN]
|
||||
model = user_input.get(CONF_MODEL)
|
||||
if user_input.get(CONF_HOST):
|
||||
self.host = user_input[CONF_HOST]
|
||||
|
||||
|
@ -103,12 +107,17 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
await connect_device_class.async_connect_device(self.host, token)
|
||||
device_info = connect_device_class.device_info
|
||||
|
||||
if device_info is not None:
|
||||
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 device_info.model.startswith(gateway_model):
|
||||
mac = format_mac(device_info.mac_address)
|
||||
unique_id = mac
|
||||
if model.startswith(gateway_model):
|
||||
unique_id = self.mac
|
||||
await self.async_set_unique_id(unique_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(
|
||||
|
@ -117,29 +126,29 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
CONF_FLOW_TYPE: CONF_GATEWAY,
|
||||
CONF_HOST: self.host,
|
||||
CONF_TOKEN: token,
|
||||
CONF_MODEL: device_info.model,
|
||||
CONF_MAC: mac,
|
||||
CONF_MODEL: model,
|
||||
CONF_MAC: self.mac,
|
||||
},
|
||||
)
|
||||
|
||||
# Setup all other Miio Devices
|
||||
name = user_input.get(CONF_NAME, DEFAULT_DEVICE_NAME)
|
||||
|
||||
if device_info.model in MODELS_SWITCH:
|
||||
mac = format_mac(device_info.mac_address)
|
||||
unique_id = mac
|
||||
await self.async_set_unique_id(unique_id)
|
||||
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: device_info.model,
|
||||
CONF_MAC: mac,
|
||||
},
|
||||
)
|
||||
for device_model in MODELS_ALL_DEVICES:
|
||||
if model.startswith(device_model):
|
||||
unique_id = self.mac
|
||||
await self.async_set_unique_id(unique_id)
|
||||
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"
|
||||
|
@ -149,4 +158,7 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
else:
|
||||
schema = DEVICE_CONFIG
|
||||
|
||||
if errors:
|
||||
schema = schema.extend(DEVICE_MODEL_CONFIG)
|
||||
|
||||
return self.async_show_form(step_id="device", data_schema=schema, errors=errors)
|
||||
|
|
|
@ -23,6 +23,10 @@ MODELS_SWITCH = [
|
|||
"chuangmi.plug.hmi206",
|
||||
"lumi.acpartner.v3",
|
||||
]
|
||||
MODELS_VACUUM = ["roborock.vacuum"]
|
||||
|
||||
MODELS_ALL_DEVICES = MODELS_SWITCH + MODELS_VACUUM
|
||||
MODELS_ALL = MODELS_ALL_DEVICES + MODELS_GATEWAY
|
||||
|
||||
# Fan Services
|
||||
SERVICE_SET_BUZZER_ON = "fan_set_buzzer_on"
|
||||
|
|
|
@ -78,10 +78,14 @@ class XiaomiMiioEntity(Entity):
|
|||
@property
|
||||
def device_info(self):
|
||||
"""Return the device info."""
|
||||
return {
|
||||
"connections": {(dr.CONNECTION_NETWORK_MAC, self._mac)},
|
||||
device_info = {
|
||||
"identifiers": {(DOMAIN, self._device_id)},
|
||||
"manufacturer": "Xiaomi",
|
||||
"name": self._name,
|
||||
"model": self._model,
|
||||
}
|
||||
|
||||
if self._mac is not None:
|
||||
device_info["connections"] = {(dr.CONNECTION_NETWORK_MAC, self._mac)}
|
||||
|
||||
return device_info
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"data": {
|
||||
"host": "[%key:common::config_flow::data::ip%]",
|
||||
"token": "[%key:common::config_flow::data::api_token%]",
|
||||
"name": "Name of the device"
|
||||
"model": "Device model (Optional)"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
},
|
||||
"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."
|
||||
},
|
||||
"flow_title": "Xiaomi Miio: {name}",
|
||||
|
@ -14,27 +13,11 @@
|
|||
"device": {
|
||||
"data": {
|
||||
"host": "IP Address",
|
||||
"name": "Name of the device",
|
||||
"token": "API Token"
|
||||
"token": "API Token",
|
||||
"model": "Device model (Optional)"
|
||||
},
|
||||
"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 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,11 +26,15 @@ from homeassistant.components.vacuum import (
|
|||
SUPPORT_STOP,
|
||||
StateVacuumEntity,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN, STATE_OFF, STATE_ON
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||
from homeassistant.util.dt import as_utc
|
||||
|
||||
from .const import (
|
||||
CONF_DEVICE,
|
||||
CONF_FLOW_TYPE,
|
||||
DOMAIN,
|
||||
SERVICE_CLEAN_SEGMENT,
|
||||
SERVICE_CLEAN_ZONE,
|
||||
SERVICE_GOTO,
|
||||
|
@ -39,11 +43,11 @@ from .const import (
|
|||
SERVICE_START_REMOTE_CONTROL,
|
||||
SERVICE_STOP_REMOTE_CONTROL,
|
||||
)
|
||||
from .device import XiaomiMiioEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = "Xiaomi Vacuum cleaner"
|
||||
DATA_KEY = "vacuum.xiaomi_miio"
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
|
@ -116,110 +120,124 @@ STATE_CODE_TO_STATE = {
|
|||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the Xiaomi vacuum cleaner robot platform."""
|
||||
if DATA_KEY not in hass.data:
|
||||
hass.data[DATA_KEY] = {}
|
||||
|
||||
host = config[CONF_HOST]
|
||||
token = config[CONF_TOKEN]
|
||||
name = config[CONF_NAME]
|
||||
|
||||
# Create handler
|
||||
_LOGGER.info("Initializing with host %s (token %s...)", host, token[:5])
|
||||
vacuum = Vacuum(host, token)
|
||||
|
||||
mirobo = MiroboVacuum(name, vacuum)
|
||||
hass.data[DATA_KEY][host] = mirobo
|
||||
|
||||
async_add_entities([mirobo], update_before_add=True)
|
||||
|
||||
platform = entity_platform.current_platform.get()
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_START_REMOTE_CONTROL,
|
||||
{},
|
||||
MiroboVacuum.async_remote_control_start.__name__,
|
||||
"""Import Miio configuration from YAML."""
|
||||
_LOGGER.warning(
|
||||
"Loading Xiaomi Miio Vacuum via platform setup is deprecated. Please remove it from your configuration."
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_STOP_REMOTE_CONTROL,
|
||||
{},
|
||||
MiroboVacuum.async_remote_control_stop.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_MOVE_REMOTE_CONTROL,
|
||||
{
|
||||
vol.Optional(ATTR_RC_VELOCITY): vol.All(
|
||||
vol.Coerce(float), vol.Clamp(min=-0.29, max=0.29)
|
||||
),
|
||||
vol.Optional(ATTR_RC_ROTATION): vol.All(
|
||||
vol.Coerce(int), vol.Clamp(min=-179, max=179)
|
||||
),
|
||||
vol.Optional(ATTR_RC_DURATION): cv.positive_int,
|
||||
},
|
||||
MiroboVacuum.async_remote_control_move.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_MOVE_REMOTE_CONTROL_STEP,
|
||||
{
|
||||
vol.Optional(ATTR_RC_VELOCITY): vol.All(
|
||||
vol.Coerce(float), vol.Clamp(min=-0.29, max=0.29)
|
||||
),
|
||||
vol.Optional(ATTR_RC_ROTATION): vol.All(
|
||||
vol.Coerce(int), vol.Clamp(min=-179, max=179)
|
||||
),
|
||||
vol.Optional(ATTR_RC_DURATION): cv.positive_int,
|
||||
},
|
||||
MiroboVacuum.async_remote_control_move_step.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_CLEAN_ZONE,
|
||||
{
|
||||
vol.Required(ATTR_ZONE_ARRAY): vol.All(
|
||||
list,
|
||||
[
|
||||
vol.ExactSequence(
|
||||
[
|
||||
vol.Coerce(int),
|
||||
vol.Coerce(int),
|
||||
vol.Coerce(int),
|
||||
vol.Coerce(int),
|
||||
]
|
||||
)
|
||||
],
|
||||
),
|
||||
vol.Required(ATTR_ZONE_REPEATER): vol.All(
|
||||
vol.Coerce(int), vol.Clamp(min=1, max=3)
|
||||
),
|
||||
},
|
||||
MiroboVacuum.async_clean_zone.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_GOTO,
|
||||
{
|
||||
vol.Required("x_coord"): vol.Coerce(int),
|
||||
vol.Required("y_coord"): vol.Coerce(int),
|
||||
},
|
||||
MiroboVacuum.async_goto.__name__,
|
||||
)
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_CLEAN_SEGMENT,
|
||||
{vol.Required("segments"): vol.Any(vol.Coerce(int), [vol.Coerce(int)])},
|
||||
MiroboVacuum.async_clean_segment.__name__,
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data=config,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class MiroboVacuum(StateVacuumEntity):
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the Xiaomi vacuum cleaner robot from a config entry."""
|
||||
entities = []
|
||||
|
||||
if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE:
|
||||
host = config_entry.data[CONF_HOST]
|
||||
token = config_entry.data[CONF_TOKEN]
|
||||
name = config_entry.title
|
||||
unique_id = config_entry.unique_id
|
||||
|
||||
# Create handler
|
||||
_LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5])
|
||||
vacuum = Vacuum(host, token)
|
||||
|
||||
mirobo = MiroboVacuum(name, vacuum, config_entry, unique_id)
|
||||
entities.append(mirobo)
|
||||
|
||||
platform = entity_platform.current_platform.get()
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_START_REMOTE_CONTROL,
|
||||
{},
|
||||
MiroboVacuum.async_remote_control_start.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_STOP_REMOTE_CONTROL,
|
||||
{},
|
||||
MiroboVacuum.async_remote_control_stop.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_MOVE_REMOTE_CONTROL,
|
||||
{
|
||||
vol.Optional(ATTR_RC_VELOCITY): vol.All(
|
||||
vol.Coerce(float), vol.Clamp(min=-0.29, max=0.29)
|
||||
),
|
||||
vol.Optional(ATTR_RC_ROTATION): vol.All(
|
||||
vol.Coerce(int), vol.Clamp(min=-179, max=179)
|
||||
),
|
||||
vol.Optional(ATTR_RC_DURATION): cv.positive_int,
|
||||
},
|
||||
MiroboVacuum.async_remote_control_move.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_MOVE_REMOTE_CONTROL_STEP,
|
||||
{
|
||||
vol.Optional(ATTR_RC_VELOCITY): vol.All(
|
||||
vol.Coerce(float), vol.Clamp(min=-0.29, max=0.29)
|
||||
),
|
||||
vol.Optional(ATTR_RC_ROTATION): vol.All(
|
||||
vol.Coerce(int), vol.Clamp(min=-179, max=179)
|
||||
),
|
||||
vol.Optional(ATTR_RC_DURATION): cv.positive_int,
|
||||
},
|
||||
MiroboVacuum.async_remote_control_move_step.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_CLEAN_ZONE,
|
||||
{
|
||||
vol.Required(ATTR_ZONE_ARRAY): vol.All(
|
||||
list,
|
||||
[
|
||||
vol.ExactSequence(
|
||||
[
|
||||
vol.Coerce(int),
|
||||
vol.Coerce(int),
|
||||
vol.Coerce(int),
|
||||
vol.Coerce(int),
|
||||
]
|
||||
)
|
||||
],
|
||||
),
|
||||
vol.Required(ATTR_ZONE_REPEATER): vol.All(
|
||||
vol.Coerce(int), vol.Clamp(min=1, max=3)
|
||||
),
|
||||
},
|
||||
MiroboVacuum.async_clean_zone.__name__,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_GOTO,
|
||||
{
|
||||
vol.Required("x_coord"): vol.Coerce(int),
|
||||
vol.Required("y_coord"): vol.Coerce(int),
|
||||
},
|
||||
MiroboVacuum.async_goto.__name__,
|
||||
)
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_CLEAN_SEGMENT,
|
||||
{vol.Required("segments"): vol.Any(vol.Coerce(int), [vol.Coerce(int)])},
|
||||
MiroboVacuum.async_clean_segment.__name__,
|
||||
)
|
||||
|
||||
async_add_entities(entities, update_before_add=True)
|
||||
|
||||
|
||||
class MiroboVacuum(XiaomiMiioEntity, StateVacuumEntity):
|
||||
"""Representation of a Xiaomi Vacuum cleaner robot."""
|
||||
|
||||
def __init__(self, name, vacuum):
|
||||
def __init__(self, name, device, entry, unique_id):
|
||||
"""Initialize the Xiaomi vacuum cleaner robot handler."""
|
||||
self._name = name
|
||||
self._vacuum = vacuum
|
||||
super().__init__(name, device, entry, unique_id)
|
||||
|
||||
self.vacuum_state = None
|
||||
self._available = False
|
||||
|
@ -233,11 +251,6 @@ class MiroboVacuum(StateVacuumEntity):
|
|||
|
||||
self._timers = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the status of the vacuum cleaner."""
|
||||
|
@ -364,16 +377,16 @@ class MiroboVacuum(StateVacuumEntity):
|
|||
async def async_start(self):
|
||||
"""Start or resume the cleaning task."""
|
||||
await self._try_command(
|
||||
"Unable to start the vacuum: %s", self._vacuum.resume_or_start
|
||||
"Unable to start the vacuum: %s", self._device.resume_or_start
|
||||
)
|
||||
|
||||
async def async_pause(self):
|
||||
"""Pause the cleaning task."""
|
||||
await self._try_command("Unable to set start/pause: %s", self._vacuum.pause)
|
||||
await self._try_command("Unable to set start/pause: %s", self._device.pause)
|
||||
|
||||
async def async_stop(self, **kwargs):
|
||||
"""Stop the vacuum cleaner."""
|
||||
await self._try_command("Unable to stop: %s", self._vacuum.stop)
|
||||
await self._try_command("Unable to stop: %s", self._device.stop)
|
||||
|
||||
async def async_set_fan_speed(self, fan_speed, **kwargs):
|
||||
"""Set fan speed."""
|
||||
|
@ -390,28 +403,28 @@ class MiroboVacuum(StateVacuumEntity):
|
|||
)
|
||||
return
|
||||
await self._try_command(
|
||||
"Unable to set fan speed: %s", self._vacuum.set_fan_speed, fan_speed
|
||||
"Unable to set fan speed: %s", self._device.set_fan_speed, fan_speed
|
||||
)
|
||||
|
||||
async def async_return_to_base(self, **kwargs):
|
||||
"""Set the vacuum cleaner to return to the dock."""
|
||||
await self._try_command("Unable to return home: %s", self._vacuum.home)
|
||||
await self._try_command("Unable to return home: %s", self._device.home)
|
||||
|
||||
async def async_clean_spot(self, **kwargs):
|
||||
"""Perform a spot clean-up."""
|
||||
await self._try_command(
|
||||
"Unable to start the vacuum for a spot clean-up: %s", self._vacuum.spot
|
||||
"Unable to start the vacuum for a spot clean-up: %s", self._device.spot
|
||||
)
|
||||
|
||||
async def async_locate(self, **kwargs):
|
||||
"""Locate the vacuum cleaner."""
|
||||
await self._try_command("Unable to locate the botvac: %s", self._vacuum.find)
|
||||
await self._try_command("Unable to locate the botvac: %s", self._device.find)
|
||||
|
||||
async def async_send_command(self, command, params=None, **kwargs):
|
||||
"""Send raw command."""
|
||||
await self._try_command(
|
||||
"Unable to send command to the vacuum: %s",
|
||||
self._vacuum.raw_command,
|
||||
self._device.raw_command,
|
||||
command,
|
||||
params,
|
||||
)
|
||||
|
@ -419,13 +432,13 @@ class MiroboVacuum(StateVacuumEntity):
|
|||
async def async_remote_control_start(self):
|
||||
"""Start remote control mode."""
|
||||
await self._try_command(
|
||||
"Unable to start remote control the vacuum: %s", self._vacuum.manual_start
|
||||
"Unable to start remote control the vacuum: %s", self._device.manual_start
|
||||
)
|
||||
|
||||
async def async_remote_control_stop(self):
|
||||
"""Stop remote control mode."""
|
||||
await self._try_command(
|
||||
"Unable to stop remote control the vacuum: %s", self._vacuum.manual_stop
|
||||
"Unable to stop remote control the vacuum: %s", self._device.manual_stop
|
||||
)
|
||||
|
||||
async def async_remote_control_move(
|
||||
|
@ -434,7 +447,7 @@ class MiroboVacuum(StateVacuumEntity):
|
|||
"""Move vacuum with remote control mode."""
|
||||
await self._try_command(
|
||||
"Unable to move with remote control the vacuum: %s",
|
||||
self._vacuum.manual_control,
|
||||
self._device.manual_control,
|
||||
velocity=velocity,
|
||||
rotation=rotation,
|
||||
duration=duration,
|
||||
|
@ -446,7 +459,7 @@ class MiroboVacuum(StateVacuumEntity):
|
|||
"""Move vacuum one step with remote control mode."""
|
||||
await self._try_command(
|
||||
"Unable to remote control the vacuum: %s",
|
||||
self._vacuum.manual_control_once,
|
||||
self._device.manual_control_once,
|
||||
velocity=velocity,
|
||||
rotation=rotation,
|
||||
duration=duration,
|
||||
|
@ -456,7 +469,7 @@ class MiroboVacuum(StateVacuumEntity):
|
|||
"""Goto the specified coordinates."""
|
||||
await self._try_command(
|
||||
"Unable to send the vacuum cleaner to the specified coordinates: %s",
|
||||
self._vacuum.goto,
|
||||
self._device.goto,
|
||||
x_coord=x_coord,
|
||||
y_coord=y_coord,
|
||||
)
|
||||
|
@ -468,23 +481,23 @@ class MiroboVacuum(StateVacuumEntity):
|
|||
|
||||
await self._try_command(
|
||||
"Unable to start cleaning of the specified segments: %s",
|
||||
self._vacuum.segment_clean,
|
||||
self._device.segment_clean,
|
||||
segments=segments,
|
||||
)
|
||||
|
||||
def update(self):
|
||||
"""Fetch state from the device."""
|
||||
try:
|
||||
state = self._vacuum.status()
|
||||
state = self._device.status()
|
||||
self.vacuum_state = state
|
||||
|
||||
self._fan_speeds = self._vacuum.fan_speed_presets()
|
||||
self._fan_speeds = self._device.fan_speed_presets()
|
||||
self._fan_speeds_reverse = {v: k for k, v in self._fan_speeds.items()}
|
||||
|
||||
self.consumable_state = self._vacuum.consumable_status()
|
||||
self.clean_history = self._vacuum.clean_history()
|
||||
self.last_clean = self._vacuum.last_clean_details()
|
||||
self.dnd_state = self._vacuum.dnd_status()
|
||||
self.consumable_state = self._device.consumable_status()
|
||||
self.clean_history = self._device.clean_history()
|
||||
self.last_clean = self._device.last_clean_details()
|
||||
self.dnd_state = self._device.dnd_status()
|
||||
|
||||
self._available = True
|
||||
except (OSError, DeviceException) as exc:
|
||||
|
@ -494,7 +507,7 @@ class MiroboVacuum(StateVacuumEntity):
|
|||
|
||||
# Fetch timers separately, see #38285
|
||||
try:
|
||||
self._timers = self._vacuum.timer()
|
||||
self._timers = self._device.timer()
|
||||
except DeviceException as exc:
|
||||
_LOGGER.debug(
|
||||
"Unable to fetch timers, this may happen on some devices: %s", exc
|
||||
|
@ -507,6 +520,6 @@ class MiroboVacuum(StateVacuumEntity):
|
|||
_zone.append(repeats)
|
||||
_LOGGER.debug("Zone with repeats: %s", zone)
|
||||
try:
|
||||
await self.hass.async_add_executor_job(self._vacuum.zoned_clean, zone)
|
||||
await self.hass.async_add_executor_job(self._device.zoned_clean, zone)
|
||||
except (OSError, DeviceException) as exc:
|
||||
_LOGGER.error("Unable to send zoned_clean command to the vacuum: %s", exc)
|
||||
|
|
|
@ -257,6 +257,53 @@ async def test_import_flow_success(hass):
|
|||
}
|
||||
|
||||
|
||||
async def test_config_flow_step_device_manual_model_succes(hass):
|
||||
"""Test config flow, device connection error, manual model."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.xiaomi_miio.device.Device.info",
|
||||
side_effect=DeviceException({}),
|
||||
):
|
||||
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"] == "device"
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
overwrite_model = const.MODELS_VACUUM[0]
|
||||
|
||||
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},
|
||||
)
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == DEFAULT_DEVICE_NAME
|
||||
assert result["data"] == {
|
||||
const.CONF_FLOW_TYPE: const.CONF_DEVICE,
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
const.CONF_MODEL: overwrite_model,
|
||||
const.CONF_MAC: None,
|
||||
}
|
||||
|
||||
|
||||
async def config_flow_device_success(hass, model_to_test):
|
||||
"""Test a successful config flow for a device (base class)."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
|
@ -342,3 +389,16 @@ async def test_zeroconf_plug_success(hass):
|
|||
test_plug_model = const.MODELS_SWITCH[0]
|
||||
test_zeroconf_name = const.MODELS_SWITCH[0].replace(".", "-")
|
||||
await zeroconf_device_success(hass, test_zeroconf_name, test_plug_model)
|
||||
|
||||
|
||||
async def test_config_flow_vacuum_success(hass):
|
||||
"""Test a successful config flow for a vacuum."""
|
||||
test_vacuum_model = const.MODELS_VACUUM[0]
|
||||
await config_flow_device_success(hass, test_vacuum_model)
|
||||
|
||||
|
||||
async def test_zeroconf_vacuum_success(hass):
|
||||
"""Test a successful zeroconf discovery of a vacuum."""
|
||||
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)
|
||||
|
|
|
@ -22,6 +22,7 @@ from homeassistant.components.vacuum import (
|
|||
STATE_CLEANING,
|
||||
STATE_ERROR,
|
||||
)
|
||||
from homeassistant.components.xiaomi_miio import const
|
||||
from homeassistant.components.xiaomi_miio.const import DOMAIN as XIAOMI_DOMAIN
|
||||
from homeassistant.components.xiaomi_miio.vacuum import (
|
||||
ATTR_CLEANED_AREA,
|
||||
|
@ -38,7 +39,6 @@ from homeassistant.components.xiaomi_miio.vacuum import (
|
|||
ATTR_SIDE_BRUSH_LEFT,
|
||||
ATTR_TIMERS,
|
||||
CONF_HOST,
|
||||
CONF_NAME,
|
||||
CONF_TOKEN,
|
||||
SERVICE_CLEAN_SEGMENT,
|
||||
SERVICE_CLEAN_ZONE,
|
||||
|
@ -51,12 +51,14 @@ from homeassistant.components.xiaomi_miio.vacuum import (
|
|||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
CONF_PLATFORM,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
)
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .test_config_flow import TEST_MAC
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
PLATFORM = "xiaomi_miio"
|
||||
|
||||
|
@ -521,17 +523,21 @@ async def setup_component(hass, entity_name):
|
|||
"""Set up vacuum component."""
|
||||
entity_id = f"{DOMAIN}.{entity_name}"
|
||||
|
||||
await async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
{
|
||||
DOMAIN: {
|
||||
CONF_PLATFORM: PLATFORM,
|
||||
CONF_HOST: "192.168.1.100",
|
||||
CONF_NAME: entity_name,
|
||||
CONF_TOKEN: "12345678901234567890123456789012",
|
||||
}
|
||||
config_entry = MockConfigEntry(
|
||||
domain=XIAOMI_DOMAIN,
|
||||
unique_id="123456",
|
||||
title=entity_name,
|
||||
data={
|
||||
const.CONF_FLOW_TYPE: const.CONF_DEVICE,
|
||||
CONF_HOST: "192.168.1.100",
|
||||
CONF_TOKEN: "12345678901234567890123456789012",
|
||||
const.CONF_MODEL: const.MODELS_VACUUM[0],
|
||||
const.CONF_MAC: TEST_MAC,
|
||||
},
|
||||
)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return entity_id
|
||||
|
|
Loading…
Reference in New Issue