Initialize docker stack repo
This commit is contained in:
@@ -0,0 +1,727 @@
|
||||
"""Config flow for Midea Air Appliance (Local) integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from ipaddress import IPv4Address, IPv4Network
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
|
||||
from homeassistant.const import (
|
||||
ATTR_ID,
|
||||
ATTR_NAME,
|
||||
CONF_API_VERSION,
|
||||
CONF_BROADCAST_ADDRESS,
|
||||
CONF_DEVICES,
|
||||
CONF_DISCOVERY,
|
||||
CONF_ID,
|
||||
CONF_INCLUDE,
|
||||
CONF_IP_ADDRESS,
|
||||
CONF_NAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_TOKEN,
|
||||
CONF_TTL,
|
||||
CONF_TYPE,
|
||||
CONF_UNIQUE_ID,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import FlowHandler, FlowResult
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
import voluptuous as vol
|
||||
|
||||
from midea_beautiful.cloud import MideaCloud
|
||||
from midea_beautiful.exceptions import (
|
||||
AuthenticationError,
|
||||
CloudAuthenticationError,
|
||||
CloudError,
|
||||
MideaError,
|
||||
MideaNetworkError,
|
||||
ProtocolError,
|
||||
RetryLaterError,
|
||||
)
|
||||
from midea_beautiful.lan import LanDevice
|
||||
from midea_beautiful.midea import (
|
||||
APPLIANCE_TYPE_DEHUMIDIFIER,
|
||||
SUPPORTED_APPS,
|
||||
)
|
||||
|
||||
from custom_components.midea_dehumidifier_lan import Hub
|
||||
from custom_components.midea_dehumidifier_lan.const import (
|
||||
NAME,
|
||||
CURRENT_CONFIG_VERSION,
|
||||
SUPPORTED_APPLIANCES,
|
||||
CONF_ADVANCED_SETTINGS,
|
||||
CONF_DEBUG,
|
||||
CONF_MOBILE_APP,
|
||||
CONF_TOKEN_KEY,
|
||||
DEFAULT_APP,
|
||||
DEFAULT_DISCOVERY_MODE,
|
||||
DEFAULT_PASSWORD,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DEFAULT_TTL,
|
||||
DEFAULT_USERNAME,
|
||||
DISCOVERY_CLOUD,
|
||||
DISCOVERY_IGNORE,
|
||||
DISCOVERY_LAN,
|
||||
DISCOVERY_MODE_LABELS,
|
||||
DISCOVERY_WAIT,
|
||||
DOMAIN,
|
||||
LOCAL_BROADCAST,
|
||||
UNKNOWN_IP,
|
||||
)
|
||||
from custom_components.midea_dehumidifier_lan.util import (
|
||||
MideaClient,
|
||||
RedactedConf,
|
||||
address_ok,
|
||||
supported_appliance,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _appliance_schema( # pylint: disable=too-many-arguments
|
||||
name: str,
|
||||
address: str = UNKNOWN_IP,
|
||||
ttl: int = DEFAULT_TTL,
|
||||
token: str = "",
|
||||
token_key: str = "",
|
||||
discovery_mode=DISCOVERY_WAIT,
|
||||
) -> vol.Schema:
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_DISCOVERY, default=str(discovery_mode)): vol.In(
|
||||
DISCOVERY_MODE_LABELS
|
||||
),
|
||||
vol.Optional(
|
||||
CONF_IP_ADDRESS,
|
||||
default=address or UNKNOWN_IP,
|
||||
): cv.string,
|
||||
vol.Required(CONF_NAME, default=name): cv.string,
|
||||
vol.Required(
|
||||
CONF_TTL,
|
||||
msg="Test",
|
||||
default=ttl,
|
||||
description={"suffix": "minutes"},
|
||||
): cv.positive_int,
|
||||
vol.Optional(CONF_TOKEN, default=token or ""): cv.string,
|
||||
vol.Optional(CONF_TOKEN_KEY, default=token_key or ""): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def _advanced_settings_schema(
|
||||
username: str = "",
|
||||
password: str = "",
|
||||
app: str = DEFAULT_APP,
|
||||
broadcast_address: str = "",
|
||||
appliances: list[str] = None,
|
||||
debug: bool = False,
|
||||
) -> vol.Schema:
|
||||
appliances = appliances or [APPLIANCE_TYPE_DEHUMIDIFIER]
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_USERNAME, default=username): cv.string,
|
||||
vol.Required(CONF_PASSWORD, default=password): cv.string,
|
||||
vol.Optional(CONF_MOBILE_APP, default=app): vol.In(SUPPORTED_APPS.keys()),
|
||||
vol.Optional(CONF_BROADCAST_ADDRESS, default=broadcast_address): cv.string,
|
||||
vol.Required(
|
||||
CONF_SCAN_INTERVAL,
|
||||
msg="Test",
|
||||
default=DEFAULT_SCAN_INTERVAL,
|
||||
description={"suffix": "minutes"},
|
||||
): cv.positive_int,
|
||||
vol.Required(CONF_INCLUDE, default=appliances): vol.All(
|
||||
cv.multi_select(SUPPORTED_APPLIANCES),
|
||||
vol.Length(min=1, msg="Must select at least one appliance category"),
|
||||
),
|
||||
vol.Required(CONF_DEBUG, default=debug): bool,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _reauth_schema(
|
||||
username: str,
|
||||
password: str,
|
||||
) -> vol.Schema:
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_USERNAME, default=username): cv.string,
|
||||
vol.Required(CONF_PASSWORD, default=password): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _user_schema(username: str, password: str, app: str) -> vol.Schema:
|
||||
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_USERNAME, default=username): cv.string,
|
||||
vol.Required(CONF_PASSWORD, default=password): cv.string,
|
||||
vol.Optional(CONF_MOBILE_APP, default=app): vol.In(SUPPORTED_APPS.keys()),
|
||||
vol.Required(CONF_ADVANCED_SETTINGS, default=False): bool,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
class _MideaFlow(FlowHandler):
|
||||
"""Base class for Midea data flows"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.appliance_idx = -1
|
||||
self.appliances: list[LanDevice] = []
|
||||
self._client: MideaClient | None = None
|
||||
self.cloud: MideaCloud | None = None # type: ignore
|
||||
self.conf = {}
|
||||
self.config_entry: ConfigEntry | None = None
|
||||
self.devices_conf: list[dict[str, Any]] = []
|
||||
self.discovered_appliances: list[LanDevice | None] = []
|
||||
self.error_cause: str = ""
|
||||
self.errors: dict[str, Any] = {}
|
||||
self.indexes_to_process = []
|
||||
|
||||
@property
|
||||
def client(self) -> MideaClient:
|
||||
"""Returns instance of MideaClient."""
|
||||
if not self._client:
|
||||
self._client = MideaClient(self.hass)
|
||||
return self._client
|
||||
|
||||
def _process_exception(self: _MideaFlow, ex: Exception) -> None:
|
||||
if isinstance(ex, _FlowException):
|
||||
_LOGGER.warning(
|
||||
"Caught flow exception during appliance step %s", ex, exc_info=True
|
||||
)
|
||||
self.error_cause = str(ex.cause)
|
||||
self.errors["base"] = ex.message
|
||||
elif isinstance(ex, CloudAuthenticationError):
|
||||
self.error_cause = f"{ex.error_code} - {ex.message}"
|
||||
self.errors["base"] = "invalid_auth"
|
||||
elif isinstance(ex, CloudError):
|
||||
self.error_cause = f"{ex.error_code} - {ex.message}"
|
||||
self.errors["base"] = "midea_client"
|
||||
elif isinstance(ex, RetryLaterError):
|
||||
self.error_cause = f"{ex.error_code} - {ex.message}"
|
||||
self.errors["base"] = "retry_later"
|
||||
elif isinstance(ex, MideaError):
|
||||
self.error_cause = f"{ex.message}"
|
||||
self.errors["base"] = "midea_client"
|
||||
else:
|
||||
raise ex
|
||||
|
||||
def _connect_to_cloud(self: _MideaFlow, extra_conf: dict[str, Any] = None) -> None:
|
||||
"""Validates that cloud credentials are valid"""
|
||||
cfg = self.conf | (extra_conf or {})
|
||||
try:
|
||||
self.cloud = self.client.connect_to_cloud(cfg)
|
||||
except MideaError as ex:
|
||||
raise _FlowException("no_cloud", str(ex)) from ex
|
||||
|
||||
def _validate_appliance(
|
||||
self: _MideaFlow, appliance: LanDevice, device_conf: dict
|
||||
) -> LanDevice | None:
|
||||
"""
|
||||
Validates that appliance configuration is correct and matches physical
|
||||
device
|
||||
"""
|
||||
discovery_mode = device_conf.get(CONF_DISCOVERY, DEFAULT_DISCOVERY_MODE)
|
||||
if discovery_mode == DISCOVERY_IGNORE:
|
||||
_LOGGER.debug("Ignoring appliance %s", appliance)
|
||||
return None
|
||||
if discovery_mode == DISCOVERY_WAIT:
|
||||
_LOGGER.debug(
|
||||
"Attempt to discover appliance %s will be made later",
|
||||
appliance,
|
||||
)
|
||||
return None
|
||||
try:
|
||||
if discovery_mode == DISCOVERY_CLOUD:
|
||||
discovered = self.client.appliance_state(
|
||||
appliance_id=appliance.appliance_id,
|
||||
cloud=self.cloud,
|
||||
use_cloud=True,
|
||||
)
|
||||
else: # DISCOVERY_LAN
|
||||
ip_address = appliance.address
|
||||
if not address_ok(ip_address):
|
||||
raise _FlowException("invalid_ip_address", ip_address)
|
||||
try:
|
||||
IPv4Address(ip_address)
|
||||
except Exception as ex:
|
||||
_LOGGER.debug("Invalid appliance address %s: %s", ip_address, ex)
|
||||
raise _FlowException("invalid_ip_address", ip_address) from ex
|
||||
discovered = self.client.appliance_state(
|
||||
address=ip_address, cloud=self.cloud
|
||||
)
|
||||
except ProtocolError as ex:
|
||||
raise _FlowException("connection_error", str(ex)) from ex
|
||||
except AuthenticationError as ex:
|
||||
raise _FlowException("invalid_auth", str(ex)) from ex
|
||||
except MideaNetworkError as ex:
|
||||
raise _FlowException("cannot_connect", str(ex)) from ex
|
||||
except MideaError as ex:
|
||||
raise _FlowException("not_discovered", str(ex)) from ex
|
||||
if discovered is None:
|
||||
raise _FlowException("not_discovered", appliance.address)
|
||||
return discovered
|
||||
|
||||
async def _async_add_entry(self: _MideaFlow) -> FlowResult:
|
||||
supported_devices_conf = []
|
||||
for i, appliance in enumerate(self.appliances):
|
||||
if not supported_appliance(self.conf, appliance):
|
||||
continue
|
||||
device_conf = self.devices_conf[i]
|
||||
|
||||
if device_conf.get(CONF_DISCOVERY) != DISCOVERY_IGNORE:
|
||||
device_conf |= {
|
||||
CONF_API_VERSION: appliance.version,
|
||||
CONF_ID: appliance.appliance_id,
|
||||
CONF_IP_ADDRESS: (
|
||||
appliance.address or device_conf[CONF_IP_ADDRESS] or UNKNOWN_IP
|
||||
),
|
||||
CONF_NAME: appliance.name,
|
||||
CONF_TOKEN_KEY: appliance.key,
|
||||
CONF_TOKEN: appliance.token,
|
||||
CONF_TYPE: appliance.type,
|
||||
CONF_UNIQUE_ID: appliance.serial_number,
|
||||
}
|
||||
suggested_discovery = (
|
||||
DISCOVERY_LAN
|
||||
if address_ok(device_conf[CONF_IP_ADDRESS])
|
||||
else DISCOVERY_WAIT
|
||||
)
|
||||
device_conf.get(CONF_DISCOVERY, suggested_discovery)
|
||||
supported_devices_conf.append(device_conf)
|
||||
self.devices_conf = supported_devices_conf
|
||||
self.conf[CONF_DEVICES] = self.devices_conf
|
||||
|
||||
# Remove not used elements
|
||||
self.conf.pop(CONF_ADVANCED_SETTINGS, None)
|
||||
if self.config_entry:
|
||||
_LOGGER.debug("Updating configuration data %s", RedactedConf(self.conf))
|
||||
self.hass.config_entries.async_update_entry(
|
||||
entry=self.config_entry, data=self.conf
|
||||
)
|
||||
# Reload the config entry otherwise devices will remain unavailable
|
||||
self.hass.async_create_task(
|
||||
self.hass.config_entries.async_reload(self.config_entry.entry_id)
|
||||
)
|
||||
|
||||
if not self.devices_conf:
|
||||
_LOGGER.debug("No configured appliances %s", RedactedConf(self.conf))
|
||||
return self.async_abort(reason="no_configured_devices")
|
||||
_LOGGER.debug("Creating configuration data %s", RedactedConf(self.conf))
|
||||
return self.async_create_entry(title=NAME, data=self.conf)
|
||||
|
||||
async def _async_step_appliance( # pylint: disable=too-many-locals
|
||||
self: _MideaFlow,
|
||||
step_id: str,
|
||||
user_input: dict[str, Any] | None = None,
|
||||
) -> FlowResult:
|
||||
"""Manage an appliances"""
|
||||
|
||||
self.errors.clear()
|
||||
self.error_cause = ""
|
||||
appliance = self.appliances[self.appliance_idx]
|
||||
device_conf = self.devices_conf[self.appliance_idx]
|
||||
discovery_mode = device_conf.get(CONF_DISCOVERY, DEFAULT_DISCOVERY_MODE)
|
||||
ttl = device_conf.get(CONF_TTL, DEFAULT_TTL)
|
||||
ip_address = appliance.address or UNKNOWN_IP
|
||||
if user_input is not None:
|
||||
try:
|
||||
|
||||
ip_address = user_input.get(
|
||||
CONF_IP_ADDRESS, device_conf.get(CONF_IP_ADDRESS, UNKNOWN_IP)
|
||||
)
|
||||
self._check_ip_address_unique(ip_address)
|
||||
|
||||
discovery_mode = user_input.get(CONF_DISCOVERY, discovery_mode)
|
||||
if discovery_mode not in [
|
||||
DISCOVERY_WAIT,
|
||||
DISCOVERY_LAN,
|
||||
DISCOVERY_IGNORE,
|
||||
DISCOVERY_CLOUD,
|
||||
]:
|
||||
discovery_mode = (
|
||||
DISCOVERY_LAN if address_ok(ip_address) else DISCOVERY_CLOUD
|
||||
)
|
||||
device_conf[CONF_DISCOVERY] = discovery_mode
|
||||
device_conf[CONF_TTL] = user_input.get(CONF_TTL, ttl)
|
||||
appliance.address = ip_address
|
||||
appliance.name = user_input.get(CONF_NAME, appliance.name)
|
||||
appliance.token = user_input.get(CONF_TOKEN, "")
|
||||
appliance.key = user_input.get(CONF_TOKEN_KEY, "")
|
||||
|
||||
if not self.cloud:
|
||||
await self.hass.async_add_executor_job(self._connect_to_cloud)
|
||||
|
||||
discovered = await self.hass.async_add_executor_job(
|
||||
self._validate_appliance,
|
||||
appliance,
|
||||
device_conf,
|
||||
)
|
||||
self.discovered_appliances[self.appliance_idx] = discovered
|
||||
|
||||
if not self.indexes_to_process:
|
||||
self._update_appliances_after_flow()
|
||||
|
||||
return await self._async_add_entry()
|
||||
|
||||
self.appliance_idx = self.indexes_to_process.pop(0)
|
||||
appliance = self.appliances[self.appliance_idx]
|
||||
device_conf = self.devices_conf[self.appliance_idx]
|
||||
ip_address = appliance.address or UNKNOWN_IP
|
||||
user_input = None
|
||||
discovery_mode = DEFAULT_DISCOVERY_MODE
|
||||
ttl = DEFAULT_TTL
|
||||
|
||||
except Exception as ex: # pylint: disable=broad-except
|
||||
self._process_exception(ex)
|
||||
|
||||
name = appliance.name
|
||||
extra = {
|
||||
"index": str(self.appliance_idx + 1),
|
||||
"count": str(len(self.appliances)),
|
||||
"serial_number": appliance.serial_number,
|
||||
}
|
||||
placeholders = self._placeholders(appliance, extra)
|
||||
schema_arg = {
|
||||
"name": name,
|
||||
"address": device_conf.get(CONF_IP_ADDRESS, ip_address),
|
||||
"token": device_conf.get(CONF_TOKEN, appliance.token),
|
||||
"token_key": device_conf.get(CONF_TOKEN_KEY, appliance.key),
|
||||
"ttl": device_conf.get(CONF_TTL, ttl),
|
||||
"discovery_mode": device_conf.get(CONF_DISCOVERY, discovery_mode),
|
||||
}
|
||||
schema = _appliance_schema(**schema_arg)
|
||||
return self.async_show_form(
|
||||
step_id=step_id,
|
||||
data_schema=schema,
|
||||
description_placeholders=placeholders,
|
||||
errors=self.errors,
|
||||
last_step=len(self.indexes_to_process) == 0,
|
||||
)
|
||||
|
||||
def _check_ip_address_unique(self, ip_address) -> None:
|
||||
if address_ok(ip_address):
|
||||
for i in range(self.appliance_idx):
|
||||
if (
|
||||
self.devices_conf[i].get(CONF_IP_ADDRESS) == ip_address
|
||||
or ip_address == self.appliances[i].address
|
||||
):
|
||||
raise _FlowException(
|
||||
"duplicate_ip_provided", self.appliances[i].name
|
||||
)
|
||||
|
||||
def _update_appliances_after_flow(self) -> None:
|
||||
for i, discovered in enumerate(self.discovered_appliances):
|
||||
if discovered:
|
||||
old_address = self.appliances[i].address
|
||||
self.appliances[i].update(discovered)
|
||||
if not discovered.address:
|
||||
self.appliances[i].address = old_address
|
||||
|
||||
def _placeholders(
|
||||
self: _MideaFlow, appliance: LanDevice = None, extra: dict[str, str] = None
|
||||
) -> dict[str, str]:
|
||||
extra = extra or {}
|
||||
placeholders = {
|
||||
"cause": self.error_cause or "",
|
||||
**extra,
|
||||
}
|
||||
if appliance:
|
||||
placeholders[ATTR_ID] = (
|
||||
appliance.serial_number or f"{appliance.appliance_id} (Missing S/N)"
|
||||
)
|
||||
placeholders[ATTR_NAME] = appliance.name
|
||||
|
||||
return placeholders
|
||||
|
||||
|
||||
def _get_broadcast_addresses(user_input: dict[str, Any]) -> list[str]:
|
||||
address_entry = str(user_input.get(CONF_BROADCAST_ADDRESS, ""))
|
||||
addresses = [LOCAL_BROADCAST]
|
||||
specified_addresses = [
|
||||
addr.strip() for addr in address_entry.split(",") if addr.strip()
|
||||
]
|
||||
for addr in specified_addresses:
|
||||
_LOGGER.debug("Trying IPv4 %s", addr)
|
||||
try:
|
||||
IPv4Network(addr)
|
||||
addresses.append(addr)
|
||||
except ValueError as ex:
|
||||
raise _FlowException("invalid_ip_range", str(ex)) from ex
|
||||
except Exception as ex:
|
||||
_LOGGER.debug("Invalid IP address %s", addr, exc_info=True)
|
||||
raise _FlowException("invalid_ip_range", addr) from ex
|
||||
return addresses
|
||||
|
||||
|
||||
class _FlowException(Exception):
|
||||
def __init__(self, message, cause: str = None) -> None:
|
||||
super().__init__()
|
||||
self.message = message
|
||||
self.cause = cause
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
class MideaConfigFlow(ConfigFlow, _MideaFlow, domain=DOMAIN):
|
||||
"""Configuration flow for Midea dehumidifiers on local network uses
|
||||
discovery based on Midea cloud, so it first requires credentials for it.
|
||||
If some appliances are registered in the cloud, but not discovered, configuration
|
||||
flow will prompt for additional information.
|
||||
"""
|
||||
|
||||
VERSION = CURRENT_CONFIG_VERSION
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.discovered_appliances: list[LanDevice | None] = []
|
||||
self.appliances: list[LanDevice] = []
|
||||
self.config_entry: ConfigEntry | None = None
|
||||
self.advanced_settings = False
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(
|
||||
config_entry: ConfigEntry,
|
||||
) -> OptionsFlow:
|
||||
"""Define the config flow to handle options."""
|
||||
return MideaOptionsFlow(config_entry)
|
||||
|
||||
def _connect_and_discover(self: MideaConfigFlow) -> None:
|
||||
"""Validates that cloud credentials are valid and discovers local appliances"""
|
||||
|
||||
self._connect_to_cloud()
|
||||
conf_addresses = self.conf.get(CONF_BROADCAST_ADDRESS, [])
|
||||
if isinstance(conf_addresses, str):
|
||||
conf_addresses = [conf_addresses]
|
||||
addresses = [
|
||||
str(IPv4Network(addr).broadcast_address) for addr in conf_addresses
|
||||
]
|
||||
self.appliances.clear()
|
||||
self.appliances += self.client.find_appliances(self.cloud, addresses)
|
||||
self.devices_conf = [{} for _ in self.appliances]
|
||||
|
||||
async def _validate_discovery_phase(
|
||||
self, user_input: dict[str, Any] | None
|
||||
) -> FlowResult:
|
||||
assert user_input is not None
|
||||
self.conf[CONF_USERNAME] = user_input[CONF_USERNAME]
|
||||
self.conf[CONF_PASSWORD] = user_input[CONF_PASSWORD]
|
||||
|
||||
if self.advanced_settings:
|
||||
assert self.conf is not None
|
||||
self.conf[CONF_MOBILE_APP] = user_input.get(CONF_MOBILE_APP, DEFAULT_APP)
|
||||
self.conf[CONF_INCLUDE] = user_input[CONF_INCLUDE]
|
||||
self.conf[CONF_SCAN_INTERVAL] = user_input[CONF_SCAN_INTERVAL]
|
||||
self.conf[CONF_DEBUG] = user_input[CONF_DEBUG]
|
||||
self.conf[CONF_BROADCAST_ADDRESS] = _get_broadcast_addresses(user_input)
|
||||
|
||||
else:
|
||||
self.conf[CONF_MOBILE_APP] = user_input.get(CONF_MOBILE_APP, DEFAULT_APP)
|
||||
if user_input.get(CONF_ADVANCED_SETTINGS):
|
||||
return await self.async_step_advanced_settings()
|
||||
|
||||
self.conf[CONF_BROADCAST_ADDRESS] = []
|
||||
self.conf[CONF_INCLUDE] = [APPLIANCE_TYPE_DEHUMIDIFIER]
|
||||
self.conf[CONF_SCAN_INTERVAL] = DEFAULT_SCAN_INTERVAL
|
||||
|
||||
if self.conf.get(CONF_DEBUG, False):
|
||||
await self.client.async_debug_mode(True)
|
||||
await self.hass.async_add_executor_job(self._connect_and_discover)
|
||||
|
||||
self.indexes_to_process = [
|
||||
index
|
||||
for index, appliance in enumerate(self.appliances)
|
||||
if supported_appliance(self.conf, appliance)
|
||||
and not address_ok(appliance.address)
|
||||
]
|
||||
if self.indexes_to_process:
|
||||
self.appliance_idx = self.indexes_to_process.pop(0)
|
||||
self.discovered_appliances = [None] * len(self.devices_conf)
|
||||
return await self.async_step_unreachable_appliance()
|
||||
|
||||
return await self._async_add_entry()
|
||||
|
||||
async def _do_validate(self, user_input: dict[str, Any]) -> FlowResult | None:
|
||||
try:
|
||||
return await self._validate_discovery_phase(user_input)
|
||||
except Exception as ex: # pylint: disable=broad-except
|
||||
self._process_exception(ex)
|
||||
return None
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
self.advanced_settings = False
|
||||
if self._async_current_entries():
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
|
||||
self.errors.clear()
|
||||
self.error_cause = ""
|
||||
|
||||
username = DEFAULT_USERNAME
|
||||
password = DEFAULT_PASSWORD
|
||||
app = DEFAULT_APP
|
||||
if user_input is not None:
|
||||
username = user_input.get(CONF_USERNAME, username)
|
||||
password = user_input.get(CONF_PASSWORD, password)
|
||||
app = user_input.get(CONF_MOBILE_APP, app)
|
||||
res = await self._do_validate(user_input)
|
||||
if res:
|
||||
return res
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=_user_schema(username=username, password=password, app=app),
|
||||
description_placeholders=self._placeholders(),
|
||||
errors=self.errors,
|
||||
)
|
||||
|
||||
async def async_step_advanced_settings(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Step for managing advanced settings"""
|
||||
self.errors = {}
|
||||
self.error_cause = ""
|
||||
self.advanced_settings = True
|
||||
if user_input is not None:
|
||||
if res := await self._do_validate(user_input):
|
||||
return res
|
||||
else:
|
||||
user_input = {}
|
||||
|
||||
username = user_input.get(
|
||||
CONF_USERNAME, self.conf.get(CONF_USERNAME, DEFAULT_USERNAME)
|
||||
)
|
||||
password = user_input.get(
|
||||
CONF_PASSWORD, self.conf.get(CONF_PASSWORD, DEFAULT_PASSWORD)
|
||||
)
|
||||
app = user_input.get(CONF_MOBILE_APP, DEFAULT_APP)
|
||||
broadcast_addresses = user_input.get(
|
||||
CONF_BROADCAST_ADDRESS, ",".join(self.conf.get(CONF_BROADCAST_ADDRESS, []))
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="advanced_settings",
|
||||
data_schema=_advanced_settings_schema(
|
||||
username=username,
|
||||
password=password,
|
||||
app=app,
|
||||
broadcast_address=broadcast_addresses,
|
||||
),
|
||||
description_placeholders=self._placeholders(),
|
||||
errors=self.errors,
|
||||
)
|
||||
|
||||
async def async_step_unreachable_appliance(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Manage the appliances that were not discovered automatically on LAN."""
|
||||
|
||||
return await self._async_step_appliance(
|
||||
step_id="unreachable_appliance",
|
||||
user_input=user_input,
|
||||
)
|
||||
|
||||
async def _async_add_entry(self) -> FlowResult:
|
||||
assert self.conf is not None
|
||||
self.config_entry = await self.async_set_unique_id(self.conf[CONF_USERNAME])
|
||||
return await super()._async_add_entry()
|
||||
|
||||
async def async_step_reauth(self, config) -> FlowResult:
|
||||
"""Handle reauthorization request from Abode."""
|
||||
self.conf = {**config}
|
||||
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle reauthorization flow."""
|
||||
self.errors.clear()
|
||||
password = ""
|
||||
username = self.conf.get(CONF_USERNAME, "")
|
||||
app = self.conf.get(CONF_MOBILE_APP, DEFAULT_APP)
|
||||
if user_input is not None:
|
||||
extra_conf = {
|
||||
CONF_USERNAME: user_input.get(CONF_USERNAME, ""),
|
||||
CONF_PASSWORD: user_input.get(CONF_PASSWORD, ""),
|
||||
CONF_MOBILE_APP: user_input.get(CONF_MOBILE_APP, app),
|
||||
}
|
||||
try:
|
||||
await self.hass.async_add_executor_job(
|
||||
self._connect_to_cloud, extra_conf
|
||||
)
|
||||
except Exception as ex: # pylint: disable=broad-except
|
||||
self._process_exception(ex)
|
||||
else:
|
||||
self.conf[CONF_USERNAME] = username
|
||||
self.conf[CONF_PASSWORD] = password
|
||||
self.conf[CONF_MOBILE_APP] = app
|
||||
return await self._async_add_entry()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reauth_confirm",
|
||||
data_schema=_reauth_schema(
|
||||
username=username,
|
||||
password=password,
|
||||
),
|
||||
description_placeholders=self._placeholders(),
|
||||
errors=self.errors,
|
||||
)
|
||||
|
||||
|
||||
class MideaOptionsFlow(OptionsFlow, _MideaFlow):
|
||||
"""Handle Midea options flow."""
|
||||
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
"""Initialize Midea options flow."""
|
||||
super().__init__()
|
||||
self.config_entry = config_entry
|
||||
self.conf = {**config_entry.data}
|
||||
self.devices_conf = self.conf.get(CONF_DEVICES, [])
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Starts options flow"""
|
||||
self._build_appliance_list()
|
||||
return await self.async_step_appliance(user_input)
|
||||
|
||||
async def async_step_appliance(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Options for an appliance"""
|
||||
|
||||
return await self._async_step_appliance(
|
||||
step_id="appliance",
|
||||
user_input=user_input,
|
||||
)
|
||||
|
||||
def _build_appliance_list(self) -> None:
|
||||
assert self.config_entry
|
||||
hub: Hub = self.hass.data[DOMAIN][self.config_entry.entry_id]
|
||||
self.appliances.clear()
|
||||
self.devices_conf = self.conf[CONF_DEVICES]
|
||||
for device in self.devices_conf:
|
||||
for coord in hub.coordinators:
|
||||
if device[CONF_UNIQUE_ID] == coord.appliance.serial_number:
|
||||
self.appliances.append(coord.appliance)
|
||||
break
|
||||
else:
|
||||
appliance = LanDevice(
|
||||
appliance_id=device[CONF_ID],
|
||||
serial_number=device[CONF_UNIQUE_ID],
|
||||
appliance_type=device[CONF_TYPE],
|
||||
)
|
||||
appliance.name = device[CONF_NAME]
|
||||
appliance.address = device.get(CONF_IP_ADDRESS, UNKNOWN_IP)
|
||||
self.appliances.append(appliance)
|
||||
self.indexes_to_process = list(range(len(self.appliances)))
|
||||
self.appliance_idx = self.indexes_to_process.pop(0)
|
||||
self.discovered_appliances = [None] * len(self.devices_conf)
|
||||
Reference in New Issue
Block a user