mirror of
https://github.com/sususweet/midea-meiju-codec.git
synced 2025-09-27 18:22:41 +00:00
feat: refactor code to add more attributes.
This commit is contained in:
@@ -57,18 +57,17 @@ class MideaDeviceStatusSensorEntity(MideaEntity, BinarySensorEntity):
|
|||||||
device.sn,
|
device.sn,
|
||||||
device.sn8,
|
device.sn8,
|
||||||
device.model,
|
device.model,
|
||||||
entity_key
|
entity_key,
|
||||||
|
device=device,
|
||||||
|
manufacturer=manufacturer,
|
||||||
|
rationale=rationale,
|
||||||
|
config=config,
|
||||||
)
|
)
|
||||||
self._device = device
|
self._device = device
|
||||||
self._manufacturer = manufacturer
|
self._manufacturer = manufacturer
|
||||||
self._rationale = rationale
|
self._rationale = rationale
|
||||||
self._config = config
|
self._config = config
|
||||||
|
|
||||||
@property
|
|
||||||
def entity_id_suffix(self) -> str:
|
|
||||||
"""Return the suffix for entity ID."""
|
|
||||||
return "status"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self):
|
def device_class(self):
|
||||||
"""Return the device class."""
|
"""Return the device class."""
|
||||||
@@ -102,7 +101,11 @@ class MideaBinarySensorEntity(MideaEntity, BinarySensorEntity):
|
|||||||
device.sn,
|
device.sn,
|
||||||
device.sn8,
|
device.sn8,
|
||||||
device.model,
|
device.model,
|
||||||
entity_key
|
entity_key,
|
||||||
|
device=device,
|
||||||
|
manufacturer=manufacturer,
|
||||||
|
rationale=rationale,
|
||||||
|
config=config,
|
||||||
)
|
)
|
||||||
self._device = device
|
self._device = device
|
||||||
self._manufacturer = manufacturer
|
self._manufacturer = manufacturer
|
||||||
@@ -110,11 +113,6 @@ class MideaBinarySensorEntity(MideaEntity, BinarySensorEntity):
|
|||||||
self._entity_key = entity_key
|
self._entity_key = entity_key
|
||||||
self._config = config
|
self._config = config
|
||||||
|
|
||||||
@property
|
|
||||||
def entity_id_suffix(self) -> str:
|
|
||||||
"""Return the suffix for entity ID."""
|
|
||||||
return f"binary_sensor_{self._entity_key}"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return if the binary sensor is on."""
|
"""Return if the binary sensor is on."""
|
||||||
|
@@ -15,7 +15,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .core.logger import MideaLogger
|
from .core.logger import MideaLogger
|
||||||
from .midea_entity import MideaEntity
|
from .midea_entity import MideaEntity
|
||||||
from .midea_entities import Rationale
|
|
||||||
from . import load_device_config
|
from . import load_device_config
|
||||||
|
|
||||||
|
|
||||||
@@ -61,7 +60,11 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
|||||||
device.sn,
|
device.sn,
|
||||||
device.sn8,
|
device.sn8,
|
||||||
device.model,
|
device.model,
|
||||||
entity_key
|
entity_key,
|
||||||
|
device=device,
|
||||||
|
manufacturer=manufacturer,
|
||||||
|
rationale=rationale,
|
||||||
|
config=config,
|
||||||
)
|
)
|
||||||
self._device = device
|
self._device = device
|
||||||
self._manufacturer = manufacturer
|
self._manufacturer = manufacturer
|
||||||
@@ -146,7 +149,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_mode(self):
|
def fan_mode(self):
|
||||||
return self._dict_get_selected(self._key_fan_modes, Rationale.EQUALLY)
|
return self._dict_get_selected(self._key_fan_modes, "EQUALLY")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def swing_modes(self):
|
def swing_modes(self):
|
||||||
@@ -154,7 +157,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def swing_mode(self):
|
def swing_mode(self):
|
||||||
return self._dict_get_selected(self._key_swing_modes)
|
return self._dict_get_selected(self._key_swing_modes, "EQUALLY")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
@@ -162,7 +165,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def hvac_mode(self):
|
def hvac_mode(self):
|
||||||
return self._dict_get_selected(self._key_hvac_modes)
|
return self._dict_get_selected(self._key_hvac_modes, "EQUALLY")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hvac_modes(self):
|
def hvac_modes(self):
|
||||||
@@ -233,7 +236,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
|||||||
return
|
return
|
||||||
await self.async_set_attribute(key, value)
|
await self.async_set_attribute(key, value)
|
||||||
|
|
||||||
def _dict_get_selected(self, dict_config, rationale=Rationale.EQUALLY):
|
def _dict_get_selected(self, dict_config, rationale="EQUALLY"):
|
||||||
"""Get selected value from dictionary configuration."""
|
"""Get selected value from dictionary configuration."""
|
||||||
if dict_config is None:
|
if dict_config is None:
|
||||||
return None
|
return None
|
||||||
@@ -248,15 +251,15 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
|||||||
if device_value is None:
|
if device_value is None:
|
||||||
match = False
|
match = False
|
||||||
break
|
break
|
||||||
if rationale == Rationale.EQUALLY:
|
if rationale == "EQUALLY":
|
||||||
if device_value != attr_value:
|
if device_value != attr_value:
|
||||||
match = False
|
match = False
|
||||||
break
|
break
|
||||||
elif rationale == Rationale.LESS:
|
elif rationale == "LESS":
|
||||||
if device_value >= attr_value:
|
if device_value >= attr_value:
|
||||||
match = False
|
match = False
|
||||||
break
|
break
|
||||||
elif rationale == Rationale.GREATER:
|
elif rationale == "GREATER":
|
||||||
if device_value <= attr_value:
|
if device_value <= attr_value:
|
||||||
match = False
|
match = False
|
||||||
break
|
break
|
||||||
|
@@ -62,6 +62,7 @@ DEVICE_MAPPING = {
|
|||||||
"work_status": {
|
"work_status": {
|
||||||
"options": {
|
"options": {
|
||||||
"power_off": {"work_status": "power_off" },
|
"power_off": {"work_status": "power_off" },
|
||||||
|
"power_on": {"work_status": "power_on" },
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .midea_entities import MideaEntity
|
from .midea_entity import MideaEntity
|
||||||
from . import load_device_config
|
from . import load_device_config
|
||||||
|
|
||||||
|
|
||||||
@@ -24,13 +24,30 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
coordinator = coordinator_map.get(device_id)
|
coordinator = coordinator_map.get(device_id)
|
||||||
device = coordinator.device if coordinator else None
|
device = coordinator.device if coordinator else None
|
||||||
for entity_key, ecfg in entities_cfg.items():
|
for entity_key, ecfg in entities_cfg.items():
|
||||||
devs.append(MideaFanEntity(device, manufacturer, rationale, entity_key, ecfg))
|
devs.append(MideaFanEntity(coordinator, device, manufacturer, rationale, entity_key, ecfg))
|
||||||
async_add_entities(devs)
|
async_add_entities(devs)
|
||||||
|
|
||||||
|
|
||||||
class MideaFanEntity(MideaEntity, FanEntity):
|
class MideaFanEntity(MideaEntity, FanEntity):
|
||||||
def __init__(self, device, manufacturer, rationale, entity_key, config):
|
def __init__(self, coordinator, device, manufacturer, rationale, entity_key, config):
|
||||||
super().__init__(device, manufacturer, rationale, entity_key, config)
|
super().__init__(
|
||||||
|
coordinator,
|
||||||
|
device.device_id,
|
||||||
|
device.device_name,
|
||||||
|
f"T0x{device.device_type:02X}",
|
||||||
|
device.sn,
|
||||||
|
device.sn8,
|
||||||
|
device.model,
|
||||||
|
entity_key,
|
||||||
|
device=device,
|
||||||
|
manufacturer=manufacturer,
|
||||||
|
rationale=rationale,
|
||||||
|
config=config,
|
||||||
|
)
|
||||||
|
self._device = device
|
||||||
|
self._manufacturer = manufacturer
|
||||||
|
self._rationale = rationale
|
||||||
|
self._config = config
|
||||||
self._key_power = self._config.get("power")
|
self._key_power = self._config.get("power")
|
||||||
self._key_preset_modes = self._config.get("preset_modes")
|
self._key_preset_modes = self._config.get("preset_modes")
|
||||||
self._key_speeds = self._config.get("speeds")
|
self._key_speeds = self._config.get("speeds")
|
||||||
@@ -74,41 +91,48 @@ class MideaFanEntity(MideaEntity, FanEntity):
|
|||||||
def oscillating(self):
|
def oscillating(self):
|
||||||
return self._get_status_on_off(self._key_oscillate)
|
return self._get_status_on_off(self._key_oscillate)
|
||||||
|
|
||||||
def turn_on(
|
async def async_turn_on(
|
||||||
self,
|
self,
|
||||||
percentage: int | None = None,
|
percentage: int | None = None,
|
||||||
preset_mode: str | None = None,
|
preset_mode: str | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
if preset_mode is not None:
|
new_status = {}
|
||||||
new_status = self._key_preset_modes.get(preset_mode)
|
if preset_mode is not None and self._key_preset_modes is not None:
|
||||||
else:
|
new_status.update(self._key_preset_modes.get(preset_mode, {}))
|
||||||
new_status = {}
|
if percentage is not None and self._key_speeds:
|
||||||
if percentage is not None:
|
|
||||||
index = round(percentage * self._attr_speed_count / 100) - 1
|
index = round(percentage * self._attr_speed_count / 100) - 1
|
||||||
|
index = max(0, min(index, len(self._key_speeds) - 1))
|
||||||
new_status.update(self._key_speeds[index])
|
new_status.update(self._key_speeds[index])
|
||||||
new_status[self._key_power] = self._rationale[1]
|
if self._key_power is not None:
|
||||||
self._device.set_attributes(new_status)
|
new_status[self._key_power] = True
|
||||||
|
if new_status:
|
||||||
|
await self.async_set_attributes(new_status)
|
||||||
|
|
||||||
def turn_off(self):
|
async def async_turn_off(self):
|
||||||
self._set_status_on_off(self._key_power, False)
|
await self._async_set_status_on_off(self._key_power, False)
|
||||||
|
|
||||||
def set_percentage(self, percentage: int):
|
async def async_set_percentage(self, percentage: int):
|
||||||
|
if not self._key_speeds:
|
||||||
|
return
|
||||||
index = round(percentage * self._attr_speed_count / 100)
|
index = round(percentage * self._attr_speed_count / 100)
|
||||||
if 0 < index < len(self._key_speeds):
|
if 0 < index <= len(self._key_speeds):
|
||||||
new_status = self._key_speeds[index - 1]
|
new_status = self._key_speeds[index - 1]
|
||||||
self._device.set_attributes(new_status)
|
await self.async_set_attributes(new_status)
|
||||||
|
|
||||||
def set_preset_mode(self, preset_mode: str):
|
async def async_set_preset_mode(self, preset_mode: str):
|
||||||
|
if not self._key_preset_modes:
|
||||||
|
return
|
||||||
new_status = self._key_preset_modes.get(preset_mode)
|
new_status = self._key_preset_modes.get(preset_mode)
|
||||||
self._device.set_attributes(new_status)
|
if new_status:
|
||||||
|
await self.async_set_attributes(new_status)
|
||||||
|
|
||||||
def oscillate(self, oscillating: bool):
|
async def async_oscillate(self, oscillating: bool):
|
||||||
if self.oscillating != oscillating:
|
if self.oscillating != oscillating:
|
||||||
self._set_status_on_off(self._key_oscillate, oscillating)
|
await self._async_set_status_on_off(self._key_oscillate, oscillating)
|
||||||
|
|
||||||
def update_state(self, status):
|
def update_state(self, status):
|
||||||
try:
|
try:
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
except Exception as e:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
@@ -1,131 +0,0 @@
|
|||||||
from enum import IntEnum
|
|
||||||
from homeassistant.helpers.entity import Entity
|
|
||||||
from homeassistant.const import (
|
|
||||||
STATE_ON,
|
|
||||||
STATE_OFF
|
|
||||||
)
|
|
||||||
from .const import DOMAIN
|
|
||||||
from .core.logger import MideaLogger
|
|
||||||
|
|
||||||
|
|
||||||
class Rationale(IntEnum):
|
|
||||||
EQUALLY = 0
|
|
||||||
GREATER = 1
|
|
||||||
LESS = 2
|
|
||||||
|
|
||||||
|
|
||||||
class MideaEntity(Entity):
|
|
||||||
def __init__(self, device, manufacturer: str | None, rationale: list | None, entity_key: str, config: dict):
|
|
||||||
self._device = device
|
|
||||||
self._device.register_update(self.update_state)
|
|
||||||
self._entity_key = entity_key
|
|
||||||
self._config = config
|
|
||||||
self._device_name = self._device.device_name
|
|
||||||
self._rationale = rationale
|
|
||||||
if rationale_local := config.get("rationale"):
|
|
||||||
self._rationale = rationale_local
|
|
||||||
if self._rationale is None:
|
|
||||||
self._rationale = ["off", "on"]
|
|
||||||
self._attr_name = self._config.get("name")
|
|
||||||
self._attr_native_unit_of_measurement = self._config.get("unit_of_measurement")
|
|
||||||
self._attr_device_class = self._config.get("device_class")
|
|
||||||
self._attr_state_class = self._config.get("state_class")
|
|
||||||
self._attr_icon = self._config.get("icon")
|
|
||||||
self._attr_unique_id = f"{DOMAIN}.{self._device.device_id}_{self._entity_key}"
|
|
||||||
self._attr_device_info = {
|
|
||||||
"manufacturer": "Midea" if manufacturer is None else manufacturer,
|
|
||||||
"model": f"{self._device.model}",
|
|
||||||
"identifiers": {(DOMAIN, self._device.device_id)},
|
|
||||||
"name": self._device_name
|
|
||||||
}
|
|
||||||
name = self._config.get("name")
|
|
||||||
if name is None:
|
|
||||||
name = self._entity_key.replace("_", " ").title()
|
|
||||||
self._attr_name = f"{self._device_name} {name}"
|
|
||||||
self.entity_id = self._attr_unique_id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device(self):
|
|
||||||
return self._device
|
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self):
|
|
||||||
return self._device.connected
|
|
||||||
|
|
||||||
def _get_status_on_off(self, status_key: str):
|
|
||||||
result = False
|
|
||||||
status = self._device.get_attribute(status_key)
|
|
||||||
if status is not None:
|
|
||||||
try:
|
|
||||||
result = bool(self._rationale.index(status))
|
|
||||||
except ValueError:
|
|
||||||
MideaLogger.error(f"The value of attribute {status_key} ('{status}') "
|
|
||||||
f"is not in rationale {self._rationale}")
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _set_status_on_off(self, status_key: str, turn_on: bool):
|
|
||||||
self._device.set_attribute(status_key, self._rationale[int(turn_on)])
|
|
||||||
|
|
||||||
def _list_get_selected(self, key_of_list: list, rationale: Rationale = Rationale.EQUALLY):
|
|
||||||
for index in range(0, len(key_of_list)):
|
|
||||||
match = True
|
|
||||||
for attr, value in key_of_list[index].items():
|
|
||||||
state_value = self._device.get_attribute(attr)
|
|
||||||
if state_value is None:
|
|
||||||
match = False
|
|
||||||
break
|
|
||||||
if rationale is Rationale.EQUALLY and state_value != value:
|
|
||||||
match = False
|
|
||||||
break
|
|
||||||
if rationale is Rationale.GREATER and state_value < value:
|
|
||||||
match = False
|
|
||||||
break
|
|
||||||
if rationale is Rationale.LESS and state_value > value:
|
|
||||||
match = False
|
|
||||||
break
|
|
||||||
if match:
|
|
||||||
return index
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _dict_get_selected(self, key_of_dict: dict, rationale: Rationale = Rationale.EQUALLY):
|
|
||||||
for mode, status in key_of_dict.items():
|
|
||||||
match = True
|
|
||||||
for attr, value in status.items():
|
|
||||||
state_value = self._device.get_attribute(attr)
|
|
||||||
if state_value is None:
|
|
||||||
match = False
|
|
||||||
break
|
|
||||||
if rationale is Rationale.EQUALLY and state_value != value:
|
|
||||||
match = False
|
|
||||||
break
|
|
||||||
if rationale is Rationale.GREATER and state_value < value:
|
|
||||||
match = False
|
|
||||||
break
|
|
||||||
if rationale is Rationale.LESS and state_value > value:
|
|
||||||
match = False
|
|
||||||
break
|
|
||||||
if match:
|
|
||||||
return mode
|
|
||||||
return None
|
|
||||||
|
|
||||||
def update_state(self, status):
|
|
||||||
if self._entity_key in status or "connected" in status:
|
|
||||||
try:
|
|
||||||
self.schedule_update_ha_state()
|
|
||||||
except Exception as e:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MideaBinaryBaseEntity(MideaEntity):
|
|
||||||
|
|
||||||
@property
|
|
||||||
def state(self):
|
|
||||||
return STATE_ON if self.is_on else STATE_OFF
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_on(self):
|
|
||||||
return self._get_status_on_off(self._entity_key)
|
|
@@ -29,7 +29,12 @@ class MideaEntity(CoordinatorEntity[MideaDataUpdateCoordinator], Entity):
|
|||||||
sn: str,
|
sn: str,
|
||||||
sn8: str,
|
sn8: str,
|
||||||
model: str,
|
model: str,
|
||||||
entity_key: str
|
entity_key: str,
|
||||||
|
*,
|
||||||
|
device: Any | None = None,
|
||||||
|
manufacturer: str | None = None,
|
||||||
|
rationale: list | None = None,
|
||||||
|
config: dict | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the entity."""
|
"""Initialize the entity."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
@@ -40,18 +45,55 @@ class MideaEntity(CoordinatorEntity[MideaDataUpdateCoordinator], Entity):
|
|||||||
self._sn = sn
|
self._sn = sn
|
||||||
self._sn8 = sn8
|
self._sn8 = sn8
|
||||||
self._model = model
|
self._model = model
|
||||||
|
# Legacy/extended fields
|
||||||
|
self._device = device
|
||||||
|
self._config = config or {}
|
||||||
|
self._rationale = rationale
|
||||||
|
if (self._config.get("rationale")) is not None:
|
||||||
|
self._rationale = self._config.get("rationale")
|
||||||
|
if self._rationale is None:
|
||||||
|
self._rationale = ["off", "on"]
|
||||||
|
|
||||||
|
# Display and identification
|
||||||
self._attr_has_entity_name = True
|
self._attr_has_entity_name = True
|
||||||
self._attr_unique_id = f"{sn8}_{self.entity_id_suffix}"
|
# Prefer legacy unique_id scheme if device object is available (device_id based)
|
||||||
self.entity_id_base = f"midea_{sn8.lower()}"
|
if self._device is not None:
|
||||||
|
self._attr_unique_id = f"{DOMAIN}.{self._device_id}_{self._entity_key}"
|
||||||
self._attr_device_info = DeviceInfo(
|
self.entity_id_base = f"midea_{self._device_id}"
|
||||||
identifiers={(DOMAIN, sn8)},
|
manu = "Midea" if manufacturer is None else manufacturer
|
||||||
model=model,
|
self._attr_device_info = DeviceInfo(
|
||||||
serial_number=sn,
|
identifiers={(DOMAIN, str(self._device_id))},
|
||||||
manufacturer="Midea",
|
model=self._model,
|
||||||
name=device_name,
|
serial_number=sn,
|
||||||
)
|
manufacturer=manu,
|
||||||
|
name=device_name,
|
||||||
|
)
|
||||||
|
# Presentation attributes from config
|
||||||
|
self._attr_native_unit_of_measurement = self._config.get("unit_of_measurement")
|
||||||
|
self._attr_device_class = self._config.get("device_class")
|
||||||
|
self._attr_state_class = self._config.get("state_class")
|
||||||
|
self._attr_icon = self._config.get("icon")
|
||||||
|
name_cfg = self._config.get("name")
|
||||||
|
if name_cfg is None:
|
||||||
|
name_cfg = self._entity_key.replace("_", " ").title()
|
||||||
|
self._attr_name = f"{name_cfg}"
|
||||||
|
self.entity_id = self._attr_unique_id
|
||||||
|
# Register device updates for HA state refresh
|
||||||
|
try:
|
||||||
|
self._device.register_update(self.update_state) # type: ignore[attr-defined]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Fallback to sn8-based unique id/device info
|
||||||
|
self._attr_unique_id = f"{sn8}_{self.entity_id_suffix}"
|
||||||
|
self.entity_id_base = f"midea_{sn8.lower()}"
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, sn8)},
|
||||||
|
model=model,
|
||||||
|
serial_number=sn,
|
||||||
|
manufacturer="Midea",
|
||||||
|
name=device_name,
|
||||||
|
)
|
||||||
|
|
||||||
# Debounced command publishing
|
# Debounced command publishing
|
||||||
self._debounced_publish_command = Debouncer(
|
self._debounced_publish_command = Debouncer(
|
||||||
@@ -89,6 +131,86 @@ class MideaEntity(CoordinatorEntity[MideaDataUpdateCoordinator], Entity):
|
|||||||
# This will be implemented by subclasses
|
# This will be implemented by subclasses
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# ===== Unified helpers migrated from legacy entity base =====
|
||||||
|
def _get_status_on_off(self, attribute_key: str | None) -> bool:
|
||||||
|
"""Return boolean value from device attributes for given key.
|
||||||
|
|
||||||
|
Accepts common truthy representations: True/1/"on"/"true".
|
||||||
|
"""
|
||||||
|
if attribute_key is None:
|
||||||
|
return False
|
||||||
|
value = self.device_attributes.get(attribute_key)
|
||||||
|
if isinstance(value, bool):
|
||||||
|
return value
|
||||||
|
return value in (1, "1", "on", "ON", "true", "TRUE")
|
||||||
|
|
||||||
|
async def _async_set_status_on_off(self, attribute_key: str | None, turn_on: bool) -> None:
|
||||||
|
"""Set boolean attribute via coordinator, no-op if key is None."""
|
||||||
|
if attribute_key is None:
|
||||||
|
return
|
||||||
|
await self.async_set_attribute(attribute_key, bool(turn_on))
|
||||||
|
|
||||||
|
def _list_get_selected(self, options: list[dict] | None, rationale: object = None) -> int | None:
|
||||||
|
"""Select index from a list of dict conditions matched against attributes.
|
||||||
|
|
||||||
|
The optional rationale supports equality/greater/less matching. It can be
|
||||||
|
a string name ("EQUALLY"/"GREATER"/"LESS") or an Enum with .name.
|
||||||
|
"""
|
||||||
|
if not options:
|
||||||
|
return None
|
||||||
|
|
||||||
|
rationale_name = getattr(rationale, "name", None) or rationale or "EQUALLY"
|
||||||
|
for index in range(0, len(options)):
|
||||||
|
match = True
|
||||||
|
for attr, expected in options[index].items():
|
||||||
|
current = self.device_attributes.get(attr)
|
||||||
|
if current is None:
|
||||||
|
match = False
|
||||||
|
break
|
||||||
|
if rationale_name == "EQUALLY" and current != expected:
|
||||||
|
match = False
|
||||||
|
break
|
||||||
|
if rationale_name == "GREATER" and current < expected:
|
||||||
|
match = False
|
||||||
|
break
|
||||||
|
if rationale_name == "LESS" and current > expected:
|
||||||
|
match = False
|
||||||
|
break
|
||||||
|
if match:
|
||||||
|
return index
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _dict_get_selected(self, mapping: dict | None, rationale: object = None):
|
||||||
|
"""Return key from a dict whose value (a condition dict) matches attributes.
|
||||||
|
|
||||||
|
The optional rationale supports equality/greater/less matching. It can be
|
||||||
|
a string name ("EQUALLY"/"GREATER"/"LESS") or an Enum with .name.
|
||||||
|
"""
|
||||||
|
if not mapping:
|
||||||
|
return None
|
||||||
|
rationale_name = getattr(rationale, "name", None) or rationale or "EQUALLY"
|
||||||
|
for key, conditions in mapping.items():
|
||||||
|
if not isinstance(conditions, dict):
|
||||||
|
continue
|
||||||
|
match = True
|
||||||
|
for attr, expected in conditions.items():
|
||||||
|
current = self.device_attributes.get(attr)
|
||||||
|
if current is None:
|
||||||
|
match = False
|
||||||
|
break
|
||||||
|
if rationale_name == "EQUALLY" and current != expected:
|
||||||
|
match = False
|
||||||
|
break
|
||||||
|
if rationale_name == "GREATER" and current <= expected:
|
||||||
|
match = False
|
||||||
|
break
|
||||||
|
if rationale_name == "LESS" and current >= expected:
|
||||||
|
match = False
|
||||||
|
break
|
||||||
|
if match:
|
||||||
|
return key
|
||||||
|
return None
|
||||||
|
|
||||||
async def publish_command_from_current_state(self) -> None:
|
async def publish_command_from_current_state(self) -> None:
|
||||||
"""Publish commands to the device from current state."""
|
"""Publish commands to the device from current state."""
|
||||||
self.coordinator.mute_state_update_for_a_while()
|
self.coordinator.mute_state_update_for_a_while()
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
from homeassistant.components.select import SelectEntity
|
from homeassistant.components.select import SelectEntity
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .midea_entities import MideaEntity
|
from .midea_entity import MideaEntity
|
||||||
from . import load_device_config
|
from . import load_device_config
|
||||||
|
|
||||||
|
|
||||||
@@ -24,13 +24,30 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
coordinator = coordinator_map.get(device_id)
|
coordinator = coordinator_map.get(device_id)
|
||||||
device = coordinator.device if coordinator else None
|
device = coordinator.device if coordinator else None
|
||||||
for entity_key, ecfg in entities_cfg.items():
|
for entity_key, ecfg in entities_cfg.items():
|
||||||
devs.append(MideaSelectEntity(device, manufacturer, rationale, entity_key, ecfg))
|
devs.append(MideaSelectEntity(coordinator, device, manufacturer, rationale, entity_key, ecfg))
|
||||||
async_add_entities(devs)
|
async_add_entities(devs)
|
||||||
|
|
||||||
|
|
||||||
class MideaSelectEntity(MideaEntity, SelectEntity):
|
class MideaSelectEntity(MideaEntity, SelectEntity):
|
||||||
def __init__(self, device, manufacturer, rationale, entity_key, config):
|
def __init__(self, coordinator, device, manufacturer, rationale, entity_key, config):
|
||||||
super().__init__(device, manufacturer, rationale, entity_key, config)
|
super().__init__(
|
||||||
|
coordinator,
|
||||||
|
device.device_id,
|
||||||
|
device.device_name,
|
||||||
|
f"T0x{device.device_type:02X}",
|
||||||
|
device.sn,
|
||||||
|
device.sn8,
|
||||||
|
device.model,
|
||||||
|
entity_key,
|
||||||
|
device=device,
|
||||||
|
manufacturer=manufacturer,
|
||||||
|
rationale=rationale,
|
||||||
|
config=config,
|
||||||
|
)
|
||||||
|
self._device = device
|
||||||
|
self._manufacturer = manufacturer
|
||||||
|
self._rationale = rationale
|
||||||
|
self._config = config
|
||||||
self._key_options = self._config.get("options")
|
self._key_options = self._config.get("options")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -41,13 +58,14 @@ class MideaSelectEntity(MideaEntity, SelectEntity):
|
|||||||
def current_option(self):
|
def current_option(self):
|
||||||
return self._dict_get_selected(self._key_options)
|
return self._dict_get_selected(self._key_options)
|
||||||
|
|
||||||
def select_option(self, option: str):
|
async def async_select_option(self, option: str):
|
||||||
new_status = self._key_options.get(option)
|
new_status = self._key_options.get(option)
|
||||||
self._device.set_attributes(new_status)
|
if new_status:
|
||||||
|
await self.async_set_attributes(new_status)
|
||||||
|
|
||||||
def update_state(self, status):
|
def update_state(self, status):
|
||||||
try:
|
try:
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
except Exception as e:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@@ -52,18 +52,17 @@ class MideaSensorEntity(MideaEntity, SensorEntity):
|
|||||||
device.sn,
|
device.sn,
|
||||||
device.sn8,
|
device.sn8,
|
||||||
device.model,
|
device.model,
|
||||||
entity_key
|
entity_key,
|
||||||
|
device=device,
|
||||||
|
manufacturer=manufacturer,
|
||||||
|
rationale=rationale,
|
||||||
|
config=config,
|
||||||
)
|
)
|
||||||
self._device = device
|
self._device = device
|
||||||
self._manufacturer = manufacturer
|
self._manufacturer = manufacturer
|
||||||
self._rationale = rationale
|
self._rationale = rationale
|
||||||
self._config = config
|
self._config = config
|
||||||
|
|
||||||
@property
|
|
||||||
def entity_id_suffix(self) -> str:
|
|
||||||
"""Return the suffix for entity ID."""
|
|
||||||
return f"sensor_{self._entity_key}"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self):
|
def native_value(self):
|
||||||
"""Return the native value of the sensor."""
|
"""Return the native value of the sensor."""
|
||||||
|
@@ -27,7 +27,7 @@ async def async_setup_entry(
|
|||||||
for device_id, info in device_list.items():
|
for device_id, info in device_list.items():
|
||||||
device_type = info.get("type")
|
device_type = info.get("type")
|
||||||
sn8 = info.get("sn8")
|
sn8 = info.get("sn8")
|
||||||
config = load_device_config(hass, device_type, sn8) or {}
|
config = await load_device_config(hass, device_type, sn8) or {}
|
||||||
entities_cfg = (config.get("entities") or {}).get(Platform.SWITCH, {})
|
entities_cfg = (config.get("entities") or {}).get(Platform.SWITCH, {})
|
||||||
manufacturer = config.get("manufacturer")
|
manufacturer = config.get("manufacturer")
|
||||||
rationale = config.get("rationale")
|
rationale = config.get("rationale")
|
||||||
@@ -52,17 +52,17 @@ class MideaSwitchEntity(MideaEntity, SwitchEntity):
|
|||||||
device.sn,
|
device.sn,
|
||||||
device.sn8,
|
device.sn8,
|
||||||
device.model,
|
device.model,
|
||||||
entity_key
|
entity_key,
|
||||||
|
device=device,
|
||||||
|
manufacturer=manufacturer,
|
||||||
|
rationale=rationale,
|
||||||
|
config=config,
|
||||||
)
|
)
|
||||||
self._device = device
|
self._device = device
|
||||||
self._manufacturer = manufacturer
|
self._manufacturer = manufacturer
|
||||||
self._rationale = rationale
|
self._rationale = rationale
|
||||||
self._config = config
|
self._config = config
|
||||||
|
|
||||||
@property
|
|
||||||
def entity_id_suffix(self) -> str:
|
|
||||||
"""Return the suffix for entity ID."""
|
|
||||||
return f"switch_{self._entity_key}"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
|
@@ -4,7 +4,7 @@ from homeassistant.const import (
|
|||||||
ATTR_TEMPERATURE
|
ATTR_TEMPERATURE
|
||||||
)
|
)
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .midea_entities import MideaEntity
|
from .midea_entity import MideaEntity
|
||||||
from . import load_device_config
|
from . import load_device_config
|
||||||
|
|
||||||
|
|
||||||
@@ -27,13 +27,54 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
coordinator = coordinator_map.get(device_id)
|
coordinator = coordinator_map.get(device_id)
|
||||||
device = coordinator.device if coordinator else None
|
device = coordinator.device if coordinator else None
|
||||||
for entity_key, ecfg in entities_cfg.items():
|
for entity_key, ecfg in entities_cfg.items():
|
||||||
devs.append(MideaWaterHeaterEntityEntity(device, manufacturer, rationale, entity_key, ecfg))
|
devs.append(MideaWaterHeaterEntityEntity(coordinator, device, manufacturer, rationale, entity_key, ecfg))
|
||||||
async_add_entities(devs)
|
async_add_entities(devs)
|
||||||
|
|
||||||
|
|
||||||
class MideaWaterHeaterEntityEntity(MideaEntity, WaterHeaterEntity):
|
class MideaWaterHeaterEntityEntity(MideaEntity, WaterHeaterEntity):
|
||||||
def __init__(self, device, manufacturer, rationale, entity_key, config):
|
def __init__(self, coordinator, device, manufacturer, rationale, entity_key, config):
|
||||||
super().__init__(device, manufacturer, rationale, entity_key, config)
|
super().__init__(
|
||||||
|
coordinator,
|
||||||
|
device.device_id,
|
||||||
|
device.device_name,
|
||||||
|
f"T0x{device.device_type:02X}",
|
||||||
|
device.sn,
|
||||||
|
device.sn8,
|
||||||
|
device.model,
|
||||||
|
entity_key,
|
||||||
|
device=device,
|
||||||
|
manufacturer=manufacturer,
|
||||||
|
rationale=rationale,
|
||||||
|
config=config,
|
||||||
|
)
|
||||||
|
self._device = device
|
||||||
|
self._manufacturer = manufacturer
|
||||||
|
self._rationale = rationale
|
||||||
|
self._config = config
|
||||||
|
# Legacy compatibility: register update and restore display attributes
|
||||||
|
if self._device:
|
||||||
|
self._device.register_update(self.update_state)
|
||||||
|
if (rationale_local := self._config.get("rationale")) is not None:
|
||||||
|
self._rationale = rationale_local
|
||||||
|
if self._rationale is None:
|
||||||
|
self._rationale = ["off", "on"]
|
||||||
|
self._attr_native_unit_of_measurement = self._config.get("unit_of_measurement")
|
||||||
|
self._attr_device_class = self._config.get("device_class")
|
||||||
|
self._attr_state_class = self._config.get("state_class")
|
||||||
|
self._attr_icon = self._config.get("icon")
|
||||||
|
from .const import DOMAIN as _DOMAIN
|
||||||
|
self._attr_unique_id = f"{_DOMAIN}.{self._device.device_id}_{self._entity_key}"
|
||||||
|
self._attr_device_info = {
|
||||||
|
"manufacturer": "Midea" if self._manufacturer is None else self._manufacturer,
|
||||||
|
"model": f"{self._device.model}",
|
||||||
|
"identifiers": {( _DOMAIN, self._device.device_id)},
|
||||||
|
"name": self._device.device_name
|
||||||
|
}
|
||||||
|
name = self._config.get("name")
|
||||||
|
if name is None:
|
||||||
|
name = self._entity_key.replace("_", " ").title()
|
||||||
|
self._attr_name = f"{self._device.device_name} {name}"
|
||||||
|
self.entity_id = self._attr_unique_id
|
||||||
self._key_power = self._config.get("power")
|
self._key_power = self._config.get("power")
|
||||||
self._key_operation_list = self._config.get("operation_list")
|
self._key_operation_list = self._config.get("operation_list")
|
||||||
self._key_min_temp = self._config.get("min_temp")
|
self._key_min_temp = self._config.get("min_temp")
|
||||||
@@ -62,30 +103,30 @@ class MideaWaterHeaterEntityEntity(MideaEntity, WaterHeaterEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self):
|
||||||
return self._device.get_attribute(self._key_current_temperature)
|
return self.device_attributes.get(self._key_current_temperature)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
if isinstance(self._key_target_temperature, list):
|
if isinstance(self._key_target_temperature, list):
|
||||||
temp_int = self._device.get_attribute(self._key_target_temperature[0])
|
temp_int = self.device_attributes.get(self._key_target_temperature[0])
|
||||||
tem_dec = self._device.get_attribute(self._key_target_temperature[1])
|
tem_dec = self.device_attributes.get(self._key_target_temperature[1])
|
||||||
if temp_int is not None and tem_dec is not None:
|
if temp_int is not None and tem_dec is not None:
|
||||||
return temp_int + tem_dec
|
return temp_int + tem_dec
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return self._device.get_attribute(self._key_target_temperature)
|
return self.device_attributes.get(self._key_target_temperature)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
if isinstance(self._key_min_temp, str):
|
if isinstance(self._key_min_temp, str):
|
||||||
return float(self._device.get_attribute(self._key_min_temp))
|
return float(self.device_attributes.get(self._key_min_temp))
|
||||||
else:
|
else:
|
||||||
return float(self._key_min_temp)
|
return float(self._key_min_temp)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_temp(self):
|
def max_temp(self):
|
||||||
if isinstance(self._key_max_temp, str):
|
if isinstance(self._key_max_temp, str):
|
||||||
return float(self._device.get_attribute(self._key_max_temp))
|
return float(self.device_attributes.get(self._key_max_temp))
|
||||||
else:
|
else:
|
||||||
return float(self._key_max_temp)
|
return float(self._key_max_temp)
|
||||||
|
|
||||||
@@ -101,13 +142,13 @@ class MideaWaterHeaterEntityEntity(MideaEntity, WaterHeaterEntity):
|
|||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
return self._get_status_on_off(self._key_power)
|
return self._get_status_on_off(self._key_power)
|
||||||
|
|
||||||
def turn_on(self):
|
async def async_turn_on(self):
|
||||||
self._set_status_on_off(self._key_power, True)
|
await self._async_set_status_on_off(self._key_power, True)
|
||||||
|
|
||||||
def turn_off(self):
|
async def async_turn_off(self):
|
||||||
self._set_status_on_off(self._key_power, False)
|
await self._async_set_status_on_off(self._key_power, False)
|
||||||
|
|
||||||
def set_temperature(self, **kwargs):
|
async def async_set_temperature(self, **kwargs):
|
||||||
if ATTR_TEMPERATURE not in kwargs:
|
if ATTR_TEMPERATURE not in kwargs:
|
||||||
return
|
return
|
||||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
@@ -119,15 +160,16 @@ class MideaWaterHeaterEntityEntity(MideaEntity, WaterHeaterEntity):
|
|||||||
new_status[self._key_target_temperature[1]] = temp_dec
|
new_status[self._key_target_temperature[1]] = temp_dec
|
||||||
else:
|
else:
|
||||||
new_status[self._key_target_temperature] = temperature
|
new_status[self._key_target_temperature] = temperature
|
||||||
self._device.set_attributes(new_status)
|
await self.async_set_attributes(new_status)
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode: str) -> None:
|
async def async_set_operation_mode(self, operation_mode: str) -> None:
|
||||||
new_status = self._key_operation_list.get(operation_mode)
|
new_status = self._key_operation_list.get(operation_mode)
|
||||||
self._device.set_attributes(new_status)
|
if new_status:
|
||||||
|
await self.async_set_attributes(new_status)
|
||||||
|
|
||||||
def update_state(self, status):
|
def update_state(self, status):
|
||||||
try:
|
try:
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
except Exception as e:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user