From e8b0bd51c94bb1131e861b6481e3999d940cbcd0 Mon Sep 17 00:00:00 2001 From: manymuch Date: Sun, 28 Jul 2024 16:38:38 +0800 Subject: [PATCH] v1.3.0 Enable multi-device selections during setup UI --- custom_components/hisense/__init__.py | 64 ++++++--------- custom_components/hisense/button.py | 23 +++--- custom_components/hisense/climate.py | 13 +-- custom_components/hisense/config_flow.py | 82 ++++++++++++++++--- custom_components/hisense/manifest.json | 2 +- custom_components/hisense/pyhisenseapi.py | 10 +-- custom_components/hisense/switch.py | 18 ++-- .../hisense/translations/en.json | 12 ++- .../hisense/translations/zh-Hans.json | 16 +++- 9 files changed, 152 insertions(+), 88 deletions(-) diff --git a/custom_components/hisense/__init__.py b/custom_components/hisense/__init__.py index 4905c45..d2c6283 100644 --- a/custom_components/hisense/__init__.py +++ b/custom_components/hisense/__init__.py @@ -1,51 +1,35 @@ from homeassistant import config_entries, core from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN -from .pyhisenseapi import HiSenseLogin, HiSenseAC +from .pyhisenseapi import HiSenseAC -async def async_setup(hass: core.HomeAssistant, config: dict): - # Initialize integration data structure - hass.data[DOMAIN] = {} - return True - async def async_setup_entry(hass: core.HomeAssistant, entry: config_entries.ConfigEntry): - # Setup the API client for a device + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][entry.entry_id] = {} + session = async_get_clientsession(hass) - hisense_login = HiSenseLogin( - session=session - ) + # Setup devices based on the selected devices from the config flow + for device_info in entry.data["devices"]: + device_id = device_info["device_id"] + wifi_id = device_info["wifi_id"] + refresh_token = device_info["refresh_token"] + hass.data[DOMAIN][entry.entry_id][device_id] = HiSenseAC( + wifi_id=wifi_id, + device_id=device_id, + refresh_token=refresh_token, + session=session + ) - access_token, refresh_token = await hisense_login.login(entry.data["username"], entry.data["password"]) - home_id_list = await hisense_login.get_home_id_list(access_token) - # TODO let the user to select home_id - home_id = home_id_list[0] - device_id_list, wifi_id_list = await hisense_login.get_device_id_list(access_token, home_id) - # TODO let the user to select device - device_id = device_id_list[0] - wifi_id = wifi_id_list[0] - - hass.data[DOMAIN][entry.entry_id] = HiSenseAC( - wifi_id=wifi_id, - device_id=device_id, - refresh_token=refresh_token, - session=session - ) - # Forward the setup to the climate platform - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, "climate")) - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, "switch")) - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, "button")) - return True + for platform in ("climate", "switch", "button"): + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, platform) + ) + return True async def async_unload_entry(hass: core.HomeAssistant, entry: config_entries.ConfigEntry): - # Unload both climate and switch config entries - unload_climate = await hass.config_entries.async_forward_entry_unload(entry, "climate") - unload_switch = await hass.config_entries.async_forward_entry_unload(entry, "switch") - unload_button = await hass.config_entries.async_forward_entry_unload(entry, "button") - if unload_climate and unload_switch and unload_button: - hass.data[DOMAIN].pop(entry.entry_id) - return unload_climate and unload_switch and unload_button + return all( + await hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in ("climate", "switch", "button") + ) diff --git a/custom_components/hisense/button.py b/custom_components/hisense/button.py index c248741..22b53ac 100644 --- a/custom_components/hisense/button.py +++ b/custom_components/hisense/button.py @@ -8,15 +8,19 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass: HomeAssistant, entry, async_add_entities: AddEntitiesCallback): - api = hass.data[DOMAIN][entry.entry_id] - async_add_entities([HisenseACUpdateButton(api)], True) - async_add_entities([HisenseACRefreshTokenButton(api)], True) + +async def async_setup_entry(hass, config_entry, async_add_entities): + api = hass.data[DOMAIN][config_entry.entry_id] + entities = [HisenseACUpdateButton(api[device_id], config_entry.entry_id) for device_id in api] + async_add_entities(entities, True) + entities = [HisenseACRefreshTokenButton(api[device_id], config_entry.entry_id) for device_id in api] + async_add_entities(entities, True) class HisenseACUpdateButton(ButtonEntity): - def __init__(self, api): + def __init__(self, api, config_entry_id): self._api = api + self._config_entry_id = config_entry_id self._attr_name = f"Force update button" self._attr_unique_id = f"{api.device_id}_force_update_button" self._attr_icon = "mdi:refresh" @@ -41,16 +45,13 @@ async def async_press(self): """Handle the button press.""" _LOGGER.debug(f"Button pressed for entity: {self._attr_unique_id}") await self._api.check_status() - # Ensure the climate entity is updated after status check - climate_entity = self.hass.data[DOMAIN].get(self._api.device_id) - if climate_entity: - await climate_entity.async_update() - climate_entity.async_write_ha_state() + self.async_schedule_update_ha_state(True) class HisenseACRefreshTokenButton(ButtonEntity): - def __init__(self, api): + def __init__(self, api, config_entry_id): self._api = api + self._config_entry_id = config_entry_id self._attr_name = f"Refresh token" self._attr_unique_id = f"{api.device_id}_refresh_token" self._attr_icon = "mdi:refresh" diff --git a/custom_components/hisense/climate.py b/custom_components/hisense/climate.py index b77a017..01b34e6 100644 --- a/custom_components/hisense/climate.py +++ b/custom_components/hisense/climate.py @@ -20,15 +20,16 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass, entry, async_add_entities): - api = hass.data[DOMAIN][entry.entry_id] - entity = HisenseACClimate(api) - async_add_entities([entity], True) +async def async_setup_entry(hass, config_entry, async_add_entities): + api = hass.data[DOMAIN][config_entry.entry_id] + entities = [HisenseACClimate(api[device_id], config_entry.entry_id) for device_id in api] + async_add_entities(entities, True) class HisenseACClimate(ClimateEntity): - def __init__(self, api): + def __init__(self, api, config_entry_id): self._api = api + self._config_entry_id = config_entry_id self._attr_name = f"Hisense AC" self._attr_unique_id = f"{api.device_id}_climate" self._attr_supported_features = ( @@ -158,7 +159,7 @@ async def async_set_swing_mode(self, swing_mode): # Update the entity's current swing mode self._attr_swing_mode = swing_mode # Notify Home Assistant that the entity's state has changed - self.async_write_ha_state() + self.async_schedule_update_ha_state(True) else: _LOGGER.error("Unsupported swing mode: %s", swing_mode) diff --git a/custom_components/hisense/config_flow.py b/custom_components/hisense/config_flow.py index 020f459..64988ed 100644 --- a/custom_components/hisense/config_flow.py +++ b/custom_components/hisense/config_flow.py @@ -1,31 +1,93 @@ import voluptuous as vol from homeassistant import config_entries - +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers import config_validation as cv from .const import DOMAIN, CONF_USERNAME, CONF_PASSWORD - +from .pyhisenseapi import HiSenseLogin class HisenseACConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + def __init__(self): + self._home_id_list = None + self._access_token = None + self._refresh_token = None + async def async_step_user(self, user_input=None): errors = {} if user_input is not None: - # Here you could add code to validate the input, such as attempting to connect to the AC - # For simplicity, we'll assume the input is valid - return self.async_create_entry(title="Hisense Smart Control", data=user_input) + session = async_get_clientsession(self.hass) + hisense_login = HiSenseLogin(session=session) + + try: + access_token, refresh_token = await hisense_login.login( + user_input[CONF_USERNAME], user_input[CONF_PASSWORD] + ) + self._home_id_list = await hisense_login.get_home_id_list(access_token) + self._access_token = access_token + self._refresh_token = refresh_token + return await self.async_step_home() + except Exception: + errors["base"] = "invalid_auth" return self.async_show_form( step_id="user", - data_schema=vol.Schema({ - vol.Required(CONF_USERNAME): str, - vol.Required(CONF_PASSWORD, ): str, - }), + data_schema=vol.Schema( + { + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + } + ), description_placeholders={ "username_hint": "app login username", "password_hint": "app login password", }, errors=errors, ) - \ No newline at end of file + + async def async_step_home(self, user_input=None): + errors = {} + + if user_input is not None: + home_id = user_input["home_id"] + session = async_get_clientsession(self.hass) + hisense_login = HiSenseLogin(session=session) + self._device_wifi_id_dict = await hisense_login.get_device_wifi_id_dict( + self._access_token, home_id + ) + + return await self.async_step_device() + + return self.async_show_form( + step_id="home", + data_schema=vol.Schema( + {vol.Required("home_id"): vol.In(self._home_id_list)} + ), + errors=errors, + ) + + async def async_step_device(self, user_input=None): + if user_input is not None: + device_ids = user_input["device_ids"] + devices = [ + {"device_id": device_id, + "wifi_id": self._device_wifi_id_dict[device_id], + "refresh_token": self._refresh_token, + } + for device_id in device_ids + ] + return self.async_create_entry( + title="Hisense Smart Control", + data={"devices": devices} + ) + + data_schema = vol.Schema( + { + vol.Required("device_ids"): cv.multi_select( + list(self._device_wifi_id_dict.keys()) + ), + } + ) + return self.async_show_form(step_id="device", data_schema=data_schema) diff --git a/custom_components/hisense/manifest.json b/custom_components/hisense/manifest.json index a8cd23e..10c38f9 100644 --- a/custom_components/hisense/manifest.json +++ b/custom_components/hisense/manifest.json @@ -2,7 +2,7 @@ "domain": "hisense", "name": "Hisense Smart Devices", "config_flow": true, - "version": "1.2.0", + "version": "1.3.0", "integration_type": "device", "documentation": "https://github.com/manymuch/HisenseHA", "requirements": ["asyncio"], diff --git a/custom_components/hisense/pyhisenseapi.py b/custom_components/hisense/pyhisenseapi.py index e9f98e1..01ed8fb 100644 --- a/custom_components/hisense/pyhisenseapi.py +++ b/custom_components/hisense/pyhisenseapi.py @@ -80,7 +80,7 @@ async def get_home_id_list(self, access_token): else: return None - async def get_device_id_list(self, access_token, home_id, device_keywords="空调"): + async def get_device_wifi_id_dict(self, access_token, home_id, device_keywords="空调"): timestamp = self.get_timestamp() url='http://api-wg.hismarttv.com/wg/dm/getHomeDeviceList' headers = { @@ -104,14 +104,12 @@ async def get_device_id_list(self, access_token, home_id, device_keywords="空 result_code = result["response"]["resultCode"] if result_code == 0: device_list = result["response"]["deviceList"] - device_id_list = [] - wifi_id_list = [] + device_wifi_id_dict = dict() for device in device_list: device_type_name = device["deviceTypeName"] if device_keywords in device_type_name: - device_id_list.append(device["deviceId"]) - wifi_id_list.append(device["wifiId"]) - return device_id_list, wifi_id_list + device_wifi_id_dict[device["deviceId"]] = device["wifiId"] + return device_wifi_id_dict else: return None diff --git a/custom_components/hisense/switch.py b/custom_components/hisense/switch.py index 78a74ea..80d338e 100644 --- a/custom_components/hisense/switch.py +++ b/custom_components/hisense/switch.py @@ -7,10 +7,12 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass: HomeAssistant, entry, async_add_entities: AddEntitiesCallback): - api = hass.data[DOMAIN][entry.entry_id] - async_add_entities([AcScreenSwitch(api)], True) - async_add_entities([AuxHeatSwitch(api)], True) +async def async_setup_entry(hass, config_entry, async_add_entities): + api = hass.data[DOMAIN][config_entry.entry_id] + entities = [AcScreenSwitch(api[device_id]) for device_id in api] + async_add_entities(entities, True) + entities = [AuxHeatSwitch(api[device_id]) for device_id in api] + async_add_entities(entities, True) class AcScreenSwitch(SwitchEntity): @@ -41,14 +43,14 @@ async def async_turn_on(self): await self._api.send_logic_command(41, 1) self._is_on = True await self.async_update() - self.async_write_ha_state() + self.async_schedule_update_ha_state(True) async def async_turn_off(self): _LOGGER.debug(f"Turning off screen for {self._attr_unique_id}") await self._api.send_logic_command(41, 0) self._is_on = False await self.async_update() - self.async_write_ha_state() + self.async_schedule_update_ha_state(True) async def async_update(self): status = self._api.get_status() @@ -82,13 +84,13 @@ async def async_turn_on(self): await self._api.send_logic_command(28, 1) self._is_on = True await self.async_update() - self.async_write_ha_state() + self.async_schedule_update_ha_state(True) async def async_turn_off(self): await self._api.send_logic_command(28, 0) self._is_on = False await self.async_update() - self.async_write_ha_state() + self.async_schedule_update_ha_state(True) async def async_update(self): status = self._api.get_status() diff --git a/custom_components/hisense/translations/en.json b/custom_components/hisense/translations/en.json index 3645c98..8536d54 100644 --- a/custom_components/hisense/translations/en.json +++ b/custom_components/hisense/translations/en.json @@ -3,16 +3,24 @@ "step": { "user": { "title": "HiSense Smart Device", - "description": "Please enter your device information", + "description": "Login with App username and password", "data": { "username": "App login username, could be the phone number", "password": "App login password, if not available set it in the app first" } + }, + "home": { + "title": "HiSense Smart Device", + "description": "Select your home ID" + }, + "device": { + "title": "HiSense Smart Device", + "description": "Select your device(s)" } }, "error": { "cannot_connect": "Unable to connect to the device", - "invalid_auth": "Invalid authentication", + "invalid_auth": "Failed to login", "unknown": "An unknown error occurred" }, "title": "Hisense AC" diff --git a/custom_components/hisense/translations/zh-Hans.json b/custom_components/hisense/translations/zh-Hans.json index dd9508e..56ba9da 100644 --- a/custom_components/hisense/translations/zh-Hans.json +++ b/custom_components/hisense/translations/zh-Hans.json @@ -3,16 +3,24 @@ "step": { "user": { "title": "海信智能设备", - "description": "请通过手机App抓包获得以下信息", + "description": "使用海信智慧家的手机号和密码登录", "data": { - "username": "海信APP登录用户手机号", - "password": "如果没有登录密码请先去海信App里设置一个" + "username": "海信智慧家登录用户手机号", + "password": "如果没有登录密码请先去海信智慧家App里设置一个" } + }, + "home": { + "title": "海信智能设备", + "description": "选择海信App中的家庭ID(单选)" + }, + "device": { + "title": "海信智能设备", + "description": "选择需要添加的设备(可多选)" } }, "error": { "cannot_connect": "无法连接到设备", - "invalid_auth": "认证无效", + "invalid_auth": "登录失败", "unknown": "发生未知错误" }, "title": "海信空调"