From faae480fd8ac21c7560af2da784513a3fd1075e5 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 3 Sep 2023 22:15:41 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AE=BE=E5=A4=87=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../midea_meiju_codec/__init__.py | 4 + .../midea_meiju_codec/binary_sensor.py | 12 +- .../midea_meiju_codec/config_flow.py | 207 +++++++++++------- .../midea_meiju_codec/core/device.py | 15 +- .../midea_meiju_codec/midea_entities.py | 4 +- .../midea_meiju_codec/translations/en.json | 16 +- .../translations/zh-Hans.json | 16 +- 7 files changed, 175 insertions(+), 99 deletions(-) diff --git a/custom_components/midea_meiju_codec/__init__.py b/custom_components/midea_meiju_codec/__init__.py index b858944..241795b 100644 --- a/custom_components/midea_meiju_codec/__init__.py +++ b/custom_components/midea_meiju_codec/__init__.py @@ -56,6 +56,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry): port = config_entry.data.get(CONF_PORT) model = config_entry.data.get(CONF_MODEL) 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") _LOGGER.error(f"lua_file = {lua_file}") 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, protocol=protocol, model=model, + sn=sn, + sn8=sn8, lua_file=lua_file, ) if device: diff --git a/custom_components/midea_meiju_codec/binary_sensor.py b/custom_components/midea_meiju_codec/binary_sensor.py index 48376aa..549150b 100644 --- a/custom_components/midea_meiju_codec/binary_sensor.py +++ b/custom_components/midea_meiju_codec/binary_sensor.py @@ -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 = hass.data[DOMAIN][DEVICES].get(device_id) binary_sensors = [] - sensor = MideaDeviceStatusSensor(device, "online") + sensor = MideaDeviceStatusSensor(device, "status") binary_sensors.append(sensor) async_add_entities(binary_sensors) @@ -32,6 +32,14 @@ class MideaDeviceStatusSensor(MideaEntity): def state(self): 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 def is_on(self): return self.state == STATE_ON @@ -46,8 +54,6 @@ class MideaDeviceStatusSensor(MideaEntity): def update_state(self, status): try: - _LOGGER.debug("=" * 50) self.schedule_update_ha_state() - _LOGGER.debug("-" * 50) except Exception as e: pass diff --git a/custom_components/midea_meiju_codec/config_flow.py b/custom_components/midea_meiju_codec/config_flow.py index 0390bd9..117f782 100644 --- a/custom_components/midea_meiju_codec/config_flow.py +++ b/custom_components/midea_meiju_codec/config_flow.py @@ -1,6 +1,7 @@ import voluptuous as vol import logging import os +import ipaddress from homeassistant.helpers.aiohttp_client import async_create_clientsession from homeassistant import config_entries from homeassistant.const import ( @@ -15,7 +16,6 @@ from homeassistant.const import ( CONF_PROTOCOL, CONF_TOKEN, CONF_NAME - ) from .core.cloud import MeijuCloudExtend from .core.discover import discover @@ -36,6 +36,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): _cloud = None _current_home = None _device_list = {} + _device = None def _get_configured_account(self): for entry in self._async_current_entries(): @@ -49,23 +50,39 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return True 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): + if self._session is None: + self._session = async_create_clientsession(self.hass) username, password = self._get_configured_account() 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: self._cloud = MeijuCloudExtend(self._session, username, password) if await self._cloud.login(): return await self.async_step_home() + else: + return await self.async_step_user(error="account_invalid") if user_input is not None: - return self.async_create_entry( - title=f"{user_input[CONF_USERNAME]}", - data={ - CONF_TYPE: CONF_ACCOUNT, - CONF_USERNAME: user_input[CONF_USERNAME], - CONF_PASSWORD: user_input[CONF_PASSWORD] - }) + if self._cloud is None: + self._cloud = MeijuCloudExtend(self._session, user_input[CONF_USERNAME], user_input[CONF_PASSWORD]) + if await self._cloud.login(): + return self.async_create_entry( + title=f"{user_input[CONF_USERNAME]}", + 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( step_id="user", data_schema=vol.Schema({ @@ -96,86 +113,17 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if user_input is not None: # 下载lua # 本地尝试连接设备 - device = self._device_list[user_input[CONF_DEVICE]] - if not device.get("online"): + self._device = self._device_list[user_input[CONF_DEVICE]] + if not self._device.get("online"): return await self.async_step_device(error="offline_error") - discover_devices = discover([device["type"]]) - _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"), - }) + return await self.async_step_discover() devices = await self._cloud.get_devices(self._current_home) self._device_list = {} device_list = {} for device in devices: if not self._device_configured(int(device.get("applianceCode"))): self._device_list[int(device.get("applianceCode"))] = { + "device_id": int(device.get("applianceCode")), "name": device.get("name"), "type": int(device.get("type"), 16), "sn8": device.get("sn8"), @@ -198,7 +146,98 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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): def __init__(self, config_entry: config_entries.ConfigEntry): - pass \ No newline at end of file + pass diff --git a/custom_components/midea_meiju_codec/core/device.py b/custom_components/midea_meiju_codec/core/device.py index 28b3b6c..8e4d16d 100644 --- a/custom_components/midea_meiju_codec/core/device.py +++ b/custom_components/midea_meiju_codec/core/device.py @@ -39,7 +39,9 @@ class MiedaDevice(threading.Thread): token: str | None, key: str | None, protocol: int, - model: str, + model: str | None, + sn: str | None, + sn8: str | None, lua_file: str | None): threading.Thread.__init__(self) self._socket = None @@ -59,7 +61,8 @@ class MiedaDevice(threading.Thread): self._is_run = False self._device_protocol_version = 0 self._sub_type = None - self._sn = None + self._sn = sn + self._sn8 = sn8 self._attributes = {} self._refresh_interval = 30 self._heartbeat_interval = 10 @@ -83,6 +86,14 @@ class MiedaDevice(threading.Thread): def model(self): return self._model + @property + def sn(self): + return self._sn + + @property + def sn8(self): + return self._sn8 + @property def attributes(self): return self._attributes diff --git a/custom_components/midea_meiju_codec/midea_entities.py b/custom_components/midea_meiju_codec/midea_entities.py index 6a24113..f11d776 100644 --- a/custom_components/midea_meiju_codec/midea_entities.py +++ b/custom_components/midea_meiju_codec/midea_entities.py @@ -9,7 +9,7 @@ class MideaEntity(Entity): self._entity_key = entity_key self._unique_id = f"{DOMAIN}.{self._device.device_id}_{entity_key}" self.entity_id = self._unique_id - self._device_name = self._device.name + self._device_name = self._device.device_name @property def device(self): @@ -19,7 +19,7 @@ class MideaEntity(Entity): def device_info(self): return { "manufacturer": "Midea", - "model": self._device.model, + "model": f"{self._device.model} ({self._device.sn8})", "identifiers": {(DOMAIN, self._device.device_id)}, "name": self._device_name } diff --git a/custom_components/midea_meiju_codec/translations/en.json b/custom_components/midea_meiju_codec/translations/en.json index bb159a0..16e50e2 100644 --- a/custom_components/midea_meiju_codec/translations/en.json +++ b/custom_components/midea_meiju_codec/translations/en.json @@ -1,6 +1,9 @@ { "config": { "error": { + "account_invalid": "登录美居失败,是否已修改过密码", + "invalid_input": "无效的输入,请输入有效IP地址或auto", + "login_failed": "无法登录到美居,请检查用户名或密码", "offline_error": "只能配置在线设备", "download_lua_failed": "下载设备协议脚本失败", "discover_failed": "无法在本地搜索到该设备", @@ -19,17 +22,22 @@ "title": "登录" }, "home": { - "description": "选择设备所在家庭", "title": "家庭", "data": { - "home": "设备所在家庭" + "home": "选择设备所在家庭" } }, "device": { - "description": "选择要添加的设备", "title": "设备", "data": { - "device": "要添加的设备" + "device": "选择要添加的设备" + } + }, + "discover": { + "description": "获取设备信息,设备必须位于本地局域网内", + "title": "设备信息", + "data": { + "ip_address": "设备地址(输入auto自动搜索设备)" } } } diff --git a/custom_components/midea_meiju_codec/translations/zh-Hans.json b/custom_components/midea_meiju_codec/translations/zh-Hans.json index bb159a0..16e50e2 100644 --- a/custom_components/midea_meiju_codec/translations/zh-Hans.json +++ b/custom_components/midea_meiju_codec/translations/zh-Hans.json @@ -1,6 +1,9 @@ { "config": { "error": { + "account_invalid": "登录美居失败,是否已修改过密码", + "invalid_input": "无效的输入,请输入有效IP地址或auto", + "login_failed": "无法登录到美居,请检查用户名或密码", "offline_error": "只能配置在线设备", "download_lua_failed": "下载设备协议脚本失败", "discover_failed": "无法在本地搜索到该设备", @@ -19,17 +22,22 @@ "title": "登录" }, "home": { - "description": "选择设备所在家庭", "title": "家庭", "data": { - "home": "设备所在家庭" + "home": "选择设备所在家庭" } }, "device": { - "description": "选择要添加的设备", "title": "设备", "data": { - "device": "要添加的设备" + "device": "选择要添加的设备" + } + }, + "discover": { + "description": "获取设备信息,设备必须位于本地局域网内", + "title": "设备信息", + "data": { + "ip_address": "设备地址(输入auto自动搜索设备)" } } }