mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Add (de)humidifier platform to Honeywell (#132287)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
1e075cdac7
commit
c9f1829c0b
@ -22,7 +22,7 @@ from .const import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
UPDATE_LOOP_SLEEP_TIME = 5
|
UPDATE_LOOP_SLEEP_TIME = 5
|
||||||
PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.SWITCH]
|
PLATFORMS = [Platform.CLIMATE, Platform.HUMIDIFIER, Platform.SENSOR, Platform.SWITCH]
|
||||||
|
|
||||||
MIGRATE_OPTIONS_KEYS = {CONF_COOL_AWAY_TEMPERATURE, CONF_HEAT_AWAY_TEMPERATURE}
|
MIGRATE_OPTIONS_KEYS = {CONF_COOL_AWAY_TEMPERATURE, CONF_HEAT_AWAY_TEMPERATURE}
|
||||||
|
|
||||||
|
136
homeassistant/components/honeywell/humidifier.py
Normal file
136
homeassistant/components/honeywell/humidifier.py
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
"""Support for Honeywell (de)humidifiers."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from aiosomecomfort.device import Device
|
||||||
|
|
||||||
|
from homeassistant.components.humidifier import (
|
||||||
|
HumidifierDeviceClass,
|
||||||
|
HumidifierEntity,
|
||||||
|
HumidifierEntityDescription,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
|
from . import HoneywellConfigEntry
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
HUMIDIFIER_KEY = "humidifier"
|
||||||
|
DEHUMIDIFIER_KEY = "dehumidifier"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class HoneywellHumidifierEntityDescription(HumidifierEntityDescription):
|
||||||
|
"""Describes a Honeywell humidifier entity."""
|
||||||
|
|
||||||
|
current_humidity: Callable[[Device], Any]
|
||||||
|
current_set_humidity: Callable[[Device], Any]
|
||||||
|
max_humidity: Callable[[Device], Any]
|
||||||
|
min_humidity: Callable[[Device], Any]
|
||||||
|
set_humidity: Callable[[Device, Any], Any]
|
||||||
|
mode: Callable[[Device], Any]
|
||||||
|
off: Callable[[Device], Any]
|
||||||
|
on: Callable[[Device], Any]
|
||||||
|
|
||||||
|
|
||||||
|
HUMIDIFIERS: dict[str, HoneywellHumidifierEntityDescription] = {
|
||||||
|
"Humidifier": HoneywellHumidifierEntityDescription(
|
||||||
|
key=HUMIDIFIER_KEY,
|
||||||
|
translation_key=HUMIDIFIER_KEY,
|
||||||
|
current_humidity=lambda device: device.current_humidity,
|
||||||
|
set_humidity=lambda device, humidity: device.set_humidifier_setpoint(humidity),
|
||||||
|
min_humidity=lambda device: device.humidifier_lower_limit,
|
||||||
|
max_humidity=lambda device: device.humidifier_upper_limit,
|
||||||
|
current_set_humidity=lambda device: device.humidifier_setpoint,
|
||||||
|
mode=lambda device: device.humidifier_mode,
|
||||||
|
off=lambda device: device.set_humidifier_off(),
|
||||||
|
on=lambda device: device.set_humidifier_auto(),
|
||||||
|
device_class=HumidifierDeviceClass.HUMIDIFIER,
|
||||||
|
),
|
||||||
|
"Dehumidifier": HoneywellHumidifierEntityDescription(
|
||||||
|
key=DEHUMIDIFIER_KEY,
|
||||||
|
translation_key=DEHUMIDIFIER_KEY,
|
||||||
|
current_humidity=lambda device: device.current_humidity,
|
||||||
|
set_humidity=lambda device, humidity: device.set_dehumidifier_setpoint(
|
||||||
|
humidity
|
||||||
|
),
|
||||||
|
min_humidity=lambda device: device.dehumidifier_lower_limit,
|
||||||
|
max_humidity=lambda device: device.dehumidifier_upper_limit,
|
||||||
|
current_set_humidity=lambda device: device.dehumidifier_setpoint,
|
||||||
|
mode=lambda device: device.dehumidifier_mode,
|
||||||
|
off=lambda device: device.set_dehumidifier_off(),
|
||||||
|
on=lambda device: device.set_dehumidifier_auto(),
|
||||||
|
device_class=HumidifierDeviceClass.DEHUMIDIFIER,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: HoneywellConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up Honeywell (de)humidifier dynamically."""
|
||||||
|
data = config_entry.runtime_data
|
||||||
|
entities: list = []
|
||||||
|
for device in data.devices.values():
|
||||||
|
if device.has_humidifier:
|
||||||
|
entities.append(HoneywellHumidifier(device, HUMIDIFIERS["Humidifier"]))
|
||||||
|
if device.has_dehumidifier:
|
||||||
|
entities.append(HoneywellHumidifier(device, HUMIDIFIERS["Dehumidifier"]))
|
||||||
|
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
class HoneywellHumidifier(HumidifierEntity):
|
||||||
|
"""Representation of a Honeywell US (De)Humidifier."""
|
||||||
|
|
||||||
|
entity_description: HoneywellHumidifierEntityDescription
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, device: Device, description: HoneywellHumidifierEntityDescription
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the (De)Humidifier."""
|
||||||
|
self._device = device
|
||||||
|
self.entity_description = description
|
||||||
|
self._attr_unique_id = f"{device.deviceid}_{description.key}"
|
||||||
|
self._attr_min_humidity = description.min_humidity(device)
|
||||||
|
self._attr_max_humidity = description.max_humidity(device)
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, device.deviceid)},
|
||||||
|
name=device.name,
|
||||||
|
manufacturer="Honeywell",
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool:
|
||||||
|
"""Return the device is on or off."""
|
||||||
|
return self.entity_description.mode(self._device) != 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_humidity(self) -> int | None:
|
||||||
|
"""Return the humidity we try to reach."""
|
||||||
|
return self.entity_description.current_set_humidity(self._device)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_humidity(self) -> int | None:
|
||||||
|
"""Return the current humidity."""
|
||||||
|
return self.entity_description.current_humidity(self._device)
|
||||||
|
|
||||||
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the device on."""
|
||||||
|
await self.entity_description.on(self._device)
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the device off."""
|
||||||
|
await self.entity_description.off(self._device)
|
||||||
|
|
||||||
|
async def async_set_humidity(self, humidity: int) -> None:
|
||||||
|
"""Set new target humidity."""
|
||||||
|
await self.entity_description.set_humidity(self._device, humidity)
|
@ -61,6 +61,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"humidifier": {
|
||||||
|
"humidifier": {
|
||||||
|
"name": "[%key:component::humidifier::title%]"
|
||||||
|
},
|
||||||
|
"dehumidifier": {
|
||||||
|
"name": "[%key:component::humidifier::entity_component::dehumidifier::name%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exceptions": {
|
"exceptions": {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Tests for honeywell component."""
|
"""Tests for Honeywell component."""
|
||||||
|
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
@ -127,7 +127,16 @@ def device():
|
|||||||
mock_device.refresh = AsyncMock()
|
mock_device.refresh = AsyncMock()
|
||||||
mock_device.heat_away_temp = HEATAWAY
|
mock_device.heat_away_temp = HEATAWAY
|
||||||
mock_device.cool_away_temp = COOLAWAY
|
mock_device.cool_away_temp = COOLAWAY
|
||||||
|
mock_device.has_humidifier = False
|
||||||
|
mock_device.has_dehumidifier = False
|
||||||
|
mock_device.humidifier_upper_limit = 60
|
||||||
|
mock_device.humidifier_lower_limit = 10
|
||||||
|
mock_device.humidifier_setpoint = 20
|
||||||
|
mock_device.dehumidifier_mode = 1
|
||||||
|
mock_device.dehumidifier_upper_limit = 55
|
||||||
|
mock_device.dehumidifier_lower_limit = 15
|
||||||
|
mock_device.dehumidifier_setpoint = 30
|
||||||
|
mock_device.dehumidifier_mode = 1
|
||||||
mock_device.raw_dr_data = {"CoolSetpLimit": None, "HeatSetpLimit": None}
|
mock_device.raw_dr_data = {"CoolSetpLimit": None, "HeatSetpLimit": None}
|
||||||
|
|
||||||
return mock_device
|
return mock_device
|
||||||
@ -149,6 +158,8 @@ def device_with_outdoor_sensor():
|
|||||||
mock_device.temperature_unit = "C"
|
mock_device.temperature_unit = "C"
|
||||||
mock_device.outdoor_temperature = OUTDOORTEMP
|
mock_device.outdoor_temperature = OUTDOORTEMP
|
||||||
mock_device.outdoor_humidity = OUTDOORHUMIDITY
|
mock_device.outdoor_humidity = OUTDOORHUMIDITY
|
||||||
|
mock_device.has_humidifier = False
|
||||||
|
mock_device.has_dehumidifier = False
|
||||||
mock_device.raw_ui_data = {
|
mock_device.raw_ui_data = {
|
||||||
"SwitchOffAllowed": True,
|
"SwitchOffAllowed": True,
|
||||||
"SwitchAutoAllowed": True,
|
"SwitchAutoAllowed": True,
|
||||||
@ -188,6 +199,16 @@ def another_device():
|
|||||||
mock_device.mac_address = "macaddress1"
|
mock_device.mac_address = "macaddress1"
|
||||||
mock_device.outdoor_temperature = None
|
mock_device.outdoor_temperature = None
|
||||||
mock_device.outdoor_humidity = None
|
mock_device.outdoor_humidity = None
|
||||||
|
mock_device.has_humidifier = False
|
||||||
|
mock_device.has_dehumidifier = False
|
||||||
|
mock_device.humidifier_upper_limit = 60
|
||||||
|
mock_device.humidifier_lower_limit = 10
|
||||||
|
mock_device.humidifier_setpoint = 20
|
||||||
|
mock_device.dehumidifier_mode = 1
|
||||||
|
mock_device.dehumidifier_upper_limit = 55
|
||||||
|
mock_device.dehumidifier_lower_limit = 15
|
||||||
|
mock_device.dehumidifier_setpoint = 30
|
||||||
|
mock_device.dehumidifier_mode = 1
|
||||||
mock_device.raw_ui_data = {
|
mock_device.raw_ui_data = {
|
||||||
"SwitchOffAllowed": True,
|
"SwitchOffAllowed": True,
|
||||||
"SwitchAutoAllowed": True,
|
"SwitchAutoAllowed": True,
|
||||||
|
39
tests/components/honeywell/snapshots/test_humidity.ambr
Normal file
39
tests/components/honeywell/snapshots/test_humidity.ambr
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# serializer version: 1
|
||||||
|
# name: test_static_attributes[dehumidifier]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'current_humidity': 50,
|
||||||
|
'device_class': 'dehumidifier',
|
||||||
|
'friendly_name': 'device1 Dehumidifier',
|
||||||
|
'humidity': 30,
|
||||||
|
'max_humidity': 55,
|
||||||
|
'min_humidity': 15,
|
||||||
|
'supported_features': <HumidifierEntityFeature: 0>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'humidifier.device1_dehumidifier',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'on',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_static_attributes[humidifier]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'current_humidity': 50,
|
||||||
|
'device_class': 'humidifier',
|
||||||
|
'friendly_name': 'device1 Humidifier',
|
||||||
|
'humidity': 20,
|
||||||
|
'max_humidity': 60,
|
||||||
|
'min_humidity': 10,
|
||||||
|
'supported_features': <HumidifierEntityFeature: 0>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'humidifier.device1_humidifier',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'on',
|
||||||
|
})
|
||||||
|
# ---
|
@ -1,4 +1,4 @@
|
|||||||
"""Test the Whirlpool Sixth Sense climate domain."""
|
"""Test the Honeywell climate domain."""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
110
tests/components/honeywell/test_humidity.py
Normal file
110
tests/components/honeywell/test_humidity.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
"""Test the Honeywell humidity domain."""
|
||||||
|
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
|
from homeassistant.components.humidifier import (
|
||||||
|
ATTR_HUMIDITY,
|
||||||
|
DOMAIN as HUMIDIFIER_DOMAIN,
|
||||||
|
SERVICE_SET_HUMIDITY,
|
||||||
|
)
|
||||||
|
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
from . import init_integration
|
||||||
|
|
||||||
|
|
||||||
|
async def test_humidifier_service_calls(
|
||||||
|
hass: HomeAssistant, device: MagicMock, config_entry: MagicMock
|
||||||
|
) -> None:
|
||||||
|
"""Test the setup of the climate entities when there are no additional options available."""
|
||||||
|
device.has_humidifier = True
|
||||||
|
await init_integration(hass, config_entry)
|
||||||
|
entity_id = f"humidifier.{device.name}_humidifier"
|
||||||
|
assert hass.states.get(f"humidifier.{device.name}_dehumidifier") is None
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
HUMIDIFIER_DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
device.set_humidifier_auto.assert_called_once()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
HUMIDIFIER_DOMAIN,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
device.set_humidifier_off.assert_called_once()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
HUMIDIFIER_DOMAIN,
|
||||||
|
SERVICE_SET_HUMIDITY,
|
||||||
|
{ATTR_ENTITY_ID: entity_id, ATTR_HUMIDITY: 40},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
device.set_humidifier_setpoint.assert_called_once_with(40)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_dehumidifier_service_calls(
|
||||||
|
hass: HomeAssistant, device: MagicMock, config_entry: MagicMock
|
||||||
|
) -> None:
|
||||||
|
"""Test the setup of the climate entities when there are no additional options available."""
|
||||||
|
device.has_dehumidifier = True
|
||||||
|
await init_integration(hass, config_entry)
|
||||||
|
entity_id = f"humidifier.{device.name}_dehumidifier"
|
||||||
|
assert hass.states.get(f"humidifier.{device.name}_humidifier") is None
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
HUMIDIFIER_DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
device.set_dehumidifier_auto.assert_called_once()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
HUMIDIFIER_DOMAIN,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
device.set_dehumidifier_off.assert_called_once()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
HUMIDIFIER_DOMAIN,
|
||||||
|
SERVICE_SET_HUMIDITY,
|
||||||
|
{ATTR_ENTITY_ID: entity_id, ATTR_HUMIDITY: 40},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
device.set_dehumidifier_setpoint.assert_called_once_with(40)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_static_attributes(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
device: MagicMock,
|
||||||
|
config_entry: MagicMock,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
) -> None:
|
||||||
|
"""Test static humidifier attributes."""
|
||||||
|
device.has_dehumidifier = True
|
||||||
|
device.has_humidifier = True
|
||||||
|
await init_integration(hass, config_entry)
|
||||||
|
|
||||||
|
entity_id_dehumidifier = f"humidifier.{device.name}_dehumidifier"
|
||||||
|
entity_id_humidifier = f"humidifier.{device.name}_humidifier"
|
||||||
|
entry = entity_registry.async_get(entity_id_dehumidifier)
|
||||||
|
assert entry
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id_dehumidifier)
|
||||||
|
|
||||||
|
assert state == snapshot(name="dehumidifier")
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id_humidifier)
|
||||||
|
|
||||||
|
assert state == snapshot(name="humidifier")
|
Loading…
x
Reference in New Issue
Block a user