Files
midea-meiju-codec/custom_components/midea_auto_codec/config_flow.py

350 lines
14 KiB
Python
Raw Normal View History

2023-09-02 16:30:03 +08:00
import voluptuous as vol
import logging
import os
2023-09-03 22:15:41 +08:00
import ipaddress
2023-09-02 16:30:03 +08:00
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from homeassistant import config_entries
2023-09-17 19:40:54 +08:00
from homeassistant.core import callback
2023-09-02 16:30:03 +08:00
from homeassistant.const import (
CONF_TYPE,
CONF_PASSWORD,
CONF_PORT,
CONF_MODEL,
CONF_IP_ADDRESS,
CONF_DEVICE_ID,
CONF_PROTOCOL,
CONF_TOKEN,
CONF_NAME
)
2023-09-17 19:40:54 +08:00
from . import remove_device_config, load_device_config
from .core.cloud import get_midea_cloud
2023-09-02 16:30:03 +08:00
from .core.discover import discover
from .core.device import MiedaDevice
from .const import (
DOMAIN,
2023-09-17 19:40:54 +08:00
CONF_REFRESH_INTERVAL,
2023-09-02 16:30:03 +08:00
STORAGE_PATH,
CONF_ACCOUNT,
2023-09-17 19:40:54 +08:00
CONF_SERVER,
2023-09-02 16:30:03 +08:00
CONF_HOME,
2023-09-17 19:40:54 +08:00
CONF_KEY,
CONF_SN8,
CONF_SN,
CONF_MODEL_NUMBER,
CONF_LUA_FILE
2023-09-02 16:30:03 +08:00
)
_LOGGER = logging.getLogger(__name__)
2023-09-17 19:40:54 +08:00
servers = {
1: "MSmartHome",
2: "美的美居",
}
2023-09-02 16:30:03 +08:00
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
_session = None
_cloud = None
_current_home = None
_device_list = {}
2023-09-03 22:15:41 +08:00
_device = None
2023-09-02 16:30:03 +08:00
2023-09-17 19:40:54 +08:00
@staticmethod
@callback
def async_get_options_flow(config_entry):
return OptionsFlowHandler(config_entry)
2023-09-02 16:30:03 +08:00
def _get_configured_account(self):
for entry in self._async_current_entries():
if entry.data.get(CONF_TYPE) == CONF_ACCOUNT:
2023-09-17 19:40:54 +08:00
return entry.data.get(CONF_ACCOUNT), entry.data.get(CONF_PASSWORD), entry.data.get(CONF_SERVER)
return None, None, None
2023-09-02 16:30:03 +08:00
def _device_configured(self, device_id):
for entry in self._async_current_entries():
if device_id == entry.data.get(CONF_DEVICE_ID):
return True
return False
2023-09-03 22:15:41 +08:00
@staticmethod
def _is_valid_ip_address(ip_address):
try:
ipaddress.ip_address(ip_address)
return True
except ValueError:
return False
2023-09-02 16:30:03 +08:00
async def async_step_user(self, user_input=None, error=None):
2023-09-03 22:15:41 +08:00
if self._session is None:
self._session = async_create_clientsession(self.hass)
2023-09-17 19:40:54 +08:00
account, password, server = self._get_configured_account()
if account is not None and password is not None:
2023-09-02 16:30:03 +08:00
if self._cloud is None:
2023-09-17 19:40:54 +08:00
self._cloud = get_midea_cloud(
session=self._session,
cloud_name=servers[server],
account=account,
password=password
)
2023-09-02 16:30:03 +08:00
if await self._cloud.login():
return await self.async_step_home()
2023-09-03 22:15:41 +08:00
else:
return await self.async_step_user(error="account_invalid")
2023-09-02 16:30:03 +08:00
if user_input is not None:
2023-09-03 22:15:41 +08:00
if self._cloud is None:
2023-09-17 19:40:54 +08:00
self._cloud = get_midea_cloud(
session=self._session,
cloud_name=servers[user_input[CONF_SERVER]],
account=user_input[CONF_ACCOUNT],
password=user_input[CONF_PASSWORD]
)
2023-09-03 22:15:41 +08:00
if await self._cloud.login():
return self.async_create_entry(
2023-09-17 19:40:54 +08:00
title=f"{user_input[CONF_ACCOUNT]}",
2023-09-03 22:15:41 +08:00
data={
CONF_TYPE: CONF_ACCOUNT,
2023-09-17 19:40:54 +08:00
CONF_ACCOUNT: user_input[CONF_ACCOUNT],
CONF_PASSWORD: user_input[CONF_PASSWORD],
CONF_SERVER: user_input[CONF_SERVER]
2023-09-03 22:15:41 +08:00
})
else:
self._cloud = None
return await self.async_step_user(error="login_failed")
2023-09-02 16:30:03 +08:00
return self.async_show_form(
step_id="user",
data_schema=vol.Schema({
2023-09-17 19:40:54 +08:00
vol.Required(CONF_ACCOUNT): str,
vol.Required(CONF_PASSWORD): str,
vol.Required(CONF_SERVER, default=1): vol.In(servers)
2023-09-02 16:30:03 +08:00
}),
errors={"base": error} if error else None
)
async def async_step_home(self, user_input=None, error=None):
if user_input is not None:
self._current_home = user_input[CONF_HOME]
return await self.async_step_device()
2023-09-17 19:40:54 +08:00
homes = await self._cloud.list_home()
if homes is None or len(homes) == 0:
return await self.async_step_device(error="no_home")
2023-09-02 16:30:03 +08:00
return self.async_show_form(
step_id="home",
data_schema=vol.Schema({
2023-09-17 19:40:54 +08:00
vol.Required(CONF_HOME, default=list(homes.keys())[0]):
vol.In(homes),
2023-09-02 16:30:03 +08:00
}),
errors={"base": error} if error else None
)
async def async_step_device(self, user_input=None, error=None):
if user_input is not None:
# 下载lua
# 本地尝试连接设备
2023-09-17 19:40:54 +08:00
self._device = self._device_list[user_input[CONF_DEVICE_ID]]
if self._device.get("online") is not True:
2023-09-02 16:30:03 +08:00
return await self.async_step_device(error="offline_error")
2023-09-03 22:15:41 +08:00
return await self.async_step_discover()
2023-09-17 19:40:54 +08:00
appliances = await self._cloud.list_appliances(self._current_home)
2023-09-02 16:30:03 +08:00
self._device_list = {}
device_list = {}
2023-09-17 19:40:54 +08:00
for appliance_code, appliance_info in appliances.items():
if not self._device_configured(appliance_code):
2023-09-10 12:08:27 +08:00
try:
2023-09-17 19:40:54 +08:00
model_number = int(appliance_info.get("model_number")) if appliance_info.get("model_number") is not None else 0
2023-09-10 12:08:27 +08:00
except ValueError:
2023-09-17 19:40:54 +08:00
model_number = 0
self._device_list[appliance_code] = {
CONF_DEVICE_ID: appliance_code,
CONF_NAME: appliance_info.get("name"),
CONF_TYPE: appliance_info.get("type"),
CONF_SN8: appliance_info.get("sn8", "00000000"),
CONF_SN: appliance_info.get("sn"),
CONF_MODEL: appliance_info.get("model", "0"),
CONF_MODEL_NUMBER: model_number,
"manufacturer_code": appliance_info.get("manufacturer_code","0000"),
"online": appliance_info.get("online")
2023-09-02 16:30:03 +08:00
}
2023-09-17 19:40:54 +08:00
device_list[appliance_code] = \
f"{appliance_info.get('name')} ({'online' if appliance_info.get('online') is True else 'offline'})"
2023-09-02 16:30:03 +08:00
if len(self._device_list) == 0:
return await self.async_step_device(error="no_new_devices")
return self.async_show_form(
step_id="device",
data_schema=vol.Schema({
2023-09-17 19:40:54 +08:00
vol.Required(CONF_DEVICE_ID, default=list(device_list.keys())[0]):
2023-09-02 16:30:03 +08:00
vol.In(device_list),
}),
errors={"base": error} if error else None
)
2023-09-03 22:15:41 +08:00
async def async_step_discover(self, user_input=None, error=None):
if user_input is not None:
if user_input[CONF_IP_ADDRESS] == "auto" or self._is_valid_ip_address(user_input[CONF_IP_ADDRESS]):
ip_address = None
if self._is_valid_ip_address(user_input[CONF_IP_ADDRESS]):
ip_address = user_input[CONF_IP_ADDRESS]
2023-09-17 19:40:54 +08:00
discover_devices = discover([self._device[CONF_TYPE]], ip_address)
2023-09-03 22:15:41 +08:00
_LOGGER.debug(discover_devices)
if discover_devices is None or len(discover_devices) == 0:
return await self.async_step_discover(error="discover_failed")
2023-09-17 19:40:54 +08:00
current_device = discover_devices.get(self._device[CONF_DEVICE_ID])
2023-09-03 22:15:41 +08:00
if current_device is None:
return await self.async_step_discover(error="discover_failed")
os.makedirs(self.hass.config.path(STORAGE_PATH), exist_ok=True)
path = self.hass.config.path(STORAGE_PATH)
2023-09-17 19:40:54 +08:00
file = await self._cloud.download_lua(
path=path,
device_type=self._device[CONF_TYPE],
sn=self._device[CONF_SN],
model_number=self._device[CONF_MODEL_NUMBER],
manufacturer_code=self._device["manufacturer_code"]
)
2023-09-03 22:15:41 +08:00
if file is None:
return await self.async_step_discover(error="download_lua_failed")
use_token = None
use_key = None
connected = False
2023-09-17 19:40:54 +08:00
if current_device.get(CONF_PROTOCOL) == 3:
keys = await self._cloud.get_keys(self._device.get(CONF_DEVICE_ID))
for method, key in keys.items():
dm = MiedaDevice(
name="",
device_id=self._device.get(CONF_DEVICE_ID),
device_type=current_device.get(CONF_TYPE),
ip_address=current_device.get(CONF_IP_ADDRESS),
port=current_device.get(CONF_PORT),
token=key["token"],
key=key["key"],
protocol=3,
model=None,
subtype = None,
sn=None,
sn8=None,
lua_file=None
)
_LOGGER.debug(
f"Successful to take token and key, token: {key['token']},"
f" key: { key['key']}, method: {method}"
)
if dm.connect():
use_token = key["token"]
use_key = key["key"]
dm.disconnect()
connected = True
break
2023-09-03 22:15:41 +08:00
else:
dm = MiedaDevice(
name=self._device.get("name"),
device_id=self._device.get("device_id"),
device_type=current_device.get(CONF_TYPE),
ip_address=current_device.get(CONF_IP_ADDRESS),
port=current_device.get(CONF_PORT),
token=None,
key=None,
protocol=2,
model=None,
2023-09-09 00:14:41 +08:00
subtype=None,
2023-09-03 22:15:41 +08:00
sn=None,
sn8=None,
lua_file=None
)
if dm.connect():
2023-09-17 19:40:54 +08:00
dm.disconnect()
2023-09-03 22:15:41 +08:00
connected = True
if not connected:
return await self.async_step_discover(error="connect_error")
return self.async_create_entry(
title=self._device.get("name"),
data={
2023-09-17 19:40:54 +08:00
CONF_NAME: self._device.get(CONF_NAME),
CONF_DEVICE_ID: self._device.get(CONF_DEVICE_ID),
CONF_TYPE: current_device.get(CONF_TYPE),
CONF_PROTOCOL: current_device.get(CONF_PROTOCOL),
CONF_IP_ADDRESS: current_device.get(CONF_IP_ADDRESS),
CONF_PORT: current_device.get(CONF_PORT),
2023-09-03 22:15:41 +08:00
CONF_TOKEN: use_token,
CONF_KEY: use_key,
2023-09-17 19:40:54 +08:00
CONF_MODEL: self._device.get(CONF_MODEL),
CONF_MODEL_NUMBER: self._device.get(CONF_MODEL_NUMBER),
CONF_SN: self._device.get(CONF_SN),
CONF_SN8: self._device.get(CONF_SN8),
CONF_LUA_FILE: file,
2023-09-03 22:15:41 +08:00
})
else:
return await self.async_step_discover(error="invalid_input")
return self.async_show_form(
step_id="discover",
data_schema=vol.Schema({
2023-09-09 00:14:41 +08:00
vol.Required(CONF_IP_ADDRESS, default="auto"): str
2023-09-03 22:15:41 +08:00
}),
errors={"base": error} if error else None
)
2023-09-02 16:30:03 +08:00
class OptionsFlowHandler(config_entries.OptionsFlow):
def __init__(self, config_entry: config_entries.ConfigEntry):
2023-09-17 19:40:54 +08:00
self._config_entry = config_entry
async def async_step_init(self, user_input=None, error=None):
if self._config_entry.data.get(CONF_TYPE) == CONF_ACCOUNT:
return self.async_abort(reason="account_unsupport_config")
if user_input is not None:
if user_input.get("option") == 1:
return await self.async_step_configure()
else:
return await self.async_step_reset()
return self.async_show_form(
step_id="init",
data_schema=vol.Schema({
vol.Required("option", default=1):
vol.In({1: "Options", 2: "Reset device configuration"})
}),
errors={"base": error} if error else None
)
async def async_step_reset(self, user_input=None):
if user_input is not None:
if user_input["check"]:
remove_device_config(self.hass, self._config_entry.data.get(CONF_SN8))
load_device_config(
self.hass,
self._config_entry.data.get(CONF_TYPE),
self._config_entry.data.get(CONF_SN8))
return self.async_abort(reason="reset_success")
return self.async_show_form(
step_id="reset",
data_schema=vol.Schema({
vol.Required("check", default=False): bool
})
)
async def async_step_configure(self, user_input=None):
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
ip_address = self._config_entry.options.get(
CONF_IP_ADDRESS, None
)
if ip_address is None:
ip_address = self._config_entry.data.get(
CONF_IP_ADDRESS, None
)
refresh_interval = self._config_entry.options.get(
CONF_REFRESH_INTERVAL, 30
)
data_schema = vol.Schema({
vol.Required(
CONF_IP_ADDRESS,
default=ip_address
): str,
vol.Required(
CONF_REFRESH_INTERVAL,
default=refresh_interval
): int
})
return self.async_show_form(
step_id="configure",
data_schema=data_schema
)