Add config flow to Xiaomi Miio switch (#46179)
parent
68809e9f43
commit
2f9fda73f4
|
@ -1085,6 +1085,7 @@ omit =
|
|||
homeassistant/components/xiaomi_miio/__init__.py
|
||||
homeassistant/components/xiaomi_miio/air_quality.py
|
||||
homeassistant/components/xiaomi_miio/alarm_control_panel.py
|
||||
homeassistant/components/xiaomi_miio/device.py
|
||||
homeassistant/components/xiaomi_miio/device_tracker.py
|
||||
homeassistant/components/xiaomi_miio/fan.py
|
||||
homeassistant/components/xiaomi_miio/gateway.py
|
||||
|
|
|
@ -3,11 +3,18 @@ from homeassistant import config_entries, core
|
|||
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .config_flow import CONF_FLOW_TYPE, CONF_GATEWAY
|
||||
from .const import DOMAIN
|
||||
from .const import (
|
||||
CONF_DEVICE,
|
||||
CONF_FLOW_TYPE,
|
||||
CONF_GATEWAY,
|
||||
CONF_MODEL,
|
||||
DOMAIN,
|
||||
MODELS_SWITCH,
|
||||
)
|
||||
from .gateway import ConnectXiaomiGateway
|
||||
|
||||
GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "light"]
|
||||
SWITCH_PLATFORMS = ["switch"]
|
||||
|
||||
|
||||
async def async_setup(hass: core.HomeAssistant, config: dict):
|
||||
|
@ -19,10 +26,13 @@ async def async_setup_entry(
|
|||
hass: core.HomeAssistant, entry: config_entries.ConfigEntry
|
||||
):
|
||||
"""Set up the Xiaomi Miio components from a config entry."""
|
||||
hass.data[DOMAIN] = {}
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
if entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY:
|
||||
if not await async_setup_gateway_entry(hass, entry):
|
||||
return False
|
||||
if entry.data[CONF_FLOW_TYPE] == CONF_DEVICE:
|
||||
if not await async_setup_device_entry(hass, entry):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
@ -67,3 +77,23 @@ async def async_setup_gateway_entry(
|
|||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
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
|
||||
if model in MODELS_SWITCH:
|
||||
platforms = SWITCH_PLATFORMS
|
||||
else:
|
||||
return False
|
||||
|
||||
for component in platforms:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||
)
|
||||
|
||||
return True
|
||||
|
|
|
@ -8,24 +8,27 @@ from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
|
|||
from homeassistant.helpers.device_registry import format_mac
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from .const import DOMAIN
|
||||
from .gateway import ConnectXiaomiGateway
|
||||
from .const import (
|
||||
CONF_DEVICE,
|
||||
CONF_FLOW_TYPE,
|
||||
CONF_GATEWAY,
|
||||
CONF_MAC,
|
||||
CONF_MODEL,
|
||||
DOMAIN,
|
||||
MODELS_GATEWAY,
|
||||
MODELS_SWITCH,
|
||||
)
|
||||
from .device import ConnectXiaomiDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_FLOW_TYPE = "config_flow_device"
|
||||
CONF_GATEWAY = "gateway"
|
||||
DEFAULT_GATEWAY_NAME = "Xiaomi Gateway"
|
||||
ZEROCONF_GATEWAY = "lumi-gateway"
|
||||
ZEROCONF_ACPARTNER = "lumi-acpartner"
|
||||
DEFAULT_DEVICE_NAME = "Xiaomi Device"
|
||||
|
||||
GATEWAY_SETTINGS = {
|
||||
DEVICE_SETTINGS = {
|
||||
vol.Required(CONF_TOKEN): vol.All(str, vol.Length(min=32, max=32)),
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_GATEWAY_NAME): str,
|
||||
}
|
||||
GATEWAY_CONFIG = vol.Schema({vol.Required(CONF_HOST): str}).extend(GATEWAY_SETTINGS)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({vol.Optional(CONF_GATEWAY, default=False): bool})
|
||||
DEVICE_CONFIG = vol.Schema({vol.Required(CONF_HOST): str}).extend(DEVICE_SETTINGS)
|
||||
|
||||
|
||||
class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
@ -38,19 +41,13 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
"""Initialize."""
|
||||
self.host = None
|
||||
|
||||
async def async_step_import(self, conf: dict):
|
||||
"""Import a configuration from config.yaml."""
|
||||
return await self.async_step_device(user_input=conf)
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a flow initialized by the user."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
# Check which device needs to be connected.
|
||||
if user_input[CONF_GATEWAY]:
|
||||
return await self.async_step_gateway()
|
||||
|
||||
errors["base"] = "no_device_selected"
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=CONFIG_SCHEMA, errors=errors
|
||||
)
|
||||
return await self.async_step_device()
|
||||
|
||||
async def async_step_zeroconf(self, discovery_info):
|
||||
"""Handle zeroconf discovery."""
|
||||
|
@ -62,16 +59,28 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
return self.async_abort(reason="not_xiaomi_miio")
|
||||
|
||||
# Check which device is discovered.
|
||||
if name.startswith(ZEROCONF_GATEWAY) or name.startswith(ZEROCONF_ACPARTNER):
|
||||
unique_id = format_mac(mac_address)
|
||||
await self.async_set_unique_id(unique_id)
|
||||
self._abort_if_unique_id_configured({CONF_HOST: self.host})
|
||||
for gateway_model in MODELS_GATEWAY:
|
||||
if name.startswith(gateway_model.replace(".", "-")):
|
||||
unique_id = format_mac(mac_address)
|
||||
await self.async_set_unique_id(unique_id)
|
||||
self._abort_if_unique_id_configured({CONF_HOST: self.host})
|
||||
|
||||
self.context.update(
|
||||
{"title_placeholders": {"name": f"Gateway {self.host}"}}
|
||||
)
|
||||
self.context.update(
|
||||
{"title_placeholders": {"name": f"Gateway {self.host}"}}
|
||||
)
|
||||
|
||||
return await self.async_step_gateway()
|
||||
return await self.async_step_device()
|
||||
for switch_model in MODELS_SWITCH:
|
||||
if name.startswith(switch_model.replace(".", "-")):
|
||||
unique_id = format_mac(mac_address)
|
||||
await self.async_set_unique_id(unique_id)
|
||||
self._abort_if_unique_id_configured({CONF_HOST: self.host})
|
||||
|
||||
self.context.update(
|
||||
{"title_placeholders": {"name": f"Miio Device {self.host}"}}
|
||||
)
|
||||
|
||||
return await self.async_step_device()
|
||||
|
||||
# Discovered device is not yet supported
|
||||
_LOGGER.debug(
|
||||
|
@ -81,42 +90,63 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
)
|
||||
return self.async_abort(reason="not_xiaomi_miio")
|
||||
|
||||
async def async_step_gateway(self, user_input=None):
|
||||
"""Handle a flow initialized by the user to configure a gateway."""
|
||||
async def async_step_device(self, user_input=None):
|
||||
"""Handle a flow initialized by the user to configure a xiaomi miio device."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
token = user_input[CONF_TOKEN]
|
||||
if user_input.get(CONF_HOST):
|
||||
self.host = user_input[CONF_HOST]
|
||||
|
||||
# Try to connect to a Xiaomi Gateway.
|
||||
connect_gateway_class = ConnectXiaomiGateway(self.hass)
|
||||
await connect_gateway_class.async_connect_gateway(self.host, token)
|
||||
gateway_info = connect_gateway_class.gateway_info
|
||||
# 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 gateway_info is not None:
|
||||
mac = format_mac(gateway_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=user_input[CONF_NAME],
|
||||
data={
|
||||
CONF_FLOW_TYPE: CONF_GATEWAY,
|
||||
CONF_HOST: self.host,
|
||||
CONF_TOKEN: token,
|
||||
"model": gateway_info.model,
|
||||
"mac": mac,
|
||||
},
|
||||
)
|
||||
if device_info is not None:
|
||||
# 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
|
||||
await self.async_set_unique_id(unique_id)
|
||||
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: device_info.model,
|
||||
CONF_MAC: mac,
|
||||
},
|
||||
)
|
||||
|
||||
errors["base"] = "cannot_connect"
|
||||
# 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,
|
||||
},
|
||||
)
|
||||
errors["base"] = "unknown_device"
|
||||
else:
|
||||
errors["base"] = "cannot_connect"
|
||||
|
||||
if self.host:
|
||||
schema = vol.Schema(GATEWAY_SETTINGS)
|
||||
schema = vol.Schema(DEVICE_SETTINGS)
|
||||
else:
|
||||
schema = GATEWAY_CONFIG
|
||||
schema = DEVICE_CONFIG
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="gateway", data_schema=schema, errors=errors
|
||||
)
|
||||
return self.async_show_form(step_id="device", data_schema=schema, errors=errors)
|
||||
|
|
|
@ -1,6 +1,27 @@
|
|||
"""Constants for the Xiaomi Miio component."""
|
||||
DOMAIN = "xiaomi_miio"
|
||||
|
||||
CONF_FLOW_TYPE = "config_flow_device"
|
||||
CONF_GATEWAY = "gateway"
|
||||
CONF_DEVICE = "device"
|
||||
CONF_MODEL = "model"
|
||||
CONF_MAC = "mac"
|
||||
|
||||
MODELS_GATEWAY = ["lumi.gateway", "lumi.acpartner"]
|
||||
MODELS_SWITCH = [
|
||||
"chuangmi.plug.v1",
|
||||
"chuangmi.plug.v3",
|
||||
"chuangmi.plug.hmi208",
|
||||
"qmi.powerstrip.v1",
|
||||
"zimi.powerstrip.v2",
|
||||
"chuangmi.plug.m1",
|
||||
"chuangmi.plug.m3",
|
||||
"chuangmi.plug.v2",
|
||||
"chuangmi.plug.hmi205",
|
||||
"chuangmi.plug.hmi206",
|
||||
"lumi.acpartner.v3",
|
||||
]
|
||||
|
||||
# Fan Services
|
||||
SERVICE_SET_BUZZER_ON = "fan_set_buzzer_on"
|
||||
SERVICE_SET_BUZZER_OFF = "fan_set_buzzer_off"
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
"""Code to handle a Xiaomi Device."""
|
||||
import logging
|
||||
|
||||
from miio import Device, DeviceException
|
||||
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import CONF_MAC, CONF_MODEL, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConnectXiaomiDevice:
|
||||
"""Class to async connect to a Xiaomi Device."""
|
||||
|
||||
def __init__(self, hass):
|
||||
"""Initialize the entity."""
|
||||
self._hass = hass
|
||||
self._device = None
|
||||
self._device_info = None
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
"""Return the class containing all connections to the device."""
|
||||
return self._device
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the class containing device info."""
|
||||
return self._device_info
|
||||
|
||||
async def async_connect_device(self, host, token):
|
||||
"""Connect to the Xiaomi Device."""
|
||||
_LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5])
|
||||
try:
|
||||
self._device = Device(host, token)
|
||||
# get the device info
|
||||
self._device_info = await self._hass.async_add_executor_job(
|
||||
self._device.info
|
||||
)
|
||||
except DeviceException:
|
||||
_LOGGER.error(
|
||||
"DeviceException during setup of xiaomi device with host %s", host
|
||||
)
|
||||
return False
|
||||
_LOGGER.debug(
|
||||
"%s %s %s detected",
|
||||
self._device_info.model,
|
||||
self._device_info.firmware_version,
|
||||
self._device_info.hardware_version,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class XiaomiMiioEntity(Entity):
|
||||
"""Representation of a base Xiaomi Miio Entity."""
|
||||
|
||||
def __init__(self, name, device, entry, unique_id):
|
||||
"""Initialize the Xiaomi Miio Device."""
|
||||
self._device = device
|
||||
self._model = entry.data[CONF_MODEL]
|
||||
self._mac = entry.data[CONF_MAC]
|
||||
self._device_id = entry.unique_id
|
||||
self._unique_id = unique_id
|
||||
self._name = name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return an unique ID."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of this entity, if any."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device info."""
|
||||
return {
|
||||
"connections": {(dr.CONNECTION_NETWORK_MAC, self._mac)},
|
||||
"identifiers": {(DOMAIN, self._device_id)},
|
||||
"manufacturer": "Xiaomi",
|
||||
"name": self._name,
|
||||
"model": self._model,
|
||||
}
|
|
@ -6,14 +6,8 @@ from functools import partial
|
|||
import logging
|
||||
from math import ceil
|
||||
|
||||
from miio import ( # pylint: disable=import-error
|
||||
Ceil,
|
||||
Device,
|
||||
DeviceException,
|
||||
PhilipsBulb,
|
||||
PhilipsEyecare,
|
||||
PhilipsMoonlight,
|
||||
)
|
||||
from miio import Ceil, DeviceException, PhilipsBulb, PhilipsEyecare, PhilipsMoonlight
|
||||
from miio import Device # pylint: disable=import-error
|
||||
from miio.gateway import (
|
||||
GATEWAY_MODEL_AC_V1,
|
||||
GATEWAY_MODEL_AC_V2,
|
||||
|
@ -37,8 +31,9 @@ from homeassistant.exceptions import PlatformNotReady
|
|||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util import color, dt
|
||||
|
||||
from .config_flow import CONF_FLOW_TYPE, CONF_GATEWAY
|
||||
from .const import (
|
||||
CONF_FLOW_TYPE,
|
||||
CONF_GATEWAY,
|
||||
DOMAIN,
|
||||
SERVICE_EYECARE_MODE_OFF,
|
||||
SERVICE_EYECARE_MODE_ON,
|
||||
|
|
|
@ -31,8 +31,7 @@ from homeassistant.exceptions import PlatformNotReady
|
|||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .config_flow import CONF_FLOW_TYPE, CONF_GATEWAY
|
||||
from .const import DOMAIN
|
||||
from .const import CONF_FLOW_TYPE, CONF_GATEWAY, DOMAIN
|
||||
from .gateway import XiaomiGatewayDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
|
|
@ -2,26 +2,19 @@
|
|||
"config": {
|
||||
"flow_title": "Xiaomi Miio: {name}",
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Xiaomi Miio",
|
||||
"description": "Select to which device you want to connect.",
|
||||
"data": {
|
||||
"gateway": "Connect to a Xiaomi Gateway"
|
||||
}
|
||||
},
|
||||
"gateway": {
|
||||
"title": "Connect to a Xiaomi Gateway",
|
||||
"device": {
|
||||
"title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway",
|
||||
"description": "You will need the 32 character [%key:common::config_flow::data::api_token%], see https://www.home-assistant.io/integrations/vacuum.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.",
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::ip%]",
|
||||
"token": "[%key:common::config_flow::data::api_token%]",
|
||||
"name": "Name of the Gateway"
|
||||
"name": "Name of the device"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_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."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
|
|
|
@ -3,17 +3,13 @@ import asyncio
|
|||
from functools import partial
|
||||
import logging
|
||||
|
||||
from miio import ( # pylint: disable=import-error
|
||||
AirConditioningCompanionV3,
|
||||
ChuangmiPlug,
|
||||
Device,
|
||||
DeviceException,
|
||||
PowerStrip,
|
||||
)
|
||||
from miio import AirConditioningCompanionV3 # pylint: disable=import-error
|
||||
from miio import ChuangmiPlug, DeviceException, PowerStrip
|
||||
from miio.powerstrip import PowerMode # pylint: disable=import-error
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_MODE,
|
||||
|
@ -21,23 +17,25 @@ from homeassistant.const import (
|
|||
CONF_NAME,
|
||||
CONF_TOKEN,
|
||||
)
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from .const import (
|
||||
CONF_DEVICE,
|
||||
CONF_FLOW_TYPE,
|
||||
CONF_MODEL,
|
||||
DOMAIN,
|
||||
SERVICE_SET_POWER_MODE,
|
||||
SERVICE_SET_POWER_PRICE,
|
||||
SERVICE_SET_WIFI_LED_OFF,
|
||||
SERVICE_SET_WIFI_LED_ON,
|
||||
)
|
||||
from .device import XiaomiMiioEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = "Xiaomi Miio Switch"
|
||||
DATA_KEY = "switch.xiaomi_miio"
|
||||
|
||||
CONF_MODEL = "model"
|
||||
MODEL_POWER_STRIP_V2 = "zimi.powerstrip.v2"
|
||||
MODEL_PLUG_V3 = "chuangmi.plug.v3"
|
||||
|
||||
|
@ -114,119 +112,124 @@ SERVICE_TO_METHOD = {
|
|||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the switch from config."""
|
||||
if DATA_KEY not in hass.data:
|
||||
hass.data[DATA_KEY] = {}
|
||||
"""Import Miio configuration from YAML."""
|
||||
_LOGGER.warning(
|
||||
"Loading Xiaomi Miio Switch via platform setup is deprecated. Please remove it from your configuration."
|
||||
)
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data=config,
|
||||
)
|
||||
)
|
||||
|
||||
host = config[CONF_HOST]
|
||||
token = config[CONF_TOKEN]
|
||||
name = config[CONF_NAME]
|
||||
model = config.get(CONF_MODEL)
|
||||
|
||||
_LOGGER.info("Initializing with host %s (token %s...)", host, token[:5])
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the switch from a config entry."""
|
||||
entities = []
|
||||
|
||||
devices = []
|
||||
unique_id = None
|
||||
if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE:
|
||||
if DATA_KEY not in hass.data:
|
||||
hass.data[DATA_KEY] = {}
|
||||
|
||||
if model is None:
|
||||
try:
|
||||
miio_device = Device(host, token)
|
||||
device_info = await hass.async_add_executor_job(miio_device.info)
|
||||
model = device_info.model
|
||||
unique_id = f"{model}-{device_info.mac_address}"
|
||||
_LOGGER.info(
|
||||
"%s %s %s detected",
|
||||
model,
|
||||
device_info.firmware_version,
|
||||
device_info.hardware_version,
|
||||
)
|
||||
except DeviceException as ex:
|
||||
raise PlatformNotReady from ex
|
||||
host = config_entry.data[CONF_HOST]
|
||||
token = config_entry.data[CONF_TOKEN]
|
||||
name = config_entry.title
|
||||
model = config_entry.data[CONF_MODEL]
|
||||
unique_id = config_entry.unique_id
|
||||
|
||||
if model in ["chuangmi.plug.v1", "chuangmi.plug.v3", "chuangmi.plug.hmi208"]:
|
||||
plug = ChuangmiPlug(host, token, model=model)
|
||||
_LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5])
|
||||
|
||||
# The device has two switchable channels (mains and a USB port).
|
||||
# A switch device per channel will be created.
|
||||
for channel_usb in [True, False]:
|
||||
device = ChuangMiPlugSwitch(name, plug, model, unique_id, channel_usb)
|
||||
devices.append(device)
|
||||
if model in ["chuangmi.plug.v1", "chuangmi.plug.v3", "chuangmi.plug.hmi208"]:
|
||||
plug = ChuangmiPlug(host, token, model=model)
|
||||
|
||||
# The device has two switchable channels (mains and a USB port).
|
||||
# A switch device per channel will be created.
|
||||
for channel_usb in [True, False]:
|
||||
if channel_usb:
|
||||
unique_id_ch = f"{unique_id}-USB"
|
||||
else:
|
||||
unique_id_ch = f"{unique_id}-mains"
|
||||
device = ChuangMiPlugSwitch(
|
||||
name, plug, config_entry, unique_id_ch, channel_usb
|
||||
)
|
||||
entities.append(device)
|
||||
hass.data[DATA_KEY][host] = device
|
||||
elif model in ["qmi.powerstrip.v1", "zimi.powerstrip.v2"]:
|
||||
plug = PowerStrip(host, token, model=model)
|
||||
device = XiaomiPowerStripSwitch(name, plug, config_entry, unique_id)
|
||||
entities.append(device)
|
||||
hass.data[DATA_KEY][host] = device
|
||||
elif model in [
|
||||
"chuangmi.plug.m1",
|
||||
"chuangmi.plug.m3",
|
||||
"chuangmi.plug.v2",
|
||||
"chuangmi.plug.hmi205",
|
||||
"chuangmi.plug.hmi206",
|
||||
]:
|
||||
plug = ChuangmiPlug(host, token, model=model)
|
||||
device = XiaomiPlugGenericSwitch(name, plug, config_entry, unique_id)
|
||||
entities.append(device)
|
||||
hass.data[DATA_KEY][host] = device
|
||||
elif model in ["lumi.acpartner.v3"]:
|
||||
plug = AirConditioningCompanionV3(host, token)
|
||||
device = XiaomiAirConditioningCompanionSwitch(
|
||||
name, plug, config_entry, unique_id
|
||||
)
|
||||
entities.append(device)
|
||||
hass.data[DATA_KEY][host] = device
|
||||
|
||||
elif model in ["qmi.powerstrip.v1", "zimi.powerstrip.v2"]:
|
||||
plug = PowerStrip(host, token, model=model)
|
||||
device = XiaomiPowerStripSwitch(name, plug, model, unique_id)
|
||||
devices.append(device)
|
||||
hass.data[DATA_KEY][host] = device
|
||||
elif model in [
|
||||
"chuangmi.plug.m1",
|
||||
"chuangmi.plug.m3",
|
||||
"chuangmi.plug.v2",
|
||||
"chuangmi.plug.hmi205",
|
||||
"chuangmi.plug.hmi206",
|
||||
]:
|
||||
plug = ChuangmiPlug(host, token, model=model)
|
||||
device = XiaomiPlugGenericSwitch(name, plug, model, unique_id)
|
||||
devices.append(device)
|
||||
hass.data[DATA_KEY][host] = device
|
||||
elif model in ["lumi.acpartner.v3"]:
|
||||
plug = AirConditioningCompanionV3(host, token)
|
||||
device = XiaomiAirConditioningCompanionSwitch(name, plug, model, unique_id)
|
||||
devices.append(device)
|
||||
hass.data[DATA_KEY][host] = device
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"Unsupported device found! Please create an issue at "
|
||||
"https://github.com/rytilahti/python-miio/issues "
|
||||
"and provide the following data: %s",
|
||||
model,
|
||||
)
|
||||
return False
|
||||
|
||||
async_add_entities(devices, update_before_add=True)
|
||||
|
||||
async def async_service_handler(service):
|
||||
"""Map services to methods on XiaomiPlugGenericSwitch."""
|
||||
method = SERVICE_TO_METHOD.get(service.service)
|
||||
params = {
|
||||
key: value for key, value in service.data.items() if key != ATTR_ENTITY_ID
|
||||
}
|
||||
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
||||
if entity_ids:
|
||||
devices = [
|
||||
device
|
||||
for device in hass.data[DATA_KEY].values()
|
||||
if device.entity_id in entity_ids
|
||||
]
|
||||
else:
|
||||
devices = hass.data[DATA_KEY].values()
|
||||
_LOGGER.error(
|
||||
"Unsupported device found! Please create an issue at "
|
||||
"https://github.com/rytilahti/python-miio/issues "
|
||||
"and provide the following data: %s",
|
||||
model,
|
||||
)
|
||||
|
||||
update_tasks = []
|
||||
for device in devices:
|
||||
if not hasattr(device, method["method"]):
|
||||
continue
|
||||
await getattr(device, method["method"])(**params)
|
||||
update_tasks.append(device.async_update_ha_state(True))
|
||||
async def async_service_handler(service):
|
||||
"""Map services to methods on XiaomiPlugGenericSwitch."""
|
||||
method = SERVICE_TO_METHOD.get(service.service)
|
||||
params = {
|
||||
key: value
|
||||
for key, value in service.data.items()
|
||||
if key != ATTR_ENTITY_ID
|
||||
}
|
||||
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
||||
if entity_ids:
|
||||
devices = [
|
||||
device
|
||||
for device in hass.data[DATA_KEY].values()
|
||||
if device.entity_id in entity_ids
|
||||
]
|
||||
else:
|
||||
devices = hass.data[DATA_KEY].values()
|
||||
|
||||
if update_tasks:
|
||||
await asyncio.wait(update_tasks)
|
||||
update_tasks = []
|
||||
for device in devices:
|
||||
if not hasattr(device, method["method"]):
|
||||
continue
|
||||
await getattr(device, method["method"])(**params)
|
||||
update_tasks.append(device.async_update_ha_state(True))
|
||||
|
||||
for plug_service in SERVICE_TO_METHOD:
|
||||
schema = SERVICE_TO_METHOD[plug_service].get("schema", SERVICE_SCHEMA)
|
||||
hass.services.async_register(
|
||||
DOMAIN, plug_service, async_service_handler, schema=schema
|
||||
)
|
||||
if update_tasks:
|
||||
await asyncio.wait(update_tasks)
|
||||
|
||||
for plug_service in SERVICE_TO_METHOD:
|
||||
schema = SERVICE_TO_METHOD[plug_service].get("schema", SERVICE_SCHEMA)
|
||||
hass.services.async_register(
|
||||
DOMAIN, plug_service, async_service_handler, schema=schema
|
||||
)
|
||||
|
||||
async_add_entities(entities, update_before_add=True)
|
||||
|
||||
|
||||
class XiaomiPlugGenericSwitch(SwitchEntity):
|
||||
class XiaomiPlugGenericSwitch(XiaomiMiioEntity, SwitchEntity):
|
||||
"""Representation of a Xiaomi Plug Generic."""
|
||||
|
||||
def __init__(self, name, plug, model, unique_id):
|
||||
def __init__(self, name, device, entry, unique_id):
|
||||
"""Initialize the plug switch."""
|
||||
self._name = name
|
||||
self._plug = plug
|
||||
self._model = model
|
||||
self._unique_id = unique_id
|
||||
super().__init__(name, device, entry, unique_id)
|
||||
|
||||
self._icon = "mdi:power-socket"
|
||||
self._available = False
|
||||
|
@ -235,16 +238,6 @@ class XiaomiPlugGenericSwitch(SwitchEntity):
|
|||
self._device_features = FEATURE_FLAGS_GENERIC
|
||||
self._skip_update = False
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return an unique ID."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device if any."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use for device if any."""
|
||||
|
@ -288,7 +281,7 @@ class XiaomiPlugGenericSwitch(SwitchEntity):
|
|||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the plug on."""
|
||||
result = await self._try_command("Turning the plug on failed.", self._plug.on)
|
||||
result = await self._try_command("Turning the plug on failed", self._device.on)
|
||||
|
||||
if result:
|
||||
self._state = True
|
||||
|
@ -296,7 +289,9 @@ class XiaomiPlugGenericSwitch(SwitchEntity):
|
|||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn the plug off."""
|
||||
result = await self._try_command("Turning the plug off failed.", self._plug.off)
|
||||
result = await self._try_command(
|
||||
"Turning the plug off failed", self._device.off
|
||||
)
|
||||
|
||||
if result:
|
||||
self._state = False
|
||||
|
@ -310,7 +305,7 @@ class XiaomiPlugGenericSwitch(SwitchEntity):
|
|||
return
|
||||
|
||||
try:
|
||||
state = await self.hass.async_add_executor_job(self._plug.status)
|
||||
state = await self.hass.async_add_executor_job(self._device.status)
|
||||
_LOGGER.debug("Got new state: %s", state)
|
||||
|
||||
self._available = True
|
||||
|
@ -328,7 +323,7 @@ class XiaomiPlugGenericSwitch(SwitchEntity):
|
|||
return
|
||||
|
||||
await self._try_command(
|
||||
"Turning the wifi led on failed.", self._plug.set_wifi_led, True
|
||||
"Turning the wifi led on failed", self._device.set_wifi_led, True
|
||||
)
|
||||
|
||||
async def async_set_wifi_led_off(self):
|
||||
|
@ -337,7 +332,7 @@ class XiaomiPlugGenericSwitch(SwitchEntity):
|
|||
return
|
||||
|
||||
await self._try_command(
|
||||
"Turning the wifi led off failed.", self._plug.set_wifi_led, False
|
||||
"Turning the wifi led off failed", self._device.set_wifi_led, False
|
||||
)
|
||||
|
||||
async def async_set_power_price(self, price: int):
|
||||
|
@ -346,8 +341,8 @@ class XiaomiPlugGenericSwitch(SwitchEntity):
|
|||
return
|
||||
|
||||
await self._try_command(
|
||||
"Setting the power price of the power strip failed.",
|
||||
self._plug.set_power_price,
|
||||
"Setting the power price of the power strip failed",
|
||||
self._device.set_power_price,
|
||||
price,
|
||||
)
|
||||
|
||||
|
@ -383,7 +378,7 @@ class XiaomiPowerStripSwitch(XiaomiPlugGenericSwitch):
|
|||
return
|
||||
|
||||
try:
|
||||
state = await self.hass.async_add_executor_job(self._plug.status)
|
||||
state = await self.hass.async_add_executor_job(self._device.status)
|
||||
_LOGGER.debug("Got new state: %s", state)
|
||||
|
||||
self._available = True
|
||||
|
@ -415,8 +410,8 @@ class XiaomiPowerStripSwitch(XiaomiPlugGenericSwitch):
|
|||
return
|
||||
|
||||
await self._try_command(
|
||||
"Setting the power mode of the power strip failed.",
|
||||
self._plug.set_power_mode,
|
||||
"Setting the power mode of the power strip failed",
|
||||
self._device.set_power_mode,
|
||||
PowerMode(mode),
|
||||
)
|
||||
|
||||
|
@ -424,14 +419,14 @@ class XiaomiPowerStripSwitch(XiaomiPlugGenericSwitch):
|
|||
class ChuangMiPlugSwitch(XiaomiPlugGenericSwitch):
|
||||
"""Representation of a Chuang Mi Plug V1 and V3."""
|
||||
|
||||
def __init__(self, name, plug, model, unique_id, channel_usb):
|
||||
def __init__(self, name, plug, entry, unique_id, channel_usb):
|
||||
"""Initialize the plug switch."""
|
||||
name = f"{name} USB" if channel_usb else name
|
||||
|
||||
if unique_id is not None and channel_usb:
|
||||
unique_id = f"{unique_id}-usb"
|
||||
|
||||
super().__init__(name, plug, model, unique_id)
|
||||
super().__init__(name, plug, entry, unique_id)
|
||||
self._channel_usb = channel_usb
|
||||
|
||||
if self._model == MODEL_PLUG_V3:
|
||||
|
@ -444,11 +439,11 @@ class ChuangMiPlugSwitch(XiaomiPlugGenericSwitch):
|
|||
"""Turn a channel on."""
|
||||
if self._channel_usb:
|
||||
result = await self._try_command(
|
||||
"Turning the plug on failed.", self._plug.usb_on
|
||||
"Turning the plug on failed", self._device.usb_on
|
||||
)
|
||||
else:
|
||||
result = await self._try_command(
|
||||
"Turning the plug on failed.", self._plug.on
|
||||
"Turning the plug on failed", self._device.on
|
||||
)
|
||||
|
||||
if result:
|
||||
|
@ -459,11 +454,11 @@ class ChuangMiPlugSwitch(XiaomiPlugGenericSwitch):
|
|||
"""Turn a channel off."""
|
||||
if self._channel_usb:
|
||||
result = await self._try_command(
|
||||
"Turning the plug on failed.", self._plug.usb_off
|
||||
"Turning the plug off failed", self._device.usb_off
|
||||
)
|
||||
else:
|
||||
result = await self._try_command(
|
||||
"Turning the plug on failed.", self._plug.off
|
||||
"Turning the plug off failed", self._device.off
|
||||
)
|
||||
|
||||
if result:
|
||||
|
@ -478,7 +473,7 @@ class ChuangMiPlugSwitch(XiaomiPlugGenericSwitch):
|
|||
return
|
||||
|
||||
try:
|
||||
state = await self.hass.async_add_executor_job(self._plug.status)
|
||||
state = await self.hass.async_add_executor_job(self._device.status)
|
||||
_LOGGER.debug("Got new state: %s", state)
|
||||
|
||||
self._available = True
|
||||
|
@ -513,7 +508,7 @@ class XiaomiAirConditioningCompanionSwitch(XiaomiPlugGenericSwitch):
|
|||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the socket on."""
|
||||
result = await self._try_command(
|
||||
"Turning the socket on failed.", self._plug.socket_on
|
||||
"Turning the socket on failed", self._device.socket_on
|
||||
)
|
||||
|
||||
if result:
|
||||
|
@ -523,7 +518,7 @@ class XiaomiAirConditioningCompanionSwitch(XiaomiPlugGenericSwitch):
|
|||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn the socket off."""
|
||||
result = await self._try_command(
|
||||
"Turning the socket off failed.", self._plug.socket_off
|
||||
"Turning the socket off failed", self._device.socket_off
|
||||
)
|
||||
|
||||
if result:
|
||||
|
@ -538,7 +533,7 @@ class XiaomiAirConditioningCompanionSwitch(XiaomiPlugGenericSwitch):
|
|||
return
|
||||
|
||||
try:
|
||||
state = await self.hass.async_add_executor_job(self._plug.status)
|
||||
state = await self.hass.async_add_executor_job(self._device.status)
|
||||
_LOGGER.debug("Got new state: %s", state)
|
||||
|
||||
self._available = True
|
||||
|
|
|
@ -6,25 +6,18 @@
|
|||
},
|
||||
"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}",
|
||||
"step": {
|
||||
"gateway": {
|
||||
"device": {
|
||||
"data": {
|
||||
"host": "IP Address",
|
||||
"name": "Name of the Gateway",
|
||||
"token": "API Token"
|
||||
"host": "IP Address",
|
||||
"name": "Name of the device",
|
||||
"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"
|
||||
"title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,11 @@ from miio import DeviceException
|
|||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.components.xiaomi_miio import config_flow, const
|
||||
from homeassistant.components.xiaomi_miio import const
|
||||
from homeassistant.components.xiaomi_miio.config_flow import (
|
||||
DEFAULT_DEVICE_NAME,
|
||||
DEFAULT_GATEWAY_NAME,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
|
||||
|
||||
ZEROCONF_NAME = "name"
|
||||
|
@ -15,7 +19,7 @@ ZEROCONF_MAC = "mac"
|
|||
TEST_HOST = "1.2.3.4"
|
||||
TEST_TOKEN = "12345678901234567890123456789012"
|
||||
TEST_NAME = "Test_Gateway"
|
||||
TEST_MODEL = "model5"
|
||||
TEST_MODEL = const.MODELS_GATEWAY[0]
|
||||
TEST_MAC = "ab:cd:ef:gh:ij:kl"
|
||||
TEST_GATEWAY_ID = TEST_MAC
|
||||
TEST_HARDWARE_VERSION = "AB123"
|
||||
|
@ -40,26 +44,6 @@ def get_mock_info(
|
|||
return gateway_info
|
||||
|
||||
|
||||
async def test_config_flow_step_user_no_device(hass):
|
||||
"""Test config flow, user step with no device selected."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {"base": "no_device_selected"}
|
||||
|
||||
|
||||
async def test_config_flow_step_gateway_connect_error(hass):
|
||||
"""Test config flow, gateway connection error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
|
@ -67,29 +51,20 @@ async def test_config_flow_step_gateway_connect_error(hass):
|
|||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{config_flow.CONF_GATEWAY: True},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "gateway"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.xiaomi_miio.gateway.gateway.Gateway.info",
|
||||
"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_NAME: TEST_NAME, CONF_TOKEN: TEST_TOKEN},
|
||||
{CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_TOKEN},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "gateway"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
|
||||
|
@ -100,42 +75,30 @@ async def test_config_flow_gateway_success(hass):
|
|||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{config_flow.CONF_GATEWAY: True},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "gateway"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["errors"] == {}
|
||||
|
||||
mock_info = get_mock_info()
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.xiaomi_miio.gateway.gateway.Gateway.info",
|
||||
"homeassistant.components.xiaomi_miio.device.Device.info",
|
||||
return_value=mock_info,
|
||||
), patch(
|
||||
"homeassistant.components.xiaomi_miio.gateway.gateway.Gateway.discover_devices",
|
||||
return_value=TEST_SUB_DEVICE_LIST,
|
||||
), 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_NAME: TEST_NAME, CONF_TOKEN: TEST_TOKEN},
|
||||
{CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_TOKEN},
|
||||
)
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == TEST_NAME
|
||||
assert result["title"] == DEFAULT_GATEWAY_NAME
|
||||
assert result["data"] == {
|
||||
config_flow.CONF_FLOW_TYPE: config_flow.CONF_GATEWAY,
|
||||
const.CONF_FLOW_TYPE: const.CONF_GATEWAY,
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
"model": TEST_MODEL,
|
||||
"mac": TEST_MAC,
|
||||
const.CONF_MODEL: TEST_MODEL,
|
||||
const.CONF_MAC: TEST_MAC,
|
||||
}
|
||||
|
||||
|
||||
|
@ -152,33 +115,30 @@ async def test_zeroconf_gateway_success(hass):
|
|||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "gateway"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["errors"] == {}
|
||||
|
||||
mock_info = get_mock_info()
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.xiaomi_miio.gateway.gateway.Gateway.info",
|
||||
"homeassistant.components.xiaomi_miio.device.Device.info",
|
||||
return_value=mock_info,
|
||||
), patch(
|
||||
"homeassistant.components.xiaomi_miio.gateway.gateway.Gateway.discover_devices",
|
||||
return_value=TEST_SUB_DEVICE_LIST,
|
||||
), patch(
|
||||
"homeassistant.components.xiaomi_miio.async_setup_entry", return_value=True
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_NAME: TEST_NAME, CONF_TOKEN: TEST_TOKEN},
|
||||
{CONF_TOKEN: TEST_TOKEN},
|
||||
)
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == TEST_NAME
|
||||
assert result["title"] == DEFAULT_GATEWAY_NAME
|
||||
assert result["data"] == {
|
||||
config_flow.CONF_FLOW_TYPE: config_flow.CONF_GATEWAY,
|
||||
const.CONF_FLOW_TYPE: const.CONF_GATEWAY,
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
"model": TEST_MODEL,
|
||||
"mac": TEST_MAC,
|
||||
const.CONF_MODEL: TEST_MODEL,
|
||||
const.CONF_MAC: TEST_MAC,
|
||||
}
|
||||
|
||||
|
||||
|
@ -218,3 +178,167 @@ async def test_zeroconf_missing_data(hass):
|
|||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "not_xiaomi_miio"
|
||||
|
||||
|
||||
async def test_config_flow_step_device_connect_error(hass):
|
||||
"""Test config flow, device connection 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"] == "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"}
|
||||
|
||||
|
||||
async def test_config_flow_step_unknown_device(hass):
|
||||
"""Test config flow, unknown device 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"] == "device"
|
||||
assert result["errors"] == {}
|
||||
|
||||
mock_info = get_mock_info(model="UNKNOWN")
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.xiaomi_miio.device.Device.info",
|
||||
return_value=mock_info,
|
||||
):
|
||||
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": "unknown_device"}
|
||||
|
||||
|
||||
async def test_import_flow_success(hass):
|
||||
"""Test a successful import form yaml for a device."""
|
||||
mock_info = get_mock_info(model=const.MODELS_SWITCH[0])
|
||||
|
||||
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,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={CONF_NAME: TEST_NAME, CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_TOKEN},
|
||||
)
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == TEST_NAME
|
||||
assert result["data"] == {
|
||||
const.CONF_FLOW_TYPE: const.CONF_DEVICE,
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
const.CONF_MODEL: const.MODELS_SWITCH[0],
|
||||
const.CONF_MAC: TEST_MAC,
|
||||
}
|
||||
|
||||
|
||||
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(
|
||||
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["errors"] == {}
|
||||
|
||||
mock_info = get_mock_info(model=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"],
|
||||
{CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_TOKEN},
|
||||
)
|
||||
|
||||
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: model_to_test,
|
||||
const.CONF_MAC: TEST_MAC,
|
||||
}
|
||||
|
||||
|
||||
async def zeroconf_device_success(hass, zeroconf_name_to_test, model_to_test):
|
||||
"""Test a successful zeroconf discovery of a device (base class)."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||
data={
|
||||
zeroconf.ATTR_HOST: TEST_HOST,
|
||||
ZEROCONF_NAME: zeroconf_name_to_test,
|
||||
ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC},
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "device"
|
||||
assert result["errors"] == {}
|
||||
|
||||
mock_info = get_mock_info(model=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"],
|
||||
{CONF_TOKEN: TEST_TOKEN},
|
||||
)
|
||||
|
||||
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: model_to_test,
|
||||
const.CONF_MAC: TEST_MAC,
|
||||
}
|
||||
|
||||
|
||||
async def test_config_flow_plug_success(hass):
|
||||
"""Test a successful config flow for a plug."""
|
||||
test_plug_model = const.MODELS_SWITCH[0]
|
||||
await config_flow_device_success(hass, test_plug_model)
|
||||
|
||||
|
||||
async def test_zeroconf_plug_success(hass):
|
||||
"""Test a successful zeroconf discovery of a plug."""
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue