mirror of
https://github.com/home-assistant/core.git
synced 2026-02-15 07:36:16 +00:00
Add humidifier entity for humidifier and dehumidifier to LG ThinQ (#152593)
Co-authored-by: yunseon.park <yunseon.park@lge.com>
This commit is contained in:
committed by
GitHub
parent
1733599442
commit
f79eef150e
@@ -42,6 +42,7 @@ PLATFORMS = [
|
||||
Platform.CLIMATE,
|
||||
Platform.EVENT,
|
||||
Platform.FAN,
|
||||
Platform.HUMIDIFIER,
|
||||
Platform.NUMBER,
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
|
||||
195
homeassistant/components/lg_thinq/humidifier.py
Normal file
195
homeassistant/components/lg_thinq/humidifier.py
Normal file
@@ -0,0 +1,195 @@
|
||||
"""Support for humidifier entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from thinqconnect import DeviceType
|
||||
from thinqconnect.devices.const import Property as ThinQProperty
|
||||
from thinqconnect.integration import ActiveMode
|
||||
|
||||
from homeassistant.components.humidifier import (
|
||||
HumidifierAction,
|
||||
HumidifierDeviceClass,
|
||||
HumidifierEntity,
|
||||
HumidifierEntityDescription,
|
||||
HumidifierEntityFeature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import ThinqConfigEntry
|
||||
from .coordinator import DeviceDataUpdateCoordinator
|
||||
from .entity import ThinQEntity
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class ThinQHumidifierEntityDescription(HumidifierEntityDescription):
|
||||
"""Describes ThinQ humidifier entity."""
|
||||
|
||||
current_humidity_key: str
|
||||
operation_key: str
|
||||
mode_key: str = ThinQProperty.CURRENT_JOB_MODE
|
||||
|
||||
|
||||
DEVICE_TYPE_HUM_MAP: dict[DeviceType, ThinQHumidifierEntityDescription] = {
|
||||
DeviceType.DEHUMIDIFIER: ThinQHumidifierEntityDescription(
|
||||
key=ThinQProperty.TARGET_HUMIDITY,
|
||||
name=None,
|
||||
device_class=HumidifierDeviceClass.DEHUMIDIFIER,
|
||||
translation_key="dehumidifier",
|
||||
current_humidity_key=ThinQProperty.CURRENT_HUMIDITY,
|
||||
operation_key=ThinQProperty.DEHUMIDIFIER_OPERATION_MODE,
|
||||
),
|
||||
DeviceType.HUMIDIFIER: ThinQHumidifierEntityDescription(
|
||||
key=ThinQProperty.TARGET_HUMIDITY,
|
||||
name=None,
|
||||
device_class=HumidifierDeviceClass.HUMIDIFIER,
|
||||
translation_key="humidifier",
|
||||
current_humidity_key=ThinQProperty.HUMIDITY,
|
||||
operation_key=ThinQProperty.HUMIDIFIER_OPERATION_MODE,
|
||||
),
|
||||
}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ThinqConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up an entry for humidifier platform."""
|
||||
entities: list[ThinQHumidifierEntity] = []
|
||||
for coordinator in entry.runtime_data.coordinators.values():
|
||||
if (
|
||||
description := DEVICE_TYPE_HUM_MAP.get(coordinator.api.device.device_type)
|
||||
) is not None:
|
||||
entities.extend(
|
||||
ThinQHumidifierEntity(coordinator, description, property_id)
|
||||
for property_id in coordinator.api.get_active_idx(
|
||||
description.key, ActiveMode.READ_WRITE
|
||||
)
|
||||
)
|
||||
|
||||
if entities:
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class ThinQHumidifierEntity(ThinQEntity, HumidifierEntity):
|
||||
"""Represent a ThinQ humidifier entity."""
|
||||
|
||||
entity_description: ThinQHumidifierEntityDescription
|
||||
_attr_supported_features = HumidifierEntityFeature.MODES
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: DeviceDataUpdateCoordinator,
|
||||
entity_description: ThinQHumidifierEntityDescription,
|
||||
property_id: str,
|
||||
) -> None:
|
||||
"""Initialize a humidifier entity."""
|
||||
super().__init__(coordinator, entity_description, property_id)
|
||||
self._attr_available_modes = self.coordinator.data[
|
||||
self.entity_description.mode_key
|
||||
].options
|
||||
|
||||
if self.data.max is not None:
|
||||
self._attr_max_humidity = self.data.max
|
||||
if self.data.min is not None:
|
||||
self._attr_min_humidity = self.data.min
|
||||
self._attr_target_humidity_step = (
|
||||
self.data.step if self.data.step is not None else 1
|
||||
)
|
||||
|
||||
def _update_status(self) -> None:
|
||||
"""Update status itself."""
|
||||
super()._update_status()
|
||||
|
||||
self._attr_target_humidity = self.data.value
|
||||
self._attr_current_humidity = self.coordinator.data[
|
||||
self.entity_description.current_humidity_key
|
||||
].value
|
||||
self._attr_is_on = self.coordinator.data[
|
||||
self.entity_description.operation_key
|
||||
].is_on
|
||||
self._attr_mode = self.coordinator.data[self.entity_description.mode_key].value
|
||||
if self.is_on:
|
||||
self._attr_action = (
|
||||
HumidifierAction.DRYING
|
||||
if self.entity_description.device_class
|
||||
== HumidifierDeviceClass.DEHUMIDIFIER
|
||||
else HumidifierAction.HUMIDIFYING
|
||||
)
|
||||
else:
|
||||
self._attr_action = HumidifierAction.OFF
|
||||
|
||||
_LOGGER.debug(
|
||||
"[%s:%s] update status: c:%s, t:%s, mode:%s, action:%s, is_on:%s",
|
||||
self.coordinator.device_name,
|
||||
self.property_id,
|
||||
self.current_humidity,
|
||||
self.target_humidity,
|
||||
self.mode,
|
||||
self.action,
|
||||
self.is_on,
|
||||
)
|
||||
|
||||
async def async_set_mode(self, mode: str) -> None:
|
||||
"""Set new target preset mode."""
|
||||
_LOGGER.debug(
|
||||
"[%s:%s] async_set_mode: %s",
|
||||
self.coordinator.device_name,
|
||||
self.entity_description.mode_key,
|
||||
mode,
|
||||
)
|
||||
await self.async_call_api(
|
||||
self.coordinator.api.post(self.entity_description.mode_key, mode)
|
||||
)
|
||||
|
||||
async def async_set_humidity(self, humidity: int) -> None:
|
||||
"""Set new target humidity."""
|
||||
_target_humidity = round(humidity / (self.target_humidity_step or 1)) * (
|
||||
self.target_humidity_step or 1
|
||||
)
|
||||
_LOGGER.debug(
|
||||
"[%s:%s] async_set_humidity: %s, target_humidity: %s, step: %s",
|
||||
self.coordinator.device_name,
|
||||
self.property_id,
|
||||
humidity,
|
||||
_target_humidity,
|
||||
self.target_humidity_step,
|
||||
)
|
||||
if _target_humidity == self.target_humidity:
|
||||
return
|
||||
await self.async_call_api(
|
||||
self.coordinator.api.post(self.property_id, _target_humidity)
|
||||
)
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity on."""
|
||||
if self.is_on:
|
||||
return
|
||||
_LOGGER.debug(
|
||||
"[%s:%s] async_turn_on",
|
||||
self.coordinator.device_name,
|
||||
self.entity_description.operation_key,
|
||||
)
|
||||
await self.async_call_api(
|
||||
self.coordinator.api.async_turn_on(self.entity_description.operation_key)
|
||||
)
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity off."""
|
||||
if not self.is_on:
|
||||
return
|
||||
_LOGGER.debug(
|
||||
"[%s:%s] async_turn_off",
|
||||
self.coordinator.device_name,
|
||||
self.entity_description.operation_key,
|
||||
)
|
||||
await self.async_call_api(
|
||||
self.coordinator.api.async_turn_off(self.entity_description.operation_key)
|
||||
)
|
||||
@@ -199,6 +199,33 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"humidifier": {
|
||||
"dehumidifier": {
|
||||
"state_attributes": {
|
||||
"mode": {
|
||||
"state": {
|
||||
"air_clean": "[%key:component::lg_thinq::entity::sensor::current_job_mode::state::air_clean%]",
|
||||
"clothes_dry": "[%key:component::lg_thinq::entity::sensor::current_job_mode::state::clothes_dry%]",
|
||||
"intensive_dry": "[%key:component::lg_thinq::entity::sensor::current_job_mode::state::intensive_dry%]",
|
||||
"quiet_humidity": "[%key:component::lg_thinq::entity::sensor::current_job_mode::state::quiet_humidity%]",
|
||||
"rapid_humidity": "[%key:component::lg_thinq::entity::sensor::current_job_mode::state::rapid_humidity%]",
|
||||
"smart_humidity": "[%key:component::lg_thinq::entity::sensor::current_job_mode::state::smart_humidity%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"humidifier": {
|
||||
"state_attributes": {
|
||||
"mode": {
|
||||
"state": {
|
||||
"air_clean": "[%key:component::lg_thinq::entity::select::current_job_mode::state::air_clean%]",
|
||||
"humidify": "[%key:component::lg_thinq::entity::select::current_job_mode::state::humidify%]",
|
||||
"humidify_and_air_clean": "[%key:component::lg_thinq::entity::select::current_job_mode::state::humidify_and_air_clean%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"fan_speed": {
|
||||
"name": "Fan"
|
||||
|
||||
@@ -116,6 +116,7 @@ def mock_thinq_mqtt_client() -> Generator[None]:
|
||||
params=[
|
||||
"air_conditioner",
|
||||
"washer",
|
||||
"dehumidifier",
|
||||
]
|
||||
)
|
||||
def device_fixture(request: pytest.FixtureRequest) -> Generator[str]:
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"deviceId": "TQS-25A89504-EF8E-4C4E-99F3-3956ED3BD397",
|
||||
"deviceInfo": {
|
||||
"deviceType": "DEVICE_DEHUMIDIFIER",
|
||||
"modelName": "DHUM_056905_KR",
|
||||
"alias": "Test dehumidifier",
|
||||
"reportable": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"resultCode": "0000",
|
||||
"result": {
|
||||
"property": ["energyUsage"]
|
||||
}
|
||||
}
|
||||
78
tests/components/lg_thinq/fixtures/dehumidifier/profile.json
Normal file
78
tests/components/lg_thinq/fixtures/dehumidifier/profile.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"notification": {
|
||||
"push": ["WATER_IS_FULL"]
|
||||
},
|
||||
"property": {
|
||||
"airFlow": {
|
||||
"windStrengthLevel": {
|
||||
"mode": ["r", "w"],
|
||||
"type": "enum",
|
||||
"value": {
|
||||
"r": ["LOW", "HIGH"],
|
||||
"w": ["LOW", "HIGH"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"operation": {
|
||||
"dehumidifierOperationMode": {
|
||||
"mode": ["r", "w"],
|
||||
"type": "enum",
|
||||
"value": {
|
||||
"r": ["POWER_ON", "POWER_OFF"],
|
||||
"w": ["POWER_ON", "POWER_OFF"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dehumidifierJobMode": {
|
||||
"currentJobMode": {
|
||||
"mode": ["r", "w"],
|
||||
"type": "enum",
|
||||
"value": {
|
||||
"r": [
|
||||
"SMART_HUMIDITY",
|
||||
"INTENSIVE_DRY",
|
||||
"RAPID_HUMIDITY",
|
||||
"QUIET_HUMIDITY",
|
||||
"CLOTHES_DRY"
|
||||
],
|
||||
"w": [
|
||||
"SMART_HUMIDITY",
|
||||
"INTENSIVE_DRY",
|
||||
"RAPID_HUMIDITY",
|
||||
"QUIET_HUMIDITY",
|
||||
"CLOTHES_DRY"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"humidity": {
|
||||
"currentHumidity": {
|
||||
"mode": ["r"],
|
||||
"type": "range",
|
||||
"value": {
|
||||
"r": {
|
||||
"max": 100,
|
||||
"min": 0,
|
||||
"step": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"targetHumidity": {
|
||||
"mode": ["r", "w"],
|
||||
"type": "range",
|
||||
"value": {
|
||||
"r": {
|
||||
"max": 70,
|
||||
"min": 30,
|
||||
"step": 5
|
||||
},
|
||||
"w": {
|
||||
"max": 70,
|
||||
"min": 30,
|
||||
"step": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
tests/components/lg_thinq/fixtures/dehumidifier/status.json
Normal file
15
tests/components/lg_thinq/fixtures/dehumidifier/status.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"airFlow": {
|
||||
"windStrengthLevel": "HIGH"
|
||||
},
|
||||
"dehumidifierJobMode": {
|
||||
"currentJobMode": "SMART_HUMIDITY"
|
||||
},
|
||||
"humidity": {
|
||||
"currentHumidity": 80,
|
||||
"targetHumidity": 55
|
||||
},
|
||||
"operation": {
|
||||
"dehumidifierOperationMode": "POWER_ON"
|
||||
}
|
||||
}
|
||||
76
tests/components/lg_thinq/snapshots/test_humidifier.ambr
Normal file
76
tests/components/lg_thinq/snapshots/test_humidifier.ambr
Normal file
@@ -0,0 +1,76 @@
|
||||
# serializer version: 1
|
||||
# name: test_humidifier_entities[dehumidifier][humidifier.test_dehumidifier-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'available_modes': list([
|
||||
'smart_humidity',
|
||||
'intensive_dry',
|
||||
'rapid_humidity',
|
||||
'quiet_humidity',
|
||||
'clothes_dry',
|
||||
]),
|
||||
'max_humidity': 70,
|
||||
'min_humidity': 30,
|
||||
'target_humidity_step': 5,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'humidifier',
|
||||
'entity_category': None,
|
||||
'entity_id': 'humidifier.test_dehumidifier',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <HumidifierDeviceClass.DEHUMIDIFIER: 'dehumidifier'>,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'lg_thinq',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <HumidifierEntityFeature: 1>,
|
||||
'translation_key': 'dehumidifier',
|
||||
'unique_id': 'TQS-25A89504-EF8E-4C4E-99F3-3956ED3BD397_target_humidity',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_humidifier_entities[dehumidifier][humidifier.test_dehumidifier-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'action': <HumidifierAction.DRYING: 'drying'>,
|
||||
'available_modes': list([
|
||||
'smart_humidity',
|
||||
'intensive_dry',
|
||||
'rapid_humidity',
|
||||
'quiet_humidity',
|
||||
'clothes_dry',
|
||||
]),
|
||||
'current_humidity': 80,
|
||||
'device_class': 'dehumidifier',
|
||||
'friendly_name': 'Test dehumidifier',
|
||||
'humidity': 55,
|
||||
'max_humidity': 70,
|
||||
'min_humidity': 30,
|
||||
'mode': 'smart_humidity',
|
||||
'supported_features': <HumidifierEntityFeature: 1>,
|
||||
'target_humidity_step': 5,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'humidifier.test_dehumidifier',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
@@ -194,4 +194,53 @@
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_entities[dehumidifier][switch.test_dehumidifier_power-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': None,
|
||||
'entity_id': 'switch.test_dehumidifier_power',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SwitchDeviceClass.SWITCH: 'switch'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Power',
|
||||
'platform': 'lg_thinq',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'operation_power',
|
||||
'unique_id': 'TQS-25A89504-EF8E-4C4E-99F3-3956ED3BD397_dehumidifier_operation_mode',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_entities[dehumidifier][switch.test_dehumidifier_power-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'switch',
|
||||
'friendly_name': 'Test dehumidifier Power',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.test_dehumidifier_power',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
31
tests/components/lg_thinq/test_humidifier.py
Normal file
31
tests/components/lg_thinq/test_humidifier.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""Tests for the LG ThinQ humidifier platform."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
@pytest.mark.parametrize("device_fixture", ["dehumidifier"])
|
||||
async def test_humidifier_entities(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
devices: AsyncMock,
|
||||
mock_thinq_api: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test all entities."""
|
||||
with patch("homeassistant.components.lg_thinq.PLATFORMS", [Platform.HUMIDIFIER]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
@@ -15,6 +15,7 @@ from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
@pytest.mark.parametrize("device_fixture", ["air_conditioner", "washer"])
|
||||
async def test_number_entities(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
|
||||
Reference in New Issue
Block a user