This commit is contained in:
Home Assistant Version Control
2026-08-12 23:21:24 +00:00
parent 4517d1682f
commit f8ab9eda84
24 changed files with 625 additions and 468 deletions
+64 -44
View File
@@ -30,9 +30,9 @@ from homeassistant.const import (
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from midealocal.device import DeviceType, MideaDevice, ProtocolVersion from midealan.device import DeviceType, MideaDevice, ProtocolVersion
from midealocal.devices import device_selector from midealan.devices import device_selector
from midealocal.discover import discover from midealan.discover import discover
from .const import ( from .const import (
ALL_PLATFORM, ALL_PLATFORM,
@@ -52,31 +52,43 @@ from .midea_devices import MIDEA_DEVICES
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def _close_device(device: MideaDevice) -> None:
"""Close a Midea device connection without failing unload/setup cleanup."""
try:
device.close()
except (OSError, ConnectionError, AttributeError) as e:
_LOGGER.warning("Failed to close Midea socket cleanly: %s", e)
def _device_store(hass: HomeAssistant) -> dict[int, MideaDevice]:
"""Return the integration's loaded device map.
Returns
-------
Device id to Midea device mapping.
"""
return cast(
"dict[int, MideaDevice]",
hass.data.setdefault(DOMAIN, {}).setdefault(DEVICES, {}),
)
async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Option flow signal update. """Option flow signal update.
register update listener for config entry that will be called when entry is updated. Registered in `async_setup_entry` via
A listener is registered by adding the following to the `async_setup_entry`: `config_entry.async_on_unload(config_entry.add_update_listener(...))`, so it
`config_entry.async_on_unload(config_entry.add_update_listener(update_listener))` is attached when the entry loads and detached at unload. Reload the entry so
means the Listener is attached when the entry is loaded and detached at unload the changed options (customize JSON, IP override, refresh interval, and the
extra sensor/switch selection) are re-applied through the normal setup path.
Reloading avoids the previous fire-and-forget re-setup, which awaited the
platform unload but then re-forwarded setup via an untracked
`async_create_task` — swallowing any setup error and racing the customize/
ip/refresh application that followed.
""" """
# Forward the unloading of an entry to platforms. await hass.config_entries.async_reload(config_entry.entry_id)
await hass.config_entries.async_unload_platforms(config_entry, ALL_PLATFORM)
# forward the Config Entry to the platforms
hass.async_create_task(
hass.config_entries.async_forward_entry_setups(config_entry, ALL_PLATFORM),
)
device_id: int = cast("int", config_entry.data.get(CONF_DEVICE_ID))
customize = config_entry.options.get(CONF_CUSTOMIZE, "")
ip_address = config_entry.options.get(CONF_IP_ADDRESS, None)
refresh_interval = config_entry.options.get(CONF_REFRESH_INTERVAL, None)
dev: MideaDevice = hass.data[DOMAIN][DEVICES].get(device_id)
if dev:
dev.set_customize(customize)
if ip_address is not None:
dev.set_ip_address(ip_address)
if refresh_interval is not None:
dev.set_refresh_interval(refresh_interval)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # ruff:ignore[unused-function-argument] async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # ruff:ignore[unused-function-argument]
@@ -217,7 +229,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
if protocol == ProtocolVersion.V3 and (key == "" or token == ""): if protocol == ProtocolVersion.V3 and (key == "" or token == ""):
_LOGGER.error("For V3 devices, the key and the token is required") _LOGGER.error("For V3 devices, the key and the token is required")
return False return False
# device_selector in `midealocal/devices/__init__.py` # device_selector in `midealan/devices/__init__.py`
# hass core version >= 2024.3 # hass core version >= 2024.3
if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 3): if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 3):
device = await hass.async_add_import_executor_job( device = await hass.async_add_import_executor_job(
@@ -257,13 +269,17 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
if refresh_interval is not None: if refresh_interval is not None:
device.set_refresh_interval(refresh_interval) device.set_refresh_interval(refresh_interval)
device.open() device.open()
if DOMAIN not in hass.data: _device_store(hass)[device_id] = device
hass.data[DOMAIN] = {} try:
if DEVICES not in hass.data[DOMAIN]: # Forward the setup of an entry to all platforms
hass.data[DOMAIN][DEVICES] = {} await hass.config_entries.async_forward_entry_setups(
hass.data[DOMAIN][DEVICES][device_id] = device config_entry,
# Forward the setup of an entry to all platforms ALL_PLATFORM,
await hass.config_entries.async_forward_entry_setups(config_entry, ALL_PLATFORM) )
except Exception:
_device_store(hass).pop(device_id, None)
_close_device(device)
raise
# Listener `update_listener` is # Listener `update_listener` is
# attached when the entry is loaded # attached when the entry is loaded
# and detached when it's unloaded # and detached when it's unloaded
@@ -283,18 +299,22 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
device_type = config_entry.data.get(CONF_TYPE) device_type = config_entry.data.get(CONF_TYPE)
if device_type == CONF_ACCOUNT: if device_type == CONF_ACCOUNT:
return True return True
device_id = config_entry.data.get(CONF_DEVICE_ID) # Unload the platforms first; only tear the device down if that succeeded,
if device_id is not None: # and report the real result so a failed platform unload isn't masked.
dm = hass.data[DOMAIN][DEVICES].get(device_id) # bool() keeps mypy happy: async_unload_platforms is typed to return Any.
if dm is not None: unload_ok = bool(
try: await hass.config_entries.async_unload_platforms(
dm.close() config_entry,
except (OSError, ConnectionError, AttributeError) as e: ALL_PLATFORM,
_LOGGER.warning("Failed to close Midea socket cleanly: %s", e) ),
hass.data[DOMAIN][DEVICES].pop(device_id) )
# Forward the unloading of an entry to platforms if unload_ok:
await hass.config_entries.async_unload_platforms(config_entry, ALL_PLATFORM) device_id = config_entry.data.get(CONF_DEVICE_ID)
return True if device_id is not None:
dm = _device_store(hass).pop(device_id, None)
if dm is not None:
_close_device(dm)
return unload_ok
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
+24 -14
View File
@@ -40,17 +40,17 @@ from homeassistant.const import (
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from midealocal.device import DeviceType from midealan.device import DeviceType
from midealocal.devices.ac import DeviceAttributes as ACAttributes from midealan.devices.ac import DeviceAttributes as ACAttributes
from midealocal.devices.ac import MideaACDevice from midealan.devices.ac import MideaACDevice
from midealocal.devices.c3 import DeviceAttributes as C3Attributes from midealan.devices.c3 import DeviceAttributes as C3Attributes
from midealocal.devices.c3 import MideaC3Device from midealan.devices.c3 import MideaC3Device
from midealocal.devices.cc import DeviceAttributes as CCAttributes from midealan.devices.cc import DeviceAttributes as CCAttributes
from midealocal.devices.cc import MideaCCDevice from midealan.devices.cc import MideaCCDevice
from midealocal.devices.cf import DeviceAttributes as CFAttributes from midealan.devices.cf import DeviceAttributes as CFAttributes
from midealocal.devices.cf import MideaCFDevice from midealan.devices.cf import MideaCFDevice
from midealocal.devices.fb import DeviceAttributes as FBAttributes from midealan.devices.fb import DeviceAttributes as FBAttributes
from midealocal.devices.fb import MideaFBDevice from midealan.devices.fb import MideaFBDevice
from .const import DEVICES, DOMAIN, FanSpeed from .const import DEVICES, DOMAIN, FanSpeed
from .midea_devices import MIDEA_DEVICES from .midea_devices import MIDEA_DEVICES
@@ -146,7 +146,10 @@ class MideaClimate(MideaEntity, ClimateEntity):
"""Midea Climate hvac mode.""" """Midea Climate hvac mode."""
if self._device.get_attribute("power"): if self._device.get_attribute("power"):
mode = cast("int", self._device.get_attribute("mode")) mode = cast("int", self._device.get_attribute("mode"))
return self.hvac_modes[mode] # Guard against an out-of-range/undefined device mode so a malformed
# frame cannot raise IndexError on every state render.
if 0 <= mode < len(self.hvac_modes):
return self.hvac_modes[mode]
return HVACMode.OFF return HVACMode.OFF
@property @property
@@ -450,7 +453,10 @@ class MideaACClimate(MideaClimate):
"""Midea AC Climate hvac mode (device mode int -> fixed map).""" """Midea AC Climate hvac mode (device mode int -> fixed map)."""
if self._device.get_attribute("power"): if self._device.get_attribute("power"):
mode = cast("int", self._device.get_attribute("mode")) mode = cast("int", self._device.get_attribute("mode"))
return self._mode_index[mode] # The AC `mode` field is a 3-bit value (0-7) but `_mode_index` only
# maps 0-5; guard so an out-of-spec 6/7 cannot raise IndexError.
if 0 <= mode < len(self._mode_index):
return self._mode_index[mode]
return HVACMode.OFF return HVACMode.OFF
def set_hvac_mode(self, hvac_mode: HVACMode) -> None: def set_hvac_mode(self, hvac_mode: HVACMode) -> None:
@@ -814,7 +820,11 @@ class MideaC3Climate(MideaClimate):
def hvac_mode(self) -> HVACMode: def hvac_mode(self) -> HVACMode:
"""Midea C3 Climate hvac mode.""" """Midea C3 Climate hvac mode."""
mode = self._device.get_attribute(C3Attributes.mode) mode = self._device.get_attribute(C3Attributes.mode)
if self._device.get_attribute(self._power_attr) and isinstance(mode, int): if (
self._device.get_attribute(self._power_attr)
and isinstance(mode, int)
and 0 <= mode < len(self.hvac_modes)
):
return self.hvac_modes[mode] return self.hvac_modes[mode]
return HVACMode.OFF return HVACMode.OFF
+65 -65
View File
@@ -47,15 +47,14 @@ from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_create_clientsession from homeassistant.helpers.aiohttp_client import async_create_clientsession
from homeassistant.helpers.json import save_json from homeassistant.helpers.json import save_json
from homeassistant.util.json import load_json from homeassistant.util.json import load_json
from midealocal.cloud import ( from midealan.cloud import (
PRESET_ACCOUNT_DATA, PRESET_ACCOUNT_DATA,
SUPPORTED_CLOUDS, SUPPORTED_CLOUDS,
MideaCloud, MideaCloud,
get_midea_cloud, get_midea_cloud,
) )
from midealocal.device import AuthException, MideaDevice, ProtocolVersion from midealan.device import MideaDevice, ProtocolVersion
from midealocal.discover import discover from midealan.discover import discover
from midealocal.exceptions import SocketException
if TYPE_CHECKING: if TYPE_CHECKING:
from aiohttp import ClientSession 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") record_file = storage_path.joinpath(f"{data[CONF_DEVICE_ID]!s}.json")
save_json(str(record_file), data) 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. """Load device config from json file with device id.
Returns Returns
@@ -356,7 +355,7 @@ class MideaLanConfigFlow(ConfigFlow, domain=DOMAIN): # type: ignore[call-arg]
""" """
# get all devices list # get all devices list
all_devices = discover() all_devices = await self.hass.async_add_executor_job(discover)
# available devices exist # available devices exist
if len(all_devices) > 0: if len(all_devices) > 0:
table = ( table = (
@@ -402,8 +401,10 @@ class MideaLanConfigFlow(ConfigFlow, domain=DOMAIN): # type: ignore[call-arg]
# ip exist # ip exist
else: else:
ip_address = discovery_info[CONF_IP_ADDRESS] ip_address = discovery_info[CONF_IP_ADDRESS]
# use midea-local discover() to get devices list with ip_address # use midea-lan discover() to get devices list with ip_address
self.devices = discover(list(self.supports.keys()), ip_address=ip_address) self.devices = await self.hass.async_add_executor_job(
lambda: discover(list(self.supports.keys()), ip_address=ip_address),
)
self.available_device = {} self.available_device = {}
for device_id, device in self.devices.items(): for device_id, device in self.devices.items():
# remove exist devices and only return new devices # remove exist devices and only return new devices
@@ -512,17 +513,8 @@ class MideaLanConfigFlow(ConfigFlow, domain=DOMAIN): # type: ignore[call-arg]
subtype=0, subtype=0,
attributes={}, attributes={},
) )
if dm.connect(): if await self.hass.async_add_executor_job(self._try_connect_device, dm):
try: return value
dm.authenticate()
except AuthException:
_LOGGER.debug("Unable to authenticate.")
dm.close_socket()
except SocketException:
_LOGGER.debug("Socket closed.")
else:
dm.close_socket()
return value
# return debug log with failed key # return debug log with failed key
_LOGGER.debug( _LOGGER.debug(
"connect device using method %s token/key failed", "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"} 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( async def async_step_auto(
self, self,
user_input: dict[str, Any] | None = None, 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_PORT: device.get(CONF_PORT),
CONF_MODEL: device.get(CONF_MODEL), 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 # device config already exist, load from local json without cloud
if self._check_storage_device(device, storage_device): if self._check_storage_device(device, storage_device):
self.found_device = { self.found_device = {
@@ -697,9 +709,8 @@ class MideaLanConfigFlow(ConfigFlow, domain=DOMAIN): # type: ignore[call-arg]
if len(self.devices) < 1: if len(self.devices) < 1:
ip = user_input[CONF_IP_ADDRESS] ip = user_input[CONF_IP_ADDRESS]
# discover device # discover device
self.devices = discover( self.devices = await self.hass.async_add_executor_job(
list(self.supports.keys()), lambda: discover(list(self.supports.keys()), ip_address=ip),
ip_address=ip,
) )
# discover result MUST exist # discover result MUST exist
if len(self.devices) != 1: if len(self.devices) != 1:
@@ -778,40 +789,31 @@ class MideaLanConfigFlow(ConfigFlow, domain=DOMAIN): # type: ignore[call-arg]
subtype=0, subtype=0,
attributes={}, attributes={},
) )
if dm.connect(): if await self.hass.async_add_executor_job(
try: self._try_connect_device,
if user_input[CONF_PROTOCOL] == ProtocolVersion.V3: dm,
dm.authenticate() ):
except SocketException: data = {
_LOGGER.exception("Socket closed.") CONF_NAME: user_input[CONF_NAME],
except AuthException: CONF_DEVICE_ID: user_input[CONF_DEVICE_ID],
_LOGGER.exception( CONF_TYPE: user_input[CONF_TYPE],
"Unable to authenticate with provided key and token.", CONF_PROTOCOL: user_input[CONF_PROTOCOL],
) CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS],
dm.close_socket() CONF_PORT: user_input[CONF_PORT],
else: CONF_MODEL: user_input[CONF_MODEL],
dm.close_socket() CONF_SUBTYPE: user_input[CONF_SUBTYPE],
data = { CONF_TOKEN: user_input[CONF_TOKEN],
CONF_NAME: user_input[CONF_NAME], CONF_KEY: user_input[CONF_KEY],
CONF_DEVICE_ID: user_input[CONF_DEVICE_ID], CONF_MAC: device.get(CONF_MAC),
CONF_TYPE: user_input[CONF_TYPE], CONF_SN: device.get(CONF_SN),
CONF_PROTOCOL: user_input[CONF_PROTOCOL], }
CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS], # save device json config when adding new device
CONF_PORT: user_input[CONF_PORT], await self.hass.async_add_executor_job(self._save_device_config, data)
CONF_MODEL: user_input[CONF_MODEL], # finish add device entry
CONF_SUBTYPE: user_input[CONF_SUBTYPE], return self.async_create_entry(
CONF_TOKEN: user_input[CONF_TOKEN], title=f"{user_input[CONF_NAME]}",
CONF_KEY: user_input[CONF_KEY], data=data,
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,
)
return await self.async_step_manually( return await self.async_step_manually(
error="Device auth failed with input config", error="Device auth failed with input config",
) )
@@ -917,14 +919,12 @@ class MideaLanOptionsFlowHandler(OptionsFlow):
self._device_type = config_entry.data.get(CONF_TYPE) self._device_type = config_entry.data.get(CONF_TYPE)
if self._device_type is None: if self._device_type is None:
self._device_type = 0xAC self._device_type = 0xAC
if CONF_SENSORS in self._config_entry.options: # Stale keys (attributes no longer in MIDEA_DEVICES) are filtered out
for key in self._config_entry.options[CONF_SENSORS]: # downstream in async_step_init, where the multi-select defaults are
if key not in MIDEA_DEVICES[self._device_type]["entities"]: # computed as `set(sensors) & set(options)` / `set(switches) & ...` —
self._config_entry.options[CONF_SENSORS].remove(key) # both `sensors` and `switches` are built only from valid entities. No
if CONF_SWITCHES in self._config_entry.options: # pruning is needed here; doing it in place mutated the list while
for key in self._config_entry.options[CONF_SWITCHES]: # iterating (skipping elements) and mutated HA-owned entry state.
if key not in MIDEA_DEVICES[self._device_type]["entities"]:
self._config_entry.options[CONF_SWITCHES].remove(key)
async def async_step_init( async def async_step_init(
self, self,
+16 -10
View File
@@ -12,15 +12,15 @@ from homeassistant.const import (
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from midealocal.device import DeviceType from midealan.device import DeviceType
from midealocal.devices.ac import DeviceAttributes as ACAttributes from midealan.devices.ac import DeviceAttributes as ACAttributes
from midealocal.devices.ac import MideaACDevice from midealan.devices.ac import MideaACDevice
from midealocal.devices.b6 import MideaB6Device from midealan.devices.b6 import MideaB6Device
from midealocal.devices.ce import DeviceAttributes as CEAttributes from midealan.devices.ce import DeviceAttributes as CEAttributes
from midealocal.devices.ce import MideaCEDevice from midealan.devices.ce import MideaCEDevice
from midealocal.devices.fa import MideaFADevice from midealan.devices.fa import MideaFADevice
from midealocal.devices.x40 import DeviceAttributes as X40Attributes from midealan.devices.x40 import DeviceAttributes as X40Attributes
from midealocal.devices.x40 import MideaX40Device from midealan.devices.x40 import MideaX40Device
from .const import DEVICES, DOMAIN from .const import DEVICES, DOMAIN
from .midea_devices import ( from .midea_devices import (
@@ -122,7 +122,13 @@ class MideaFan(MideaEntity, FanEntity):
def set_preset_mode(self, preset_mode: str) -> None: def set_preset_mode(self, preset_mode: str) -> None:
"""Midea Fan set preset mode.""" """Midea Fan set preset mode."""
self._device.set_attribute(attr="mode", value=preset_mode.capitalize()) # Pass the preset value through unchanged. `preset_modes` already
# returns the device's own strings and HA only ever hands one of them
# back, so the value is guaranteed valid. Do NOT `.capitalize()` it:
# that corrupts multi-word / mixed-case names such as the CE fan's
# "ECO mode" -> "Eco mode", which the device matches case-sensitively
# and silently drops, making the ECO preset unselectable.
self._device.set_attribute(attr="mode", value=preset_mode)
@property @property
def percentage(self) -> int | None: def percentage(self) -> int | None:
+3 -3
View File
@@ -12,9 +12,9 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from midealocal.device import DeviceType from midealan.device import DeviceType
from midealocal.devices.a1 import MideaA1Device from midealan.devices.a1 import MideaA1Device
from midealocal.devices.fd import MideaFDDevice from midealan.devices.fd import MideaFDDevice
from .const import DEVICES, DOMAIN from .const import DEVICES, DOMAIN
from .midea_devices import MIDEA_DEVICES from .midea_devices import MIDEA_DEVICES
+16
View File
@@ -0,0 +1,16 @@
{
"entity": {
"climate": {
"climate_key": {
"state_attributes": {
"fan_mode": {
"state": {
"silent": "mdi:fan-clock",
"full": "mdi:fan-alert"
}
}
}
}
}
}
}
+26 -12
View File
@@ -15,8 +15,8 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from midealocal.devices.x13 import DeviceAttributes as X13Attributes from midealan.devices.x13 import DeviceAttributes as X13Attributes
from midealocal.devices.x13 import Midea13Device from midealan.devices.x13 import Midea13Device
from .const import DEVICES, DOMAIN from .const import DEVICES, DOMAIN
from .midea_devices import MIDEA_DEVICES from .midea_devices import MIDEA_DEVICES
@@ -73,18 +73,32 @@ def _calc_supported_color_modes(device: Midea13Device) -> set[ColorMode]:
class MideaLight(MideaEntity, LightEntity): class MideaLight(MideaEntity, LightEntity):
"""Midea Light Entries.""" """Midea Light Entries."""
_attr_color_mode: ColorMode | str | None = None
_attr_supported_color_modes: set[ColorMode] | set[str] | None = None
_attr_supported_features: LightEntityFeature = LightEntityFeature(0)
_device: Midea13Device _device: Midea13Device
def __init__(self, device: Midea13Device, entity_key: str) -> None: # NOTE: supported_features / supported_color_modes / color_mode are computed
"""Midea Light entity init.""" # as properties (not latched in __init__). The device starts its socket
super().__init__(device, entity_key) # refresh on a background thread, so at construction time brightness,
self._attr_supported_features = _calc_supported_features(device) # color_temperature and rgb_color are all still None. Latching these in
self._attr_supported_color_modes = _calc_supported_color_modes(device) # __init__ would lock a dimmable/color-temp light to ONOFF forever, because
self._attr_color_mode = self._calc_color_mode(self._attr_supported_color_modes) # update_state never recomputes them. Recomputing per access reflects the
# capabilities once the first status arrives.
@property
def supported_features(self) -> LightEntityFeature:
"""Midea Light supported features."""
return _calc_supported_features(self._device)
@property
def supported_color_modes(self) -> set[ColorMode] | set[str] | None:
"""Midea Light supported color modes."""
return _calc_supported_color_modes(self._device)
@property
def color_mode(self) -> ColorMode | str | None:
"""Midea Light current color mode."""
# Call the concrete helper (returns set[ColorMode]) rather than the
# supported_color_modes property, whose type is widened to
# set[ColorMode] | set[str] | None to match LightEntity's base override.
return self._calc_color_mode(_calc_supported_color_modes(self._device))
def _calc_color_mode(self, supported: set[ColorMode]) -> ColorMode: def _calc_color_mode(self, supported: set[ColorMode]) -> ColorMode:
"""Midea Light calculate color mode. """Midea Light calculate color mode.
+14 -5
View File
@@ -1,14 +1,23 @@
{ {
"domain": "midea_ac_lan", "domain": "midea_ac_lan",
"name": "Midea AC LAN", "name": "Midea AC LAN",
"codeowners": ["@wuwentao", "@rokam", "@chemelli74", "@Necroneco"], "codeowners": [
"@wuwentao",
"@rokam",
"@chemelli74",
"@caibinqing"
],
"config_flow": true, "config_flow": true,
"dependencies": [], "dependencies": [],
"documentation": "https://github.com/wuwentao/midea_ac_lan#readme", "documentation": "https://github.com/wuwentao/midea_ac_lan#readme",
"integration_type": "device", "integration_type": "device",
"iot_class": "local_push", "iot_class": "local_push",
"issue_tracker": "https://github.com/wuwentao/midea_ac_lan/issues", "issue_tracker": "https://github.com/wuwentao/midea_ac_lan/issues",
"loggers": ["midealocal"], "loggers": [
"requirements": ["midea-local==6.11.1"], "midealan"
"version": "0.7.1" ],
} "requirements": [
"midea-lan==2026.8.0"
],
"version": "2026.8.0"
}
+91 -53
View File
@@ -5,8 +5,8 @@ from typing import Any
from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
from homeassistant.const import ( from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, MAJOR_VERSION,
CONCENTRATION_PARTS_PER_MILLION, MINOR_VERSION,
PERCENTAGE, PERCENTAGE,
REVOLUTIONS_PER_MINUTE, REVOLUTIONS_PER_MINUTE,
Platform, Platform,
@@ -19,40 +19,61 @@ from homeassistant.const import (
UnitOfTime, UnitOfTime,
UnitOfVolume, UnitOfVolume,
) )
from midealocal.devices.a1 import DeviceAttributes as A1Attributes
from midealocal.devices.ac import DeviceAttributes as ACAttributes # HA 2026.7 added UnitOfDensity/UnitOfRatio, and HA 2026.8 started deprecating
from midealocal.devices.ad import DeviceAttributes as ADAttributes # CONCENTRATION_MICROGRAMS_PER_CUBIC_METER / CONCENTRATION_PARTS_PER_MILLION in favor of
from midealocal.devices.b0 import DeviceAttributes as B0Attributes # them (scheduled for removal in HA 2027.8). Both old and new names resolve to the
from midealocal.devices.b1 import DeviceAttributes as B1Attributes # identical runtime string ("μg/m³" / "ppm"), so this is purely cosmetic. This
from midealocal.devices.b3 import DeviceAttributes as B3Attributes # integration's floor is HA 2024.4.1, where UnitOfDensity/UnitOfRatio do not exist yet,
from midealocal.devices.b4 import DeviceAttributes as B4Attributes # so branch on the HA version like the rest of this codebase does for newer HA APIs (see
from midealocal.devices.b6 import DeviceAttributes as B6Attributes # other MAJOR_VERSION/MINOR_VERSION usages, e.g. midea_entity.py).
from midealocal.devices.bf import DeviceAttributes as BFAttributes if (MAJOR_VERSION, MINOR_VERSION) >= (2026, 7):
from midealocal.devices.c2 import DeviceAttributes as C2Attributes from homeassistant.const import ( # pylint: disable=E0611
from midealocal.devices.c3 import DeviceAttributes as C3Attributes UnitOfDensity,
from midealocal.devices.ca import DeviceAttributes as CAAttributes UnitOfRatio,
from midealocal.devices.cc import DeviceAttributes as CCAttributes )
from midealocal.devices.cd import DeviceAttributes as CDAttributes
from midealocal.devices.ce import DeviceAttributes as CEAttributes CONCENTRATION_MICROGRAMS_PER_CUBIC_METER = UnitOfDensity.MICROGRAMS_PER_CUBIC_METER
from midealocal.devices.cf import DeviceAttributes as CFAttributes CONCENTRATION_PARTS_PER_MILLION = UnitOfRatio.PARTS_PER_MILLION
from midealocal.devices.da import DeviceAttributes as DAAttributes else:
from midealocal.devices.db import DeviceAttributes as DBAttributes from homeassistant.const import ( # type: ignore[no-redef]
from midealocal.devices.dc import DeviceAttributes as DCAttributes CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
from midealocal.devices.e1 import DeviceAttributes as E1Attributes CONCENTRATION_PARTS_PER_MILLION,
from midealocal.devices.e2 import DeviceAttributes as E2Attributes )
from midealocal.devices.e3 import DeviceAttributes as E3Attributes from midealan.devices.a1 import DeviceAttributes as A1Attributes
from midealocal.devices.e6 import DeviceAttributes as E6Attributes from midealan.devices.ac import DeviceAttributes as ACAttributes
from midealocal.devices.e8 import DeviceAttributes as E8Attributes from midealan.devices.ad import DeviceAttributes as ADAttributes
from midealocal.devices.ea import DeviceAttributes as EAAttributes from midealan.devices.b0 import DeviceAttributes as B0Attributes
from midealocal.devices.ec import DeviceAttributes as ECAttributes from midealan.devices.b1 import DeviceAttributes as B1Attributes
from midealocal.devices.ed import DeviceAttributes as EDAttributes from midealan.devices.b3 import DeviceAttributes as B3Attributes
from midealocal.devices.fa import DeviceAttributes as FAAttributes from midealan.devices.b4 import DeviceAttributes as B4Attributes
from midealocal.devices.fb import DeviceAttributes as FBAttributes from midealan.devices.b6 import DeviceAttributes as B6Attributes
from midealocal.devices.fc import DeviceAttributes as FCAttributes from midealan.devices.bf import DeviceAttributes as BFAttributes
from midealocal.devices.fd import DeviceAttributes as FDAttributes from midealan.devices.c2 import DeviceAttributes as C2Attributes
from midealocal.devices.x26 import DeviceAttributes as X26Attributes from midealan.devices.c3 import DeviceAttributes as C3Attributes
from midealocal.devices.x34 import DeviceAttributes as X34Attributes from midealan.devices.ca import DeviceAttributes as CAAttributes
from midealocal.devices.x40 import DeviceAttributes as X40Attributes from midealan.devices.cc import DeviceAttributes as CCAttributes
from midealan.devices.cd import DeviceAttributes as CDAttributes
from midealan.devices.ce import DeviceAttributes as CEAttributes
from midealan.devices.cf import DeviceAttributes as CFAttributes
from midealan.devices.da import DeviceAttributes as DAAttributes
from midealan.devices.db import DeviceAttributes as DBAttributes
from midealan.devices.dc import DeviceAttributes as DCAttributes
from midealan.devices.e1 import DeviceAttributes as E1Attributes
from midealan.devices.e2 import DeviceAttributes as E2Attributes
from midealan.devices.e3 import DeviceAttributes as E3Attributes
from midealan.devices.e6 import DeviceAttributes as E6Attributes
from midealan.devices.e8 import DeviceAttributes as E8Attributes
from midealan.devices.ea import DeviceAttributes as EAAttributes
from midealan.devices.ec import DeviceAttributes as ECAttributes
from midealan.devices.ed import DeviceAttributes as EDAttributes
from midealan.devices.fa import DeviceAttributes as FAAttributes
from midealan.devices.fb import DeviceAttributes as FBAttributes
from midealan.devices.fc import DeviceAttributes as FCAttributes
from midealan.devices.fd import DeviceAttributes as FDAttributes
from midealan.devices.x26 import DeviceAttributes as X26Attributes
from midealan.devices.x34 import DeviceAttributes as X34Attributes
from midealan.devices.x40 import DeviceAttributes as X40Attributes
FRESH_AIR_EXHAUST = "fresh_air_exhaust" FRESH_AIR_EXHAUST = "fresh_air_exhaust"
FRESH_AIR_EXHAUST_MODE = "fresh_air_exhaust_mode" FRESH_AIR_EXHAUST_MODE = "fresh_air_exhaust_mode"
@@ -647,28 +668,28 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = {
"unit": UnitOfElectricPotential.VOLT, "unit": UnitOfElectricPotential.VOLT,
"state_class": SensorStateClass.MEASUREMENT, "state_class": SensorStateClass.MEASUREMENT,
}, },
ACAttributes.indoor_coil_temperature: { ACAttributes.indoor_ambient_temperature: {
"type": Platform.SENSOR, "type": Platform.SENSOR,
"required_attribute": ACAttributes.indoor_coil_temperature, "required_attribute": ACAttributes.indoor_ambient_temperature,
"translation_key": "indoor_coil_temperature", "translation_key": "indoor_ambient_temperature",
"name": "Indoor Coil Temperature (T1)", "name": "Indoor Coil Temperature (T1)",
"device_class": SensorDeviceClass.TEMPERATURE, "device_class": SensorDeviceClass.TEMPERATURE,
"unit": UnitOfTemperature.CELSIUS, "unit": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT, "state_class": SensorStateClass.MEASUREMENT,
}, },
ACAttributes.evaporator_temperature: { ACAttributes.indoor_coil_temperature: {
"type": Platform.SENSOR, "type": Platform.SENSOR,
"required_attribute": ACAttributes.evaporator_temperature, "required_attribute": ACAttributes.indoor_coil_temperature,
"translation_key": "evaporator_temperature", "translation_key": "indoor_coil_temperature",
"name": "Evaporator Temperature (T2)", "name": "Evaporator Temperature (T2)",
"device_class": SensorDeviceClass.TEMPERATURE, "device_class": SensorDeviceClass.TEMPERATURE,
"unit": UnitOfTemperature.CELSIUS, "unit": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT, "state_class": SensorStateClass.MEASUREMENT,
}, },
ACAttributes.condenser_temperature: { ACAttributes.outdoor_coil_temperature: {
"type": Platform.SENSOR, "type": Platform.SENSOR,
"required_attribute": ACAttributes.condenser_temperature, "required_attribute": ACAttributes.outdoor_coil_temperature,
"translation_key": "condenser_temperature", "translation_key": "outdoor_coil_temperature",
"name": "Condenser Temperature (T3)", "name": "Condenser Temperature (T3)",
"device_class": SensorDeviceClass.TEMPERATURE, "device_class": SensorDeviceClass.TEMPERATURE,
"unit": UnitOfTemperature.CELSIUS, "unit": UnitOfTemperature.CELSIUS,
@@ -904,6 +925,14 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = {
"icon": "mdi:box-shadow", "icon": "mdi:box-shadow",
"device_class": BinarySensorDeviceClass.DOOR, "device_class": BinarySensorDeviceClass.DOOR,
}, },
# Deliberately NO device_class here. BinarySensorDeviceClass.LOCK
# defines on = UNLOCKED, but this attribute is True when the child
# lock is ENGAGED, so that device class would display it inverted.
B0Attributes.child_lock: {
"type": Platform.BINARY_SENSOR,
"name": "Child Lock",
"icon": "mdi:lock",
},
B0Attributes.tank_ejected: { B0Attributes.tank_ejected: {
"type": Platform.BINARY_SENSOR, "type": Platform.BINARY_SENSOR,
"translation_key": "tank_ejected", "translation_key": "tank_ejected",
@@ -938,6 +967,16 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = {
"name": "Status", "name": "Status",
"icon": "mdi:information", "icon": "mdi:information",
}, },
B0Attributes.mode: {
"type": Platform.SENSOR,
"name": "Mode",
"icon": "mdi:chef-hat",
},
B0Attributes.fire_power: {
"type": Platform.SENSOR,
"name": "Fire Power",
"icon": "mdi:fire",
},
B0Attributes.time_remaining: { B0Attributes.time_remaining: {
"type": Platform.SENSOR, "type": Platform.SENSOR,
"translation_key": "time_remaining", "translation_key": "time_remaining",
@@ -1022,7 +1061,7 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = {
"type": Platform.BINARY_SENSOR, "type": Platform.BINARY_SENSOR,
"translation_key": "top_compartment_cooling", "translation_key": "top_compartment_cooling",
"name": "Top Compartment Cooling", "name": "Top Compartment Cooling",
"icon": "snowflake-variant", "icon": "mdi:snowflake-variant",
"device_class": BinarySensorDeviceClass.RUNNING, "device_class": BinarySensorDeviceClass.RUNNING,
}, },
B3Attributes.middle_compartment_door: { B3Attributes.middle_compartment_door: {
@@ -1043,7 +1082,7 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = {
"type": Platform.BINARY_SENSOR, "type": Platform.BINARY_SENSOR,
"translation_key": "middle_compartment_cooling", "translation_key": "middle_compartment_cooling",
"name": "Middle Compartment Cooling", "name": "Middle Compartment Cooling",
"icon": "snowflake-variant", "icon": "mdi:snowflake-variant",
"device_class": BinarySensorDeviceClass.RUNNING, "device_class": BinarySensorDeviceClass.RUNNING,
}, },
B3Attributes.bottom_compartment_door: { B3Attributes.bottom_compartment_door: {
@@ -1064,7 +1103,7 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = {
"type": Platform.BINARY_SENSOR, "type": Platform.BINARY_SENSOR,
"translation_key": "bottom_compartment_cooling", "translation_key": "bottom_compartment_cooling",
"name": "Bottom Compartment Cooling", "name": "Bottom Compartment Cooling",
"icon": "snowflake-variant", "icon": "mdi:snowflake-variant",
"device_class": BinarySensorDeviceClass.RUNNING, "device_class": BinarySensorDeviceClass.RUNNING,
}, },
B3Attributes.top_compartment_status: { B3Attributes.top_compartment_status: {
@@ -1434,7 +1473,7 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = {
"name": "Silent Mode", "name": "Silent Mode",
"icon": "mdi:fan-remove", "icon": "mdi:fan-remove",
}, },
C3Attributes.SILENT_LEVEL: { C3Attributes.silent_level: {
"type": Platform.SELECT, "type": Platform.SELECT,
"translation_key": "silent_level", "translation_key": "silent_level",
"name": "Silent Level", "name": "Silent Level",
@@ -1750,7 +1789,7 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = {
"entities": { "entities": {
"climate": { "climate": {
"type": Platform.CLIMATE, "type": Platform.CLIMATE,
"icon": "hass:air-conditioner", "icon": "mdi:air-conditioner",
"default": True, "default": True,
}, },
CCAttributes.aux_heating: { CCAttributes.aux_heating: {
@@ -2161,7 +2200,7 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = {
"entities": { "entities": {
"climate": { "climate": {
"type": Platform.CLIMATE, "type": Platform.CLIMATE,
"icon": "hass:air-conditioner", "icon": "mdi:air-conditioner",
"default": True, "default": True,
}, },
CFAttributes.aux_heating: { CFAttributes.aux_heating: {
@@ -2247,7 +2286,7 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = {
DAAttributes.wash_level: { DAAttributes.wash_level: {
"type": Platform.SENSOR, "type": Platform.SENSOR,
"translation_key": "wash_level", "translation_key": "wash_level",
"name": "Rinse count", "name": "Wash level",
"icon": "mdi:hydraulic-oil-level", "icon": "mdi:hydraulic-oil-level",
}, },
DAAttributes.wash_strength: { DAAttributes.wash_strength: {
@@ -2922,7 +2961,6 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = {
"type": Platform.BINARY_SENSOR, "type": Platform.BINARY_SENSOR,
"translation_key": "finished", "translation_key": "finished",
"name": "Finished", "name": "Finished",
"icon": "",
}, },
E8Attributes.water_shortage: { E8Attributes.water_shortage: {
"type": Platform.BINARY_SENSOR, "type": Platform.BINARY_SENSOR,
@@ -14,7 +14,7 @@ else:
) )
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, format_mac from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, format_mac
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from midealocal.device import MideaDevice from midealan.device import MideaDevice
from .const import DOMAIN from .const import DOMAIN
from .midea_devices import MIDEA_DEVICES from .midea_devices import MIDEA_DEVICES
@@ -79,13 +79,15 @@ class MideaEntity(Entity):
# Example: device_class = temperature -> "Temperature". # Example: device_class = temperature -> "Temperature".
elif "device_class" in self._config: elif "device_class" in self._config:
self._attr_name = None # Let HA generate from device_class self._attr_name = None # Let HA generate from device_class
# Step 4: Nothing available, # Step 4: Nothing available (no translation_key, no name, no
# device_class). With has_entity_name=True HA already prepends the
# device name, so the entity's own name must be None — the entity then
# represents the device's main feature and shows just the device name.
# (The previous fallback embedded the device name itself, yielding a
# doubled "DeviceName DeviceName", or "DeviceName None" when a null
# "name" key was present.)
else: else:
self._attr_name = ( self._attr_name = None
f"{self._device_name} {self._config.get('name')}"
if "name" in self._config
else f"{self._device_name}"
)
@property @property
def device(self) -> MideaDevice: def device(self) -> MideaDevice:
+25 -37
View File
@@ -7,7 +7,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from midealocal.device import MideaDevice from midealan.device import MideaDevice
from .const import DEVICES, DOMAIN from .const import DEVICES, DOMAIN
from .midea_devices import MIDEA_DEVICES from .midea_devices import MIDEA_DEVICES
@@ -44,53 +44,41 @@ class MideaNumber(MideaEntity, NumberEntity):
self._min_value = self._config.get("min") self._min_value = self._config.get("min")
self._step_value = self._config.get("step") self._step_value = self._config.get("step")
def _resolve_bound(self, bound: Any) -> float: # ruff:ignore[any-type]
"""Resolve a min/max/step config value to a concrete number.
A numeric literal is used as-is. Otherwise the value is treated as an
attribute name: prefer the device attribute of that name, falling back
to a same-named device property populated by ``set_customize``.
Returns
-------
The resolved bound as a float.
"""
if isinstance(bound, (int, float)):
return cast("float", bound)
# `bound` is an attribute name. Use `is not None` (not truthiness) so a
# legitimate 0 is not treated as "missing", and read the attribute once.
value = self._device.get_attribute(attr=bound)
if value is None:
value = getattr(self._device, bound)
return cast("float", value)
@property @property
def native_min_value(self) -> float: def native_min_value(self) -> float:
"""Minimum value allowed.""" """Minimum value allowed."""
return cast( return self._resolve_bound(self._min_value)
"float",
(
self._min_value
if isinstance(self._min_value, int)
else (
self._device.get_attribute(attr=self._min_value)
if self._device.get_attribute(attr=self._min_value)
else getattr(self._device, self._min_value)
)
),
)
@property @property
def native_max_value(self) -> float: def native_max_value(self) -> float:
"""Maximum value allowed.""" """Maximum value allowed."""
return cast( return self._resolve_bound(self._max_value)
"float",
(
self._max_value
if isinstance(self._max_value, int)
else (
self._device.get_attribute(attr=self._max_value)
if self._device.get_attribute(attr=self._max_value)
else getattr(self._device, self._max_value)
)
),
)
@property @property
def native_step(self) -> float: def native_step(self) -> float:
"""Step value between allowed values.""" """Step value between allowed values."""
return cast( return self._resolve_bound(self._step_value)
"float",
(
self._step_value
if isinstance(self._step_value, int)
else (
self._device.get_attribute(attr=self._step_value)
if self._device.get_attribute(attr=self._step_value)
else getattr(self._device, self._step_value)
)
),
)
@property @property
def native_value(self) -> float: def native_value(self) -> float:
+5 -6
View File
@@ -7,14 +7,14 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from midealocal.device import MideaDevice from midealan.device import MideaDevice
from .const import DEVICES, DOMAIN, supports_model from .const import DEVICES, DOMAIN, supports_model
from .midea_devices import MIDEA_DEVICES from .midea_devices import MIDEA_DEVICES
from .midea_entity import MideaEntity from .midea_entity import MideaEntity
if TYPE_CHECKING: if TYPE_CHECKING:
from midealocal.devices.e1 import MideaE1Device from midealan.devices.e1 import MideaE1Device
async def async_setup_entry( async def async_setup_entry(
@@ -94,7 +94,7 @@ class MideaSelect(MideaEntity, SelectEntity):
self._device.set_attribute(self._attribute_key, option) self._device.set_attribute(self._attribute_key, option)
def _get_options_dict(self) -> dict[int, str]: def _get_options_dict(self) -> dict[int, str]:
"""Return option dict from the backing midea-local device. """Return option dict from the backing midea-lan device.
Returns Returns
------- -------
@@ -104,7 +104,7 @@ class MideaSelect(MideaEntity, SelectEntity):
return cast("dict[int, str]", getattr(self._device, self._options_dict_name)) return cast("dict[int, str]", getattr(self._device, self._options_dict_name))
def _select_e1_work_mode(self, option: str) -> None: def _select_e1_work_mode(self, option: str) -> None:
"""Set dishwasher work mode via midea-local's public E1 API. """Set dishwasher work mode via midea-lan's public E1 API.
Raises Raises
------ ------
@@ -125,10 +125,9 @@ class MideaSelect(MideaEntity, SelectEntity):
if ( if (
power_attribute power_attribute
and self.hass and self.hass
and not self.hass.is_stopping
and (self._attribute_key in status or power_attribute in status) and (self._attribute_key in status or power_attribute in status)
): ):
self.schedule_update_ha_state() self.schedule_update_if_running()
@staticmethod @staticmethod
def _get_dict_key_by_value(source: dict[int, str], value: str) -> int | None: def _get_dict_key_by_value(source: dict[int, str], value: str) -> int | None:
+4 -6
View File
@@ -13,7 +13,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from midealocal.device import MideaDevice from midealan.device import MideaDevice
from .const import DEVICES, DOMAIN, supports_model from .const import DEVICES, DOMAIN, supports_model
from .midea_devices import MIDEA_DEVICES from .midea_devices import MIDEA_DEVICES
@@ -185,9 +185,7 @@ class MideaEstimatedUsageSensor(MideaSensor, RestoreEntity):
self._last_progress = current_progress self._last_progress = current_progress
super().update_state(status) super().update_state(status)
if ( if self.hass and (
self.hass "progress" in status or "status" in status or "mode" in status
and not self.hass.is_stopping
and ("progress" in status or "status" in status or "mode" in status)
): ):
self.schedule_update_ha_state() self.schedule_update_if_running()
@@ -1,7 +1,7 @@
{ {
"config": { "config": {
"error": { "error": {
"preset_account": "Anmeldung mit voreingestelltem Konto fehlgeschlagen, bitte melden Sie dieses Problem", "preset_account": "Anmeldung mit voreingestelltem Konto fehlgeschlagen, bitte dieses Problem melden",
"login_failed": "Anmeldung fehlgeschlagen, Konto oder Passwort ist falsch", "login_failed": "Anmeldung fehlgeschlagen, Konto oder Passwort ist falsch",
"no_devices": "Keine neuen verfügbaren Geräte im Netzwerk gefunden", "no_devices": "Keine neuen verfügbaren Geräte im Netzwerk gefunden",
"device_exist": "Das Gerät ist bereits konfiguriert", "device_exist": "Das Gerät ist bereits konfiguriert",
@@ -14,7 +14,7 @@
"data": { "data": {
"way": "Hinzufügen von Geräten" "way": "Hinzufügen von Geräten"
}, },
"description": "Wählen Sie die Art des Hinzufügens eines Geräts", "description": "Die Art des Hinzufügens eines Geräts wählen",
"title": "Neues Gerät hinzufügen" "title": "Neues Gerät hinzufügen"
}, },
"login": { "login": {
@@ -22,11 +22,11 @@
"account": "Konto", "account": "Konto",
"password": "Passwort" "password": "Passwort"
}, },
"description": "Melden Sie sich mit Ihrem Midea-Konto an. Ihr Konto wird nur verwendet, um die Geräteinformationen zu erhalten.\nSie können die Konfiguration entfernen, nachdem alle Geräte konfiguriert wurden.", "description": "Mit dem Midea-Konto anmelden. Das Konto wird nur verwendet, um die Geräteinformationen zu erhalten.\nDie Konfiguration kann wieder entfernt werden, nachdem alle Geräte konfiguriert wurden.",
"title": "Login" "title": "Login"
}, },
"discovery": { "discovery": {
"description": "IP-Adresse des Geräts, geben Sie \"auto\" ein, um es automatisch zu finden.\nSie können auch eine IP-Adresse verwenden, um innerhalb eines bestimmten Netzwerks zu suchen, z.B. \"192.168.1.255\".", "description": "IP-Adresse des Geräts, \"auto\" eingeben, um es automatisch zu finden.\nEs kann auch eine IP-Adresse verwendet werden, um innerhalb eines bestimmten Netzwerks zu suchen, z.B. \"192.168.1.255\".",
"title": "Suche", "title": "Suche",
"data": { "data": {
"ip_address": "IP-Adresse" "ip_address": "IP-Adresse"
@@ -40,7 +40,7 @@
"data": { "data": {
"device": "Geräte" "device": "Geräte"
}, },
"description": "Wählen Sie ein Gerät zum Hinzufügen", "description": "Gerät auswählen um es hinzufügen",
"title": "Neues Gerät gefunden" "title": "Neues Gerät gefunden"
}, },
"manually": { "manually": {
@@ -64,85 +64,85 @@
"entity": { "entity": {
"binary_sensor": { "binary_sensor": {
"bar_door": { "bar_door": {
"name": "Bar Door" "name": "Bar-Tür"
}, },
"bar_door_overtime": { "bar_door_overtime": {
"name": "Bar Door Overtime" "name": "Bar-Tür Overtime"
}, },
"bathing_working": { "bathing_working": {
"name": "Bathing Working Status" "name": "Bathing Working Status"
}, },
"bottom_compartment_cooling": { "bottom_compartment_cooling": {
"name": "Bottom Compartment Cooling" "name": "Bodenfach Kühlung"
}, },
"bottom_compartment_door": { "bottom_compartment_door": {
"name": "Bottom Compartment Door" "name": "Bodenfach Tür"
}, },
"bottom_compartment_preheating": { "bottom_compartment_preheating": {
"name": "Bottom Compartment Preheating" "name": "Bodenfach Vorheizer"
}, },
"burning_state": { "burning_state": {
"name": "Burning State" "name": "Brenner-Status"
}, },
"cleaning_reminder": { "cleaning_reminder": {
"name": "Cleaning Reminder" "name": "Reinigungs-Erinnerung"
}, },
"compressor_status": { "compressor_status": {
"name": "Compressor Status" "name": "Kompressor-Status"
}, },
"cooking": { "cooking": {
"name": "Cooking" "name": "Kochen"
}, },
"error_code": { "error_code": {
"name": "Fehlercode" "name": "Fehlercode"
}, },
"filter_change_reminder": { "filter_change_reminder": {
"name": "Filter Change Reminder" "name": "Filter-Wechsel-Erinnerung"
}, },
"filter_cleaning_reminder": { "filter_cleaning_reminder": {
"name": "Filter Cleaning Reminder" "name": "Filter-Reinigungs-Erinnerung"
}, },
"finished": { "finished": {
"name": "Finished" "name": "Fertig"
}, },
"flex_zone_door": { "flex_zone_door": {
"name": "Flex Door" "name": "Flex-Tür"
}, },
"flex_zone_door_overtime": { "flex_zone_door_overtime": {
"name": "Flex Zone Door" "name": "Flexzonen-Tür"
}, },
"freezer_door": { "freezer_door": {
"name": "Freezer Door" "name": "Gefrierfachtür"
}, },
"freezer_door_overtime": { "freezer_door_overtime": {
"name": "Freezer Door Overtime" "name": "Gefrierfachtür Overtime"
}, },
"full_dust": { "full_dust": {
"name": "Full of Dust" "name": "Voller Staub"
}, },
"heating": { "heating": {
"name": "Heating" "name": "Heizen"
}, },
"heating_working": { "heating_working": {
"name": "Heating Working Status" "name": "Heizen Arbeitsstatus"
}, },
"keep_warm": { "keep_warm": {
"name": "Keep Warm" "name": "Warmhalten"
}, },
"leak_water": { "leak_water": {
"name": "Leckwasser" "name": "Leckwasser"
}, },
"lid_status": { "lid_status": {
"name": "Lid Status" "name": "Deckel-Status"
}, },
"middle_compartment_cooling": { "middle_compartment_cooling": {
"name": "Middle Compartment Cooling" "name": "Mittelfach-Kühlung"
}, },
"middle_compartment_door": { "middle_compartment_door": {
"name": "Middle Compartment Door" "name": "Mittelfach-Tür"
}, },
"middle_compartment_preheating": { "middle_compartment_preheating": {
"name": "Middle Compartment Preheating" "name": "Mittelfach Vorheizen"
}, },
"top_elec_heat": { "top_elec_heat": {
"name": "Elektroheizung oben" "name": "Elektroheizung oben"
@@ -160,28 +160,28 @@
"name": "Multi-Terminal" "name": "Multi-Terminal"
}, },
"oilcup_full": { "oilcup_full": {
"name": "Oil-cup Full" "name": "Ölbehälter voll"
}, },
"protection": { "protection": {
"name": "Protection" "name": "Schutz"
}, },
"refrigerator_door": { "refrigerator_door": {
"name": "Refrigerator Door" "name": "Kühlschranktür"
}, },
"refrigerator_door_overtime": { "refrigerator_door_overtime": {
"name": "Refrigerator Door Overtime" "name": "Kühlrschranktür Overtime"
}, },
"rinse_aid": { "rinse_aid": {
"name": "Rinse Aid Shortage" "name": "Spülhilfe leer"
}, },
"rsj_stand_by": { "rsj_stand_by": {
"name": "Standby" "name": "Standby"
}, },
"salt": { "salt": {
"name": "Salt Shortage" "name": "Salz leer"
}, },
"seat_status": { "seat_status": {
"name": "Seat Status" "name": "Sitz Status"
}, },
"smart_grid": { "smart_grid": {
"name": "Smart Grid" "name": "Smart Grid"
@@ -202,52 +202,52 @@
"name": "Zeitplan 2 aktiv" "name": "Zeitplan 2 aktiv"
}, },
"status_dhw": { "status_dhw": {
"name": "DHW status" "name": "DHW-Status"
}, },
"status_heating": { "status_heating": {
"name": "Heating status" "name": "Aufheiz-Status"
}, },
"status_ibh": { "status_ibh": {
"name": "IBH status" "name": "IBH-Status"
}, },
"status_tbh": { "status_tbh": {
"name": "TBH status" "name": "TBH-Status"
}, },
"tank_ejected": { "tank_ejected": {
"name": "Tank Ejected" "name": "Behälter ausgeworfen"
}, },
"tank_full": { "tank_full": {
"name": "Tank status" "name": "Behälter-Status"
}, },
"top_compartment_cooling": { "top_compartment_cooling": {
"name": "Top Compartment Cooling" "name": "Top Compartment Cooling"
}, },
"top_compartment_door": { "top_compartment_door": {
"name": "Top Compartment Door" "name": "Oberfach-Tür"
}, },
"top_compartment_preheating": { "top_compartment_preheating": {
"name": "Top Compartment Preheating" "name": "Oberfach Vorhzeizen"
}, },
"water_change_reminder": { "water_change_reminder": {
"name": "Water Change Reminder" "name": "Wasserwechsel-Erinnerung"
}, },
"water_shortage": { "water_shortage": {
"name": "Water Shortage" "name": "Wasser leer"
}, },
"with_pressure": { "with_pressure": {
"name": "With Pressure" "name": "Mit Druck"
}, },
"zone1_room_temp_mode": { "zone1_room_temp_mode": {
"name": "Zone1 Room-temperature Mode" "name": "Zone1 Taumtemperatur-Modus"
}, },
"zone1_water_temp_mode": { "zone1_water_temp_mode": {
"name": "Zone1 Water-temperature Mode" "name": "Zone1 Wassertemperatur-Modus"
}, },
"zone2_room_temp_mode": { "zone2_room_temp_mode": {
"name": "Zone2 Room-temperature Mode" "name": "Zone2 Zimmertemperatur-Modus"
}, },
"zone2_water_temp_mode": { "zone2_water_temp_mode": {
"name": "Zone2 Water-temperature Mode" "name": "Zone2 Wassertemperatur-Modus"
}, },
"microcrystal_fresh": { "microcrystal_fresh": {
"name": "Mikrokristall-Frische" "name": "Mikrokristall-Frische"
@@ -284,7 +284,7 @@
}, },
"fan": { "fan": {
"fresh_air": { "fresh_air": {
"name": "Fresh Air" "name": "Frischluft"
}, },
"fresh_air_exhaust": { "fresh_air_exhaust": {
"name": "Frischluft-Abluft" "name": "Frischluft-Abluft"
@@ -297,16 +297,16 @@
}, },
"number": { "number": {
"dry_level": { "dry_level": {
"name": "Dry Level" "name": "Trocken-Level"
}, },
"heating_level": { "heating_level": {
"name": "Heating Level" "name": "Heiz-Level"
}, },
"seat_temp_level": { "seat_temp_level": {
"name": "Seat Temperature Level" "name": "Sitztemperatur-Level"
}, },
"water_temp_level": { "water_temp_level": {
"name": "Water Temperature Level" "name": "Wassertemperatur-Level"
}, },
"fan_speed_percent": { "fan_speed_percent": {
"name": "Lüftergeschwindigkeit (Prozent)" "name": "Lüftergeschwindigkeit (Prozent)"
@@ -326,13 +326,13 @@
}, },
"select": { "select": {
"detect_mode": { "detect_mode": {
"name": "Detect Mode" "name": "Detektions-Modus"
}, },
"direction": { "direction": {
"name": "Direction" "name": "Richtung"
}, },
"fan_speed": { "fan_speed": {
"name": "Fan Speed" "name": "Lüftergeschwindigkeit"
}, },
"fresh_air_exhaust_mode": { "fresh_air_exhaust_mode": {
"name": "Frischluft-Abluftgeschwindigkeit", "name": "Frischluft-Abluftgeschwindigkeit",
@@ -354,31 +354,31 @@
} }
}, },
"mode": { "mode": {
"name": "Mode" "name": "Modus"
}, },
"oscillation_angle": { "oscillation_angle": {
"name": "Oscillation Angle" "name": "Oszillationswinkel"
}, },
"oscillation_mode": { "oscillation_mode": {
"name": "Oscillation Mode" "name": "Oszillationsmodus"
}, },
"rate_select": { "rate_select": {
"name": "Power Rate Limit" "name": "Leistungslimit"
}, },
"screen_display": { "screen_display": {
"name": "Screen Display" "name": "Bildschirmanzeige"
}, },
"silent_level": { "silent_level": {
"name": "Silent Level" "name": "Silent-Stufe"
}, },
"tilting_angle": { "tilting_angle": {
"name": "Tilting Angle" "name": "Kippwinkel"
}, },
"wash_mode": { "wash_mode": {
"name": "Waschmodus" "name": "Waschmodus"
}, },
"water_level_set": { "water_level_set": {
"name": "Water Level Setting" "name": "Einstellung Wasserhöhe"
}, },
"wind_lr_angle": { "wind_lr_angle": {
"name": "Luftstrom horizontal", "name": "Luftstrom horizontal",
@@ -425,19 +425,19 @@
"name": "Bottom Temperature" "name": "Bottom Temperature"
}, },
"bright": { "bright": {
"name": "Bright Level" "name": "Helligkeitsstufe"
}, },
"compressor_temperature": { "compressor_temperature": {
"name": "Compressor Temperature" "name": "Kompressor-Temperatur"
}, },
"condenser_temperature": { "condenser_temperature": {
"name": "Condenser Temperature" "name": "Kondensator-Temperature"
}, },
"current_energy_consumption": { "current_energy_consumption": {
"name": "Current Energy Consumption" "name": "Aktueller Energieverbrauch"
}, },
"current_temperature": { "current_temperature": {
"name": "Current Temperature" "name": "Aktuelle Temperatur"
}, },
"dehydration_speed": { "dehydration_speed": {
"name": "dehydration speed" "name": "dehydration speed"
@@ -461,7 +461,7 @@
"name": "Verschmutzungsgrad" "name": "Verschmutzungsgrad"
}, },
"detergent": { "detergent": {
"name": "detergent" "name": "Reinigungsmittel"
}, },
"intensity": { "intensity": {
"name": "Intensität" "name": "Intensität"
@@ -502,7 +502,7 @@
} }
}, },
"error_code": { "error_code": {
"name": "Error Code" "name": "Fehlercode"
}, },
"estimated_energy_consumption": { "estimated_energy_consumption": {
"name": "Geschätzter Energieverbrauch" "name": "Geschätzter Energieverbrauch"
@@ -511,7 +511,7 @@
"name": "Geschätzter Wasserverbrauch" "name": "Geschätzter Wasserverbrauch"
}, },
"fan_level": { "fan_level": {
"name": "Fan level" "name": "Lüfterstufe"
}, },
"filter_life": { "filter_life": {
"name": "Filter Life" "name": "Filter Life"
@@ -520,19 +520,19 @@
"name": "Filter1 Verfügbare Tage" "name": "Filter1 Verfügbare Tage"
}, },
"filter1_life": { "filter1_life": {
"name": "Filter1 Lebensdauer Level" "name": "Filter1 Lebensdauer Stufe"
}, },
"filter2_days": { "filter2_days": {
"name": "Filter2 Verfügbare Tage" "name": "Filter2 Verfügbare Tage"
}, },
"filter2_life": { "filter2_life": {
"name": "Filter2 Lebensdauer Level" "name": "Filter2 Lebensdauer Stufe"
}, },
"filter3_days": { "filter3_days": {
"name": "Filter3 Verfügbare Tage" "name": "Filter3 Verfügbare Tage"
}, },
"filter3_life": { "filter3_life": {
"name": "Filter3 Lebensdauer Level" "name": "Filter3 Lebensdauer Stufe"
}, },
"flex_zone_actual_temp": { "flex_zone_actual_temp": {
"name": "Flex Zone Actual Temperature" "name": "Flex Zone Actual Temperature"
@@ -562,10 +562,10 @@
"name": "TDS-Wert rein" "name": "TDS-Wert rein"
}, },
"indoor_humidity": { "indoor_humidity": {
"name": "Indoor Humidity" "name": "Innenraumluftfeuchtigkeit"
}, },
"indoor_temperature": { "indoor_temperature": {
"name": "Indoor Temperature" "name": "Innenraumtemperatur"
}, },
"keep_warm_remaining": { "keep_warm_remaining": {
"name": "Keep Warm Remaining" "name": "Keep Warm Remaining"
@@ -592,7 +592,7 @@
"name": "TDS-Wert raus" "name": "TDS-Wert raus"
}, },
"outdoor_temperature": { "outdoor_temperature": {
"name": "Outdoor Temperature" "name": "Außentemperatur"
}, },
"program": { "program": {
"name": "Program" "name": "Program"
@@ -601,7 +601,7 @@
"name": "Progress" "name": "Progress"
}, },
"realtime_power": { "realtime_power": {
"name": "Realtime Power" "name": "Echtzeitleistung"
}, },
"pmv": { "pmv": {
"name": "PMV" "name": "PMV"
@@ -661,10 +661,10 @@
"name": "Tank Actual Temperature" "name": "Tank Actual Temperature"
}, },
"target_temperature": { "target_temperature": {
"name": "Target Temperature" "name": "Temperaturziel"
}, },
"time_remaining": { "time_remaining": {
"name": "Time Remaining" "name": "Verbleibende Zeit"
}, },
"top_compartment_remaining": { "top_compartment_remaining": {
"name": "Top Compartment Remaining" "name": "Top Compartment Remaining"
@@ -679,16 +679,16 @@
"name": "Top Temperature" "name": "Top Temperature"
}, },
"total_energy_consumption": { "total_energy_consumption": {
"name": "Total Energy Consumption" "name": "Gesamter Energieverbrauch"
}, },
"total_produced_energy": { "total_produced_energy": {
"name": "Total produced energy" "name": "Gesamte produzierte Energie"
}, },
"tvoc": { "tvoc": {
"name": "TVOC" "name": "TVOC"
}, },
"wash_level": { "wash_level": {
"name": "rinse count" "name": "Waschstufe"
}, },
"wash_strength": { "wash_strength": {
"name": "wash strength" "name": "wash strength"
@@ -739,7 +739,7 @@
"name": "Desinfektionstemperatur" "name": "Desinfektionstemperatur"
}, },
"max_temperature": { "max_temperature": {
"name": "Maximale Zieltemperatur" "name": "Maximales Temperaturziel"
}, },
"vacation_start_year": { "vacation_start_year": {
"name": "Urlaubsbeginn Jahr" "name": "Urlaubsbeginn Jahr"
@@ -772,7 +772,7 @@
"name": "Kompressorfrequenz" "name": "Kompressorfrequenz"
}, },
"target_compressor_frequency": { "target_compressor_frequency": {
"name": "Ziel-Kompressorfrequenz" "name": "Kompressorfrequenz-Ziel"
}, },
"compressor_current": { "compressor_current": {
"name": "Kompressorstrom" "name": "Kompressorstrom"
@@ -783,23 +783,26 @@
"compressor_power": { "compressor_power": {
"name": "Kompressorleistung" "name": "Kompressorleistung"
}, },
"indoor_coil_temperature": { "indoor_ambient_temperature": {
"name": "Innenraum-Wärmetauschertemperatur (T1)" "name": "Innenraum-Umgebungstemperatur(T1)"
}, },
"evaporator_temperature": { "indoor_coil_temperature": {
"name": "Verdampfertemperatur (T2)" "name": "Innentemperatur der Heizschlange(T2)"
},
"outdoor_coil_temperature": {
"name": "Außentemperatur der Heizschlange(T3)"
}, },
"outdoor_ambient_temperature": { "outdoor_ambient_temperature": {
"name": "Außenumgebungstemperatur (T4)" "name": "Außenumgebungstemperatur(T4)"
}, },
"discharge_pipe_temperature": { "discharge_pipe_temperature": {
"name": "Druckleitungstemperatur (TP)" "name": "Druckleitungstemperatur(TP)"
}, },
"indoor_fan_speed": { "indoor_fan_speed": {
"name": "Innenlüftergeschwindigkeit" "name": "Innengerät Lüftergeschwindigkeit"
}, },
"target_indoor_fan_speed": { "target_indoor_fan_speed": {
"name": "Ziel-Innenlüftergeschwindigkeit" "name": "Innengerät Lüftergeschwindigkeit-Ziel"
}, },
"velocity": { "velocity": {
"name": "Durchflussgeschwindigkeit" "name": "Durchflussgeschwindigkeit"
@@ -813,7 +816,7 @@
"name": "Aux Heating" "name": "Aux Heating"
}, },
"boost_mode": { "boost_mode": {
"name": "Boost Mode" "name": "Turbo-Modus"
}, },
"breezeless": { "breezeless": {
"name": "Breezeless" "name": "Breezeless"
@@ -825,19 +828,19 @@
"name": "CL-Sterilisation" "name": "CL-Sterilisation"
}, },
"comfort_mode": { "comfort_mode": {
"name": "Comfort Mode" "name": "Comfort-Modus"
}, },
"dhw_power": { "dhw_power": {
"name": "DHW Power" "name": "DHW Power"
}, },
"disinfect": { "disinfect": {
"name": "Disinfect" "name": "Desinfizieren"
}, },
"vacation_mode": { "vacation_mode": {
"name": "Urlaubsmodus" "name": "Urlaubsmodus"
}, },
"dry": { "dry": {
"name": "Dry" "name": "Trocknen"
}, },
"cold_water_single": { "cold_water_single": {
"name": "Kaltwasser einzeln" "name": "Kaltwasser einzeln"
@@ -846,7 +849,7 @@
"name": "Kaltwasser Punkt" "name": "Kaltwasser Punkt"
}, },
"eco_mode": { "eco_mode": {
"name": "ECO Mode" "name": "ECO-Modus"
}, },
"memory": { "memory": {
"name": "Memo U" "name": "Memo U"
@@ -858,13 +861,13 @@
"name": "Foam Shield" "name": "Foam Shield"
}, },
"frost_protect": { "frost_protect": {
"name": "Frost Protect" "name": "Frostschutz"
}, },
"heating_power": { "heating_power": {
"name": "Heating Power" "name": "Heizleistung"
}, },
"indirect_wind": { "indirect_wind": {
"name": "Indirect Wind" "name": "Indirekter Wind"
}, },
"leak_water_protection": { "leak_water_protection": {
"name": "Leckschutz" "name": "Leckschutz"
@@ -882,13 +885,13 @@
"name": "Main Power" "name": "Main Power"
}, },
"natural_wind": { "natural_wind": {
"name": "Natural Wind" "name": "Natürlicher Wind"
}, },
"night_light": { "night_light": {
"name": "Night Light" "name": "Nachtlicht"
}, },
"oscillate": { "oscillate": {
"name": "Oscillate" "name": "Oszillieren"
}, },
"power": { "power": {
"name": "Strom" "name": "Strom"
@@ -897,7 +900,7 @@
"name": "Powerful Purification" "name": "Powerful Purification"
}, },
"prompt_tone": { "prompt_tone": {
"name": "Prompt Tone" "name": "Bestätigungston"
}, },
"regeneration": { "regeneration": {
"name": "Regeneration" "name": "Regeneration"
@@ -909,19 +912,19 @@
"name": "Wasserweg" "name": "Wasserweg"
}, },
"screen_display": { "screen_display": {
"name": "Screen Display" "name": "Bildschirmanzeige"
}, },
"screen_display_alternate": { "screen_display_alternate": {
"name": "Screen Display Alternate" "name": "Bildschirmanzeige Alternative"
}, },
"sensor_light": { "sensor_light": {
"name": "Sensor Light" "name": "Sensor Light"
}, },
"silent_mode": { "silent_mode": {
"name": "Silent Mode" "name": "Silent-Modus"
}, },
"sleep_mode": { "sleep_mode": {
"name": "Sleep Mode" "name": "Sleep-Modus"
}, },
"out_silent": { "out_silent": {
"name": "Außengerät Leisemodus" "name": "Außengerät Leisemodus"
@@ -948,22 +951,22 @@
"name": "Sterilisation" "name": "Sterilisation"
}, },
"storage": { "storage": {
"name": "Storage" "name": "Aufbewahrung"
}, },
"swing": { "swing": {
"name": "swing" "name": "Schwingen"
}, },
"swing_horizontal": { "swing_horizontal": {
"name": "Swing Horizontal" "name": "Horizontal schwingen"
}, },
"swing_vertical": { "swing_vertical": {
"name": "Swing Vertical" "name": "Vertikal schwingen"
}, },
"self_clean": { "self_clean": {
"name": "Self Clean" "name": "Selbstreinigung"
}, },
"sound": { "sound": {
"name": "Sound" "name": "Ton"
}, },
"tbh": { "tbh": {
"name": "TBH" "name": "TBH"
@@ -998,13 +1001,13 @@
}, },
"water_heater": { "water_heater": {
"domestic_hot_water": { "domestic_hot_water": {
"name": "Domestic hot water" "name": "Heimisches heißes Wasser"
}, },
"bathing": { "bathing": {
"name": "Bathing" "name": "Baden"
}, },
"heating": { "heating": {
"name": "Heating" "name": "Heizen"
} }
} }
}, },
@@ -688,7 +688,7 @@
"name": "TVOC" "name": "TVOC"
}, },
"wash_level": { "wash_level": {
"name": "Rinse count" "name": "Wash level"
}, },
"wash_strength": { "wash_strength": {
"name": "Wash strength" "name": "Wash strength"
@@ -783,17 +783,20 @@
"compressor_power": { "compressor_power": {
"name": "Compressor Power" "name": "Compressor Power"
}, },
"indoor_coil_temperature": { "indoor_ambient_temperature": {
"name": "Indoor Coil Temperature (T1)" "name": "Indoor Ambient Temperature(T1)"
}, },
"evaporator_temperature": { "indoor_coil_temperature": {
"name": "Evaporator Temperature (T2)" "name": "Indoor Coil Temperature(T2)"
},
"outdoor_coil_temperature": {
"name": "Outdoor Coil Temperature(T3)"
}, },
"outdoor_ambient_temperature": { "outdoor_ambient_temperature": {
"name": "Outdoor Ambient Temperature (T4)" "name": "Outdoor Ambient Temperature(T4)"
}, },
"discharge_pipe_temperature": { "discharge_pipe_temperature": {
"name": "Discharge Pipe Temperature (TP)" "name": "Discharge Pipe Temperature(TP)"
}, },
"indoor_fan_speed": { "indoor_fan_speed": {
"name": "Indoor Fan Speed" "name": "Indoor Fan Speed"
@@ -688,7 +688,7 @@
"name": "COVT" "name": "COVT"
}, },
"wash_level": { "wash_level": {
"name": "Recuento de aclarados" "name": "Nivel de lavado"
}, },
"wash_strength": { "wash_strength": {
"name": "Potencia de lavado" "name": "Potencia de lavado"
@@ -783,17 +783,20 @@
"compressor_power": { "compressor_power": {
"name": "Potencia del compresor" "name": "Potencia del compresor"
}, },
"indoor_coil_temperature": { "indoor_ambient_temperature": {
"name": "Temperatura del serpentín interior (T1)" "name": "Temperatura ambiente interior(T1)"
}, },
"evaporator_temperature": { "indoor_coil_temperature": {
"name": "Temperatura del evaporador (T2)" "name": "Temperatura del serpentín interior(T2)"
},
"outdoor_coil_temperature": {
"name": "Temperatura del serpentín exterior(T3)"
}, },
"outdoor_ambient_temperature": { "outdoor_ambient_temperature": {
"name": "Temperatura ambiente exterior (T4)" "name": "Temperatura ambiente exterior(T4)"
}, },
"discharge_pipe_temperature": { "discharge_pipe_temperature": {
"name": "Temperatura del tubo de descarga (TP)" "name": "Temperatura del tubo de descarga(TP)"
}, },
"indoor_fan_speed": { "indoor_fan_speed": {
"name": "Velocidad del ventilador interior" "name": "Velocidad del ventilador interior"
@@ -688,7 +688,7 @@
"name": "TVOC" "name": "TVOC"
}, },
"wash_level": { "wash_level": {
"name": "rinse count" "name": "Niveau de lavage"
}, },
"wash_strength": { "wash_strength": {
"name": "wash strength" "name": "wash strength"
@@ -783,17 +783,20 @@
"compressor_power": { "compressor_power": {
"name": "Puissance du compresseur" "name": "Puissance du compresseur"
}, },
"indoor_coil_temperature": { "indoor_ambient_temperature": {
"name": "Température de la batterie intérieure (T1)" "name": "Température ambiante intérieure(T1)"
}, },
"evaporator_temperature": { "indoor_coil_temperature": {
"name": "Température de l'évaporateur (T2)" "name": "Température del'échangeur intérieur(T2)"
},
"outdoor_coil_temperature": {
"name": "Température de l'échangeur extérieur(T3)"
}, },
"outdoor_ambient_temperature": { "outdoor_ambient_temperature": {
"name": "Température ambiante extérieure (T4)" "name": "Température ambiante extérieure(T4)"
}, },
"discharge_pipe_temperature": { "discharge_pipe_temperature": {
"name": "Température du tuyau de refoulement (TP)" "name": "Température du tuyau de refoulement(TP)"
}, },
"indoor_fan_speed": { "indoor_fan_speed": {
"name": "Vitesse du ventilateur intérieur" "name": "Vitesse du ventilateur intérieur"
@@ -688,7 +688,7 @@
"name": "TVOC" "name": "TVOC"
}, },
"wash_level": { "wash_level": {
"name": "rinse count" "name": "Mosási szint"
}, },
"wash_strength": { "wash_strength": {
"name": "wash strength" "name": "wash strength"
@@ -783,17 +783,20 @@
"compressor_power": { "compressor_power": {
"name": "Kompresszor teljesítmény" "name": "Kompresszor teljesítmény"
}, },
"indoor_coil_temperature": { "indoor_ambient_temperature": {
"name": "Beltéri hőcserélő hőmérséklet (T1)" "name": "Beltéri környezeti hőmérséklet(T1)"
}, },
"evaporator_temperature": { "indoor_coil_temperature": {
"name": "Párologtató hőmérséklet (T2)" "name": "Beltéri hőcserélő hőmérséklet(T2)"
},
"outdoor_coil_temperature": {
"name": "Kültéri hőcserélő hőmérséklet(T3)"
}, },
"outdoor_ambient_temperature": { "outdoor_ambient_temperature": {
"name": "Kültéri környezeti hőmérséklet (T4)" "name": "Kültéri környezeti hőmérséklet(T4)"
}, },
"discharge_pipe_temperature": { "discharge_pipe_temperature": {
"name": "Nyomócső hőmérséklet (TP)" "name": "Nyomócső hőmérséklet(TP)"
}, },
"indoor_fan_speed": { "indoor_fan_speed": {
"name": "Beltéri ventilátor sebesség" "name": "Beltéri ventilátor sebesség"
@@ -688,7 +688,7 @@
"name": "TVOC" "name": "TVOC"
}, },
"wash_level": { "wash_level": {
"name": "Numero Risciacqui" "name": "Livello di lavaggio"
}, },
"wash_strength": { "wash_strength": {
"name": "Intensità Lavaggio" "name": "Intensità Lavaggio"
@@ -783,17 +783,20 @@
"compressor_power": { "compressor_power": {
"name": "Potenza Compressore" "name": "Potenza Compressore"
}, },
"indoor_coil_temperature": { "indoor_ambient_temperature": {
"name": "Temperatura Batteria Interna (T1)" "name": "Temperatura ambiente interna(T1)"
}, },
"evaporator_temperature": { "indoor_coil_temperature": {
"name": "Temperatura Evaporatore (T2)" "name": "Temperatura della serpentina interna(T2)"
},
"outdoor_coil_temperature": {
"name": "Temperatura della serpentina esterna(T3)"
}, },
"outdoor_ambient_temperature": { "outdoor_ambient_temperature": {
"name": "Temperatura Ambiente Esterna (T4)" "name": "Temperatura Ambiente Esterna(T4)"
}, },
"discharge_pipe_temperature": { "discharge_pipe_temperature": {
"name": "Temperatura Tubo di Scarico (TP)" "name": "Temperatura Tubo di Scarico(TP)"
}, },
"indoor_fan_speed": { "indoor_fan_speed": {
"name": "Velocità Ventola Interna" "name": "Velocità Ventola Interna"
@@ -688,7 +688,7 @@
"name": "TVOC" "name": "TVOC"
}, },
"wash_level": { "wash_level": {
"name": "Количество полосканий" "name": "Уровень стирки"
}, },
"wash_strength": { "wash_strength": {
"name": "Wash strength" "name": "Wash strength"
@@ -783,17 +783,20 @@
"compressor_power": { "compressor_power": {
"name": "Мощность компрессора" "name": "Мощность компрессора"
}, },
"indoor_coil_temperature": { "indoor_ambient_temperature": {
"name": "Температура внутреннего теплообменника (T1)" "name": "Комнатная температура(T1)"
}, },
"evaporator_temperature": { "indoor_coil_temperature": {
"name": "Температура испарителя (T2)" "name": "Температура внутреннего теплообменника(T2)"
},
"outdoor_coil_temperature": {
"name": "Температура наружного теплообменника(T3)"
}, },
"outdoor_ambient_temperature": { "outdoor_ambient_temperature": {
"name": "Наружная температура окружающей среды (T4)" "name": "Наружная температура окружающей среды(T4)"
}, },
"discharge_pipe_temperature": { "discharge_pipe_temperature": {
"name": "Температура нагнетательной трубы (TP)" "name": "Температура нагнетательной трубы(TP)"
}, },
"indoor_fan_speed": { "indoor_fan_speed": {
"name": "Скорость внутреннего вентилятора" "name": "Скорость внутреннего вентилятора"
@@ -688,7 +688,7 @@
"name": "TVOC" "name": "TVOC"
}, },
"wash_level": { "wash_level": {
"name": "rinse count" "name": "Úroveň prania"
}, },
"wash_strength": { "wash_strength": {
"name": "wash strength" "name": "wash strength"
@@ -783,17 +783,20 @@
"compressor_power": { "compressor_power": {
"name": "Výkon kompresora" "name": "Výkon kompresora"
}, },
"indoor_coil_temperature": { "indoor_ambient_temperature": {
"name": "Teplota vnútorného výmenníka (T1)" "name": "Vnútorná teplota prostredia(T1)"
}, },
"evaporator_temperature": { "indoor_coil_temperature": {
"name": "Teplota výparníka (T2)" "name": "Teplota vnútornej špirály(T2)"
},
"outdoor_coil_temperature": {
"name": "Teplota vonkajšej špirály(T3)"
}, },
"outdoor_ambient_temperature": { "outdoor_ambient_temperature": {
"name": "Vonkajšia teplota okolia (T4)" "name": "Vonkajšia teplota okolia(T4)"
}, },
"discharge_pipe_temperature": { "discharge_pipe_temperature": {
"name": "Teplota výtlačného potrubia (TP)" "name": "Teplota výtlačného potrubia(TP)"
}, },
"indoor_fan_speed": { "indoor_fan_speed": {
"name": "Rýchlosť vnútorného ventilátora" "name": "Rýchlosť vnútorného ventilátora"
@@ -783,17 +783,20 @@
"compressor_power": { "compressor_power": {
"name": "压缩机功率" "name": "压缩机功率"
}, },
"indoor_coil_temperature": { "indoor_ambient_temperature": {
"name": "室内盘管温度 (T1)" "name": "室内环境温度(T1)"
}, },
"evaporator_temperature": { "indoor_coil_temperature": {
"name": "蒸发器温度 (T2)" "name": "室内盘管温度(T2)"
},
"outdoor_coil_temperature": {
"name": "室外盘管温度(T3)"
}, },
"outdoor_ambient_temperature": { "outdoor_ambient_temperature": {
"name": "室外环境温度 (T4)" "name": "室外环境温度(T4)"
}, },
"discharge_pipe_temperature": { "discharge_pipe_temperature": {
"name": "排气管温度 (TP)" "name": "排气管温度(TP)"
}, },
"indoor_fan_speed": { "indoor_fan_speed": {
"name": "室内风机转速" "name": "室内风机转速"
+40 -10
View File
@@ -22,16 +22,16 @@ from homeassistant.const import (
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from midealocal.device import DeviceType from midealan.device import DeviceType
from midealocal.devices.c3 import DeviceAttributes as C3Attributes from midealan.devices.c3 import DeviceAttributes as C3Attributes
from midealocal.devices.c3 import MideaC3Device from midealan.devices.c3 import MideaC3Device
from midealocal.devices.cd import DeviceAttributes as CDAttributes from midealan.devices.cd import DeviceAttributes as CDAttributes
from midealocal.devices.cd import MideaCDDevice from midealan.devices.cd import MideaCDDevice
from midealocal.devices.e2 import DeviceAttributes as E2Attributes from midealan.devices.e2 import DeviceAttributes as E2Attributes
from midealocal.devices.e2 import MideaE2Device from midealan.devices.e2 import MideaE2Device
from midealocal.devices.e3 import MideaE3Device from midealan.devices.e3 import MideaE3Device
from midealocal.devices.e6 import DeviceAttributes as E6Attributes from midealan.devices.e6 import DeviceAttributes as E6Attributes
from midealocal.devices.e6 import MideaE6Device from midealan.devices.e6 import MideaE6Device
from .const import DEVICES, DOMAIN from .const import DEVICES, DOMAIN
from .midea_devices import MIDEA_DEVICES from .midea_devices import MIDEA_DEVICES
@@ -242,6 +242,16 @@ class MideaE3WaterHeater(MideaWaterHeater):
"""Midea E3 Water Heater entity init.""" """Midea E3 Water Heater entity init."""
super().__init__(device, entity_key) super().__init__(device, entity_key)
@property
def supported_features(self) -> WaterHeaterEntityFeature:
"""Midea E3 Water Heater supported features."""
# E3 implements turn_on/turn_off and reports on/off state, so advertise
# ON_OFF to self-document that support (matches E2).
return (
WaterHeaterEntityFeature.TARGET_TEMPERATURE
| WaterHeaterEntityFeature.ON_OFF
)
@property @property
def min_temp(self) -> float: def min_temp(self) -> float:
"""Midea E3 Water Heater min temperature.""" """Midea E3 Water Heater min temperature."""
@@ -276,6 +286,16 @@ class MideaC3WaterHeater(MideaWaterHeater):
"""Midea C3 Water Heater entity init.""" """Midea C3 Water Heater entity init."""
super().__init__(device, entity_key) super().__init__(device, entity_key)
@property
def supported_features(self) -> WaterHeaterEntityFeature:
"""Midea C3 Water Heater supported features."""
# C3 implements turn_on/turn_off (dhw_power) and reports on/off state,
# so advertise ON_OFF to self-document that support (matches E2).
return (
WaterHeaterEntityFeature.TARGET_TEMPERATURE
| WaterHeaterEntityFeature.ON_OFF
)
@property @property
def current_operation(self) -> str: def current_operation(self) -> str:
"""Midea C3 Water Heater current operation.""" """Midea C3 Water Heater current operation."""
@@ -356,6 +376,16 @@ class MideaE6WaterHeater(MideaWaterHeater):
self._use self._use
] ]
@property
def supported_features(self) -> WaterHeaterEntityFeature:
"""Midea E6 Water Heater supported features."""
# E6 implements turn_on/turn_off and reports on/off state, so advertise
# ON_OFF to self-document that support (matches E2).
return (
WaterHeaterEntityFeature.TARGET_TEMPERATURE
| WaterHeaterEntityFeature.ON_OFF
)
@property @property
def current_operation(self) -> str: def current_operation(self) -> str:
"""Midea E6 Water Heater current operation.""" """Midea E6 Water Heater current operation."""
+19 -19
View File
@@ -30,7 +30,7 @@
"power": 2.1, "power": 2.1,
"current": 0.12, "current": 0.12,
"energy": 28.33, "energy": 28.33,
"power_factor": 0.16, "power_factor": 0.13,
"update": { "update": {
"state": "idle", "state": "idle",
"installed_version": 268513381, "installed_version": 268513381,
@@ -47,8 +47,8 @@
"countdown_to_turn_off": 0, "countdown_to_turn_off": 0,
"voltage": 121.5, "voltage": 121.5,
"countdown_to_turn_on": 0, "countdown_to_turn_on": 0,
"energy": 54.71, "energy": 54.72,
"power_factor": 0.87, "power_factor": 0.3,
"ac_frequency": 60, "ac_frequency": 60,
"update": { "update": {
"state": "idle", "state": "idle",
@@ -58,8 +58,8 @@
"latest_release_notes": null "latest_release_notes": null
}, },
"linkquality": 134, "linkquality": 134,
"power": 84.2, "power": 0.2,
"current": 0.82, "current": 0.01,
"power_on_behavior": "on" "power_on_behavior": "on"
}, },
"0xb40e060fffe031e3": { "0xb40e060fffe031e3": {
@@ -74,12 +74,12 @@
"led_brightness": 100, "led_brightness": 100,
"countdown_to_turn_off": 0, "countdown_to_turn_off": 0,
"countdown_to_turn_on": 0, "countdown_to_turn_on": 0,
"voltage": 119.8, "voltage": 120.5,
"state": "ON", "state": "ON",
"ac_frequency": 60, "ac_frequency": 60,
"energy": 112.38, "energy": 112.4,
"power": 98.7, "power": 0.8,
"current": 0.89, "current": 0.02,
"power_factor": 0.38, "power_factor": 0.38,
"update": { "update": {
"state": "idle", "state": "idle",
@@ -96,10 +96,10 @@
"countdown_to_turn_off": 0, "countdown_to_turn_off": 0,
"countdown_to_turn_on": 0, "countdown_to_turn_on": 0,
"voltage": 121.9, "voltage": 121.9,
"energy": 49.6, "energy": 49.61,
"state": "ON", "state": "ON",
"power": 46.6, "power": 41.4,
"current": 0.56, "current": 0.47,
"ac_frequency": 60, "ac_frequency": 60,
"power_factor": 0.74, "power_factor": 0.74,
"update": { "update": {
@@ -114,12 +114,12 @@
}, },
"0xffffb40e060895b3": { "0xffffb40e060895b3": {
"state": "ON", "state": "ON",
"voltage": 121.8, "voltage": 121.7,
"ac_frequency": 60, "ac_frequency": 60,
"energy": 7.14, "energy": 7.14,
"current": 0.01, "current": 0.01,
"power": 0.1, "power": 0.1,
"power_factor": 0.11, "power_factor": 0.22,
"linkquality": 123, "linkquality": 123,
"update": { "update": {
"state": "idle", "state": "idle",
@@ -136,13 +136,13 @@
"0xffffb40e0608864e": { "0xffffb40e0608864e": {
"led_brightness": 100, "led_brightness": 100,
"countdown_to_turn_off": 0, "countdown_to_turn_off": 0,
"voltage": 121.3, "voltage": 121.8,
"energy": 17.5, "energy": 17.5,
"countdown_to_turn_on": 0, "countdown_to_turn_on": 0,
"state": "ON", "state": "ON",
"current": 0.02, "current": 0.02,
"ac_frequency": 60, "ac_frequency": 60,
"power": 0.4, "power": 0.3,
"power_factor": 0.14, "power_factor": 0.14,
"update": { "update": {
"state": "idle", "state": "idle",
@@ -172,7 +172,7 @@
"0xffffb40e060893d8": { "0xffffb40e060893d8": {
"state": "ON", "state": "ON",
"led_brightness": 100, "led_brightness": 100,
"voltage": 121.5, "voltage": 122,
"countdown_to_turn_off": 0, "countdown_to_turn_off": 0,
"countdown_to_turn_on": 0, "countdown_to_turn_on": 0,
"energy": 3.11, "energy": 3.11,
@@ -188,11 +188,11 @@
"latest_release_notes": null "latest_release_notes": null
}, },
"power_factor": 0.06, "power_factor": 0.06,
"power": 0 "power": 0.1
}, },
"0xa4c1380d0679ffff": { "0xa4c1380d0679ffff": {
"battery": 100, "battery": 100,
"temperature": 27.7, "temperature": 27.6,
"temperature_units": "celsius", "temperature_units": "celsius",
"temperature_calibration": 0, "temperature_calibration": 0,
"update": { "update": {