diff --git a/custom_components/midea_ac_lan/__init__.py b/custom_components/midea_ac_lan/__init__.py index b8211cb8..bc1d9bfc 100644 --- a/custom_components/midea_ac_lan/__init__.py +++ b/custom_components/midea_ac_lan/__init__.py @@ -1,5 +1,5 @@ """ -midea_ac_lan integration init file +midea_lan integration init file integration load process: 1. component setup: `async_setup` @@ -234,7 +234,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b hass.data[DOMAIN][DEVICES] = {} hass.data[DOMAIN][DEVICES][device_id] = device # Forward the setup of an entry to all platforms - await hass.config_entries.async_forward_entry_setups(config_entry, ALL_PLATFORM) + hass.async_create_task( + hass.config_entries.async_forward_entry_setups(config_entry, ALL_PLATFORM), + ) # Listener `update_listener` is attached when the entry is loaded and detached at unload config_entry.async_on_unload(config_entry.add_update_listener(update_listener)) return True diff --git a/custom_components/midea_ac_lan/config_flow.py b/custom_components/midea_ac_lan/config_flow.py index 405e2219..aae14948 100644 --- a/custom_components/midea_ac_lan/config_flow.py +++ b/custom_components/midea_ac_lan/config_flow.py @@ -19,6 +19,7 @@ import logging import os +import shutil from typing import Any, cast import homeassistant.helpers.config_validation as cv @@ -61,6 +62,7 @@ CONF_SERVER, CONF_SUBTYPE, DOMAIN, + OLD_DOMAIN, EXTRA_CONTROL, EXTRA_SENSOR, ) @@ -75,6 +77,8 @@ } PROTOCOLS = {1: "V1", 2: "V2", 3: "V3"} STORAGE_PATH = f".storage/{DOMAIN}" +OLD_STORAGE_PATH = f".storage/{OLD_DOMAIN}" +MIGRATED = f"{STORAGE_PATH}/.migrated" SERVERS = { 1: "MSmartHome", @@ -150,6 +154,93 @@ def _already_configured(self, device_id: str, ip_address: str) -> bool: return True return False + def _check_old_domain(self) -> bool: + """check midea_ac_lan domain configs""" + # user skip, migrated flag exist + if os.path.isfile(self.hass.config.path(MIGRATED)): + _LOGGER.debug("user disabled migrate, return false") + return False + # new user with old domain data + if os.path.exists(self.hass.config.path(OLD_STORAGE_PATH)): + _LOGGER.debug("midea_ac_lan exist, return true") + return True + _LOGGER.debug("midea_ac_lan NOT exist, return false") + return False + + async def _disable_migrate(self) -> bool: + """disable midea_ac_lan configs migrate""" + + def write_file() -> bool: + # user skip, migrated flag exist + if not os.path.exists(self.hass.config.path(STORAGE_PATH)): + os.makedirs(STORAGE_PATH) + # write to file + try: + with open(MIGRATED, "w") as file: + file.write("true") + _LOGGER.debug("migrate done, return true") + return True + except OSError as e: + _LOGGER.error(f"File operation failed: {e}") + _LOGGER.debug("disable migrate failed, return false") + return False + + # run write_file job + return await self.hass.async_add_executor_job(write_file) + + async def _do_migrate(self) -> bool: + """migrate midea_ac_lan domain configs""" + + def move_configs() -> bool: + try: + _LOGGER.debug("Checking if old storage path exists.") + # old domain exist + if os.path.exists(self.hass.config.path(OLD_STORAGE_PATH)): + _LOGGER.debug("Old storage path exists.") + # new domain not exist, rename old domain to new domain + if not os.path.exists(self.hass.config.path(STORAGE_PATH)): + _LOGGER.debug( + "New storage path does not exist. Renaming old domain to new domain." + ) + shutil.move( + self.hass.config.path(OLD_STORAGE_PATH), + self.hass.config.path(STORAGE_PATH), + ) + return True + # new domain dir exist, move old configs to new domain dir + for item in os.listdir(self.hass.config.path(OLD_STORAGE_PATH)): + s = os.path.join(self.hass.config.path(OLD_STORAGE_PATH), item) + d = os.path.join(self.hass.config.path(STORAGE_PATH), item) + # confirm destination does not exist in new domain + if not os.path.exists(d): + _LOGGER.debug(f"Moving {s} to {d}") + shutil.move(s, d) + # remove old domain dir + os.rmdir(self.hass.config.path(OLD_STORAGE_PATH)) + return True + # old domain not exist + _LOGGER.debug("Old storage path does not exist.") + return False + except Exception as e: + _LOGGER.error(f"Error during migration: {e}") + return False + + # run move_configs job + return await self.hass.async_add_executor_job(move_configs) + + def _get_migrate_devices(self) -> list: + """get migrate midea_ac_lan domain devices""" + # old domain exist + migrate = [] + if os.path.exists(self.hass.config.path(OLD_STORAGE_PATH)): + # new domain dir exist, mv old configs to new domain dir + for item in os.listdir(OLD_STORAGE_PATH): + # read file content + record_file = self.hass.config.path(f"{STORAGE_PATH}/item") + json_data = load_json(record_file, default={}) + migrate.append(json_data) + return migrate + async def async_step_user( self, user_input: dict[str, Any] | None = None, error: str | None = None ) -> ConfigFlowResult: @@ -165,14 +256,23 @@ async def async_step_user( elif user_input["action"] == "manually": self.found_device = {} return await self.async_step_manually() + # migrate midea_ac_lan device + elif user_input["action"] == "migrate": + return await self.async_step_migrate() # only list all devices else: return await self.async_step_list() # user not input, show device discovery select form in UI + # migrate data exist, show migrate option in WEB UI + if self._check_old_domain(): + ADD_WAY["migrate"] = "Migrate [midea_ac_lan] configs" + default_option = "migrate" + else: + default_option = "discovery" return self.async_show_form( step_id="user", data_schema=vol.Schema( - {vol.Required("action", default="discovery"): vol.In(ADD_WAY)}, + {vol.Required("action", default=default_option): vol.In(ADD_WAY)}, ), errors={"base": error} if error else None, ) @@ -214,6 +314,51 @@ async def async_step_login( errors={"base": error} if error else None, ) + async def async_step_migrate( + self, user_input: dict[str, Any] | None = None, error: str | None = None + ) -> ConfigFlowResult: + """migrate old domain device info in web UI""" + + # user select a device discovery mode + if user_input is not None: + # user select 'migrate' + _LOGGER.debug(f"user_input: {user_input}") + if user_input["action"] == "Migrate": + _LOGGER.debug("start do migrate") + # migrated = self._get_migrate_devices() + result = await self._do_migrate() + if result: + await self._disable_migrate() + # remove migrate option + ADD_WAY.pop("migrate", None) + # show migrate result in Web UI + return self.async_abort(reason="Migrate successfully!") + _LOGGER.debug(f"migrate result {result}") + elif user_input["action"] == "Disable": + _LOGGER.debug("user select disable") + await self._disable_migrate() + # remove migrate option + ADD_WAY.pop("migrate", None) + return self.async_abort(reason="Disabled migrate successfully!") + # no action, not use + else: + return self.async_abort(reason="User canceled") + # show migrate option in web UI + _LOGGER.debug("no input, show web form to user") + return self.async_show_form( + step_id="migrate", + data_schema=vol.Schema( + { + vol.Required("action", default="Migrate"): vol.In( + ["Migrate", "Disable"] + ), + } + ), + description_placeholders={ + "desc": "Do you want to migrate from midea_ac_lan?", + }, + ) + async def async_step_list( self, user_input: dict[str, Any] | None = None, error: str | None = None ) -> ConfigFlowResult: @@ -261,9 +406,10 @@ async def async_step_discovery( for device_id, device in self.devices.items(): # remove exist devices and only return new devices if not self._already_configured(device_id, device.get(CONF_IP_ADDRESS)): - self.available_device[device_id] = ( - f"{device_id} ({self.supports.get(device.get(CONF_TYPE))})" - ) + self.available_device[ + device_id + ] = f"{device_id} ({self.supports.get( + device.get(CONF_TYPE))})" if len(self.available_device) > 0: return await self.async_step_auto() else: diff --git a/custom_components/midea_ac_lan/const.py b/custom_components/midea_ac_lan/const.py index d6d89474..5bf04fc0 100644 --- a/custom_components/midea_ac_lan/const.py +++ b/custom_components/midea_ac_lan/const.py @@ -1,6 +1,7 @@ from homeassistant.const import Platform -DOMAIN = "midea_ac_lan" +DOMAIN = "midea_lan" +OLD_DOMAIN = "midea_ac_lan" COMPONENT = "component" DEVICES = "devices" CONF_KEY = "key" diff --git a/custom_components/midea_ac_lan/manifest.json b/custom_components/midea_ac_lan/manifest.json index 48680cc7..7109e7b6 100644 --- a/custom_components/midea_ac_lan/manifest.json +++ b/custom_components/midea_ac_lan/manifest.json @@ -1,13 +1,13 @@ { - "domain": "midea_ac_lan", - "name": "Midea AC LAN", + "domain": "midea_lan", + "name": "Midea LAN", "codeowners": ["@wuwentao", "@rokam", "@chemelli74"], "config_flow": true, "dependencies": [], - "documentation": "https://github.com/wuwentao/midea_ac_lan#readme", + "documentation": "https://github.com/wuwentao/midea_lan#readme", "integration_type": "device", "iot_class": "local_push", - "issue_tracker": "https://github.com/wuwentao/midea_ac_lan/issues", + "issue_tracker": "https://github.com/wuwentao/midea_lan/issues", "requirements": ["pycryptodome", "midea-local==1.0.5"], "version": "v0.4.1" } diff --git a/custom_components/midea_ac_lan/translations/en.json b/custom_components/midea_ac_lan/translations/en.json index c772aadd..bcc21145 100644 --- a/custom_components/midea_ac_lan/translations/en.json +++ b/custom_components/midea_ac_lan/translations/en.json @@ -58,6 +58,13 @@ }, "description": "Configuration of appliance", "title": "New appliance" + }, + "migrate": { + "data": { + "action": "Select an action" + }, + "description": "Do you want to migrate from midea_ac_lan", + "title": "Migrate from midea_ac_lan" } } }, diff --git a/hacs.json b/hacs.json index 8c1c58bb..7d44101e 100644 --- a/hacs.json +++ b/hacs.json @@ -1,7 +1,7 @@ { - "name": "Midea AC LAN", + "name": "Midea LAN", "render_readme": true, "homeassistant": "2023.3", "zip_release": true, - "filename": "midea_ac_lan.zip" + "filename": "midea_lan.zip" }