优化设备添加流程

This commit is contained in:
unknown
2023-09-03 22:15:41 +08:00
parent fc82b6de79
commit faae480fd8
7 changed files with 175 additions and 99 deletions

View File

@@ -56,6 +56,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry):
port = config_entry.data.get(CONF_PORT) port = config_entry.data.get(CONF_PORT)
model = config_entry.data.get(CONF_MODEL) model = config_entry.data.get(CONF_MODEL)
protocol = config_entry.data.get(CONF_PROTOCOL) protocol = config_entry.data.get(CONF_PROTOCOL)
sn = config_entry.data.get("sn")
sn8 = config_entry.data.get("sn8")
lua_file = config_entry.data.get("lua_file") lua_file = config_entry.data.get("lua_file")
_LOGGER.error(f"lua_file = {lua_file}") _LOGGER.error(f"lua_file = {lua_file}")
if protocol == 3 and (key is None or key is None): if protocol == 3 and (key is None or key is None):
@@ -71,6 +73,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry):
key=key, key=key,
protocol=protocol, protocol=protocol,
model=model, model=model,
sn=sn,
sn8=sn8,
lua_file=lua_file, lua_file=lua_file,
) )
if device: if device:

View File

@@ -18,7 +18,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
device_id = config_entry.data.get(CONF_DEVICE_ID) device_id = config_entry.data.get(CONF_DEVICE_ID)
device = hass.data[DOMAIN][DEVICES].get(device_id) device = hass.data[DOMAIN][DEVICES].get(device_id)
binary_sensors = [] binary_sensors = []
sensor = MideaDeviceStatusSensor(device, "online") sensor = MideaDeviceStatusSensor(device, "status")
binary_sensors.append(sensor) binary_sensors.append(sensor)
async_add_entities(binary_sensors) async_add_entities(binary_sensors)
@@ -32,6 +32,14 @@ class MideaDeviceStatusSensor(MideaEntity):
def state(self): def state(self):
return STATE_ON if self._device.connected else STATE_OFF return STATE_ON if self._device.connected else STATE_OFF
@property
def name(self):
return f"{self._device_name} Status"
@property
def icon(self):
return "mdi:devices"
@property @property
def is_on(self): def is_on(self):
return self.state == STATE_ON return self.state == STATE_ON
@@ -46,8 +54,6 @@ class MideaDeviceStatusSensor(MideaEntity):
def update_state(self, status): def update_state(self, status):
try: try:
_LOGGER.debug("=" * 50)
self.schedule_update_ha_state() self.schedule_update_ha_state()
_LOGGER.debug("-" * 50)
except Exception as e: except Exception as e:
pass pass

View File

@@ -1,6 +1,7 @@
import voluptuous as vol import voluptuous as vol
import logging import logging
import os import os
import ipaddress
from homeassistant.helpers.aiohttp_client import async_create_clientsession from homeassistant.helpers.aiohttp_client import async_create_clientsession
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import ( from homeassistant.const import (
@@ -15,7 +16,6 @@ from homeassistant.const import (
CONF_PROTOCOL, CONF_PROTOCOL,
CONF_TOKEN, CONF_TOKEN,
CONF_NAME CONF_NAME
) )
from .core.cloud import MeijuCloudExtend from .core.cloud import MeijuCloudExtend
from .core.discover import discover from .core.discover import discover
@@ -36,6 +36,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
_cloud = None _cloud = None
_current_home = None _current_home = None
_device_list = {} _device_list = {}
_device = None
def _get_configured_account(self): def _get_configured_account(self):
for entry in self._async_current_entries(): for entry in self._async_current_entries():
@@ -49,23 +50,39 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return True return True
return False return False
@staticmethod
def _is_valid_ip_address(ip_address):
try:
ipaddress.ip_address(ip_address)
return True
except ValueError:
return False
async def async_step_user(self, user_input=None, error=None): async def async_step_user(self, user_input=None, error=None):
if self._session is None:
self._session = async_create_clientsession(self.hass)
username, password = self._get_configured_account() username, password = self._get_configured_account()
if username is not None and password is not None: if username is not None and password is not None:
if self._session is None:
self._session = async_create_clientsession(self.hass)
if self._cloud is None: if self._cloud is None:
self._cloud = MeijuCloudExtend(self._session, username, password) self._cloud = MeijuCloudExtend(self._session, username, password)
if await self._cloud.login(): if await self._cloud.login():
return await self.async_step_home() return await self.async_step_home()
else:
return await self.async_step_user(error="account_invalid")
if user_input is not None: if user_input is not None:
return self.async_create_entry( if self._cloud is None:
title=f"{user_input[CONF_USERNAME]}", self._cloud = MeijuCloudExtend(self._session, user_input[CONF_USERNAME], user_input[CONF_PASSWORD])
data={ if await self._cloud.login():
CONF_TYPE: CONF_ACCOUNT, return self.async_create_entry(
CONF_USERNAME: user_input[CONF_USERNAME], title=f"{user_input[CONF_USERNAME]}",
CONF_PASSWORD: user_input[CONF_PASSWORD] data={
}) CONF_TYPE: CONF_ACCOUNT,
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_PASSWORD: user_input[CONF_PASSWORD]
})
else:
self._cloud = None
return await self.async_step_user(error="login_failed")
return self.async_show_form( return self.async_show_form(
step_id="user", step_id="user",
data_schema=vol.Schema({ data_schema=vol.Schema({
@@ -96,86 +113,17 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
if user_input is not None: if user_input is not None:
# 下载lua # 下载lua
# 本地尝试连接设备 # 本地尝试连接设备
device = self._device_list[user_input[CONF_DEVICE]] self._device = self._device_list[user_input[CONF_DEVICE]]
if not device.get("online"): if not self._device.get("online"):
return await self.async_step_device(error="offline_error") return await self.async_step_device(error="offline_error")
discover_devices = discover([device["type"]]) return await self.async_step_discover()
_LOGGER.debug(discover_devices)
if discover_devices is None or len(discover_devices) == 0:
return await self.async_step_device(error="discover_failed")
current_device = discover_devices.get(user_input[CONF_DEVICE])
if current_device is None:
return await self.async_step_device(error="discover_failed")
os.makedirs(self.hass.config.path(STORAGE_PATH), exist_ok=True)
path = self.hass.config.path(STORAGE_PATH)
file = await self._cloud.get_lua(device["sn"], device["type"], path, device["enterprise_code"])
if file is None:
return await self.async_step_device(error="download_lua_failed")
use_token = None
use_key = None
connected = False
if current_device.get("protocol") == 3:
for byte_order_big in [False, True]:
token, key = await self._cloud.get_token(user_input[CONF_DEVICE], byte_order_big=byte_order_big)
if token and key:
dm = MiedaDevice(
name=device.get("name"),
device_id=user_input[CONF_DEVICE],
device_type=current_device.get(CONF_TYPE),
ip_address=current_device.get(CONF_IP_ADDRESS),
port=current_device.get(CONF_PORT),
token=token,
key=key,
protocol=3,
model=device.get(CONF_MODEL),
lua_file=None
)
if dm.connect():
use_token = token
use_key = key
connected = True
else:
return await self.async_step_device(error="cant_get_token")
else:
dm = MiedaDevice(
name=device.get("name"),
device_id=user_input[CONF_DEVICE],
device_type=current_device.get(CONF_TYPE),
ip_address=current_device.get(CONF_IP_ADDRESS),
port=current_device.get(CONF_PORT),
token=use_token,
key=use_key,
protocol=2,
model=device.get(CONF_MODEL),
lua_file=None
)
if dm.connect():
connected = True
if not connected:
return await self.async_step_device(error="connect_error")
return self.async_create_entry(
title=device.get("name"),
data={
CONF_NAME: device.get("name"),
CONF_DEVICE_ID: user_input[CONF_DEVICE],
CONF_TYPE: current_device.get("type"),
CONF_PROTOCOL: current_device.get("protocol"),
CONF_IP_ADDRESS: current_device.get("ip_address"),
CONF_PORT: current_device.get("port"),
CONF_MODEL: device.get("model"),
CONF_TOKEN: use_token,
CONF_KEY: use_key,
"lua_file": file,
"sn": device.get("sn"),
"sn8": device.get("sn8"),
})
devices = await self._cloud.get_devices(self._current_home) devices = await self._cloud.get_devices(self._current_home)
self._device_list = {} self._device_list = {}
device_list = {} device_list = {}
for device in devices: for device in devices:
if not self._device_configured(int(device.get("applianceCode"))): if not self._device_configured(int(device.get("applianceCode"))):
self._device_list[int(device.get("applianceCode"))] = { self._device_list[int(device.get("applianceCode"))] = {
"device_id": int(device.get("applianceCode")),
"name": device.get("name"), "name": device.get("name"),
"type": int(device.get("type"), 16), "type": int(device.get("type"), 16),
"sn8": device.get("sn8"), "sn8": device.get("sn8"),
@@ -198,7 +146,98 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors={"base": error} if error else None errors={"base": error} if error else None
) )
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]
discover_devices = discover([self._device["type"]], ip_address)
_LOGGER.debug(discover_devices)
if discover_devices is None or len(discover_devices) == 0:
return await self.async_step_discover(error="discover_failed")
current_device = discover_devices.get(self._device["device_id"])
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)
file = await self._cloud.get_lua(self._device["sn"], self._device["type"], path, self._device["enterprise_code"])
if file is None:
return await self.async_step_discover(error="download_lua_failed")
use_token = None
use_key = None
connected = False
if current_device.get("protocol") == 3:
for byte_order_big in [False, True]:
token, key = await self._cloud.get_token(self._device.get("device_id"), byte_order_big=byte_order_big)
if token and key:
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=token,
key=key,
protocol=3,
model=None,
sn=None,
sn8=None,
lua_file=None
)
if dm.connect():
use_token = token
use_key = key
connected = True
else:
return await self.async_step_discover(error="cant_get_token")
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,
sn=None,
sn8=None,
lua_file=None
)
if dm.connect():
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={
CONF_NAME: self._device.get("name"),
CONF_DEVICE_ID: self._device.get("device_id"),
CONF_TYPE: current_device.get("type"),
CONF_PROTOCOL: current_device.get("protocol"),
CONF_IP_ADDRESS: current_device.get("ip_address"),
CONF_PORT: current_device.get("port"),
CONF_MODEL: self._device.get("model"),
CONF_TOKEN: use_token,
CONF_KEY: use_key,
"lua_file": file,
"sn": self._device.get("sn"),
"sn8": self._device.get("sn8"),
})
else:
return await self.async_step_discover(error="invalid_input")
return self.async_show_form(
step_id="discover",
data_schema=vol.Schema({
vol.Required(CONF_IP_ADDRESS): str
}),
errors={"base": error} if error else None
)
class OptionsFlowHandler(config_entries.OptionsFlow): class OptionsFlowHandler(config_entries.OptionsFlow):
def __init__(self, config_entry: config_entries.ConfigEntry): def __init__(self, config_entry: config_entries.ConfigEntry):
pass pass

View File

@@ -39,7 +39,9 @@ class MiedaDevice(threading.Thread):
token: str | None, token: str | None,
key: str | None, key: str | None,
protocol: int, protocol: int,
model: str, model: str | None,
sn: str | None,
sn8: str | None,
lua_file: str | None): lua_file: str | None):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self._socket = None self._socket = None
@@ -59,7 +61,8 @@ class MiedaDevice(threading.Thread):
self._is_run = False self._is_run = False
self._device_protocol_version = 0 self._device_protocol_version = 0
self._sub_type = None self._sub_type = None
self._sn = None self._sn = sn
self._sn8 = sn8
self._attributes = {} self._attributes = {}
self._refresh_interval = 30 self._refresh_interval = 30
self._heartbeat_interval = 10 self._heartbeat_interval = 10
@@ -83,6 +86,14 @@ class MiedaDevice(threading.Thread):
def model(self): def model(self):
return self._model return self._model
@property
def sn(self):
return self._sn
@property
def sn8(self):
return self._sn8
@property @property
def attributes(self): def attributes(self):
return self._attributes return self._attributes

View File

@@ -9,7 +9,7 @@ class MideaEntity(Entity):
self._entity_key = entity_key self._entity_key = entity_key
self._unique_id = f"{DOMAIN}.{self._device.device_id}_{entity_key}" self._unique_id = f"{DOMAIN}.{self._device.device_id}_{entity_key}"
self.entity_id = self._unique_id self.entity_id = self._unique_id
self._device_name = self._device.name self._device_name = self._device.device_name
@property @property
def device(self): def device(self):
@@ -19,7 +19,7 @@ class MideaEntity(Entity):
def device_info(self): def device_info(self):
return { return {
"manufacturer": "Midea", "manufacturer": "Midea",
"model": self._device.model, "model": f"{self._device.model} ({self._device.sn8})",
"identifiers": {(DOMAIN, self._device.device_id)}, "identifiers": {(DOMAIN, self._device.device_id)},
"name": self._device_name "name": self._device_name
} }

View File

@@ -1,6 +1,9 @@
{ {
"config": { "config": {
"error": { "error": {
"account_invalid": "登录美居失败,是否已修改过密码",
"invalid_input": "无效的输入请输入有效IP地址或auto",
"login_failed": "无法登录到美居,请检查用户名或密码",
"offline_error": "只能配置在线设备", "offline_error": "只能配置在线设备",
"download_lua_failed": "下载设备协议脚本失败", "download_lua_failed": "下载设备协议脚本失败",
"discover_failed": "无法在本地搜索到该设备", "discover_failed": "无法在本地搜索到该设备",
@@ -19,17 +22,22 @@
"title": "登录" "title": "登录"
}, },
"home": { "home": {
"description": "选择设备所在家庭",
"title": "家庭", "title": "家庭",
"data": { "data": {
"home": "设备所在家庭" "home": "选择设备所在家庭"
} }
}, },
"device": { "device": {
"description": "选择要添加的设备",
"title": "设备", "title": "设备",
"data": { "data": {
"device": "要添加的设备" "device": "选择要添加的设备"
}
},
"discover": {
"description": "获取设备信息,设备必须位于本地局域网内",
"title": "设备信息",
"data": {
"ip_address": "设备地址(输入auto自动搜索设备)"
} }
} }
} }

View File

@@ -1,6 +1,9 @@
{ {
"config": { "config": {
"error": { "error": {
"account_invalid": "登录美居失败,是否已修改过密码",
"invalid_input": "无效的输入请输入有效IP地址或auto",
"login_failed": "无法登录到美居,请检查用户名或密码",
"offline_error": "只能配置在线设备", "offline_error": "只能配置在线设备",
"download_lua_failed": "下载设备协议脚本失败", "download_lua_failed": "下载设备协议脚本失败",
"discover_failed": "无法在本地搜索到该设备", "discover_failed": "无法在本地搜索到该设备",
@@ -19,17 +22,22 @@
"title": "登录" "title": "登录"
}, },
"home": { "home": {
"description": "选择设备所在家庭",
"title": "家庭", "title": "家庭",
"data": { "data": {
"home": "设备所在家庭" "home": "选择设备所在家庭"
} }
}, },
"device": { "device": {
"description": "选择要添加的设备",
"title": "设备", "title": "设备",
"data": { "data": {
"device": "要添加的设备" "device": "选择要添加的设备"
}
},
"discover": {
"description": "获取设备信息,设备必须位于本地局域网内",
"title": "设备信息",
"data": {
"ip_address": "设备地址(输入auto自动搜索设备)"
} }
} }
} }