mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 12:47:08 +00:00
Add support for preset modes in homekit fans (#45962)
This commit is contained in:
parent
5e26bda52d
commit
d9ab1482bc
@ -8,12 +8,15 @@ from homeassistant.components.fan import (
|
|||||||
ATTR_OSCILLATING,
|
ATTR_OSCILLATING,
|
||||||
ATTR_PERCENTAGE,
|
ATTR_PERCENTAGE,
|
||||||
ATTR_PERCENTAGE_STEP,
|
ATTR_PERCENTAGE_STEP,
|
||||||
|
ATTR_PRESET_MODE,
|
||||||
|
ATTR_PRESET_MODES,
|
||||||
DIRECTION_FORWARD,
|
DIRECTION_FORWARD,
|
||||||
DIRECTION_REVERSE,
|
DIRECTION_REVERSE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_OSCILLATE,
|
SERVICE_OSCILLATE,
|
||||||
SERVICE_SET_DIRECTION,
|
SERVICE_SET_DIRECTION,
|
||||||
SERVICE_SET_PERCENTAGE,
|
SERVICE_SET_PERCENTAGE,
|
||||||
|
SERVICE_SET_PRESET_MODE,
|
||||||
SUPPORT_DIRECTION,
|
SUPPORT_DIRECTION,
|
||||||
SUPPORT_OSCILLATE,
|
SUPPORT_OSCILLATE,
|
||||||
SUPPORT_SET_SPEED,
|
SUPPORT_SET_SPEED,
|
||||||
@ -31,11 +34,14 @@ from homeassistant.core import callback
|
|||||||
from .accessories import TYPES, HomeAccessory
|
from .accessories import TYPES, HomeAccessory
|
||||||
from .const import (
|
from .const import (
|
||||||
CHAR_ACTIVE,
|
CHAR_ACTIVE,
|
||||||
|
CHAR_NAME,
|
||||||
|
CHAR_ON,
|
||||||
CHAR_ROTATION_DIRECTION,
|
CHAR_ROTATION_DIRECTION,
|
||||||
CHAR_ROTATION_SPEED,
|
CHAR_ROTATION_SPEED,
|
||||||
CHAR_SWING_MODE,
|
CHAR_SWING_MODE,
|
||||||
PROP_MIN_STEP,
|
PROP_MIN_STEP,
|
||||||
SERV_FANV2,
|
SERV_FANV2,
|
||||||
|
SERV_SWITCH,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -56,6 +62,7 @@ class Fan(HomeAccessory):
|
|||||||
|
|
||||||
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
percentage_step = state.attributes.get(ATTR_PERCENTAGE_STEP, 1)
|
percentage_step = state.attributes.get(ATTR_PERCENTAGE_STEP, 1)
|
||||||
|
preset_modes = state.attributes.get(ATTR_PRESET_MODES)
|
||||||
|
|
||||||
if features & SUPPORT_DIRECTION:
|
if features & SUPPORT_DIRECTION:
|
||||||
chars.append(CHAR_ROTATION_DIRECTION)
|
chars.append(CHAR_ROTATION_DIRECTION)
|
||||||
@ -65,11 +72,13 @@ class Fan(HomeAccessory):
|
|||||||
chars.append(CHAR_ROTATION_SPEED)
|
chars.append(CHAR_ROTATION_SPEED)
|
||||||
|
|
||||||
serv_fan = self.add_preload_service(SERV_FANV2, chars)
|
serv_fan = self.add_preload_service(SERV_FANV2, chars)
|
||||||
|
self.set_primary_service(serv_fan)
|
||||||
self.char_active = serv_fan.configure_char(CHAR_ACTIVE, value=0)
|
self.char_active = serv_fan.configure_char(CHAR_ACTIVE, value=0)
|
||||||
|
|
||||||
self.char_direction = None
|
self.char_direction = None
|
||||||
self.char_speed = None
|
self.char_speed = None
|
||||||
self.char_swing = None
|
self.char_swing = None
|
||||||
|
self.preset_mode_chars = {}
|
||||||
|
|
||||||
if CHAR_ROTATION_DIRECTION in chars:
|
if CHAR_ROTATION_DIRECTION in chars:
|
||||||
self.char_direction = serv_fan.configure_char(
|
self.char_direction = serv_fan.configure_char(
|
||||||
@ -86,6 +95,22 @@ class Fan(HomeAccessory):
|
|||||||
properties={PROP_MIN_STEP: percentage_step},
|
properties={PROP_MIN_STEP: percentage_step},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if preset_modes:
|
||||||
|
for preset_mode in preset_modes:
|
||||||
|
preset_serv = self.add_preload_service(SERV_SWITCH, CHAR_NAME)
|
||||||
|
serv_fan.add_linked_service(preset_serv)
|
||||||
|
preset_serv.configure_char(
|
||||||
|
CHAR_NAME, value=f"{self.display_name} {preset_mode}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.preset_mode_chars[preset_mode] = preset_serv.configure_char(
|
||||||
|
CHAR_ON,
|
||||||
|
value=False,
|
||||||
|
setter_callback=lambda value, preset_mode=preset_mode: self.set_preset_mode(
|
||||||
|
value, preset_mode
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
if CHAR_SWING_MODE in chars:
|
if CHAR_SWING_MODE in chars:
|
||||||
self.char_swing = serv_fan.configure_char(CHAR_SWING_MODE, value=0)
|
self.char_swing = serv_fan.configure_char(CHAR_SWING_MODE, value=0)
|
||||||
self.async_update_state(state)
|
self.async_update_state(state)
|
||||||
@ -120,6 +145,18 @@ class Fan(HomeAccessory):
|
|||||||
if CHAR_ROTATION_SPEED in char_values:
|
if CHAR_ROTATION_SPEED in char_values:
|
||||||
self.set_percentage(char_values[CHAR_ROTATION_SPEED])
|
self.set_percentage(char_values[CHAR_ROTATION_SPEED])
|
||||||
|
|
||||||
|
def set_preset_mode(self, value, preset_mode):
|
||||||
|
"""Set preset_mode if call came from HomeKit."""
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s: Set preset_mode %s to %d", self.entity_id, preset_mode, value
|
||||||
|
)
|
||||||
|
params = {ATTR_ENTITY_ID: self.entity_id}
|
||||||
|
if value:
|
||||||
|
params[ATTR_PRESET_MODE] = preset_mode
|
||||||
|
self.async_call_service(DOMAIN, SERVICE_SET_PRESET_MODE, params)
|
||||||
|
else:
|
||||||
|
self.async_call_service(DOMAIN, SERVICE_TURN_ON, params)
|
||||||
|
|
||||||
def set_state(self, value):
|
def set_state(self, value):
|
||||||
"""Set state if call came from HomeKit."""
|
"""Set state if call came from HomeKit."""
|
||||||
_LOGGER.debug("%s: Set state to %d", self.entity_id, value)
|
_LOGGER.debug("%s: Set state to %d", self.entity_id, value)
|
||||||
@ -193,3 +230,9 @@ class Fan(HomeAccessory):
|
|||||||
hk_oscillating = 1 if oscillating else 0
|
hk_oscillating = 1 if oscillating else 0
|
||||||
if self.char_swing.value != hk_oscillating:
|
if self.char_swing.value != hk_oscillating:
|
||||||
self.char_swing.set_value(hk_oscillating)
|
self.char_swing.set_value(hk_oscillating)
|
||||||
|
|
||||||
|
current_preset_mode = new_state.attributes.get(ATTR_PRESET_MODE)
|
||||||
|
for preset_mode, char in self.preset_mode_chars.items():
|
||||||
|
hk_value = 1 if preset_mode == current_preset_mode else 0
|
||||||
|
if char.value != hk_value:
|
||||||
|
char.set_value(hk_value)
|
||||||
|
@ -7,11 +7,14 @@ from homeassistant.components.fan import (
|
|||||||
ATTR_OSCILLATING,
|
ATTR_OSCILLATING,
|
||||||
ATTR_PERCENTAGE,
|
ATTR_PERCENTAGE,
|
||||||
ATTR_PERCENTAGE_STEP,
|
ATTR_PERCENTAGE_STEP,
|
||||||
|
ATTR_PRESET_MODE,
|
||||||
|
ATTR_PRESET_MODES,
|
||||||
DIRECTION_FORWARD,
|
DIRECTION_FORWARD,
|
||||||
DIRECTION_REVERSE,
|
DIRECTION_REVERSE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SUPPORT_DIRECTION,
|
SUPPORT_DIRECTION,
|
||||||
SUPPORT_OSCILLATE,
|
SUPPORT_OSCILLATE,
|
||||||
|
SUPPORT_PRESET_MODE,
|
||||||
SUPPORT_SET_SPEED,
|
SUPPORT_SET_SPEED,
|
||||||
)
|
)
|
||||||
from homeassistant.components.homekit.const import ATTR_VALUE, PROP_MIN_STEP
|
from homeassistant.components.homekit.const import ATTR_VALUE, PROP_MIN_STEP
|
||||||
@ -557,3 +560,83 @@ async def test_fan_restore(hass, hk_driver, events):
|
|||||||
assert acc.char_direction is not None
|
assert acc.char_direction is not None
|
||||||
assert acc.char_speed is not None
|
assert acc.char_speed is not None
|
||||||
assert acc.char_swing is not None
|
assert acc.char_swing is not None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_fan_preset_modes(hass, hk_driver, events):
|
||||||
|
"""Test fan with direction."""
|
||||||
|
entity_id = "fan.demo"
|
||||||
|
|
||||||
|
hass.states.async_set(
|
||||||
|
entity_id,
|
||||||
|
STATE_ON,
|
||||||
|
{
|
||||||
|
ATTR_SUPPORTED_FEATURES: SUPPORT_PRESET_MODE,
|
||||||
|
ATTR_PRESET_MODE: "auto",
|
||||||
|
ATTR_PRESET_MODES: ["auto", "smart"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
acc = Fan(hass, hk_driver, "Fan", entity_id, 1, None)
|
||||||
|
hk_driver.add_accessory(acc)
|
||||||
|
|
||||||
|
assert acc.preset_mode_chars["auto"].value == 1
|
||||||
|
assert acc.preset_mode_chars["smart"].value == 0
|
||||||
|
|
||||||
|
await acc.run()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
hass.states.async_set(
|
||||||
|
entity_id,
|
||||||
|
STATE_ON,
|
||||||
|
{
|
||||||
|
ATTR_SUPPORTED_FEATURES: SUPPORT_PRESET_MODE,
|
||||||
|
ATTR_PRESET_MODE: "smart",
|
||||||
|
ATTR_PRESET_MODES: ["auto", "smart"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert acc.preset_mode_chars["auto"].value == 0
|
||||||
|
assert acc.preset_mode_chars["smart"].value == 1
|
||||||
|
# Set from HomeKit
|
||||||
|
call_set_preset_mode = async_mock_service(hass, DOMAIN, "set_preset_mode")
|
||||||
|
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
|
||||||
|
|
||||||
|
char_auto_iid = acc.preset_mode_chars["auto"].to_HAP()[HAP_REPR_IID]
|
||||||
|
|
||||||
|
hk_driver.set_characteristics(
|
||||||
|
{
|
||||||
|
HAP_REPR_CHARS: [
|
||||||
|
{
|
||||||
|
HAP_REPR_AID: acc.aid,
|
||||||
|
HAP_REPR_IID: char_auto_iid,
|
||||||
|
HAP_REPR_VALUE: 1,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mock_addr",
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert call_set_preset_mode[0]
|
||||||
|
assert call_set_preset_mode[0].data[ATTR_ENTITY_ID] == entity_id
|
||||||
|
assert call_set_preset_mode[0].data[ATTR_PRESET_MODE] == "auto"
|
||||||
|
assert len(events) == 1
|
||||||
|
assert events[-1].data["service"] == "set_preset_mode"
|
||||||
|
|
||||||
|
hk_driver.set_characteristics(
|
||||||
|
{
|
||||||
|
HAP_REPR_CHARS: [
|
||||||
|
{
|
||||||
|
HAP_REPR_AID: acc.aid,
|
||||||
|
HAP_REPR_IID: char_auto_iid,
|
||||||
|
HAP_REPR_VALUE: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mock_addr",
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert call_turn_on[0]
|
||||||
|
assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id
|
||||||
|
assert events[-1].data["service"] == "turn_on"
|
||||||
|
assert len(events) == 2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user