23 files
This commit is contained in:
1 parent
64c401593c
commit
a0b8f10638
23 files changed
+5343
-79
No files matched your search
@@ -2,7 +2,7 @@
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import Any, ClassVar, cast
|
||||
from typing import Any, ClassVar, cast, override
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_HVAC_MODE,
|
||||
@@ -23,6 +23,7 @@ from homeassistant.components.climate import (
|
||||
SWING_VERTICAL,
|
||||
ClimateEntity,
|
||||
ClimateEntityFeature,
|
||||
HVACAction,
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@@ -49,10 +50,12 @@ 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.ed import DeviceAttributes as EDAttributes
|
||||
from midealan.devices.ed import MideaEDDevice
|
||||
from midealan.devices.fb import DeviceAttributes as FBAttributes
|
||||
from midealan.devices.fb import MideaFBDevice
|
||||
|
||||
from .const import DEVICES, DOMAIN, FanSpeed
|
||||
from .const import DEVICES, DOMAIN, FanSpeed, supports_device
|
||||
from .midea_devices import MIDEA_DEVICES
|
||||
from .midea_entity import MideaEntity
|
||||
|
||||
@@ -81,13 +84,16 @@ async def async_setup_entry(
|
||||
| MideaCFClimate
|
||||
| MideaC3Climate
|
||||
| MideaFBClimate
|
||||
| MideaEDTeaBarClimate
|
||||
] = []
|
||||
for entity_key, config in cast(
|
||||
"dict",
|
||||
MIDEA_DEVICES[device.device_type]["entities"],
|
||||
).items():
|
||||
if config["type"] == Platform.CLIMATE and (
|
||||
config.get("default") or entity_key in extra_switches
|
||||
if (
|
||||
config["type"] == Platform.CLIMATE
|
||||
and supports_device(device.model, device.subtype, config)
|
||||
and (config.get("default") or entity_key in extra_switches)
|
||||
):
|
||||
if device.device_type == DeviceType.AC:
|
||||
# add config_entry args to fix indoor_humidity error bug
|
||||
@@ -100,6 +106,8 @@ async def async_setup_entry(
|
||||
devs.append(MideaC3Climate(device, entity_key, config["zone"]))
|
||||
elif device.device_type == DeviceType.FB:
|
||||
devs.append(MideaFBClimate(device, entity_key))
|
||||
elif device.device_type == DeviceType.ED:
|
||||
devs.append(MideaEDTeaBarClimate(device, entity_key))
|
||||
async_add_entities(devs)
|
||||
|
||||
|
||||
@@ -258,6 +266,115 @@ class MideaClimate(MideaEntity, ClimateEntity):
|
||||
self.schedule_update_if_running()
|
||||
|
||||
|
||||
class MideaEDTeaBarClimate(MideaEntity, ClimateEntity):
|
||||
"""On/off heater and temperature control for the subtype-395 tea bar."""
|
||||
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
_device: MideaEDDevice
|
||||
_attr_hvac_modes: ClassVar[list[HVACMode]] = [HVACMode.OFF, HVACMode.HEAT]
|
||||
_attr_max_temp = 100
|
||||
_attr_min_temp = 40
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
|
||||
@property
|
||||
def supported_features(self) -> ClimateEntityFeature:
|
||||
"""Expose the official boil toggle and direct target control."""
|
||||
features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2):
|
||||
features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||
return features
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> HVACMode:
|
||||
"""Report heat only while the automatic fill-and-boil cycle is active."""
|
||||
active = bool(
|
||||
self._device.get_attribute(EDAttributes.boiling)
|
||||
or self._device.get_attribute(EDAttributes.dispensing),
|
||||
)
|
||||
return HVACMode.HEAT if active else HVACMode.OFF
|
||||
|
||||
@property
|
||||
def hvac_action(self) -> HVACAction:
|
||||
"""Distinguish idle from the automatic fill-and-boil cycle."""
|
||||
active = bool(
|
||||
self._device.get_attribute(EDAttributes.boiling)
|
||||
or self._device.get_attribute(EDAttributes.dispensing),
|
||||
)
|
||||
return HVACAction.HEATING if active else HVACAction.OFF
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> float | None:
|
||||
"""Current kettle temperature."""
|
||||
return cast(
|
||||
"float | None",
|
||||
self._device.get_attribute(EDAttributes.current_temperature),
|
||||
)
|
||||
|
||||
@property
|
||||
def target_temperature(self) -> float | None:
|
||||
"""Selected boil target temperature."""
|
||||
return cast(
|
||||
"float | None",
|
||||
self._device.get_attribute(EDAttributes.target_temperature),
|
||||
)
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
"""Expose only tea-bar states backed by the official protocol."""
|
||||
return {
|
||||
str(EDAttributes.heating): self._device.get_attribute(
|
||||
EDAttributes.heating,
|
||||
),
|
||||
str(EDAttributes.dispensing): self._device.get_attribute(
|
||||
EDAttributes.dispensing,
|
||||
),
|
||||
str(EDAttributes.keep_warm_remaining): self._device.get_attribute(
|
||||
EDAttributes.keep_warm_remaining,
|
||||
),
|
||||
}
|
||||
|
||||
def set_temperature(self, **kwargs: Any) -> None: # ruff:ignore[any-type]
|
||||
"""Start boiling with the official selected-temperature command."""
|
||||
if ATTR_TEMPERATURE not in kwargs:
|
||||
return
|
||||
self._device.set_attribute(
|
||||
EDAttributes.boil_temperature,
|
||||
float(kwargs[ATTR_TEMPERATURE]),
|
||||
)
|
||||
|
||||
@override
|
||||
def set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Use the official normal-boil command for heat and stop for off."""
|
||||
if hvac_mode == HVACMode.HEAT:
|
||||
self.turn_on()
|
||||
elif hvac_mode == HVACMode.OFF:
|
||||
self.turn_off()
|
||||
else:
|
||||
_LOGGER.warning("Ignoring unsupported tea bar HVAC mode: %s", hvac_mode)
|
||||
|
||||
@override
|
||||
def turn_on(self) -> None:
|
||||
"""Start the official automatic fill-and-boil-to-100 cycle."""
|
||||
self._device.set_attribute(EDAttributes.boiling, True)
|
||||
|
||||
@override
|
||||
def turn_off(self) -> None:
|
||||
"""Stop the active cycle with the official heat-start off command."""
|
||||
self._device.set_attribute(EDAttributes.boiling, False)
|
||||
|
||||
def update_state(self, status: Any) -> None: # ruff:ignore[any-type, unused-method-argument]
|
||||
"""Schedule a Home Assistant state update."""
|
||||
if not self.hass:
|
||||
_LOGGER.warning(
|
||||
"Climate update_state skipped for %s [%s]: HASS is None",
|
||||
self.name,
|
||||
type(self),
|
||||
)
|
||||
return
|
||||
self.schedule_update_if_running()
|
||||
|
||||
|
||||
class MideaACClimate(MideaClimate):
|
||||
"""Midea AC Climate Entries."""
|
||||
|
||||
@@ -288,7 +405,7 @@ class MideaACClimate(MideaClimate):
|
||||
self._customize_swing: bool | None = None
|
||||
self._customize_hvac_modes: list[HVACMode] | None = None
|
||||
self._customize_preset_modes: list[str] | None = None
|
||||
self._parse_capability_customize(config_entry)
|
||||
self._customize_fan_modes: list[str] | None = None
|
||||
self._fan_speeds: dict[str, int] = {
|
||||
FAN_SILENT: 20,
|
||||
FAN_LOW: 40,
|
||||
@@ -318,6 +435,7 @@ class MideaACClimate(MideaClimate):
|
||||
"sensors" in config_entry.options
|
||||
and "indoor_humidity" in config_entry.options["sensors"]
|
||||
)
|
||||
self._parse_capability_customize(config_entry)
|
||||
|
||||
def _parse_capability_customize(self, config_entry: ConfigEntry) -> None:
|
||||
"""Parse the swing / hvac_modes customize overrides (highest priority).
|
||||
@@ -366,6 +484,16 @@ class MideaACClimate(MideaClimate):
|
||||
wanted_p.insert(0, PRESET_NONE)
|
||||
# honor even an empty / none-only list (user wants no presets)
|
||||
self._customize_preset_modes = wanted_p
|
||||
fan_modes_override = params.get("fan_modes")
|
||||
if isinstance(fan_modes_override, list):
|
||||
valid_fans = list(self._fan_speeds.keys())
|
||||
wanted_f: list[str] = []
|
||||
for name in fan_modes_override:
|
||||
fan = str(name)
|
||||
if fan in valid_fans and fan not in wanted_f:
|
||||
wanted_f.append(fan)
|
||||
if wanted_f:
|
||||
self._customize_fan_modes = wanted_f
|
||||
|
||||
def _capability_swing(self) -> bool:
|
||||
"""Whether swing is available (customize > B5 capability > default).
|
||||
@@ -497,20 +625,26 @@ class MideaACClimate(MideaClimate):
|
||||
"""Midea AC Climate fan mode."""
|
||||
fan_speed = cast("int", self._device.get_attribute(ACAttributes.fan_speed))
|
||||
if fan_speed > FanSpeed.AUTO:
|
||||
return str(FAN_AUTO)
|
||||
if fan_speed > FanSpeed.FULL_SPEED:
|
||||
return str(FAN_FULL_SPEED)
|
||||
if fan_speed > FanSpeed.HIGH:
|
||||
return str(FAN_HIGH)
|
||||
if fan_speed > FanSpeed.MEDIUM:
|
||||
return str(FAN_MEDIUM)
|
||||
if fan_speed > FanSpeed.LOW:
|
||||
return str(FAN_LOW)
|
||||
return str(FAN_SILENT)
|
||||
current_mode = str(FAN_AUTO)
|
||||
elif fan_speed > FanSpeed.FULL_SPEED:
|
||||
current_mode = str(FAN_FULL_SPEED)
|
||||
elif fan_speed > FanSpeed.HIGH:
|
||||
current_mode = str(FAN_HIGH)
|
||||
elif fan_speed > FanSpeed.MEDIUM:
|
||||
current_mode = str(FAN_MEDIUM)
|
||||
elif fan_speed > FanSpeed.LOW:
|
||||
current_mode = str(FAN_LOW)
|
||||
else:
|
||||
current_mode = str(FAN_SILENT)
|
||||
|
||||
valid_modes = self.fan_modes
|
||||
if valid_modes and current_mode not in valid_modes:
|
||||
return str(FAN_AUTO) if str(FAN_AUTO) in valid_modes else valid_modes[0]
|
||||
return current_mode
|
||||
|
||||
@property
|
||||
def fan_modes(self) -> list[str] | None:
|
||||
"""fan_modes: B5 capabilities > default full set.
|
||||
"""fan_modes: customize > B5 capabilities > default full set.
|
||||
|
||||
Read dynamically so capabilities decoded after the first refresh are
|
||||
reflected (they are not yet available when the entity is created).
|
||||
@@ -521,6 +655,8 @@ class MideaACClimate(MideaClimate):
|
||||
``fan_custom`` to ``full`` alone collapsed the list to ``["full"]`` on
|
||||
such units (issue #904); short-circuit to the full set instead.
|
||||
"""
|
||||
if self._customize_fan_modes is not None:
|
||||
return self._customize_fan_modes
|
||||
caps = getattr(self._device, "capabilities", {})
|
||||
if not caps:
|
||||
return list(self._fan_speeds.keys())
|
||||
|
||||
Reference in new issue
Block a user