diff --git a/custom_components/midea_ac_lan/__init__.py b/custom_components/midea_ac_lan/__init__.py index 9ed3e3cb..abcce474 100644 --- a/custom_components/midea_ac_lan/__init__.py +++ b/custom_components/midea_ac_lan/__init__.py @@ -30,9 +30,9 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.helpers.typing import ConfigType -from midealocal.device import DeviceType, MideaDevice, ProtocolVersion -from midealocal.devices import device_selector -from midealocal.discover import discover +from midealan.device import DeviceType, MideaDevice, ProtocolVersion +from midealan.devices import device_selector +from midealan.discover import discover from .const import ( ALL_PLATFORM, @@ -52,31 +52,43 @@ from .midea_devices import MIDEA_DEVICES _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: """Option flow signal update. - register update listener for config entry that will be called when entry is updated. - A listener is registered by adding the following to the `async_setup_entry`: - `config_entry.async_on_unload(config_entry.add_update_listener(update_listener))` - means the Listener is attached when the entry is loaded and detached at unload + Registered in `async_setup_entry` via + `config_entry.async_on_unload(config_entry.add_update_listener(...))`, so it + is attached when the entry loads and detached at unload. Reload the entry so + 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_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) + await hass.config_entries.async_reload(config_entry.entry_id) 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 == ""): _LOGGER.error("For V3 devices, the key and the token is required") return False - # device_selector in `midealocal/devices/__init__.py` + # device_selector in `midealan/devices/__init__.py` # hass core version >= 2024.3 if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 3): 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: device.set_refresh_interval(refresh_interval) device.open() - if DOMAIN not in hass.data: - hass.data[DOMAIN] = {} - if DEVICES not in hass.data[DOMAIN]: - hass.data[DOMAIN][DEVICES] = {} - hass.data[DOMAIN][DEVICES][device_id] = device - # Forward the setup of an entry to all platforms - await hass.config_entries.async_forward_entry_setups(config_entry, ALL_PLATFORM) + _device_store(hass)[device_id] = device + try: + # Forward the setup of an entry to all platforms + 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 # attached when the entry is loaded # 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) if device_type == CONF_ACCOUNT: return True - device_id = config_entry.data.get(CONF_DEVICE_ID) - if device_id is not None: - dm = hass.data[DOMAIN][DEVICES].get(device_id) - if dm is not None: - try: - dm.close() - except (OSError, ConnectionError, AttributeError) as e: - _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 - await hass.config_entries.async_unload_platforms(config_entry, ALL_PLATFORM) - return True + # Unload the platforms first; only tear the device down if that succeeded, + # and report the real result so a failed platform unload isn't masked. + # bool() keeps mypy happy: async_unload_platforms is typed to return Any. + unload_ok = bool( + await hass.config_entries.async_unload_platforms( + config_entry, + ALL_PLATFORM, + ), + ) + if unload_ok: + device_id = config_entry.data.get(CONF_DEVICE_ID) + 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: diff --git a/custom_components/midea_ac_lan/climate.py b/custom_components/midea_ac_lan/climate.py index 5f6e203f..b97454d5 100644 --- a/custom_components/midea_ac_lan/climate.py +++ b/custom_components/midea_ac_lan/climate.py @@ -40,17 +40,17 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from midealocal.device import DeviceType -from midealocal.devices.ac import DeviceAttributes as ACAttributes -from midealocal.devices.ac import MideaACDevice -from midealocal.devices.c3 import DeviceAttributes as C3Attributes -from midealocal.devices.c3 import MideaC3Device -from midealocal.devices.cc import DeviceAttributes as CCAttributes -from midealocal.devices.cc import MideaCCDevice -from midealocal.devices.cf import DeviceAttributes as CFAttributes -from midealocal.devices.cf import MideaCFDevice -from midealocal.devices.fb import DeviceAttributes as FBAttributes -from midealocal.devices.fb import MideaFBDevice +from midealan.device import DeviceType +from midealan.devices.ac import DeviceAttributes as ACAttributes +from midealan.devices.ac import MideaACDevice +from midealan.devices.c3 import DeviceAttributes as C3Attributes +from midealan.devices.c3 import MideaC3Device +from midealan.devices.cc import DeviceAttributes as CCAttributes +from midealan.devices.cc import MideaCCDevice +from midealan.devices.cf import DeviceAttributes as CFAttributes +from midealan.devices.cf import MideaCFDevice +from midealan.devices.fb import DeviceAttributes as FBAttributes +from midealan.devices.fb import MideaFBDevice from .const import DEVICES, DOMAIN, FanSpeed from .midea_devices import MIDEA_DEVICES @@ -146,7 +146,10 @@ class MideaClimate(MideaEntity, ClimateEntity): """Midea Climate hvac mode.""" if self._device.get_attribute("power"): 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 @property @@ -450,7 +453,10 @@ class MideaACClimate(MideaClimate): """Midea AC Climate hvac mode (device mode int -> fixed map).""" if self._device.get_attribute("power"): 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 def set_hvac_mode(self, hvac_mode: HVACMode) -> None: @@ -814,7 +820,11 @@ class MideaC3Climate(MideaClimate): def hvac_mode(self) -> HVACMode: """Midea C3 Climate hvac 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 HVACMode.OFF diff --git a/custom_components/midea_ac_lan/config_flow.py b/custom_components/midea_ac_lan/config_flow.py index b5a5a8aa..c8322ee4 100644 --- a/custom_components/midea_ac_lan/config_flow.py +++ b/custom_components/midea_ac_lan/config_flow.py @@ -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, diff --git a/custom_components/midea_ac_lan/fan.py b/custom_components/midea_ac_lan/fan.py index d059d5c5..61e930e9 100644 --- a/custom_components/midea_ac_lan/fan.py +++ b/custom_components/midea_ac_lan/fan.py @@ -12,15 +12,15 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from midealocal.device import DeviceType -from midealocal.devices.ac import DeviceAttributes as ACAttributes -from midealocal.devices.ac import MideaACDevice -from midealocal.devices.b6 import MideaB6Device -from midealocal.devices.ce import DeviceAttributes as CEAttributes -from midealocal.devices.ce import MideaCEDevice -from midealocal.devices.fa import MideaFADevice -from midealocal.devices.x40 import DeviceAttributes as X40Attributes -from midealocal.devices.x40 import MideaX40Device +from midealan.device import DeviceType +from midealan.devices.ac import DeviceAttributes as ACAttributes +from midealan.devices.ac import MideaACDevice +from midealan.devices.b6 import MideaB6Device +from midealan.devices.ce import DeviceAttributes as CEAttributes +from midealan.devices.ce import MideaCEDevice +from midealan.devices.fa import MideaFADevice +from midealan.devices.x40 import DeviceAttributes as X40Attributes +from midealan.devices.x40 import MideaX40Device from .const import DEVICES, DOMAIN from .midea_devices import ( @@ -122,7 +122,13 @@ class MideaFan(MideaEntity, FanEntity): def set_preset_mode(self, preset_mode: str) -> None: """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 def percentage(self) -> int | None: diff --git a/custom_components/midea_ac_lan/humidifier.py b/custom_components/midea_ac_lan/humidifier.py index 5326d4dc..f82662c0 100644 --- a/custom_components/midea_ac_lan/humidifier.py +++ b/custom_components/midea_ac_lan/humidifier.py @@ -12,9 +12,9 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from midealocal.device import DeviceType -from midealocal.devices.a1 import MideaA1Device -from midealocal.devices.fd import MideaFDDevice +from midealan.device import DeviceType +from midealan.devices.a1 import MideaA1Device +from midealan.devices.fd import MideaFDDevice from .const import DEVICES, DOMAIN from .midea_devices import MIDEA_DEVICES diff --git a/custom_components/midea_ac_lan/icons.json b/custom_components/midea_ac_lan/icons.json new file mode 100644 index 00000000..6962076e --- /dev/null +++ b/custom_components/midea_ac_lan/icons.json @@ -0,0 +1,16 @@ +{ + "entity": { + "climate": { + "climate_key": { + "state_attributes": { + "fan_mode": { + "state": { + "silent": "mdi:fan-clock", + "full": "mdi:fan-alert" + } + } + } + } + } + } +} diff --git a/custom_components/midea_ac_lan/light.py b/custom_components/midea_ac_lan/light.py index e8eed2df..5c0b77e5 100644 --- a/custom_components/midea_ac_lan/light.py +++ b/custom_components/midea_ac_lan/light.py @@ -15,8 +15,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from midealocal.devices.x13 import DeviceAttributes as X13Attributes -from midealocal.devices.x13 import Midea13Device +from midealan.devices.x13 import DeviceAttributes as X13Attributes +from midealan.devices.x13 import Midea13Device from .const import DEVICES, DOMAIN from .midea_devices import MIDEA_DEVICES @@ -73,18 +73,32 @@ def _calc_supported_color_modes(device: Midea13Device) -> set[ColorMode]: class MideaLight(MideaEntity, LightEntity): """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 - def __init__(self, device: Midea13Device, entity_key: str) -> None: - """Midea Light entity init.""" - super().__init__(device, entity_key) - self._attr_supported_features = _calc_supported_features(device) - self._attr_supported_color_modes = _calc_supported_color_modes(device) - self._attr_color_mode = self._calc_color_mode(self._attr_supported_color_modes) + # NOTE: supported_features / supported_color_modes / color_mode are computed + # as properties (not latched in __init__). The device starts its socket + # refresh on a background thread, so at construction time brightness, + # color_temperature and rgb_color are all still None. Latching these in + # __init__ would lock a dimmable/color-temp light to ONOFF forever, because + # 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: """Midea Light calculate color mode. diff --git a/custom_components/midea_ac_lan/manifest.json b/custom_components/midea_ac_lan/manifest.json index 8dccb04e..091fe3ee 100644 --- a/custom_components/midea_ac_lan/manifest.json +++ b/custom_components/midea_ac_lan/manifest.json @@ -1,14 +1,23 @@ { "domain": "midea_ac_lan", "name": "Midea AC LAN", - "codeowners": ["@wuwentao", "@rokam", "@chemelli74", "@Necroneco"], + "codeowners": [ + "@wuwentao", + "@rokam", + "@chemelli74", + "@caibinqing" + ], "config_flow": true, "dependencies": [], "documentation": "https://github.com/wuwentao/midea_ac_lan#readme", "integration_type": "device", "iot_class": "local_push", "issue_tracker": "https://github.com/wuwentao/midea_ac_lan/issues", - "loggers": ["midealocal"], - "requirements": ["midea-local==6.11.1"], - "version": "0.7.1" -} + "loggers": [ + "midealan" + ], + "requirements": [ + "midea-lan==2026.8.0" + ], + "version": "2026.8.0" +} \ No newline at end of file diff --git a/custom_components/midea_ac_lan/midea_devices.py b/custom_components/midea_ac_lan/midea_devices.py index 4d5f53c9..3940f45d 100644 --- a/custom_components/midea_ac_lan/midea_devices.py +++ b/custom_components/midea_ac_lan/midea_devices.py @@ -5,8 +5,8 @@ from typing import Any from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass from homeassistant.const import ( - CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - CONCENTRATION_PARTS_PER_MILLION, + MAJOR_VERSION, + MINOR_VERSION, PERCENTAGE, REVOLUTIONS_PER_MINUTE, Platform, @@ -19,40 +19,61 @@ from homeassistant.const import ( UnitOfTime, UnitOfVolume, ) -from midealocal.devices.a1 import DeviceAttributes as A1Attributes -from midealocal.devices.ac import DeviceAttributes as ACAttributes -from midealocal.devices.ad import DeviceAttributes as ADAttributes -from midealocal.devices.b0 import DeviceAttributes as B0Attributes -from midealocal.devices.b1 import DeviceAttributes as B1Attributes -from midealocal.devices.b3 import DeviceAttributes as B3Attributes -from midealocal.devices.b4 import DeviceAttributes as B4Attributes -from midealocal.devices.b6 import DeviceAttributes as B6Attributes -from midealocal.devices.bf import DeviceAttributes as BFAttributes -from midealocal.devices.c2 import DeviceAttributes as C2Attributes -from midealocal.devices.c3 import DeviceAttributes as C3Attributes -from midealocal.devices.ca import DeviceAttributes as CAAttributes -from midealocal.devices.cc import DeviceAttributes as CCAttributes -from midealocal.devices.cd import DeviceAttributes as CDAttributes -from midealocal.devices.ce import DeviceAttributes as CEAttributes -from midealocal.devices.cf import DeviceAttributes as CFAttributes -from midealocal.devices.da import DeviceAttributes as DAAttributes -from midealocal.devices.db import DeviceAttributes as DBAttributes -from midealocal.devices.dc import DeviceAttributes as DCAttributes -from midealocal.devices.e1 import DeviceAttributes as E1Attributes -from midealocal.devices.e2 import DeviceAttributes as E2Attributes -from midealocal.devices.e3 import DeviceAttributes as E3Attributes -from midealocal.devices.e6 import DeviceAttributes as E6Attributes -from midealocal.devices.e8 import DeviceAttributes as E8Attributes -from midealocal.devices.ea import DeviceAttributes as EAAttributes -from midealocal.devices.ec import DeviceAttributes as ECAttributes -from midealocal.devices.ed import DeviceAttributes as EDAttributes -from midealocal.devices.fa import DeviceAttributes as FAAttributes -from midealocal.devices.fb import DeviceAttributes as FBAttributes -from midealocal.devices.fc import DeviceAttributes as FCAttributes -from midealocal.devices.fd import DeviceAttributes as FDAttributes -from midealocal.devices.x26 import DeviceAttributes as X26Attributes -from midealocal.devices.x34 import DeviceAttributes as X34Attributes -from midealocal.devices.x40 import DeviceAttributes as X40Attributes + +# HA 2026.7 added UnitOfDensity/UnitOfRatio, and HA 2026.8 started deprecating +# CONCENTRATION_MICROGRAMS_PER_CUBIC_METER / CONCENTRATION_PARTS_PER_MILLION in favor of +# them (scheduled for removal in HA 2027.8). Both old and new names resolve to the +# identical runtime string ("μg/m³" / "ppm"), so this is purely cosmetic. This +# integration's floor is HA 2024.4.1, where UnitOfDensity/UnitOfRatio do not exist yet, +# so branch on the HA version like the rest of this codebase does for newer HA APIs (see +# other MAJOR_VERSION/MINOR_VERSION usages, e.g. midea_entity.py). +if (MAJOR_VERSION, MINOR_VERSION) >= (2026, 7): + from homeassistant.const import ( # pylint: disable=E0611 + UnitOfDensity, + UnitOfRatio, + ) + + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER = UnitOfDensity.MICROGRAMS_PER_CUBIC_METER + CONCENTRATION_PARTS_PER_MILLION = UnitOfRatio.PARTS_PER_MILLION +else: + from homeassistant.const import ( # type: ignore[no-redef] + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + CONCENTRATION_PARTS_PER_MILLION, + ) +from midealan.devices.a1 import DeviceAttributes as A1Attributes +from midealan.devices.ac import DeviceAttributes as ACAttributes +from midealan.devices.ad import DeviceAttributes as ADAttributes +from midealan.devices.b0 import DeviceAttributes as B0Attributes +from midealan.devices.b1 import DeviceAttributes as B1Attributes +from midealan.devices.b3 import DeviceAttributes as B3Attributes +from midealan.devices.b4 import DeviceAttributes as B4Attributes +from midealan.devices.b6 import DeviceAttributes as B6Attributes +from midealan.devices.bf import DeviceAttributes as BFAttributes +from midealan.devices.c2 import DeviceAttributes as C2Attributes +from midealan.devices.c3 import DeviceAttributes as C3Attributes +from midealan.devices.ca import DeviceAttributes as CAAttributes +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_MODE = "fresh_air_exhaust_mode" @@ -647,28 +668,28 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = { "unit": UnitOfElectricPotential.VOLT, "state_class": SensorStateClass.MEASUREMENT, }, - ACAttributes.indoor_coil_temperature: { + ACAttributes.indoor_ambient_temperature: { "type": Platform.SENSOR, - "required_attribute": ACAttributes.indoor_coil_temperature, - "translation_key": "indoor_coil_temperature", + "required_attribute": ACAttributes.indoor_ambient_temperature, + "translation_key": "indoor_ambient_temperature", "name": "Indoor Coil Temperature (T1)", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, "state_class": SensorStateClass.MEASUREMENT, }, - ACAttributes.evaporator_temperature: { + ACAttributes.indoor_coil_temperature: { "type": Platform.SENSOR, - "required_attribute": ACAttributes.evaporator_temperature, - "translation_key": "evaporator_temperature", + "required_attribute": ACAttributes.indoor_coil_temperature, + "translation_key": "indoor_coil_temperature", "name": "Evaporator Temperature (T2)", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, "state_class": SensorStateClass.MEASUREMENT, }, - ACAttributes.condenser_temperature: { + ACAttributes.outdoor_coil_temperature: { "type": Platform.SENSOR, - "required_attribute": ACAttributes.condenser_temperature, - "translation_key": "condenser_temperature", + "required_attribute": ACAttributes.outdoor_coil_temperature, + "translation_key": "outdoor_coil_temperature", "name": "Condenser Temperature (T3)", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, @@ -904,6 +925,14 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = { "icon": "mdi:box-shadow", "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: { "type": Platform.BINARY_SENSOR, "translation_key": "tank_ejected", @@ -938,6 +967,16 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = { "name": "Status", "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: { "type": Platform.SENSOR, "translation_key": "time_remaining", @@ -1022,7 +1061,7 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = { "type": Platform.BINARY_SENSOR, "translation_key": "top_compartment_cooling", "name": "Top Compartment Cooling", - "icon": "snowflake-variant", + "icon": "mdi:snowflake-variant", "device_class": BinarySensorDeviceClass.RUNNING, }, B3Attributes.middle_compartment_door: { @@ -1043,7 +1082,7 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = { "type": Platform.BINARY_SENSOR, "translation_key": "middle_compartment_cooling", "name": "Middle Compartment Cooling", - "icon": "snowflake-variant", + "icon": "mdi:snowflake-variant", "device_class": BinarySensorDeviceClass.RUNNING, }, B3Attributes.bottom_compartment_door: { @@ -1064,7 +1103,7 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = { "type": Platform.BINARY_SENSOR, "translation_key": "bottom_compartment_cooling", "name": "Bottom Compartment Cooling", - "icon": "snowflake-variant", + "icon": "mdi:snowflake-variant", "device_class": BinarySensorDeviceClass.RUNNING, }, B3Attributes.top_compartment_status: { @@ -1434,7 +1473,7 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = { "name": "Silent Mode", "icon": "mdi:fan-remove", }, - C3Attributes.SILENT_LEVEL: { + C3Attributes.silent_level: { "type": Platform.SELECT, "translation_key": "silent_level", "name": "Silent Level", @@ -1750,7 +1789,7 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = { "entities": { "climate": { "type": Platform.CLIMATE, - "icon": "hass:air-conditioner", + "icon": "mdi:air-conditioner", "default": True, }, CCAttributes.aux_heating: { @@ -2161,7 +2200,7 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = { "entities": { "climate": { "type": Platform.CLIMATE, - "icon": "hass:air-conditioner", + "icon": "mdi:air-conditioner", "default": True, }, CFAttributes.aux_heating: { @@ -2247,7 +2286,7 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = { DAAttributes.wash_level: { "type": Platform.SENSOR, "translation_key": "wash_level", - "name": "Rinse count", + "name": "Wash level", "icon": "mdi:hydraulic-oil-level", }, DAAttributes.wash_strength: { @@ -2922,7 +2961,6 @@ MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = { "type": Platform.BINARY_SENSOR, "translation_key": "finished", "name": "Finished", - "icon": "", }, E8Attributes.water_shortage: { "type": Platform.BINARY_SENSOR, diff --git a/custom_components/midea_ac_lan/midea_entity.py b/custom_components/midea_ac_lan/midea_entity.py index f717fcbe..a51f6394 100644 --- a/custom_components/midea_ac_lan/midea_entity.py +++ b/custom_components/midea_ac_lan/midea_entity.py @@ -14,7 +14,7 @@ else: ) from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, format_mac from homeassistant.helpers.entity import Entity -from midealocal.device import MideaDevice +from midealan.device import MideaDevice from .const import DOMAIN from .midea_devices import MIDEA_DEVICES @@ -79,13 +79,15 @@ class MideaEntity(Entity): # Example: device_class = temperature -> "Temperature". elif "device_class" in self._config: 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: - self._attr_name = ( - f"{self._device_name} {self._config.get('name')}" - if "name" in self._config - else f"{self._device_name}" - ) + self._attr_name = None @property def device(self) -> MideaDevice: diff --git a/custom_components/midea_ac_lan/number.py b/custom_components/midea_ac_lan/number.py index b2bc8a6a..839ed330 100644 --- a/custom_components/midea_ac_lan/number.py +++ b/custom_components/midea_ac_lan/number.py @@ -7,7 +7,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from midealocal.device import MideaDevice +from midealan.device import MideaDevice from .const import DEVICES, DOMAIN from .midea_devices import MIDEA_DEVICES @@ -44,53 +44,41 @@ class MideaNumber(MideaEntity, NumberEntity): self._min_value = self._config.get("min") 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 def native_min_value(self) -> float: """Minimum value allowed.""" - return cast( - "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) - ) - ), - ) + return self._resolve_bound(self._min_value) @property def native_max_value(self) -> float: """Maximum value allowed.""" - return cast( - "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) - ) - ), - ) + return self._resolve_bound(self._max_value) @property def native_step(self) -> float: """Step value between allowed values.""" - return cast( - "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) - ) - ), - ) + return self._resolve_bound(self._step_value) @property def native_value(self) -> float: diff --git a/custom_components/midea_ac_lan/select.py b/custom_components/midea_ac_lan/select.py index ed41e263..10331e03 100644 --- a/custom_components/midea_ac_lan/select.py +++ b/custom_components/midea_ac_lan/select.py @@ -7,14 +7,14 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform from homeassistant.core import HomeAssistant, callback 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 .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity if TYPE_CHECKING: - from midealocal.devices.e1 import MideaE1Device + from midealan.devices.e1 import MideaE1Device async def async_setup_entry( @@ -94,7 +94,7 @@ class MideaSelect(MideaEntity, SelectEntity): self._device.set_attribute(self._attribute_key, option) 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 ------- @@ -104,7 +104,7 @@ class MideaSelect(MideaEntity, SelectEntity): return cast("dict[int, str]", getattr(self._device, self._options_dict_name)) 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 ------ @@ -125,10 +125,9 @@ class MideaSelect(MideaEntity, SelectEntity): if ( power_attribute and self.hass - and not self.hass.is_stopping and (self._attribute_key in status or power_attribute in status) ): - self.schedule_update_ha_state() + self.schedule_update_if_running() @staticmethod def _get_dict_key_by_value(source: dict[int, str], value: str) -> int | None: diff --git a/custom_components/midea_ac_lan/sensor.py b/custom_components/midea_ac_lan/sensor.py index 9648590b..daa14152 100644 --- a/custom_components/midea_ac_lan/sensor.py +++ b/custom_components/midea_ac_lan/sensor.py @@ -13,7 +13,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import StateType -from midealocal.device import MideaDevice +from midealan.device import MideaDevice from .const import DEVICES, DOMAIN, supports_model from .midea_devices import MIDEA_DEVICES @@ -185,9 +185,7 @@ class MideaEstimatedUsageSensor(MideaSensor, RestoreEntity): self._last_progress = current_progress super().update_state(status) - if ( - self.hass - and not self.hass.is_stopping - and ("progress" in status or "status" in status or "mode" in status) + if self.hass and ( + "progress" in status or "status" in status or "mode" in status ): - self.schedule_update_ha_state() + self.schedule_update_if_running() diff --git a/custom_components/midea_ac_lan/translations/de.json b/custom_components/midea_ac_lan/translations/de.json index 119dc4fb..8ca9758e 100644 --- a/custom_components/midea_ac_lan/translations/de.json +++ b/custom_components/midea_ac_lan/translations/de.json @@ -1,7 +1,7 @@ { "config": { "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", "no_devices": "Keine neuen verfügbaren Geräte im Netzwerk gefunden", "device_exist": "Das Gerät ist bereits konfiguriert", @@ -14,7 +14,7 @@ "data": { "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" }, "login": { @@ -22,11 +22,11 @@ "account": "Konto", "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" }, "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", "data": { "ip_address": "IP-Adresse" @@ -40,7 +40,7 @@ "data": { "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" }, "manually": { @@ -64,85 +64,85 @@ "entity": { "binary_sensor": { "bar_door": { - "name": "Bar Door" + "name": "Bar-Tür" }, "bar_door_overtime": { - "name": "Bar Door Overtime" + "name": "Bar-Tür Overtime" }, "bathing_working": { "name": "Bathing Working Status" }, "bottom_compartment_cooling": { - "name": "Bottom Compartment Cooling" + "name": "Bodenfach Kühlung" }, "bottom_compartment_door": { - "name": "Bottom Compartment Door" + "name": "Bodenfach Tür" }, "bottom_compartment_preheating": { - "name": "Bottom Compartment Preheating" + "name": "Bodenfach Vorheizer" }, "burning_state": { - "name": "Burning State" + "name": "Brenner-Status" }, "cleaning_reminder": { - "name": "Cleaning Reminder" + "name": "Reinigungs-Erinnerung" }, "compressor_status": { - "name": "Compressor Status" + "name": "Kompressor-Status" }, "cooking": { - "name": "Cooking" + "name": "Kochen" }, "error_code": { "name": "Fehlercode" }, "filter_change_reminder": { - "name": "Filter Change Reminder" + "name": "Filter-Wechsel-Erinnerung" }, "filter_cleaning_reminder": { - "name": "Filter Cleaning Reminder" + "name": "Filter-Reinigungs-Erinnerung" }, "finished": { - "name": "Finished" + "name": "Fertig" }, "flex_zone_door": { - "name": "Flex Door" + "name": "Flex-Tür" }, "flex_zone_door_overtime": { - "name": "Flex Zone Door" + "name": "Flexzonen-Tür" }, "freezer_door": { - "name": "Freezer Door" + "name": "Gefrierfachtür" }, "freezer_door_overtime": { - "name": "Freezer Door Overtime" + "name": "Gefrierfachtür Overtime" }, "full_dust": { - "name": "Full of Dust" + "name": "Voller Staub" }, "heating": { - "name": "Heating" + "name": "Heizen" }, "heating_working": { - "name": "Heating Working Status" + "name": "Heizen Arbeitsstatus" }, "keep_warm": { - "name": "Keep Warm" + "name": "Warmhalten" }, "leak_water": { "name": "Leckwasser" }, "lid_status": { - "name": "Lid Status" + "name": "Deckel-Status" }, "middle_compartment_cooling": { - "name": "Middle Compartment Cooling" + "name": "Mittelfach-Kühlung" }, "middle_compartment_door": { - "name": "Middle Compartment Door" + "name": "Mittelfach-Tür" }, "middle_compartment_preheating": { - "name": "Middle Compartment Preheating" + "name": "Mittelfach Vorheizen" }, "top_elec_heat": { "name": "Elektroheizung oben" @@ -160,28 +160,28 @@ "name": "Multi-Terminal" }, "oilcup_full": { - "name": "Oil-cup Full" + "name": "Ölbehälter voll" }, "protection": { - "name": "Protection" + "name": "Schutz" }, "refrigerator_door": { - "name": "Refrigerator Door" + "name": "Kühlschranktür" }, "refrigerator_door_overtime": { - "name": "Refrigerator Door Overtime" + "name": "Kühlrschranktür Overtime" }, "rinse_aid": { - "name": "Rinse Aid Shortage" + "name": "Spülhilfe leer" }, "rsj_stand_by": { "name": "Standby" }, "salt": { - "name": "Salt Shortage" + "name": "Salz leer" }, "seat_status": { - "name": "Seat Status" + "name": "Sitz Status" }, "smart_grid": { "name": "Smart Grid" @@ -202,52 +202,52 @@ "name": "Zeitplan 2 aktiv" }, "status_dhw": { - "name": "DHW status" + "name": "DHW-Status" }, "status_heating": { - "name": "Heating status" + "name": "Aufheiz-Status" }, "status_ibh": { - "name": "IBH status" + "name": "IBH-Status" }, "status_tbh": { - "name": "TBH status" + "name": "TBH-Status" }, "tank_ejected": { - "name": "Tank Ejected" + "name": "Behälter ausgeworfen" }, "tank_full": { - "name": "Tank status" + "name": "Behälter-Status" }, "top_compartment_cooling": { "name": "Top Compartment Cooling" }, "top_compartment_door": { - "name": "Top Compartment Door" + "name": "Oberfach-Tür" }, "top_compartment_preheating": { - "name": "Top Compartment Preheating" + "name": "Oberfach Vorhzeizen" }, "water_change_reminder": { - "name": "Water Change Reminder" + "name": "Wasserwechsel-Erinnerung" }, "water_shortage": { - "name": "Water Shortage" + "name": "Wasser leer" }, "with_pressure": { - "name": "With Pressure" + "name": "Mit Druck" }, "zone1_room_temp_mode": { - "name": "Zone1 Room-temperature Mode" + "name": "Zone1 Taumtemperatur-Modus" }, "zone1_water_temp_mode": { - "name": "Zone1 Water-temperature Mode" + "name": "Zone1 Wassertemperatur-Modus" }, "zone2_room_temp_mode": { - "name": "Zone2 Room-temperature Mode" + "name": "Zone2 Zimmertemperatur-Modus" }, "zone2_water_temp_mode": { - "name": "Zone2 Water-temperature Mode" + "name": "Zone2 Wassertemperatur-Modus" }, "microcrystal_fresh": { "name": "Mikrokristall-Frische" @@ -284,7 +284,7 @@ }, "fan": { "fresh_air": { - "name": "Fresh Air" + "name": "Frischluft" }, "fresh_air_exhaust": { "name": "Frischluft-Abluft" @@ -297,16 +297,16 @@ }, "number": { "dry_level": { - "name": "Dry Level" + "name": "Trocken-Level" }, "heating_level": { - "name": "Heating Level" + "name": "Heiz-Level" }, "seat_temp_level": { - "name": "Seat Temperature Level" + "name": "Sitztemperatur-Level" }, "water_temp_level": { - "name": "Water Temperature Level" + "name": "Wassertemperatur-Level" }, "fan_speed_percent": { "name": "Lüftergeschwindigkeit (Prozent)" @@ -326,13 +326,13 @@ }, "select": { "detect_mode": { - "name": "Detect Mode" + "name": "Detektions-Modus" }, "direction": { - "name": "Direction" + "name": "Richtung" }, "fan_speed": { - "name": "Fan Speed" + "name": "Lüftergeschwindigkeit" }, "fresh_air_exhaust_mode": { "name": "Frischluft-Abluftgeschwindigkeit", @@ -354,31 +354,31 @@ } }, "mode": { - "name": "Mode" + "name": "Modus" }, "oscillation_angle": { - "name": "Oscillation Angle" + "name": "Oszillationswinkel" }, "oscillation_mode": { - "name": "Oscillation Mode" + "name": "Oszillationsmodus" }, "rate_select": { - "name": "Power Rate Limit" + "name": "Leistungslimit" }, "screen_display": { - "name": "Screen Display" + "name": "Bildschirmanzeige" }, "silent_level": { - "name": "Silent Level" + "name": "Silent-Stufe" }, "tilting_angle": { - "name": "Tilting Angle" + "name": "Kippwinkel" }, "wash_mode": { "name": "Waschmodus" }, "water_level_set": { - "name": "Water Level Setting" + "name": "Einstellung Wasserhöhe" }, "wind_lr_angle": { "name": "Luftstrom horizontal", @@ -425,19 +425,19 @@ "name": "Bottom Temperature" }, "bright": { - "name": "Bright Level" + "name": "Helligkeitsstufe" }, "compressor_temperature": { - "name": "Compressor Temperature" + "name": "Kompressor-Temperatur" }, "condenser_temperature": { - "name": "Condenser Temperature" + "name": "Kondensator-Temperature" }, "current_energy_consumption": { - "name": "Current Energy Consumption" + "name": "Aktueller Energieverbrauch" }, "current_temperature": { - "name": "Current Temperature" + "name": "Aktuelle Temperatur" }, "dehydration_speed": { "name": "dehydration speed" @@ -461,7 +461,7 @@ "name": "Verschmutzungsgrad" }, "detergent": { - "name": "detergent" + "name": "Reinigungsmittel" }, "intensity": { "name": "Intensität" @@ -502,7 +502,7 @@ } }, "error_code": { - "name": "Error Code" + "name": "Fehlercode" }, "estimated_energy_consumption": { "name": "Geschätzter Energieverbrauch" @@ -511,7 +511,7 @@ "name": "Geschätzter Wasserverbrauch" }, "fan_level": { - "name": "Fan level" + "name": "Lüfterstufe" }, "filter_life": { "name": "Filter Life" @@ -520,19 +520,19 @@ "name": "Filter1 Verfügbare Tage" }, "filter1_life": { - "name": "Filter1 Lebensdauer Level" + "name": "Filter1 Lebensdauer Stufe" }, "filter2_days": { "name": "Filter2 Verfügbare Tage" }, "filter2_life": { - "name": "Filter2 Lebensdauer Level" + "name": "Filter2 Lebensdauer Stufe" }, "filter3_days": { "name": "Filter3 Verfügbare Tage" }, "filter3_life": { - "name": "Filter3 Lebensdauer Level" + "name": "Filter3 Lebensdauer Stufe" }, "flex_zone_actual_temp": { "name": "Flex Zone Actual Temperature" @@ -562,10 +562,10 @@ "name": "TDS-Wert rein" }, "indoor_humidity": { - "name": "Indoor Humidity" + "name": "Innenraumluftfeuchtigkeit" }, "indoor_temperature": { - "name": "Indoor Temperature" + "name": "Innenraumtemperatur" }, "keep_warm_remaining": { "name": "Keep Warm Remaining" @@ -592,7 +592,7 @@ "name": "TDS-Wert raus" }, "outdoor_temperature": { - "name": "Outdoor Temperature" + "name": "Außentemperatur" }, "program": { "name": "Program" @@ -601,7 +601,7 @@ "name": "Progress" }, "realtime_power": { - "name": "Realtime Power" + "name": "Echtzeitleistung" }, "pmv": { "name": "PMV" @@ -661,10 +661,10 @@ "name": "Tank Actual Temperature" }, "target_temperature": { - "name": "Target Temperature" + "name": "Temperaturziel" }, "time_remaining": { - "name": "Time Remaining" + "name": "Verbleibende Zeit" }, "top_compartment_remaining": { "name": "Top Compartment Remaining" @@ -679,16 +679,16 @@ "name": "Top Temperature" }, "total_energy_consumption": { - "name": "Total Energy Consumption" + "name": "Gesamter Energieverbrauch" }, "total_produced_energy": { - "name": "Total produced energy" + "name": "Gesamte produzierte Energie" }, "tvoc": { "name": "TVOC" }, "wash_level": { - "name": "rinse count" + "name": "Waschstufe" }, "wash_strength": { "name": "wash strength" @@ -739,7 +739,7 @@ "name": "Desinfektionstemperatur" }, "max_temperature": { - "name": "Maximale Zieltemperatur" + "name": "Maximales Temperaturziel" }, "vacation_start_year": { "name": "Urlaubsbeginn Jahr" @@ -772,7 +772,7 @@ "name": "Kompressorfrequenz" }, "target_compressor_frequency": { - "name": "Ziel-Kompressorfrequenz" + "name": "Kompressorfrequenz-Ziel" }, "compressor_current": { "name": "Kompressorstrom" @@ -783,23 +783,26 @@ "compressor_power": { "name": "Kompressorleistung" }, - "indoor_coil_temperature": { - "name": "Innenraum-Wärmetauschertemperatur (T1)" + "indoor_ambient_temperature": { + "name": "Innenraum-Umgebungstemperatur(T1)" }, - "evaporator_temperature": { - "name": "Verdampfertemperatur (T2)" + "indoor_coil_temperature": { + "name": "Innentemperatur der Heizschlange(T2)" + }, + "outdoor_coil_temperature": { + "name": "Außentemperatur der Heizschlange(T3)" }, "outdoor_ambient_temperature": { - "name": "Außenumgebungstemperatur (T4)" + "name": "Außenumgebungstemperatur(T4)" }, "discharge_pipe_temperature": { - "name": "Druckleitungstemperatur (TP)" + "name": "Druckleitungstemperatur(TP)" }, "indoor_fan_speed": { - "name": "Innenlüftergeschwindigkeit" + "name": "Innengerät Lüftergeschwindigkeit" }, "target_indoor_fan_speed": { - "name": "Ziel-Innenlüftergeschwindigkeit" + "name": "Innengerät Lüftergeschwindigkeit-Ziel" }, "velocity": { "name": "Durchflussgeschwindigkeit" @@ -813,7 +816,7 @@ "name": "Aux Heating" }, "boost_mode": { - "name": "Boost Mode" + "name": "Turbo-Modus" }, "breezeless": { "name": "Breezeless" @@ -825,19 +828,19 @@ "name": "CL-Sterilisation" }, "comfort_mode": { - "name": "Comfort Mode" + "name": "Comfort-Modus" }, "dhw_power": { "name": "DHW Power" }, "disinfect": { - "name": "Disinfect" + "name": "Desinfizieren" }, "vacation_mode": { "name": "Urlaubsmodus" }, "dry": { - "name": "Dry" + "name": "Trocknen" }, "cold_water_single": { "name": "Kaltwasser einzeln" @@ -846,7 +849,7 @@ "name": "Kaltwasser Punkt" }, "eco_mode": { - "name": "ECO Mode" + "name": "ECO-Modus" }, "memory": { "name": "Memo U" @@ -858,13 +861,13 @@ "name": "Foam Shield" }, "frost_protect": { - "name": "Frost Protect" + "name": "Frostschutz" }, "heating_power": { - "name": "Heating Power" + "name": "Heizleistung" }, "indirect_wind": { - "name": "Indirect Wind" + "name": "Indirekter Wind" }, "leak_water_protection": { "name": "Leckschutz" @@ -882,13 +885,13 @@ "name": "Main Power" }, "natural_wind": { - "name": "Natural Wind" + "name": "Natürlicher Wind" }, "night_light": { - "name": "Night Light" + "name": "Nachtlicht" }, "oscillate": { - "name": "Oscillate" + "name": "Oszillieren" }, "power": { "name": "Strom" @@ -897,7 +900,7 @@ "name": "Powerful Purification" }, "prompt_tone": { - "name": "Prompt Tone" + "name": "Bestätigungston" }, "regeneration": { "name": "Regeneration" @@ -909,19 +912,19 @@ "name": "Wasserweg" }, "screen_display": { - "name": "Screen Display" + "name": "Bildschirmanzeige" }, "screen_display_alternate": { - "name": "Screen Display Alternate" + "name": "Bildschirmanzeige Alternative" }, "sensor_light": { "name": "Sensor Light" }, "silent_mode": { - "name": "Silent Mode" + "name": "Silent-Modus" }, "sleep_mode": { - "name": "Sleep Mode" + "name": "Sleep-Modus" }, "out_silent": { "name": "Außengerät Leisemodus" @@ -948,22 +951,22 @@ "name": "Sterilisation" }, "storage": { - "name": "Storage" + "name": "Aufbewahrung" }, "swing": { - "name": "swing" + "name": "Schwingen" }, "swing_horizontal": { - "name": "Swing Horizontal" + "name": "Horizontal schwingen" }, "swing_vertical": { - "name": "Swing Vertical" + "name": "Vertikal schwingen" }, "self_clean": { - "name": "Self Clean" + "name": "Selbstreinigung" }, "sound": { - "name": "Sound" + "name": "Ton" }, "tbh": { "name": "TBH" @@ -998,13 +1001,13 @@ }, "water_heater": { "domestic_hot_water": { - "name": "Domestic hot water" + "name": "Heimisches heißes Wasser" }, "bathing": { - "name": "Bathing" + "name": "Baden" }, "heating": { - "name": "Heating" + "name": "Heizen" } } }, diff --git a/custom_components/midea_ac_lan/translations/en.json b/custom_components/midea_ac_lan/translations/en.json index d33740de..5dac0e67 100644 --- a/custom_components/midea_ac_lan/translations/en.json +++ b/custom_components/midea_ac_lan/translations/en.json @@ -688,7 +688,7 @@ "name": "TVOC" }, "wash_level": { - "name": "Rinse count" + "name": "Wash level" }, "wash_strength": { "name": "Wash strength" @@ -783,17 +783,20 @@ "compressor_power": { "name": "Compressor Power" }, - "indoor_coil_temperature": { - "name": "Indoor Coil Temperature (T1)" + "indoor_ambient_temperature": { + "name": "Indoor Ambient Temperature(T1)" }, - "evaporator_temperature": { - "name": "Evaporator Temperature (T2)" + "indoor_coil_temperature": { + "name": "Indoor Coil Temperature(T2)" + }, + "outdoor_coil_temperature": { + "name": "Outdoor Coil Temperature(T3)" }, "outdoor_ambient_temperature": { - "name": "Outdoor Ambient Temperature (T4)" + "name": "Outdoor Ambient Temperature(T4)" }, "discharge_pipe_temperature": { - "name": "Discharge Pipe Temperature (TP)" + "name": "Discharge Pipe Temperature(TP)" }, "indoor_fan_speed": { "name": "Indoor Fan Speed" diff --git a/custom_components/midea_ac_lan/translations/es.json b/custom_components/midea_ac_lan/translations/es.json index 47596319..f526d260 100644 --- a/custom_components/midea_ac_lan/translations/es.json +++ b/custom_components/midea_ac_lan/translations/es.json @@ -688,7 +688,7 @@ "name": "COVT" }, "wash_level": { - "name": "Recuento de aclarados" + "name": "Nivel de lavado" }, "wash_strength": { "name": "Potencia de lavado" @@ -783,17 +783,20 @@ "compressor_power": { "name": "Potencia del compresor" }, - "indoor_coil_temperature": { - "name": "Temperatura del serpentín interior (T1)" + "indoor_ambient_temperature": { + "name": "Temperatura ambiente interior(T1)" }, - "evaporator_temperature": { - "name": "Temperatura del evaporador (T2)" + "indoor_coil_temperature": { + "name": "Temperatura del serpentín interior(T2)" + }, + "outdoor_coil_temperature": { + "name": "Temperatura del serpentín exterior(T3)" }, "outdoor_ambient_temperature": { - "name": "Temperatura ambiente exterior (T4)" + "name": "Temperatura ambiente exterior(T4)" }, "discharge_pipe_temperature": { - "name": "Temperatura del tubo de descarga (TP)" + "name": "Temperatura del tubo de descarga(TP)" }, "indoor_fan_speed": { "name": "Velocidad del ventilador interior" diff --git a/custom_components/midea_ac_lan/translations/fr.json b/custom_components/midea_ac_lan/translations/fr.json index 381e96a8..48eafd03 100644 --- a/custom_components/midea_ac_lan/translations/fr.json +++ b/custom_components/midea_ac_lan/translations/fr.json @@ -688,7 +688,7 @@ "name": "TVOC" }, "wash_level": { - "name": "rinse count" + "name": "Niveau de lavage" }, "wash_strength": { "name": "wash strength" @@ -783,17 +783,20 @@ "compressor_power": { "name": "Puissance du compresseur" }, - "indoor_coil_temperature": { - "name": "Température de la batterie intérieure (T1)" + "indoor_ambient_temperature": { + "name": "Température ambiante intérieure(T1)" }, - "evaporator_temperature": { - "name": "Température de l'évaporateur (T2)" + "indoor_coil_temperature": { + "name": "Température del'échangeur intérieur(T2)" + }, + "outdoor_coil_temperature": { + "name": "Température de l'échangeur extérieur(T3)" }, "outdoor_ambient_temperature": { - "name": "Température ambiante extérieure (T4)" + "name": "Température ambiante extérieure(T4)" }, "discharge_pipe_temperature": { - "name": "Température du tuyau de refoulement (TP)" + "name": "Température du tuyau de refoulement(TP)" }, "indoor_fan_speed": { "name": "Vitesse du ventilateur intérieur" diff --git a/custom_components/midea_ac_lan/translations/hu.json b/custom_components/midea_ac_lan/translations/hu.json index 9c6265e2..a3c701df 100644 --- a/custom_components/midea_ac_lan/translations/hu.json +++ b/custom_components/midea_ac_lan/translations/hu.json @@ -688,7 +688,7 @@ "name": "TVOC" }, "wash_level": { - "name": "rinse count" + "name": "Mosási szint" }, "wash_strength": { "name": "wash strength" @@ -783,17 +783,20 @@ "compressor_power": { "name": "Kompresszor teljesítmény" }, - "indoor_coil_temperature": { - "name": "Beltéri hőcserélő hőmérséklet (T1)" + "indoor_ambient_temperature": { + "name": "Beltéri környezeti hőmérséklet(T1)" }, - "evaporator_temperature": { - "name": "Párologtató hőmérséklet (T2)" + "indoor_coil_temperature": { + "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": { - "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": { - "name": "Nyomócső hőmérséklet (TP)" + "name": "Nyomócső hőmérséklet(TP)" }, "indoor_fan_speed": { "name": "Beltéri ventilátor sebesség" diff --git a/custom_components/midea_ac_lan/translations/it.json b/custom_components/midea_ac_lan/translations/it.json index b26e9753..f0ed5ad7 100644 --- a/custom_components/midea_ac_lan/translations/it.json +++ b/custom_components/midea_ac_lan/translations/it.json @@ -688,7 +688,7 @@ "name": "TVOC" }, "wash_level": { - "name": "Numero Risciacqui" + "name": "Livello di lavaggio" }, "wash_strength": { "name": "Intensità Lavaggio" @@ -783,17 +783,20 @@ "compressor_power": { "name": "Potenza Compressore" }, - "indoor_coil_temperature": { - "name": "Temperatura Batteria Interna (T1)" + "indoor_ambient_temperature": { + "name": "Temperatura ambiente interna(T1)" }, - "evaporator_temperature": { - "name": "Temperatura Evaporatore (T2)" + "indoor_coil_temperature": { + "name": "Temperatura della serpentina interna(T2)" + }, + "outdoor_coil_temperature": { + "name": "Temperatura della serpentina esterna(T3)" }, "outdoor_ambient_temperature": { - "name": "Temperatura Ambiente Esterna (T4)" + "name": "Temperatura Ambiente Esterna(T4)" }, "discharge_pipe_temperature": { - "name": "Temperatura Tubo di Scarico (TP)" + "name": "Temperatura Tubo di Scarico(TP)" }, "indoor_fan_speed": { "name": "Velocità Ventola Interna" diff --git a/custom_components/midea_ac_lan/translations/ru.json b/custom_components/midea_ac_lan/translations/ru.json index 309a9749..13c5f194 100644 --- a/custom_components/midea_ac_lan/translations/ru.json +++ b/custom_components/midea_ac_lan/translations/ru.json @@ -688,7 +688,7 @@ "name": "TVOC" }, "wash_level": { - "name": "Количество полосканий" + "name": "Уровень стирки" }, "wash_strength": { "name": "Wash strength" @@ -783,17 +783,20 @@ "compressor_power": { "name": "Мощность компрессора" }, - "indoor_coil_temperature": { - "name": "Температура внутреннего теплообменника (T1)" + "indoor_ambient_temperature": { + "name": "Комнатная температура(T1)" }, - "evaporator_temperature": { - "name": "Температура испарителя (T2)" + "indoor_coil_temperature": { + "name": "Температура внутреннего теплообменника(T2)" + }, + "outdoor_coil_temperature": { + "name": "Температура наружного теплообменника(T3)" }, "outdoor_ambient_temperature": { - "name": "Наружная температура окружающей среды (T4)" + "name": "Наружная температура окружающей среды(T4)" }, "discharge_pipe_temperature": { - "name": "Температура нагнетательной трубы (TP)" + "name": "Температура нагнетательной трубы(TP)" }, "indoor_fan_speed": { "name": "Скорость внутреннего вентилятора" diff --git a/custom_components/midea_ac_lan/translations/sk.json b/custom_components/midea_ac_lan/translations/sk.json index 8b074a97..deea85f8 100644 --- a/custom_components/midea_ac_lan/translations/sk.json +++ b/custom_components/midea_ac_lan/translations/sk.json @@ -688,7 +688,7 @@ "name": "TVOC" }, "wash_level": { - "name": "rinse count" + "name": "Úroveň prania" }, "wash_strength": { "name": "wash strength" @@ -783,17 +783,20 @@ "compressor_power": { "name": "Výkon kompresora" }, - "indoor_coil_temperature": { - "name": "Teplota vnútorného výmenníka (T1)" + "indoor_ambient_temperature": { + "name": "Vnútorná teplota prostredia(T1)" }, - "evaporator_temperature": { - "name": "Teplota výparníka (T2)" + "indoor_coil_temperature": { + "name": "Teplota vnútornej špirály(T2)" + }, + "outdoor_coil_temperature": { + "name": "Teplota vonkajšej špirály(T3)" }, "outdoor_ambient_temperature": { - "name": "Vonkajšia teplota okolia (T4)" + "name": "Vonkajšia teplota okolia(T4)" }, "discharge_pipe_temperature": { - "name": "Teplota výtlačného potrubia (TP)" + "name": "Teplota výtlačného potrubia(TP)" }, "indoor_fan_speed": { "name": "Rýchlosť vnútorného ventilátora" diff --git a/custom_components/midea_ac_lan/translations/zh-Hans.json b/custom_components/midea_ac_lan/translations/zh-Hans.json index 1087a0c3..85d0cefd 100644 --- a/custom_components/midea_ac_lan/translations/zh-Hans.json +++ b/custom_components/midea_ac_lan/translations/zh-Hans.json @@ -783,17 +783,20 @@ "compressor_power": { "name": "压缩机功率" }, - "indoor_coil_temperature": { - "name": "室内盘管温度 (T1)" + "indoor_ambient_temperature": { + "name": "室内环境温度(T1)" }, - "evaporator_temperature": { - "name": "蒸发器温度 (T2)" + "indoor_coil_temperature": { + "name": "室内盘管温度(T2)" + }, + "outdoor_coil_temperature": { + "name": "室外盘管温度(T3)" }, "outdoor_ambient_temperature": { - "name": "室外环境温度 (T4)" + "name": "室外环境温度(T4)" }, "discharge_pipe_temperature": { - "name": "排气管温度 (TP)" + "name": "排气管温度(TP)" }, "indoor_fan_speed": { "name": "室内风机转速" diff --git a/custom_components/midea_ac_lan/water_heater.py b/custom_components/midea_ac_lan/water_heater.py index 42472958..0dc766fd 100644 --- a/custom_components/midea_ac_lan/water_heater.py +++ b/custom_components/midea_ac_lan/water_heater.py @@ -22,16 +22,16 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from midealocal.device import DeviceType -from midealocal.devices.c3 import DeviceAttributes as C3Attributes -from midealocal.devices.c3 import MideaC3Device -from midealocal.devices.cd import DeviceAttributes as CDAttributes -from midealocal.devices.cd import MideaCDDevice -from midealocal.devices.e2 import DeviceAttributes as E2Attributes -from midealocal.devices.e2 import MideaE2Device -from midealocal.devices.e3 import MideaE3Device -from midealocal.devices.e6 import DeviceAttributes as E6Attributes -from midealocal.devices.e6 import MideaE6Device +from midealan.device import DeviceType +from midealan.devices.c3 import DeviceAttributes as C3Attributes +from midealan.devices.c3 import MideaC3Device +from midealan.devices.cd import DeviceAttributes as CDAttributes +from midealan.devices.cd import MideaCDDevice +from midealan.devices.e2 import DeviceAttributes as E2Attributes +from midealan.devices.e2 import MideaE2Device +from midealan.devices.e3 import MideaE3Device +from midealan.devices.e6 import DeviceAttributes as E6Attributes +from midealan.devices.e6 import MideaE6Device from .const import DEVICES, DOMAIN from .midea_devices import MIDEA_DEVICES @@ -242,6 +242,16 @@ class MideaE3WaterHeater(MideaWaterHeater): """Midea E3 Water Heater entity init.""" 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 def min_temp(self) -> float: """Midea E3 Water Heater min temperature.""" @@ -276,6 +286,16 @@ class MideaC3WaterHeater(MideaWaterHeater): """Midea C3 Water Heater entity init.""" 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 def current_operation(self) -> str: """Midea C3 Water Heater current operation.""" @@ -356,6 +376,16 @@ class MideaE6WaterHeater(MideaWaterHeater): 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 def current_operation(self) -> str: """Midea E6 Water Heater current operation.""" diff --git a/zigbee2mqtt/state.json b/zigbee2mqtt/state.json index 037998ae..18b61d7f 100644 --- a/zigbee2mqtt/state.json +++ b/zigbee2mqtt/state.json @@ -30,7 +30,7 @@ "power": 2.1, "current": 0.12, "energy": 28.33, - "power_factor": 0.16, + "power_factor": 0.13, "update": { "state": "idle", "installed_version": 268513381, @@ -47,8 +47,8 @@ "countdown_to_turn_off": 0, "voltage": 121.5, "countdown_to_turn_on": 0, - "energy": 54.71, - "power_factor": 0.87, + "energy": 54.72, + "power_factor": 0.3, "ac_frequency": 60, "update": { "state": "idle", @@ -58,8 +58,8 @@ "latest_release_notes": null }, "linkquality": 134, - "power": 84.2, - "current": 0.82, + "power": 0.2, + "current": 0.01, "power_on_behavior": "on" }, "0xb40e060fffe031e3": { @@ -74,12 +74,12 @@ "led_brightness": 100, "countdown_to_turn_off": 0, "countdown_to_turn_on": 0, - "voltage": 119.8, + "voltage": 120.5, "state": "ON", "ac_frequency": 60, - "energy": 112.38, - "power": 98.7, - "current": 0.89, + "energy": 112.4, + "power": 0.8, + "current": 0.02, "power_factor": 0.38, "update": { "state": "idle", @@ -96,10 +96,10 @@ "countdown_to_turn_off": 0, "countdown_to_turn_on": 0, "voltage": 121.9, - "energy": 49.6, + "energy": 49.61, "state": "ON", - "power": 46.6, - "current": 0.56, + "power": 41.4, + "current": 0.47, "ac_frequency": 60, "power_factor": 0.74, "update": { @@ -114,12 +114,12 @@ }, "0xffffb40e060895b3": { "state": "ON", - "voltage": 121.8, + "voltage": 121.7, "ac_frequency": 60, "energy": 7.14, "current": 0.01, "power": 0.1, - "power_factor": 0.11, + "power_factor": 0.22, "linkquality": 123, "update": { "state": "idle", @@ -136,13 +136,13 @@ "0xffffb40e0608864e": { "led_brightness": 100, "countdown_to_turn_off": 0, - "voltage": 121.3, + "voltage": 121.8, "energy": 17.5, "countdown_to_turn_on": 0, "state": "ON", "current": 0.02, "ac_frequency": 60, - "power": 0.4, + "power": 0.3, "power_factor": 0.14, "update": { "state": "idle", @@ -172,7 +172,7 @@ "0xffffb40e060893d8": { "state": "ON", "led_brightness": 100, - "voltage": 121.5, + "voltage": 122, "countdown_to_turn_off": 0, "countdown_to_turn_on": 0, "energy": 3.11, @@ -188,11 +188,11 @@ "latest_release_notes": null }, "power_factor": 0.06, - "power": 0 + "power": 0.1 }, "0xa4c1380d0679ffff": { "battery": 100, - "temperature": 27.7, + "temperature": 27.6, "temperature_units": "celsius", "temperature_calibration": 0, "update": {