mirror of
https://github.com/home-assistant/core.git
synced 2026-04-09 17:06:37 +00:00
Compare commits
8 Commits
trigger_ad
...
thinq-hood
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1e65fb535 | ||
|
|
fe964bc93f | ||
|
|
48fdc5e1b7 | ||
|
|
1f1fe1b7ce | ||
|
|
48ee57c234 | ||
|
|
f8ea687aa4 | ||
|
|
4b77b00a95 | ||
|
|
7119c5da3a |
@@ -20,6 +20,8 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util.percentage import (
|
||||
ordered_list_item_to_percentage,
|
||||
percentage_to_ordered_list_item,
|
||||
percentage_to_ranged_value,
|
||||
ranged_value_to_percentage,
|
||||
)
|
||||
|
||||
from . import ThinqConfigEntry
|
||||
@@ -35,6 +37,11 @@ class ThinQFanEntityDescription(FanEntityDescription):
|
||||
preset_modes: list[str] | None = None
|
||||
|
||||
|
||||
HOOD_FAN_DESC = FanEntityDescription(
|
||||
key=ThinQProperty.FAN_SPEED,
|
||||
translation_key=ThinQProperty.FAN_SPEED,
|
||||
)
|
||||
|
||||
DEVICE_TYPE_FAN_MAP: dict[DeviceType, tuple[ThinQFanEntityDescription, ...]] = {
|
||||
DeviceType.CEILING_FAN: (
|
||||
ThinQFanEntityDescription(
|
||||
@@ -54,6 +61,8 @@ DEVICE_TYPE_FAN_MAP: dict[DeviceType, tuple[ThinQFanEntityDescription, ...]] = {
|
||||
),
|
||||
}
|
||||
|
||||
HOOD_DEVICE_TYPES: set[DeviceType] = {DeviceType.HOOD, DeviceType.MICROWAVE_OVEN}
|
||||
|
||||
ORDERED_NAMED_FAN_SPEEDS = ["low", "mid", "high", "turbo", "power"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -65,11 +74,20 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up an entry for fan platform."""
|
||||
entities: list[ThinQFanEntity] = []
|
||||
entities: list[ThinQFanEntity | ThinQHoodFanEntity] = []
|
||||
for coordinator in entry.runtime_data.coordinators.values():
|
||||
if (
|
||||
descriptions := DEVICE_TYPE_FAN_MAP.get(coordinator.api.device.device_type)
|
||||
) is not None:
|
||||
device_type = coordinator.api.device.device_type
|
||||
|
||||
# Handle hood-type devices with numeric fan speed
|
||||
if device_type in HOOD_DEVICE_TYPES:
|
||||
entities.extend(
|
||||
ThinQHoodFanEntity(coordinator, HOOD_FAN_DESC, property_id)
|
||||
for property_id in coordinator.api.get_active_idx(
|
||||
HOOD_FAN_DESC.key, ActiveMode.READ_WRITE
|
||||
)
|
||||
)
|
||||
# Handle other fan devices with named speeds
|
||||
elif (descriptions := DEVICE_TYPE_FAN_MAP.get(device_type)) is not None:
|
||||
for description in descriptions:
|
||||
entities.extend(
|
||||
ThinQFanEntity(coordinator, description, property_id)
|
||||
@@ -212,3 +230,112 @@ class ThinQFanEntity(ThinQEntity, FanEntity):
|
||||
await self.async_call_api(
|
||||
self.coordinator.api.async_turn_off(self._operation_id)
|
||||
)
|
||||
|
||||
|
||||
class ThinQHoodFanEntity(ThinQEntity, FanEntity):
|
||||
"""Represent a thinq hood fan platform.
|
||||
|
||||
Hood fans use numeric speed values (e.g., 0=off, 1=low, 2=high)
|
||||
rather than named speed presets.
|
||||
"""
|
||||
|
||||
_attr_supported_features = (
|
||||
FanEntityFeature.SET_SPEED
|
||||
| FanEntityFeature.TURN_ON
|
||||
| FanEntityFeature.TURN_OFF
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: DeviceDataUpdateCoordinator,
|
||||
entity_description: FanEntityDescription,
|
||||
property_id: str,
|
||||
) -> None:
|
||||
"""Initialize hood fan platform."""
|
||||
super().__init__(coordinator, entity_description, property_id)
|
||||
|
||||
self._min_speed: int = int(self.data.min)
|
||||
self._max_speed: int = int(self.data.max)
|
||||
|
||||
# Speed count is the number of non-zero speeds
|
||||
self._attr_speed_count = self._max_speed - self._min_speed
|
||||
|
||||
@property
|
||||
def _speed_range(self) -> tuple[int, int]:
|
||||
"""Return the speed range excluding off (0)."""
|
||||
return (self._min_speed + 1, self._max_speed)
|
||||
|
||||
def _update_status(self) -> None:
|
||||
"""Update status itself."""
|
||||
super()._update_status()
|
||||
|
||||
# Get current speed value
|
||||
current_speed = self.data.value
|
||||
if current_speed is None or current_speed == self._min_speed:
|
||||
self._attr_is_on = False
|
||||
self._attr_percentage = 0
|
||||
else:
|
||||
self._attr_is_on = True
|
||||
self._attr_percentage = ranged_value_to_percentage(
|
||||
self._speed_range, current_speed
|
||||
)
|
||||
|
||||
_LOGGER.debug(
|
||||
"[%s:%s] update status: is_on=%s, percentage=%s, speed=%s, min=%s, max=%s",
|
||||
self.coordinator.device_name,
|
||||
self.property_id,
|
||||
self.is_on,
|
||||
self.percentage,
|
||||
current_speed,
|
||||
self._min_speed,
|
||||
self._max_speed,
|
||||
)
|
||||
|
||||
async def async_set_percentage(self, percentage: int) -> None:
|
||||
"""Set the speed percentage of the fan."""
|
||||
if percentage == 0:
|
||||
await self.async_turn_off()
|
||||
return
|
||||
|
||||
speed = round(percentage_to_ranged_value(self._speed_range, percentage))
|
||||
|
||||
_LOGGER.debug(
|
||||
"[%s:%s] async_set_percentage: percentage=%s -> speed=%s",
|
||||
self.coordinator.device_name,
|
||||
self.property_id,
|
||||
percentage,
|
||||
speed,
|
||||
)
|
||||
await self.async_call_api(self.coordinator.api.post(self.property_id, speed))
|
||||
|
||||
async def async_turn_on(
|
||||
self,
|
||||
percentage: int | None = None,
|
||||
preset_mode: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
if percentage is not None:
|
||||
await self.async_set_percentage(percentage)
|
||||
return
|
||||
|
||||
# Default to lowest non-zero speed
|
||||
speed = self._min_speed + 1
|
||||
_LOGGER.debug(
|
||||
"[%s:%s] async_turn_on: speed=%s",
|
||||
self.coordinator.device_name,
|
||||
self.property_id,
|
||||
speed,
|
||||
)
|
||||
await self.async_call_api(self.coordinator.api.post(self.property_id, speed))
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the fan off."""
|
||||
_LOGGER.debug(
|
||||
"[%s:%s] async_turn_off",
|
||||
self.coordinator.device_name,
|
||||
self.property_id,
|
||||
)
|
||||
await self.async_call_api(
|
||||
self.coordinator.api.post(self.property_id, self._min_speed)
|
||||
)
|
||||
|
||||
@@ -8,23 +8,33 @@ from thinqconnect import DeviceType
|
||||
from thinqconnect.devices.const import Property as ThinQProperty
|
||||
from thinqconnect.integration import ActiveMode, TimerProperty
|
||||
|
||||
from homeassistant.components.automation import automations_with_entity
|
||||
from homeassistant.components.number import (
|
||||
NumberDeviceClass,
|
||||
NumberEntity,
|
||||
NumberEntityDescription,
|
||||
NumberMode,
|
||||
)
|
||||
from homeassistant.components.script import scripts_with_entity
|
||||
from homeassistant.const import PERCENTAGE, UnitOfTemperature, UnitOfTime
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.issue_registry import (
|
||||
IssueSeverity,
|
||||
async_create_issue,
|
||||
async_delete_issue,
|
||||
)
|
||||
|
||||
from . import ThinqConfigEntry
|
||||
from .const import DOMAIN
|
||||
from .entity import ThinQEntity
|
||||
|
||||
NUMBER_DESC: dict[ThinQProperty, NumberEntityDescription] = {
|
||||
ThinQProperty.FAN_SPEED: NumberEntityDescription(
|
||||
key=ThinQProperty.FAN_SPEED,
|
||||
translation_key=ThinQProperty.FAN_SPEED,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
ThinQProperty.LAMP_BRIGHTNESS: NumberEntityDescription(
|
||||
key=ThinQProperty.LAMP_BRIGHTNESS,
|
||||
@@ -128,9 +138,71 @@ DEVICE_TYPE_NUMBER_MAP: dict[DeviceType, tuple[NumberEntityDescription, ...]] =
|
||||
),
|
||||
}
|
||||
|
||||
DEPRECATED_FAN_SPEED_DEVICE_TYPES: set[DeviceType] = {
|
||||
DeviceType.HOOD,
|
||||
DeviceType.MICROWAVE_OVEN,
|
||||
}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _check_deprecated_fan_speed_entity(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
unique_id: str,
|
||||
) -> bool:
|
||||
"""Check if a deprecated fan speed number entity should be created.
|
||||
|
||||
Returns True if the entity exists and is enabled (should still be created).
|
||||
"""
|
||||
if not (
|
||||
entity_id := entity_registry.async_get_entity_id("number", DOMAIN, unique_id)
|
||||
):
|
||||
return False
|
||||
|
||||
entity_entry = entity_registry.async_get(entity_id)
|
||||
if not entity_entry:
|
||||
return False
|
||||
|
||||
if entity_entry.disabled:
|
||||
entity_registry.async_remove(entity_id)
|
||||
async_delete_issue(hass, DOMAIN, f"deprecated_fan_speed_number_{entity_id}")
|
||||
return False
|
||||
|
||||
translation_key = "deprecated_fan_speed_number"
|
||||
placeholders: dict[str, str] = {
|
||||
"entity_id": entity_id,
|
||||
"entity_name": entity_entry.name or entity_entry.original_name or "Unknown",
|
||||
}
|
||||
|
||||
automation_entities = automations_with_entity(hass, entity_id)
|
||||
script_entities = scripts_with_entity(hass, entity_id)
|
||||
if automation_entities or script_entities:
|
||||
translation_key = f"{translation_key}_scripts"
|
||||
placeholders["items"] = "\n".join(
|
||||
f"- [{item.original_name}](/config/{integration}/edit/{item.unique_id})"
|
||||
for integration, entities in (
|
||||
("automation", automation_entities),
|
||||
("script", script_entities),
|
||||
)
|
||||
for eid in entities
|
||||
if (item := entity_registry.async_get(eid))
|
||||
)
|
||||
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
f"deprecated_fan_speed_number_{entity_id}",
|
||||
breaks_in_ha_version="2026.11.0",
|
||||
is_fixable=True,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key=translation_key,
|
||||
translation_placeholders=placeholders,
|
||||
data={"entity_id": entity_id, **placeholders},
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ThinqConfigEntry,
|
||||
@@ -138,18 +210,27 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up an entry for number platform."""
|
||||
entities: list[ThinQNumberEntity] = []
|
||||
entity_registry = er.async_get(hass)
|
||||
for coordinator in entry.runtime_data.coordinators.values():
|
||||
if (
|
||||
descriptions := DEVICE_TYPE_NUMBER_MAP.get(
|
||||
coordinator.api.device.device_type
|
||||
)
|
||||
) is not None:
|
||||
for description in descriptions:
|
||||
entities.extend(
|
||||
descriptions = DEVICE_TYPE_NUMBER_MAP.get(coordinator.api.device.device_type)
|
||||
if descriptions is None:
|
||||
continue
|
||||
for description in descriptions:
|
||||
for property_id in coordinator.api.get_active_idx(
|
||||
description.key, ActiveMode.READ_WRITE
|
||||
):
|
||||
if (
|
||||
description.key == ThinQProperty.FAN_SPEED
|
||||
and coordinator.api.device.device_type
|
||||
in DEPRECATED_FAN_SPEED_DEVICE_TYPES
|
||||
):
|
||||
unique_id = f"{coordinator.unique_id}_{property_id}"
|
||||
if not _check_deprecated_fan_speed_entity(
|
||||
hass, entity_registry, unique_id
|
||||
):
|
||||
continue
|
||||
entities.append(
|
||||
ThinQNumberEntity(coordinator, description, property_id)
|
||||
for property_id in coordinator.api.get_active_idx(
|
||||
description.key, ActiveMode.READ_WRITE
|
||||
)
|
||||
)
|
||||
|
||||
if entities:
|
||||
|
||||
55
homeassistant/components/lg_thinq/repairs.py
Normal file
55
homeassistant/components/lg_thinq/repairs.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""Repairs for LG ThinQ integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
|
||||
class DeprecatedFanSpeedRepairFlow(RepairsFlow):
|
||||
"""Handler for deprecated fan speed number entity fixing flow."""
|
||||
|
||||
def __init__(self, data: dict[str, str]) -> None:
|
||||
"""Initialize."""
|
||||
self.entity_id = data["entity_id"]
|
||||
self._placeholders = data
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> data_entry_flow.FlowResult:
|
||||
"""Handle the first step of a fix flow."""
|
||||
return await self.async_step_confirm()
|
||||
|
||||
async def async_step_confirm(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> data_entry_flow.FlowResult:
|
||||
"""Handle the confirm step of a fix flow."""
|
||||
if user_input is not None:
|
||||
entity_registry = er.async_get(self.hass)
|
||||
if entity_registry.async_get(self.entity_id):
|
||||
entity_registry.async_update_entity(
|
||||
self.entity_id,
|
||||
disabled_by=er.RegistryEntryDisabler.USER,
|
||||
)
|
||||
return self.async_create_entry(data={})
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="confirm",
|
||||
data_schema=vol.Schema({}),
|
||||
description_placeholders=self._placeholders,
|
||||
)
|
||||
|
||||
|
||||
async def async_create_fix_flow(
|
||||
hass: HomeAssistant,
|
||||
issue_id: str,
|
||||
data: dict[str, str],
|
||||
) -> RepairsFlow:
|
||||
"""Create flow."""
|
||||
if issue_id.startswith("deprecated_fan_speed_number_"):
|
||||
return DeprecatedFanSpeedRepairFlow(data)
|
||||
return ConfirmRepairFlow()
|
||||
@@ -199,6 +199,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"fan": {
|
||||
"fan_speed": {
|
||||
"name": "Hood"
|
||||
}
|
||||
},
|
||||
"humidifier": {
|
||||
"dehumidifier": {
|
||||
"state_attributes": {
|
||||
@@ -1154,5 +1159,29 @@
|
||||
"failed_to_connect_mqtt": {
|
||||
"message": "Failed to connect MQTT: {error}"
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_fan_speed_number": {
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "The number entity {entity_name} (`{entity_id}`) is deprecated because it has been replaced with a fan entity.\n\nPlease update your dashboards and templates to use the new fan entity.\n\nClick **Submit** to disable the number entity and fix this issue.",
|
||||
"title": "Fan speed number entity deprecated"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "[%key:component::lg_thinq::issues::deprecated_fan_speed_number::fix_flow::step::confirm::title%]"
|
||||
},
|
||||
"deprecated_fan_speed_number_scripts": {
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "The number entity {entity_name} (`{entity_id}`) is deprecated because it has been replaced with a fan entity.\n\nThe entity was used in the following automations or scripts:\n{items}\n\nPlease update the above automations or scripts to use the new fan entity.\n\nClick **Submit** to disable the number entity and fix this issue.",
|
||||
"title": "[%key:component::lg_thinq::issues::deprecated_fan_speed_number::fix_flow::step::confirm::title%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "[%key:component::lg_thinq::issues::deprecated_fan_speed_number::fix_flow::step::confirm::title%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
9
tests/components/lg_thinq/fixtures/hood/device.json
Normal file
9
tests/components/lg_thinq/fixtures/hood/device.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"deviceId": "MW2-2E247F93-B570-46A6-B827-920E9E10F966",
|
||||
"deviceInfo": {
|
||||
"deviceType": "DEVICE_HOOD",
|
||||
"modelName": "HOOD_TEST",
|
||||
"alias": "Test hood",
|
||||
"reportable": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"resultCode": "0000",
|
||||
"result": {}
|
||||
}
|
||||
40
tests/components/lg_thinq/fixtures/hood/profile.json
Normal file
40
tests/components/lg_thinq/fixtures/hood/profile.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"property": {
|
||||
"ventilation": {
|
||||
"fanSpeed": {
|
||||
"mode": ["r", "w"],
|
||||
"type": "range",
|
||||
"value": {
|
||||
"r": {
|
||||
"max": 5,
|
||||
"min": 0,
|
||||
"step": 1
|
||||
},
|
||||
"w": {
|
||||
"max": 5,
|
||||
"min": 0,
|
||||
"step": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lamp": {
|
||||
"lampBrightness": {
|
||||
"mode": ["r", "w"],
|
||||
"type": "range",
|
||||
"value": {
|
||||
"r": {
|
||||
"max": 2,
|
||||
"min": 0,
|
||||
"step": 1
|
||||
},
|
||||
"w": {
|
||||
"max": 2,
|
||||
"min": 0,
|
||||
"step": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
tests/components/lg_thinq/fixtures/hood/status.json
Normal file
8
tests/components/lg_thinq/fixtures/hood/status.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"ventilation": {
|
||||
"fanSpeed": 1
|
||||
},
|
||||
"lamp": {
|
||||
"lampBrightness": 2
|
||||
}
|
||||
}
|
||||
58
tests/components/lg_thinq/snapshots/test_fan.ambr
Normal file
58
tests/components/lg_thinq/snapshots/test_fan.ambr
Normal file
@@ -0,0 +1,58 @@
|
||||
# serializer version: 1
|
||||
# name: test_fan_entities[hood][fan.test_hood_hood-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'preset_modes': None,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'fan',
|
||||
'entity_category': None,
|
||||
'entity_id': 'fan.test_hood_hood',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Hood',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Hood',
|
||||
'platform': 'lg_thinq',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <FanEntityFeature: 49>,
|
||||
'translation_key': <Property.FAN_SPEED: 'fan_speed'>,
|
||||
'unique_id': 'MW2-2E247F93-B570-46A6-B827-920E9E10F966_fan_speed',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_fan_entities[hood][fan.test_hood_hood-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Test hood Hood',
|
||||
'percentage': 20,
|
||||
'percentage_step': 20.0,
|
||||
'preset_mode': None,
|
||||
'preset_modes': None,
|
||||
'supported_features': <FanEntityFeature: 49>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'fan.test_hood_hood',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
78
tests/components/lg_thinq/test_fan.py
Normal file
78
tests/components/lg_thinq/test_fan.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""Tests for the LG ThinQ fan platform."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.fan import (
|
||||
ATTR_PERCENTAGE,
|
||||
DOMAIN as FAN_DOMAIN,
|
||||
SERVICE_SET_PERCENTAGE,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
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
|
||||
|
||||
HOOD_FAN_ENTITY_ID = "fan.test_hood_hood"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["hood"])
|
||||
async def test_fan_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.FAN]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("device_fixture", "service", "service_data", "expected_value"),
|
||||
[
|
||||
("hood", SERVICE_TURN_ON, {}, 1),
|
||||
("hood", SERVICE_TURN_OFF, {}, 0),
|
||||
("hood", SERVICE_SET_PERCENTAGE, {ATTR_PERCENTAGE: 100}, 5),
|
||||
("hood", SERVICE_TURN_ON, {ATTR_PERCENTAGE: 60}, 3),
|
||||
("hood", SERVICE_SET_PERCENTAGE, {ATTR_PERCENTAGE: 0}, 0),
|
||||
],
|
||||
)
|
||||
async def test_fan_service_calls(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_thinq_api: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
service: str,
|
||||
service_data: dict,
|
||||
expected_value: int,
|
||||
) -> None:
|
||||
"""Test hood fan service calls post the correct speed values."""
|
||||
with patch("homeassistant.components.lg_thinq.PLATFORMS", [Platform.FAN]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
coordinator = next(iter(mock_config_entry.runtime_data.coordinators.values()))
|
||||
coordinator.api.post = AsyncMock()
|
||||
|
||||
await hass.services.async_call(
|
||||
FAN_DOMAIN,
|
||||
service,
|
||||
{ATTR_ENTITY_ID: HOOD_FAN_ENTITY_ID, **service_data},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
coordinator.api.post.assert_awaited_once_with("fan_speed", expected_value)
|
||||
83
tests/components/lg_thinq/test_repairs.py
Normal file
83
tests/components/lg_thinq/test_repairs.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""Tests for the LG ThinQ repairs."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.lg_thinq.const import DOMAIN
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er, issue_registry as ir
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.components.repairs import (
|
||||
async_process_repairs_platforms,
|
||||
process_repair_fix_flow,
|
||||
start_repair_fix_flow,
|
||||
)
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["hood"])
|
||||
async def test_deprecated_fan_speed_number_repair_flow(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_thinq_api: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
hass_client: ClientSessionGenerator,
|
||||
) -> None:
|
||||
"""Test the deprecated fan speed number entity repair flow."""
|
||||
assert await async_setup_component(hass, "repairs", {})
|
||||
|
||||
# Add config entry first so we can pre-create the deprecated entity
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
# Pre-create the deprecated number entity so it's found during setup
|
||||
entity_registry.async_get_or_create(
|
||||
"number",
|
||||
DOMAIN,
|
||||
"MW2-2E247F93-B570-46A6-B827-920E9E10F966_fan_speed",
|
||||
suggested_object_id="test_hood_fan_speed",
|
||||
original_name="Fan speed",
|
||||
config_entry=mock_config_entry,
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.lg_thinq.PLATFORMS",
|
||||
[Platform.FAN, Platform.NUMBER],
|
||||
):
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_id = "number.test_hood_fan_speed"
|
||||
|
||||
# Verify the issue was created
|
||||
issue = issue_registry.async_get_issue(
|
||||
domain=DOMAIN,
|
||||
issue_id=f"deprecated_fan_speed_number_{entity_id}",
|
||||
)
|
||||
assert issue is not None
|
||||
assert issue.is_fixable is True
|
||||
|
||||
# Start the repair flow
|
||||
await async_process_repairs_platforms(hass)
|
||||
client = await hass_client()
|
||||
result = await start_repair_fix_flow(
|
||||
client, DOMAIN, f"deprecated_fan_speed_number_{entity_id}"
|
||||
)
|
||||
|
||||
flow_id = result["flow_id"]
|
||||
assert result["step_id"] == "confirm"
|
||||
|
||||
# Submit the repair flow
|
||||
result = await process_repair_fix_flow(client, flow_id)
|
||||
assert result["type"] == "create_entry"
|
||||
|
||||
# Verify the entity was disabled
|
||||
entity = entity_registry.async_get(entity_id)
|
||||
assert entity is not None
|
||||
assert entity.disabled is True
|
||||
assert entity.disabled_by is er.RegistryEntryDisabler.USER
|
||||
Reference in New Issue
Block a user