mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Add preset modes to HKC fans (#142528)
This commit is contained in:
parent
35187a4b52
commit
53b991fb54
@ -5,6 +5,10 @@ from __future__ import annotations
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aiohomekit.model.characteristics import CharacteristicsTypes
|
from aiohomekit.model.characteristics import CharacteristicsTypes
|
||||||
|
from aiohomekit.model.characteristics.const import (
|
||||||
|
TargetAirPurifierStateValues,
|
||||||
|
TargetFanStateValues,
|
||||||
|
)
|
||||||
from aiohomekit.model.services import Service, ServicesTypes
|
from aiohomekit.model.services import Service, ServicesTypes
|
||||||
from propcache.api import cached_property
|
from propcache.api import cached_property
|
||||||
|
|
||||||
@ -35,6 +39,8 @@ DIRECTION_TO_HK = {
|
|||||||
}
|
}
|
||||||
HK_DIRECTION_TO_HA = {v: k for (k, v) in DIRECTION_TO_HK.items()}
|
HK_DIRECTION_TO_HA = {v: k for (k, v) in DIRECTION_TO_HK.items()}
|
||||||
|
|
||||||
|
PRESET_AUTO = "auto"
|
||||||
|
|
||||||
|
|
||||||
class BaseHomeKitFan(HomeKitEntity, FanEntity):
|
class BaseHomeKitFan(HomeKitEntity, FanEntity):
|
||||||
"""Representation of a Homekit fan."""
|
"""Representation of a Homekit fan."""
|
||||||
@ -42,6 +48,9 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
|
|||||||
# This must be set in subclasses to the name of a boolean characteristic
|
# This must be set in subclasses to the name of a boolean characteristic
|
||||||
# that controls whether the fan is on or off.
|
# that controls whether the fan is on or off.
|
||||||
on_characteristic: str
|
on_characteristic: str
|
||||||
|
preset_char = CharacteristicsTypes.FAN_STATE_TARGET
|
||||||
|
preset_manual_value: int = TargetFanStateValues.MANUAL
|
||||||
|
preset_automatic_value: int = TargetFanStateValues.AUTOMATIC
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_reconfigure(self) -> None:
|
def _async_reconfigure(self) -> None:
|
||||||
@ -51,6 +60,7 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
|
|||||||
"_speed_range",
|
"_speed_range",
|
||||||
"_min_speed",
|
"_min_speed",
|
||||||
"_max_speed",
|
"_max_speed",
|
||||||
|
"preset_modes",
|
||||||
"speed_count",
|
"speed_count",
|
||||||
"supported_features",
|
"supported_features",
|
||||||
)
|
)
|
||||||
@ -59,12 +69,15 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
|
|||||||
|
|
||||||
def get_characteristic_types(self) -> list[str]:
|
def get_characteristic_types(self) -> list[str]:
|
||||||
"""Define the homekit characteristics the entity cares about."""
|
"""Define the homekit characteristics the entity cares about."""
|
||||||
return [
|
types = [
|
||||||
CharacteristicsTypes.SWING_MODE,
|
CharacteristicsTypes.SWING_MODE,
|
||||||
CharacteristicsTypes.ROTATION_DIRECTION,
|
CharacteristicsTypes.ROTATION_DIRECTION,
|
||||||
CharacteristicsTypes.ROTATION_SPEED,
|
CharacteristicsTypes.ROTATION_SPEED,
|
||||||
self.on_characteristic,
|
self.on_characteristic,
|
||||||
]
|
]
|
||||||
|
if self.service.has(self.preset_char):
|
||||||
|
types.append(self.preset_char)
|
||||||
|
return types
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
@ -124,6 +137,9 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
|
|||||||
if self.service.has(CharacteristicsTypes.SWING_MODE):
|
if self.service.has(CharacteristicsTypes.SWING_MODE):
|
||||||
features |= FanEntityFeature.OSCILLATE
|
features |= FanEntityFeature.OSCILLATE
|
||||||
|
|
||||||
|
if self.service.has(self.preset_char):
|
||||||
|
features |= FanEntityFeature.PRESET_MODE
|
||||||
|
|
||||||
return features
|
return features
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
@ -134,6 +150,32 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
|
|||||||
/ max(1, self.service[CharacteristicsTypes.ROTATION_SPEED].minStep or 0)
|
/ max(1, self.service[CharacteristicsTypes.ROTATION_SPEED].minStep or 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def preset_modes(self) -> list[str]:
|
||||||
|
"""Return the preset modes."""
|
||||||
|
return [PRESET_AUTO] if self.service.has(self.preset_char) else []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_mode(self) -> str | None:
|
||||||
|
"""Return the current preset mode."""
|
||||||
|
if (
|
||||||
|
self.service.has(self.preset_char)
|
||||||
|
and self.service.value(self.preset_char) == self.preset_automatic_value
|
||||||
|
):
|
||||||
|
return PRESET_AUTO
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
|
"""Set the preset mode of the fan."""
|
||||||
|
if self.service.has(self.preset_char):
|
||||||
|
await self.async_put_characteristics(
|
||||||
|
{
|
||||||
|
self.preset_char: self.preset_automatic_value
|
||||||
|
if preset_mode == PRESET_AUTO
|
||||||
|
else self.preset_manual_value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
async def async_set_direction(self, direction: str) -> None:
|
async def async_set_direction(self, direction: str) -> None:
|
||||||
"""Set the direction of the fan."""
|
"""Set the direction of the fan."""
|
||||||
await self.async_put_characteristics(
|
await self.async_put_characteristics(
|
||||||
@ -146,13 +188,16 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
|
|||||||
await self.async_turn_off()
|
await self.async_turn_off()
|
||||||
return
|
return
|
||||||
|
|
||||||
await self.async_put_characteristics(
|
characteristics = {
|
||||||
{
|
|
||||||
CharacteristicsTypes.ROTATION_SPEED: round(
|
CharacteristicsTypes.ROTATION_SPEED: round(
|
||||||
percentage_to_ranged_value(self._speed_range, percentage)
|
percentage_to_ranged_value(self._speed_range, percentage)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
if FanEntityFeature.PRESET_MODE in self.supported_features:
|
||||||
|
characteristics[self.preset_char] = self.preset_manual_value
|
||||||
|
|
||||||
|
await self.async_put_characteristics(characteristics)
|
||||||
|
|
||||||
async def async_oscillate(self, oscillating: bool) -> None:
|
async def async_oscillate(self, oscillating: bool) -> None:
|
||||||
"""Oscillate the fan."""
|
"""Oscillate the fan."""
|
||||||
@ -172,13 +217,17 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
|
|||||||
if not self.is_on:
|
if not self.is_on:
|
||||||
characteristics[self.on_characteristic] = True
|
characteristics[self.on_characteristic] = True
|
||||||
|
|
||||||
if (
|
if preset_mode == PRESET_AUTO:
|
||||||
|
characteristics[self.preset_char] = self.preset_automatic_value
|
||||||
|
elif (
|
||||||
percentage is not None
|
percentage is not None
|
||||||
and FanEntityFeature.SET_SPEED in self.supported_features
|
and FanEntityFeature.SET_SPEED in self.supported_features
|
||||||
):
|
):
|
||||||
characteristics[CharacteristicsTypes.ROTATION_SPEED] = round(
|
characteristics[CharacteristicsTypes.ROTATION_SPEED] = round(
|
||||||
percentage_to_ranged_value(self._speed_range, percentage)
|
percentage_to_ranged_value(self._speed_range, percentage)
|
||||||
)
|
)
|
||||||
|
if FanEntityFeature.PRESET_MODE in self.supported_features:
|
||||||
|
characteristics[self.preset_char] = self.preset_manual_value
|
||||||
|
|
||||||
if characteristics:
|
if characteristics:
|
||||||
await self.async_put_characteristics(characteristics)
|
await self.async_put_characteristics(characteristics)
|
||||||
@ -200,10 +249,18 @@ class HomeKitFanV2(BaseHomeKitFan):
|
|||||||
on_characteristic = CharacteristicsTypes.ACTIVE
|
on_characteristic = CharacteristicsTypes.ACTIVE
|
||||||
|
|
||||||
|
|
||||||
|
class HomeKitAirPurifer(HomeKitFanV2):
|
||||||
|
"""Implement air purifier support for public.hap.service.airpurifier."""
|
||||||
|
|
||||||
|
preset_char = CharacteristicsTypes.AIR_PURIFIER_STATE_TARGET
|
||||||
|
preset_manual_value = TargetAirPurifierStateValues.MANUAL
|
||||||
|
preset_automatic_value = TargetAirPurifierStateValues.AUTOMATIC
|
||||||
|
|
||||||
|
|
||||||
ENTITY_TYPES = {
|
ENTITY_TYPES = {
|
||||||
ServicesTypes.FAN: HomeKitFanV1,
|
ServicesTypes.FAN: HomeKitFanV1,
|
||||||
ServicesTypes.FAN_V2: HomeKitFanV2,
|
ServicesTypes.FAN_V2: HomeKitFanV2,
|
||||||
ServicesTypes.AIR_PURIFIER: HomeKitFanV2,
|
ServicesTypes.AIR_PURIFIER: HomeKitAirPurifer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -86,7 +86,9 @@
|
|||||||
]),
|
]),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
'auto',
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
'categories': dict({
|
'categories': dict({
|
||||||
}),
|
}),
|
||||||
@ -110,7 +112,7 @@
|
|||||||
'original_name': 'Airversa AP2 1808 AirPurifier',
|
'original_name': 'Airversa AP2 1808 AirPurifier',
|
||||||
'platform': 'homekit_controller',
|
'platform': 'homekit_controller',
|
||||||
'previous_unique_id': None,
|
'previous_unique_id': None,
|
||||||
'supported_features': <FanEntityFeature: 49>,
|
'supported_features': <FanEntityFeature: 57>,
|
||||||
'translation_key': None,
|
'translation_key': None,
|
||||||
'unique_id': '00:00:00:00:00:00_1_32832',
|
'unique_id': '00:00:00:00:00:00_1_32832',
|
||||||
'unit_of_measurement': None,
|
'unit_of_measurement': None,
|
||||||
@ -120,9 +122,11 @@
|
|||||||
'friendly_name': 'Airversa AP2 1808 AirPurifier',
|
'friendly_name': 'Airversa AP2 1808 AirPurifier',
|
||||||
'percentage': 0,
|
'percentage': 0,
|
||||||
'percentage_step': 20.0,
|
'percentage_step': 20.0,
|
||||||
'preset_mode': None,
|
'preset_mode': 'auto',
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
'supported_features': <FanEntityFeature: 49>,
|
'auto',
|
||||||
|
]),
|
||||||
|
'supported_features': <FanEntityFeature: 57>,
|
||||||
}),
|
}),
|
||||||
'entity_id': 'fan.airversa_ap2_1808_airpurifier',
|
'entity_id': 'fan.airversa_ap2_1808_airpurifier',
|
||||||
'state': 'off',
|
'state': 'off',
|
||||||
@ -10562,7 +10566,8 @@
|
|||||||
]),
|
]),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
'categories': dict({
|
'categories': dict({
|
||||||
}),
|
}),
|
||||||
@ -10597,7 +10602,8 @@
|
|||||||
'percentage': 66,
|
'percentage': 66,
|
||||||
'percentage_step': 33.333333333333336,
|
'percentage_step': 33.333333333333336,
|
||||||
'preset_mode': None,
|
'preset_mode': None,
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
]),
|
||||||
'supported_features': <FanEntityFeature: 49>,
|
'supported_features': <FanEntityFeature: 49>,
|
||||||
}),
|
}),
|
||||||
'entity_id': 'fan.haa_c718b3',
|
'entity_id': 'fan.haa_c718b3',
|
||||||
@ -11248,7 +11254,8 @@
|
|||||||
]),
|
]),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
'categories': dict({
|
'categories': dict({
|
||||||
}),
|
}),
|
||||||
@ -11283,7 +11290,8 @@
|
|||||||
'percentage': 0,
|
'percentage': 0,
|
||||||
'percentage_step': 1.0,
|
'percentage_step': 1.0,
|
||||||
'preset_mode': None,
|
'preset_mode': None,
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
]),
|
||||||
'supported_features': <FanEntityFeature: 49>,
|
'supported_features': <FanEntityFeature: 49>,
|
||||||
}),
|
}),
|
||||||
'entity_id': 'fan.ceiling_fan',
|
'entity_id': 'fan.ceiling_fan',
|
||||||
@ -11458,7 +11466,8 @@
|
|||||||
]),
|
]),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
'categories': dict({
|
'categories': dict({
|
||||||
}),
|
}),
|
||||||
@ -11494,7 +11503,8 @@
|
|||||||
'percentage': 0,
|
'percentage': 0,
|
||||||
'percentage_step': 1.0,
|
'percentage_step': 1.0,
|
||||||
'preset_mode': None,
|
'preset_mode': None,
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
]),
|
||||||
'supported_features': <FanEntityFeature: 53>,
|
'supported_features': <FanEntityFeature: 53>,
|
||||||
}),
|
}),
|
||||||
'entity_id': 'fan.living_room_fan',
|
'entity_id': 'fan.living_room_fan',
|
||||||
@ -11655,7 +11665,9 @@
|
|||||||
]),
|
]),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
'auto',
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
'categories': dict({
|
'categories': dict({
|
||||||
}),
|
}),
|
||||||
@ -11679,7 +11691,7 @@
|
|||||||
'original_name': '89 Living Room',
|
'original_name': '89 Living Room',
|
||||||
'platform': 'homekit_controller',
|
'platform': 'homekit_controller',
|
||||||
'previous_unique_id': None,
|
'previous_unique_id': None,
|
||||||
'supported_features': <FanEntityFeature: 51>,
|
'supported_features': <FanEntityFeature: 59>,
|
||||||
'translation_key': None,
|
'translation_key': None,
|
||||||
'unique_id': '00:00:00:00:00:00_1233851541_175',
|
'unique_id': '00:00:00:00:00:00_1233851541_175',
|
||||||
'unit_of_measurement': None,
|
'unit_of_measurement': None,
|
||||||
@ -11691,8 +11703,10 @@
|
|||||||
'percentage': 33,
|
'percentage': 33,
|
||||||
'percentage_step': 33.333333333333336,
|
'percentage_step': 33.333333333333336,
|
||||||
'preset_mode': None,
|
'preset_mode': None,
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
'supported_features': <FanEntityFeature: 51>,
|
'auto',
|
||||||
|
]),
|
||||||
|
'supported_features': <FanEntityFeature: 59>,
|
||||||
}),
|
}),
|
||||||
'entity_id': 'fan.89_living_room',
|
'entity_id': 'fan.89_living_room',
|
||||||
'state': 'on',
|
'state': 'on',
|
||||||
@ -12703,7 +12717,8 @@
|
|||||||
]),
|
]),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
'categories': dict({
|
'categories': dict({
|
||||||
}),
|
}),
|
||||||
@ -12738,7 +12753,8 @@
|
|||||||
'percentage': 0,
|
'percentage': 0,
|
||||||
'percentage_step': 1.0,
|
'percentage_step': 1.0,
|
||||||
'preset_mode': None,
|
'preset_mode': None,
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
]),
|
||||||
'supported_features': <FanEntityFeature: 49>,
|
'supported_features': <FanEntityFeature: 49>,
|
||||||
}),
|
}),
|
||||||
'entity_id': 'fan.ceiling_fan',
|
'entity_id': 'fan.ceiling_fan',
|
||||||
@ -12913,7 +12929,8 @@
|
|||||||
]),
|
]),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
'categories': dict({
|
'categories': dict({
|
||||||
}),
|
}),
|
||||||
@ -12950,7 +12967,8 @@
|
|||||||
'percentage': 0,
|
'percentage': 0,
|
||||||
'percentage_step': 1.0,
|
'percentage_step': 1.0,
|
||||||
'preset_mode': None,
|
'preset_mode': None,
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
]),
|
||||||
'supported_features': <FanEntityFeature: 55>,
|
'supported_features': <FanEntityFeature: 55>,
|
||||||
}),
|
}),
|
||||||
'entity_id': 'fan.living_room_fan',
|
'entity_id': 'fan.living_room_fan',
|
||||||
@ -13129,7 +13147,8 @@
|
|||||||
]),
|
]),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
'categories': dict({
|
'categories': dict({
|
||||||
}),
|
}),
|
||||||
@ -13166,7 +13185,8 @@
|
|||||||
'percentage': 0,
|
'percentage': 0,
|
||||||
'percentage_step': 1.0,
|
'percentage_step': 1.0,
|
||||||
'preset_mode': None,
|
'preset_mode': None,
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
]),
|
||||||
'supported_features': <FanEntityFeature: 55>,
|
'supported_features': <FanEntityFeature: 55>,
|
||||||
}),
|
}),
|
||||||
'entity_id': 'fan.living_room_fan',
|
'entity_id': 'fan.living_room_fan',
|
||||||
@ -13336,7 +13356,9 @@
|
|||||||
]),
|
]),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
'auto',
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
'categories': dict({
|
'categories': dict({
|
||||||
}),
|
}),
|
||||||
@ -13360,7 +13382,7 @@
|
|||||||
'original_name': '89 Living Room',
|
'original_name': '89 Living Room',
|
||||||
'platform': 'homekit_controller',
|
'platform': 'homekit_controller',
|
||||||
'previous_unique_id': None,
|
'previous_unique_id': None,
|
||||||
'supported_features': <FanEntityFeature: 51>,
|
'supported_features': <FanEntityFeature: 59>,
|
||||||
'translation_key': None,
|
'translation_key': None,
|
||||||
'unique_id': '00:00:00:00:00:00_1233851541_175',
|
'unique_id': '00:00:00:00:00:00_1233851541_175',
|
||||||
'unit_of_measurement': None,
|
'unit_of_measurement': None,
|
||||||
@ -13372,8 +13394,10 @@
|
|||||||
'percentage': 33,
|
'percentage': 33,
|
||||||
'percentage_step': 33.333333333333336,
|
'percentage_step': 33.333333333333336,
|
||||||
'preset_mode': None,
|
'preset_mode': None,
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
'supported_features': <FanEntityFeature: 51>,
|
'auto',
|
||||||
|
]),
|
||||||
|
'supported_features': <FanEntityFeature: 59>,
|
||||||
}),
|
}),
|
||||||
'entity_id': 'fan.89_living_room',
|
'entity_id': 'fan.89_living_room',
|
||||||
'state': 'on',
|
'state': 'on',
|
||||||
@ -17967,7 +17991,8 @@
|
|||||||
]),
|
]),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
'categories': dict({
|
'categories': dict({
|
||||||
}),
|
}),
|
||||||
@ -18002,7 +18027,8 @@
|
|||||||
'percentage': 0,
|
'percentage': 0,
|
||||||
'percentage_step': 25.0,
|
'percentage_step': 25.0,
|
||||||
'preset_mode': None,
|
'preset_mode': None,
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
]),
|
||||||
'supported_features': <FanEntityFeature: 49>,
|
'supported_features': <FanEntityFeature: 49>,
|
||||||
}),
|
}),
|
||||||
'entity_id': 'fan.caseta_r_wireless_fan_speed_control',
|
'entity_id': 'fan.caseta_r_wireless_fan_speed_control',
|
||||||
@ -21777,7 +21803,8 @@
|
|||||||
]),
|
]),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
'categories': dict({
|
'categories': dict({
|
||||||
}),
|
}),
|
||||||
@ -21813,7 +21840,8 @@
|
|||||||
'percentage': 0,
|
'percentage': 0,
|
||||||
'percentage_step': 25.0,
|
'percentage_step': 25.0,
|
||||||
'preset_mode': None,
|
'preset_mode': None,
|
||||||
'preset_modes': None,
|
'preset_modes': list([
|
||||||
|
]),
|
||||||
'supported_features': <FanEntityFeature: 53>,
|
'supported_features': <FanEntityFeature: 53>,
|
||||||
}),
|
}),
|
||||||
'entity_id': 'fan.simpleconnect_fan_06f674_hunter_fan',
|
'entity_id': 'fan.simpleconnect_fan_06f674_hunter_fan',
|
||||||
|
@ -47,6 +47,26 @@ def create_fanv2_service(accessory: Accessory) -> None:
|
|||||||
swing_mode.value = 0
|
swing_mode.value = 0
|
||||||
|
|
||||||
|
|
||||||
|
def create_fanv2_service_with_target_state(accessory: Accessory) -> None:
|
||||||
|
"""Define fan v2 characteristics with target as per HAP spec."""
|
||||||
|
service = accessory.add_service(ServicesTypes.FAN_V2)
|
||||||
|
|
||||||
|
target_state = service.add_char(CharacteristicsTypes.FAN_STATE_TARGET)
|
||||||
|
target_state.value = 0
|
||||||
|
|
||||||
|
cur_state = service.add_char(CharacteristicsTypes.ACTIVE)
|
||||||
|
cur_state.value = 0
|
||||||
|
|
||||||
|
direction = service.add_char(CharacteristicsTypes.ROTATION_DIRECTION)
|
||||||
|
direction.value = 0
|
||||||
|
|
||||||
|
speed = service.add_char(CharacteristicsTypes.ROTATION_SPEED)
|
||||||
|
speed.value = 0
|
||||||
|
|
||||||
|
swing_mode = service.add_char(CharacteristicsTypes.SWING_MODE)
|
||||||
|
swing_mode.value = 0
|
||||||
|
|
||||||
|
|
||||||
def create_fanv2_service_non_standard_rotation_range(accessory: Accessory) -> None:
|
def create_fanv2_service_non_standard_rotation_range(accessory: Accessory) -> None:
|
||||||
"""Define fan v2 with a non-standard rotation range."""
|
"""Define fan v2 with a non-standard rotation range."""
|
||||||
service = accessory.add_service(ServicesTypes.FAN_V2)
|
service = accessory.add_service(ServicesTypes.FAN_V2)
|
||||||
@ -93,6 +113,27 @@ def create_fanv2_service_without_rotation_speed(accessory: Accessory) -> None:
|
|||||||
swing_mode.value = 0
|
swing_mode.value = 0
|
||||||
|
|
||||||
|
|
||||||
|
def create_air_purifier_service(accessory: Accessory) -> None:
|
||||||
|
"""Define air purifier characteristics as per HAP spec."""
|
||||||
|
service = accessory.add_service(ServicesTypes.AIR_PURIFIER)
|
||||||
|
|
||||||
|
target_state = service.add_char(CharacteristicsTypes.AIR_PURIFIER_STATE_TARGET)
|
||||||
|
target_state.value = 0
|
||||||
|
|
||||||
|
cur_state = service.add_char(CharacteristicsTypes.ACTIVE)
|
||||||
|
cur_state.value = 0
|
||||||
|
|
||||||
|
direction = service.add_char(CharacteristicsTypes.ROTATION_DIRECTION)
|
||||||
|
direction.value = 0
|
||||||
|
|
||||||
|
speed = service.add_char(CharacteristicsTypes.ROTATION_SPEED)
|
||||||
|
speed.value = 0
|
||||||
|
speed.minStep = 25
|
||||||
|
|
||||||
|
swing_mode = service.add_char(CharacteristicsTypes.SWING_MODE)
|
||||||
|
swing_mode.value = 0
|
||||||
|
|
||||||
|
|
||||||
async def test_fan_read_state(
|
async def test_fan_read_state(
|
||||||
hass: HomeAssistant, get_next_aid: Callable[[], int]
|
hass: HomeAssistant, get_next_aid: Callable[[], int]
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -606,6 +647,70 @@ async def test_v2_set_percentage(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_fanv2_set_preset_mode(
|
||||||
|
hass: HomeAssistant, get_next_aid: Callable[[], int]
|
||||||
|
) -> None:
|
||||||
|
"""Test that we set preset mode when target state is available."""
|
||||||
|
helper = await setup_test_component(
|
||||||
|
hass, get_next_aid(), create_fanv2_service_with_target_state
|
||||||
|
)
|
||||||
|
|
||||||
|
await helper.async_update(ServicesTypes.FAN_V2, {CharacteristicsTypes.ACTIVE: 1})
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"set_percentage",
|
||||||
|
{"entity_id": "fan.testdevice", "percentage": 100},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.FAN_V2,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.ROTATION_SPEED: 100.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"set_preset_mode",
|
||||||
|
{"entity_id": "fan.testdevice", "preset_mode": "auto"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.FAN_V2,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.FAN_STATE_TARGET: 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"set_percentage",
|
||||||
|
{"entity_id": "fan.testdevice", "percentage": 33},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.FAN_V2,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.ROTATION_SPEED: 33.0,
|
||||||
|
CharacteristicsTypes.FAN_STATE_TARGET: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": "fan.testdevice", "preset_mode": "auto"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.FAN_V2,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.FAN_STATE_TARGET: 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_v2_set_percentage_with_min_step(
|
async def test_v2_set_percentage_with_min_step(
|
||||||
hass: HomeAssistant, get_next_aid: Callable[[], int]
|
hass: HomeAssistant, get_next_aid: Callable[[], int]
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -847,6 +952,281 @@ async def test_v2_set_percentage_non_standard_rotation_range(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_air_purifier_turn_on(
|
||||||
|
hass: HomeAssistant, get_next_aid: Callable[[], int]
|
||||||
|
) -> None:
|
||||||
|
"""Test that we can turn on an air purifier."""
|
||||||
|
helper = await setup_test_component(
|
||||||
|
hass, get_next_aid(), create_air_purifier_service
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": "fan.testdevice", "percentage": 100},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.AIR_PURIFIER,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.ACTIVE: 1,
|
||||||
|
CharacteristicsTypes.ROTATION_SPEED: 100,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": "fan.testdevice", "percentage": 66},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.AIR_PURIFIER,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.ACTIVE: 1,
|
||||||
|
CharacteristicsTypes.ROTATION_SPEED: 75.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": "fan.testdevice", "percentage": 33},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.AIR_PURIFIER,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.ACTIVE: 1,
|
||||||
|
CharacteristicsTypes.ROTATION_SPEED: 25.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"turn_off",
|
||||||
|
{"entity_id": "fan.testdevice"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.AIR_PURIFIER,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.ACTIVE: 0,
|
||||||
|
CharacteristicsTypes.ROTATION_SPEED: 25.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": "fan.testdevice"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.AIR_PURIFIER,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.ACTIVE: 1,
|
||||||
|
CharacteristicsTypes.ROTATION_SPEED: 25.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_air_purifier_turn_off(
|
||||||
|
hass: HomeAssistant, get_next_aid: Callable[[], int]
|
||||||
|
) -> None:
|
||||||
|
"""Test that we can turn an air purifier fan off."""
|
||||||
|
helper = await setup_test_component(
|
||||||
|
hass, get_next_aid(), create_air_purifier_service
|
||||||
|
)
|
||||||
|
|
||||||
|
await helper.async_update(
|
||||||
|
ServicesTypes.AIR_PURIFIER, {CharacteristicsTypes.ACTIVE: 1}
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"turn_off",
|
||||||
|
{"entity_id": "fan.testdevice"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.AIR_PURIFIER,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.ACTIVE: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_air_purifier_set_speed(
|
||||||
|
hass: HomeAssistant, get_next_aid: Callable[[], int]
|
||||||
|
) -> None:
|
||||||
|
"""Test that we set air purifier fan speed."""
|
||||||
|
helper = await setup_test_component(
|
||||||
|
hass, get_next_aid(), create_air_purifier_service
|
||||||
|
)
|
||||||
|
|
||||||
|
await helper.async_update(
|
||||||
|
ServicesTypes.AIR_PURIFIER, {CharacteristicsTypes.ACTIVE: 1}
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"set_percentage",
|
||||||
|
{"entity_id": "fan.testdevice", "percentage": 100},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.AIR_PURIFIER,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.ROTATION_SPEED: 100,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"set_percentage",
|
||||||
|
{"entity_id": "fan.testdevice", "percentage": 66},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.AIR_PURIFIER,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.ROTATION_SPEED: 75.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"set_percentage",
|
||||||
|
{"entity_id": "fan.testdevice", "percentage": 33},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.AIR_PURIFIER,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.ROTATION_SPEED: 25.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"set_percentage",
|
||||||
|
{"entity_id": "fan.testdevice", "percentage": 0},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.AIR_PURIFIER,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.ACTIVE: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_air_purifier_set_percentage(
|
||||||
|
hass: HomeAssistant, get_next_aid: Callable[[], int]
|
||||||
|
) -> None:
|
||||||
|
"""Test that we set air purifier fan speed by percentage."""
|
||||||
|
helper = await setup_test_component(
|
||||||
|
hass, get_next_aid(), create_air_purifier_service
|
||||||
|
)
|
||||||
|
|
||||||
|
await helper.async_update(
|
||||||
|
ServicesTypes.AIR_PURIFIER, {CharacteristicsTypes.ACTIVE: 1}
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"set_percentage",
|
||||||
|
{"entity_id": "fan.testdevice", "percentage": 75},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.AIR_PURIFIER,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.ROTATION_SPEED: 75,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"set_percentage",
|
||||||
|
{"entity_id": "fan.testdevice", "percentage": 0},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.AIR_PURIFIER,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.ACTIVE: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_air_purifier_set_preset_mode(
|
||||||
|
hass: HomeAssistant, get_next_aid: Callable[[], int]
|
||||||
|
) -> None:
|
||||||
|
"""Test that we set preset mode when target state is available."""
|
||||||
|
helper = await setup_test_component(
|
||||||
|
hass, get_next_aid(), create_air_purifier_service
|
||||||
|
)
|
||||||
|
|
||||||
|
await helper.async_update(
|
||||||
|
ServicesTypes.AIR_PURIFIER, {CharacteristicsTypes.ACTIVE: 1}
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"set_percentage",
|
||||||
|
{"entity_id": "fan.testdevice", "percentage": 100},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.AIR_PURIFIER,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.ROTATION_SPEED: 100.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"set_preset_mode",
|
||||||
|
{"entity_id": "fan.testdevice", "preset_mode": "auto"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.AIR_PURIFIER,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.AIR_PURIFIER_STATE_TARGET: 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"set_percentage",
|
||||||
|
{"entity_id": "fan.testdevice", "percentage": 33},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.AIR_PURIFIER,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.ROTATION_SPEED: 25.0,
|
||||||
|
CharacteristicsTypes.AIR_PURIFIER_STATE_TARGET: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"fan",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": "fan.testdevice", "preset_mode": "auto"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
helper.async_assert_service_values(
|
||||||
|
ServicesTypes.AIR_PURIFIER,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.AIR_PURIFIER_STATE_TARGET: 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_migrate_unique_id(
|
async def test_migrate_unique_id(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user