24 files
This commit is contained in:
@@ -47,15 +47,14 @@ from homeassistant.core import callback
|
||||
from homeassistant.helpers.aiohttp_client import async_create_clientsession
|
||||
from homeassistant.helpers.json import save_json
|
||||
from homeassistant.util.json import load_json
|
||||
from midealocal.cloud import (
|
||||
from midealan.cloud import (
|
||||
PRESET_ACCOUNT_DATA,
|
||||
SUPPORTED_CLOUDS,
|
||||
MideaCloud,
|
||||
get_midea_cloud,
|
||||
)
|
||||
from midealocal.device import AuthException, MideaDevice, ProtocolVersion
|
||||
from midealocal.discover import discover
|
||||
from midealocal.exceptions import SocketException
|
||||
from midealan.device import MideaDevice, ProtocolVersion
|
||||
from midealan.discover import discover
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiohttp import ClientSession
|
||||
@@ -144,7 +143,7 @@ class MideaLanConfigFlow(ConfigFlow, domain=DOMAIN): # type: ignore[call-arg]
|
||||
record_file = storage_path.joinpath(f"{data[CONF_DEVICE_ID]!s}.json")
|
||||
save_json(str(record_file), data)
|
||||
|
||||
def _load_device_config(self, device_id: str) -> Any: # ruff:ignore[any-type]
|
||||
def _load_device_config(self, device_id: int | str) -> Any: # ruff:ignore[any-type]
|
||||
"""Load device config from json file with device id.
|
||||
|
||||
Returns
|
||||
@@ -356,7 +355,7 @@ class MideaLanConfigFlow(ConfigFlow, domain=DOMAIN): # type: ignore[call-arg]
|
||||
|
||||
"""
|
||||
# get all devices list
|
||||
all_devices = discover()
|
||||
all_devices = await self.hass.async_add_executor_job(discover)
|
||||
# available devices exist
|
||||
if len(all_devices) > 0:
|
||||
table = (
|
||||
@@ -402,8 +401,10 @@ class MideaLanConfigFlow(ConfigFlow, domain=DOMAIN): # type: ignore[call-arg]
|
||||
# ip exist
|
||||
else:
|
||||
ip_address = discovery_info[CONF_IP_ADDRESS]
|
||||
# use midea-local discover() to get devices list with ip_address
|
||||
self.devices = discover(list(self.supports.keys()), ip_address=ip_address)
|
||||
# use midea-lan discover() to get devices list with ip_address
|
||||
self.devices = await self.hass.async_add_executor_job(
|
||||
lambda: discover(list(self.supports.keys()), ip_address=ip_address),
|
||||
)
|
||||
self.available_device = {}
|
||||
for device_id, device in self.devices.items():
|
||||
# remove exist devices and only return new devices
|
||||
@@ -512,17 +513,8 @@ class MideaLanConfigFlow(ConfigFlow, domain=DOMAIN): # type: ignore[call-arg]
|
||||
subtype=0,
|
||||
attributes={},
|
||||
)
|
||||
if dm.connect():
|
||||
try:
|
||||
dm.authenticate()
|
||||
except AuthException:
|
||||
_LOGGER.debug("Unable to authenticate.")
|
||||
dm.close_socket()
|
||||
except SocketException:
|
||||
_LOGGER.debug("Socket closed.")
|
||||
else:
|
||||
dm.close_socket()
|
||||
return value
|
||||
if await self.hass.async_add_executor_job(self._try_connect_device, dm):
|
||||
return value
|
||||
# return debug log with failed key
|
||||
_LOGGER.debug(
|
||||
"connect device using method %s token/key failed",
|
||||
@@ -533,6 +525,23 @@ class MideaLanConfigFlow(ConfigFlow, domain=DOMAIN): # type: ignore[call-arg]
|
||||
)
|
||||
return {"error": "connect_error"}
|
||||
|
||||
@staticmethod
|
||||
def _try_connect_device(dm: MideaDevice) -> bool:
|
||||
"""Connect to a device, closing the socket afterwards.
|
||||
|
||||
Runs the blocking socket I/O in an executor so it never blocks the
|
||||
event loop. V3 authentication is handled inside midea-lan's connect().
|
||||
|
||||
Returns
|
||||
-------
|
||||
True if the device connected successfully.
|
||||
|
||||
"""
|
||||
try:
|
||||
return dm.connect()
|
||||
finally:
|
||||
dm.close_socket()
|
||||
|
||||
async def async_step_auto(
|
||||
self,
|
||||
user_input: dict[str, Any] | None = None,
|
||||
@@ -559,7 +568,10 @@ class MideaLanConfigFlow(ConfigFlow, domain=DOMAIN): # type: ignore[call-arg]
|
||||
CONF_PORT: device.get(CONF_PORT),
|
||||
CONF_MODEL: device.get(CONF_MODEL),
|
||||
}
|
||||
storage_device = self._load_device_config(device_id)
|
||||
storage_device = await self.hass.async_add_executor_job(
|
||||
self._load_device_config,
|
||||
device_id,
|
||||
)
|
||||
# device config already exist, load from local json without cloud
|
||||
if self._check_storage_device(device, storage_device):
|
||||
self.found_device = {
|
||||
@@ -697,9 +709,8 @@ class MideaLanConfigFlow(ConfigFlow, domain=DOMAIN): # type: ignore[call-arg]
|
||||
if len(self.devices) < 1:
|
||||
ip = user_input[CONF_IP_ADDRESS]
|
||||
# discover device
|
||||
self.devices = discover(
|
||||
list(self.supports.keys()),
|
||||
ip_address=ip,
|
||||
self.devices = await self.hass.async_add_executor_job(
|
||||
lambda: discover(list(self.supports.keys()), ip_address=ip),
|
||||
)
|
||||
# discover result MUST exist
|
||||
if len(self.devices) != 1:
|
||||
@@ -778,40 +789,31 @@ class MideaLanConfigFlow(ConfigFlow, domain=DOMAIN): # type: ignore[call-arg]
|
||||
subtype=0,
|
||||
attributes={},
|
||||
)
|
||||
if dm.connect():
|
||||
try:
|
||||
if user_input[CONF_PROTOCOL] == ProtocolVersion.V3:
|
||||
dm.authenticate()
|
||||
except SocketException:
|
||||
_LOGGER.exception("Socket closed.")
|
||||
except AuthException:
|
||||
_LOGGER.exception(
|
||||
"Unable to authenticate with provided key and token.",
|
||||
)
|
||||
dm.close_socket()
|
||||
else:
|
||||
dm.close_socket()
|
||||
data = {
|
||||
CONF_NAME: user_input[CONF_NAME],
|
||||
CONF_DEVICE_ID: user_input[CONF_DEVICE_ID],
|
||||
CONF_TYPE: user_input[CONF_TYPE],
|
||||
CONF_PROTOCOL: user_input[CONF_PROTOCOL],
|
||||
CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS],
|
||||
CONF_PORT: user_input[CONF_PORT],
|
||||
CONF_MODEL: user_input[CONF_MODEL],
|
||||
CONF_SUBTYPE: user_input[CONF_SUBTYPE],
|
||||
CONF_TOKEN: user_input[CONF_TOKEN],
|
||||
CONF_KEY: user_input[CONF_KEY],
|
||||
CONF_MAC: device.get(CONF_MAC),
|
||||
CONF_SN: device.get(CONF_SN),
|
||||
}
|
||||
# save device json config when adding new device
|
||||
self._save_device_config(data)
|
||||
# finish add device entry
|
||||
return self.async_create_entry(
|
||||
title=f"{user_input[CONF_NAME]}",
|
||||
data=data,
|
||||
)
|
||||
if await self.hass.async_add_executor_job(
|
||||
self._try_connect_device,
|
||||
dm,
|
||||
):
|
||||
data = {
|
||||
CONF_NAME: user_input[CONF_NAME],
|
||||
CONF_DEVICE_ID: user_input[CONF_DEVICE_ID],
|
||||
CONF_TYPE: user_input[CONF_TYPE],
|
||||
CONF_PROTOCOL: user_input[CONF_PROTOCOL],
|
||||
CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS],
|
||||
CONF_PORT: user_input[CONF_PORT],
|
||||
CONF_MODEL: user_input[CONF_MODEL],
|
||||
CONF_SUBTYPE: user_input[CONF_SUBTYPE],
|
||||
CONF_TOKEN: user_input[CONF_TOKEN],
|
||||
CONF_KEY: user_input[CONF_KEY],
|
||||
CONF_MAC: device.get(CONF_MAC),
|
||||
CONF_SN: device.get(CONF_SN),
|
||||
}
|
||||
# save device json config when adding new device
|
||||
await self.hass.async_add_executor_job(self._save_device_config, data)
|
||||
# finish add device entry
|
||||
return self.async_create_entry(
|
||||
title=f"{user_input[CONF_NAME]}",
|
||||
data=data,
|
||||
)
|
||||
return await self.async_step_manually(
|
||||
error="Device auth failed with input config",
|
||||
)
|
||||
@@ -917,14 +919,12 @@ class MideaLanOptionsFlowHandler(OptionsFlow):
|
||||
self._device_type = config_entry.data.get(CONF_TYPE)
|
||||
if self._device_type is None:
|
||||
self._device_type = 0xAC
|
||||
if CONF_SENSORS in self._config_entry.options:
|
||||
for key in self._config_entry.options[CONF_SENSORS]:
|
||||
if key not in MIDEA_DEVICES[self._device_type]["entities"]:
|
||||
self._config_entry.options[CONF_SENSORS].remove(key)
|
||||
if CONF_SWITCHES in self._config_entry.options:
|
||||
for key in self._config_entry.options[CONF_SWITCHES]:
|
||||
if key not in MIDEA_DEVICES[self._device_type]["entities"]:
|
||||
self._config_entry.options[CONF_SWITCHES].remove(key)
|
||||
# Stale keys (attributes no longer in MIDEA_DEVICES) are filtered out
|
||||
# downstream in async_step_init, where the multi-select defaults are
|
||||
# computed as `set(sensors) & set(options)` / `set(switches) & ...` —
|
||||
# both `sensors` and `switches` are built only from valid entities. No
|
||||
# pruning is needed here; doing it in place mutated the list while
|
||||
# iterating (skipping elements) and mutated HA-owned entry state.
|
||||
|
||||
async def async_step_init(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user