mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +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
|
||||
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}
|
||||
|
||||
|
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": {
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Tests for honeywell component."""
|
||||
"""Tests for Honeywell component."""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
|
@ -127,7 +127,16 @@ def device():
|
||||
mock_device.refresh = AsyncMock()
|
||||
mock_device.heat_away_temp = HEATAWAY
|
||||
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}
|
||||
|
||||
return mock_device
|
||||
@ -149,6 +158,8 @@ def device_with_outdoor_sensor():
|
||||
mock_device.temperature_unit = "C"
|
||||
mock_device.outdoor_temperature = OUTDOORTEMP
|
||||
mock_device.outdoor_humidity = OUTDOORHUMIDITY
|
||||
mock_device.has_humidifier = False
|
||||
mock_device.has_dehumidifier = False
|
||||
mock_device.raw_ui_data = {
|
||||
"SwitchOffAllowed": True,
|
||||
"SwitchAutoAllowed": True,
|
||||
@ -188,6 +199,16 @@ def another_device():
|
||||
mock_device.mac_address = "macaddress1"
|
||||
mock_device.outdoor_temperature = 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 = {
|
||||
"SwitchOffAllowed": 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
|
||||
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