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.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:
+24 -14
View File
@@ -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
+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.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,
+16 -10
View File
@@ -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:
+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.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
+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.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.
+14 -5
View File
@@ -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"
}
+91 -53
View File
@@ -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,
@@ -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:
+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.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:
+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.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:
+4 -6
View File
@@ -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()
@@ -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"
}
}
},
@@ -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"
@@ -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"
@@ -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"
@@ -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"
@@ -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"
@@ -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": "Скорость внутреннего вентилятора"
@@ -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"
@@ -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": "室内风机转速"
+40 -10
View File
@@ -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."""
+19 -19
View File
@@ -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": {