Convert homekit fans to use service callbacks (#34229)

* Convert homekit fans to use service callbacks

* Convert homekit fans to use service callbacks

Service callbacks allow us ensure that we call
fan services in the correct order.

* Avoid calling turn_on if a speed is sent and the device is on

* Fix test to not leave files behind

* Fix test

* Update homeassistant/components/homekit/type_fans.py

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
J. Nick Koston 2020-04-15 21:39:31 -05:00 committed by GitHub
parent d6a47cb3e0
commit e8710002b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 420 additions and 138 deletions

View File

@ -28,7 +28,7 @@ from homeassistant.const import (
)
from . import TYPES
from .accessories import HomeAccessory, debounce
from .accessories import HomeAccessory
from .const import (
CHAR_ACTIVE,
CHAR_ROTATION_DIRECTION,
@ -51,17 +51,11 @@ class Fan(HomeAccessory):
def __init__(self, *args):
"""Initialize a new Light accessory object."""
super().__init__(*args, category=CATEGORY_FAN)
self._flag = {
CHAR_ACTIVE: False,
CHAR_ROTATION_DIRECTION: False,
CHAR_SWING_MODE: False,
}
self._state = 0
chars = []
features = self.hass.states.get(self.entity_id).attributes.get(
ATTR_SUPPORTED_FEATURES
)
state = self.hass.states.get(self.entity_id)
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if features & SUPPORT_DIRECTION:
chars.append(CHAR_ROTATION_DIRECTION)
if features & SUPPORT_OSCILLATE:
@ -74,9 +68,7 @@ class Fan(HomeAccessory):
chars.append(CHAR_ROTATION_SPEED)
serv_fan = self.add_preload_service(SERV_FANV2, chars)
self.char_active = serv_fan.configure_char(
CHAR_ACTIVE, value=0, setter_callback=self.set_state
)
self.char_active = serv_fan.configure_char(CHAR_ACTIVE, value=0)
self.char_direction = None
self.char_speed = None
@ -84,26 +76,52 @@ class Fan(HomeAccessory):
if CHAR_ROTATION_DIRECTION in chars:
self.char_direction = serv_fan.configure_char(
CHAR_ROTATION_DIRECTION, value=0, setter_callback=self.set_direction
CHAR_ROTATION_DIRECTION, value=0
)
if CHAR_ROTATION_SPEED in chars:
# Initial value is set to 100 because 0 is a special value (off). 100 is
# an arbitrary non-zero value. It is updated immediately by update_state
# to set to the correct initial value.
self.char_speed = serv_fan.configure_char(
CHAR_ROTATION_SPEED, value=100, setter_callback=self.set_speed
)
self.char_speed = serv_fan.configure_char(CHAR_ROTATION_SPEED, value=100)
if CHAR_SWING_MODE in chars:
self.char_swing = serv_fan.configure_char(
CHAR_SWING_MODE, value=0, setter_callback=self.set_oscillating
)
self.char_swing = serv_fan.configure_char(CHAR_SWING_MODE, value=0)
self.update_state(state)
serv_fan.setter_callback = self._set_chars
def _set_chars(self, char_values):
_LOGGER.debug("Fan _set_chars: %s", char_values)
if CHAR_ACTIVE in char_values:
if char_values[CHAR_ACTIVE]:
is_on = False
state = self.hass.states.get(self.entity_id)
if state and state.state == STATE_ON:
is_on = True
# Only set the state to active if we
# did not get a rotation speed or its off
if not is_on or CHAR_ROTATION_SPEED not in char_values:
self.set_state(1)
else:
# Its off, nothing more to do as setting the
# other chars will likely turn it back on which
# is what we want to avoid
self.set_state(0)
return
if CHAR_SWING_MODE in char_values:
self.set_oscillating(char_values[CHAR_SWING_MODE])
if CHAR_ROTATION_DIRECTION in char_values:
self.set_direction(char_values[CHAR_ROTATION_DIRECTION])
# We always do this LAST to ensure they
# get the speed they asked for
if CHAR_ROTATION_SPEED in char_values:
self.set_speed(char_values[CHAR_ROTATION_SPEED])
def set_state(self, value):
"""Set state if call came from HomeKit."""
_LOGGER.debug("%s: Set state to %d", self.entity_id, value)
self._flag[CHAR_ACTIVE] = True
service = SERVICE_TURN_ON if value == 1 else SERVICE_TURN_OFF
params = {ATTR_ENTITY_ID: self.entity_id}
self.call_service(DOMAIN, service, params)
@ -111,7 +129,6 @@ class Fan(HomeAccessory):
def set_direction(self, value):
"""Set state if call came from HomeKit."""
_LOGGER.debug("%s: Set direction to %d", self.entity_id, value)
self._flag[CHAR_ROTATION_DIRECTION] = True
direction = DIRECTION_REVERSE if value == 1 else DIRECTION_FORWARD
params = {ATTR_ENTITY_ID: self.entity_id, ATTR_DIRECTION: direction}
self.call_service(DOMAIN, SERVICE_SET_DIRECTION, params, direction)
@ -119,12 +136,10 @@ class Fan(HomeAccessory):
def set_oscillating(self, value):
"""Set state if call came from HomeKit."""
_LOGGER.debug("%s: Set oscillating to %d", self.entity_id, value)
self._flag[CHAR_SWING_MODE] = True
oscillating = value == 1
params = {ATTR_ENTITY_ID: self.entity_id, ATTR_OSCILLATING: oscillating}
self.call_service(DOMAIN, SERVICE_OSCILLATE, params, oscillating)
@debounce
def set_speed(self, value):
"""Set state if call came from HomeKit."""
_LOGGER.debug("%s: Set speed to %d", self.entity_id, value)
@ -138,21 +153,16 @@ class Fan(HomeAccessory):
state = new_state.state
if state in (STATE_ON, STATE_OFF):
self._state = 1 if state == STATE_ON else 0
if not self._flag[CHAR_ACTIVE] and self.char_active.value != self._state:
if self.char_active.value != self._state:
self.char_active.set_value(self._state)
self._flag[CHAR_ACTIVE] = False
# Handle Direction
if self.char_direction is not None:
direction = new_state.attributes.get(ATTR_DIRECTION)
if not self._flag[CHAR_ROTATION_DIRECTION] and direction in (
DIRECTION_FORWARD,
DIRECTION_REVERSE,
):
if direction in (DIRECTION_FORWARD, DIRECTION_REVERSE):
hk_direction = 1 if direction == DIRECTION_REVERSE else 0
if self.char_direction.value != hk_direction:
self.char_direction.set_value(hk_direction)
self._flag[CHAR_ROTATION_DIRECTION] = False
# Handle Speed
if self.char_speed is not None:
@ -170,17 +180,15 @@ class Fan(HomeAccessory):
# Therefore, if the hk_speed_value is 0 and the device is still on,
# the rotation speed is mapped to 1 otherwise the update is ignored
# in order to avoid this incorrect behavior.
if hk_speed_value == 0:
if state == STATE_ON:
self.char_speed.set_value(1)
else:
if hk_speed_value == 0 and state == STATE_ON:
hk_speed_value = 1
if self.char_speed.value != hk_speed_value:
self.char_speed.set_value(hk_speed_value)
# Handle Oscillating
if self.char_swing is not None:
oscillating = new_state.attributes.get(ATTR_OSCILLATING)
if not self._flag[CHAR_SWING_MODE] and oscillating in (True, False):
if isinstance(oscillating, bool):
hk_oscillating = 1 if oscillating else 0
if self.char_swing.value != hk_oscillating:
self.char_swing.set_value(hk_oscillating)
self._flag[CHAR_SWING_MODE] = False

View File

@ -15,8 +15,10 @@ def hk_driver():
"pyhap.accessory_driver.AccessoryEncoder"
), patch("pyhap.accessory_driver.HAPServer"), patch(
"pyhap.accessory_driver.AccessoryDriver.publish"
), patch(
"pyhap.accessory_driver.AccessoryDriver.persist"
):
return AccessoryDriver(pincode=b"123-45-678", address="127.0.0.1")
yield AccessoryDriver(pincode=b"123-45-678", address="127.0.0.1")
@pytest.fixture

View File

@ -2,6 +2,7 @@
from collections import namedtuple
from unittest.mock import Mock
from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE
import pytest
from homeassistant.components.fan import (
@ -53,11 +54,12 @@ async def test_fan_basic(hass, hk_driver, cls, events):
hass.states.async_set(entity_id, STATE_ON, {ATTR_SUPPORTED_FEATURES: 0})
await hass.async_block_till_done()
acc = cls.fan(hass, hk_driver, "Fan", entity_id, 2, None)
acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None)
hk_driver.add_accessory(acc)
assert acc.aid == 2
assert acc.aid == 1
assert acc.category == 3 # Fan
assert acc.char_active.value == 0
assert acc.char_active.value == 1
# If there are no speed_list values, then HomeKit speed is unsupported
assert acc.char_speed is None
@ -82,7 +84,20 @@ async def test_fan_basic(hass, hk_driver, cls, events):
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
call_turn_off = async_mock_service(hass, DOMAIN, "turn_off")
await hass.async_add_executor_job(acc.char_active.client_update_value, 1)
char_active_iid = acc.char_active.to_HAP()[HAP_REPR_IID]
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_active_iid,
HAP_REPR_VALUE: 1,
},
]
},
"mock_addr",
)
await hass.async_block_till_done()
assert call_turn_on
assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id
@ -92,7 +107,18 @@ async def test_fan_basic(hass, hk_driver, cls, events):
hass.states.async_set(entity_id, STATE_ON)
await hass.async_block_till_done()
await hass.async_add_executor_job(acc.char_active.client_update_value, 0)
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_active_iid,
HAP_REPR_VALUE: 0,
},
]
},
"mock_addr",
)
await hass.async_block_till_done()
assert call_turn_off
assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id
@ -110,7 +136,8 @@ async def test_fan_direction(hass, hk_driver, cls, events):
{ATTR_SUPPORTED_FEATURES: SUPPORT_DIRECTION, ATTR_DIRECTION: DIRECTION_FORWARD},
)
await hass.async_block_till_done()
acc = cls.fan(hass, hk_driver, "Fan", entity_id, 2, None)
acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None)
hk_driver.add_accessory(acc)
assert acc.char_direction.value == 0
@ -125,7 +152,20 @@ async def test_fan_direction(hass, hk_driver, cls, events):
# Set from HomeKit
call_set_direction = async_mock_service(hass, DOMAIN, "set_direction")
await hass.async_add_executor_job(acc.char_direction.client_update_value, 0)
char_direction_iid = acc.char_direction.to_HAP()[HAP_REPR_IID]
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_direction_iid,
HAP_REPR_VALUE: 0,
},
]
},
"mock_addr",
)
await hass.async_block_till_done()
assert call_set_direction[0]
assert call_set_direction[0].data[ATTR_ENTITY_ID] == entity_id
@ -133,6 +173,18 @@ async def test_fan_direction(hass, hk_driver, cls, events):
assert len(events) == 1
assert events[-1].data[ATTR_VALUE] == DIRECTION_FORWARD
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_direction_iid,
HAP_REPR_VALUE: 1,
},
]
},
"mock_addr",
)
await hass.async_add_executor_job(acc.char_direction.client_update_value, 1)
await hass.async_block_till_done()
assert call_set_direction[1]
@ -152,7 +204,8 @@ async def test_fan_oscillate(hass, hk_driver, cls, events):
{ATTR_SUPPORTED_FEATURES: SUPPORT_OSCILLATE, ATTR_OSCILLATING: False},
)
await hass.async_block_till_done()
acc = cls.fan(hass, hk_driver, "Fan", entity_id, 2, None)
acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None)
hk_driver.add_accessory(acc)
assert acc.char_swing.value == 0
@ -167,6 +220,20 @@ async def test_fan_oscillate(hass, hk_driver, cls, events):
# Set from HomeKit
call_oscillate = async_mock_service(hass, DOMAIN, "oscillate")
char_swing_iid = acc.char_swing.to_HAP()[HAP_REPR_IID]
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_swing_iid,
HAP_REPR_VALUE: 0,
},
]
},
"mock_addr",
)
await hass.async_add_executor_job(acc.char_swing.client_update_value, 0)
await hass.async_block_till_done()
assert call_oscillate[0]
@ -175,6 +242,18 @@ async def test_fan_oscillate(hass, hk_driver, cls, events):
assert len(events) == 1
assert events[-1].data[ATTR_VALUE] is False
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_swing_iid,
HAP_REPR_VALUE: 1,
},
]
},
"mock_addr",
)
await hass.async_add_executor_job(acc.char_swing.client_update_value, 1)
await hass.async_block_till_done()
assert call_oscillate[1]
@ -199,7 +278,8 @@ async def test_fan_speed(hass, hk_driver, cls, events):
},
)
await hass.async_block_till_done()
acc = cls.fan(hass, hk_driver, "Fan", entity_id, 2, None)
acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None)
hk_driver.add_accessory(acc)
# Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the
# speed to 100 when turning on a fan on a freshly booted up server.
@ -221,6 +301,20 @@ async def test_fan_speed(hass, hk_driver, cls, events):
# Set from HomeKit
call_set_speed = async_mock_service(hass, DOMAIN, "set_speed")
char_speed_iid = acc.char_speed.to_HAP()[HAP_REPR_IID]
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_speed_iid,
HAP_REPR_VALUE: 42,
},
]
},
"mock_addr",
)
await hass.async_add_executor_job(acc.char_speed.client_update_value, 42)
await hass.async_block_till_done()
acc.speed_mapping.speed_to_states.assert_called_with(42)
@ -231,6 +325,211 @@ async def test_fan_speed(hass, hk_driver, cls, events):
assert events[-1].data[ATTR_VALUE] == "ludicrous"
async def test_fan_set_all_one_shot(hass, hk_driver, cls, events):
"""Test fan with speed."""
entity_id = "fan.demo"
speed_list = [SPEED_OFF, SPEED_LOW, SPEED_HIGH]
hass.states.async_set(
entity_id,
STATE_ON,
{
ATTR_SUPPORTED_FEATURES: SUPPORT_SET_SPEED
| SUPPORT_OSCILLATE
| SUPPORT_DIRECTION,
ATTR_SPEED: SPEED_OFF,
ATTR_OSCILLATING: False,
ATTR_DIRECTION: DIRECTION_FORWARD,
ATTR_SPEED_LIST: speed_list,
},
)
await hass.async_block_till_done()
acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None)
hk_driver.add_accessory(acc)
# Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the
# speed to 100 when turning on a fan on a freshly booted up server.
assert acc.char_speed.value != 0
await acc.run_handler()
assert (
acc.speed_mapping.speed_ranges == HomeKitSpeedMapping(speed_list).speed_ranges
)
acc.speed_mapping.speed_to_homekit = Mock(return_value=42)
acc.speed_mapping.speed_to_states = Mock(return_value="ludicrous")
hass.states.async_set(
entity_id,
STATE_OFF,
{
ATTR_SUPPORTED_FEATURES: SUPPORT_SET_SPEED
| SUPPORT_OSCILLATE
| SUPPORT_DIRECTION,
ATTR_SPEED: SPEED_OFF,
ATTR_OSCILLATING: False,
ATTR_DIRECTION: DIRECTION_FORWARD,
ATTR_SPEED_LIST: speed_list,
},
)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == STATE_OFF
# Set from HomeKit
call_set_speed = async_mock_service(hass, DOMAIN, "set_speed")
call_oscillate = async_mock_service(hass, DOMAIN, "oscillate")
call_set_direction = async_mock_service(hass, DOMAIN, "set_direction")
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
call_turn_off = async_mock_service(hass, DOMAIN, "turn_off")
char_active_iid = acc.char_active.to_HAP()[HAP_REPR_IID]
char_direction_iid = acc.char_direction.to_HAP()[HAP_REPR_IID]
char_swing_iid = acc.char_swing.to_HAP()[HAP_REPR_IID]
char_speed_iid = acc.char_speed.to_HAP()[HAP_REPR_IID]
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_active_iid,
HAP_REPR_VALUE: 1,
},
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_speed_iid,
HAP_REPR_VALUE: 42,
},
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_swing_iid,
HAP_REPR_VALUE: 1,
},
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_direction_iid,
HAP_REPR_VALUE: 1,
},
]
},
"mock_addr",
)
await hass.async_block_till_done()
acc.speed_mapping.speed_to_states.assert_called_with(42)
assert call_turn_on
assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id
assert call_set_speed[0]
assert call_set_speed[0].data[ATTR_ENTITY_ID] == entity_id
assert call_set_speed[0].data[ATTR_SPEED] == "ludicrous"
assert call_oscillate[0]
assert call_oscillate[0].data[ATTR_ENTITY_ID] == entity_id
assert call_oscillate[0].data[ATTR_OSCILLATING] is True
assert call_set_direction[0]
assert call_set_direction[0].data[ATTR_ENTITY_ID] == entity_id
assert call_set_direction[0].data[ATTR_DIRECTION] == DIRECTION_REVERSE
assert len(events) == 4
assert events[1].data[ATTR_VALUE] is True
assert events[2].data[ATTR_VALUE] == DIRECTION_REVERSE
assert events[3].data[ATTR_VALUE] == "ludicrous"
hass.states.async_set(
entity_id,
STATE_ON,
{
ATTR_SUPPORTED_FEATURES: SUPPORT_SET_SPEED
| SUPPORT_OSCILLATE
| SUPPORT_DIRECTION,
ATTR_SPEED: SPEED_OFF,
ATTR_OSCILLATING: False,
ATTR_DIRECTION: DIRECTION_FORWARD,
ATTR_SPEED_LIST: speed_list,
},
)
await hass.async_block_till_done()
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_active_iid,
HAP_REPR_VALUE: 1,
},
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_speed_iid,
HAP_REPR_VALUE: 42,
},
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_swing_iid,
HAP_REPR_VALUE: 1,
},
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_direction_iid,
HAP_REPR_VALUE: 1,
},
]
},
"mock_addr",
)
# Turn on should not be called if its already on
# and we set a fan speed
await hass.async_block_till_done()
acc.speed_mapping.speed_to_states.assert_called_with(42)
assert len(events) == 7
assert call_set_speed[1]
assert call_set_speed[1].data[ATTR_ENTITY_ID] == entity_id
assert call_set_speed[1].data[ATTR_SPEED] == "ludicrous"
assert call_oscillate[1]
assert call_oscillate[1].data[ATTR_ENTITY_ID] == entity_id
assert call_oscillate[1].data[ATTR_OSCILLATING] is True
assert call_set_direction[1]
assert call_set_direction[1].data[ATTR_ENTITY_ID] == entity_id
assert call_set_direction[1].data[ATTR_DIRECTION] == DIRECTION_REVERSE
assert events[-3].data[ATTR_VALUE] is True
assert events[-2].data[ATTR_VALUE] == DIRECTION_REVERSE
assert events[-1].data[ATTR_VALUE] == "ludicrous"
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_active_iid,
HAP_REPR_VALUE: 0,
},
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_speed_iid,
HAP_REPR_VALUE: 42,
},
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_swing_iid,
HAP_REPR_VALUE: 1,
},
{
HAP_REPR_AID: acc.aid,
HAP_REPR_IID: char_direction_iid,
HAP_REPR_VALUE: 1,
},
]
},
"mock_addr",
)
await hass.async_block_till_done()
assert len(events) == 8
assert call_turn_off
assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id
assert len(call_set_speed) == 2
assert len(call_oscillate) == 2
assert len(call_set_direction) == 2
async def test_fan_restore(hass, hk_driver, cls, events):
"""Test setting up an entity from state in the event registry."""
hass.state = CoreState.not_running

View File

@ -1,8 +1,6 @@
"""Test different accessory types: Lights."""
from collections import namedtuple
from asynctest import patch
from pyhap.accessory_driver import AccessoryDriver
from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE
import pytest
@ -33,15 +31,6 @@ from tests.common import async_mock_service
from tests.components.homekit.common import patch_debounce
@pytest.fixture
def driver():
"""Patch AccessoryDriver without zeroconf or HAPServer."""
with patch("pyhap.accessory_driver.HAPServer"), patch(
"pyhap.accessory_driver.Zeroconf"
), patch("pyhap.accessory_driver.AccessoryDriver.persist"):
yield AccessoryDriver()
@pytest.fixture(scope="module")
def cls():
"""Patch debounce decorator during import of type_lights."""
@ -55,14 +44,14 @@ def cls():
patcher.stop()
async def test_light_basic(hass, hk_driver, cls, events, driver):
async def test_light_basic(hass, hk_driver, cls, events):
"""Test light with char state."""
entity_id = "light.demo"
hass.states.async_set(entity_id, STATE_ON, {ATTR_SUPPORTED_FEATURES: 0})
await hass.async_block_till_done()
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
assert acc.aid == 1
assert acc.category == 5 # Lightbulb
@ -90,7 +79,7 @@ async def test_light_basic(hass, hk_driver, cls, events, driver):
char_on_iid = acc.char_on.to_HAP()[HAP_REPR_IID]
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1}
@ -109,7 +98,7 @@ async def test_light_basic(hass, hk_driver, cls, events, driver):
hass.states.async_set(entity_id, STATE_ON)
await hass.async_block_till_done()
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 0}
@ -124,7 +113,7 @@ async def test_light_basic(hass, hk_driver, cls, events, driver):
assert events[-1].data[ATTR_VALUE] == "Set state to 0"
async def test_light_brightness(hass, hk_driver, cls, events, driver):
async def test_light_brightness(hass, hk_driver, cls, events):
"""Test light with brightness."""
entity_id = "light.demo"
@ -135,7 +124,7 @@ async def test_light_brightness(hass, hk_driver, cls, events, driver):
)
await hass.async_block_till_done()
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
# Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the
# brightness to 100 when turning on a light on a freshly booted up server.
@ -155,7 +144,7 @@ async def test_light_brightness(hass, hk_driver, cls, events, driver):
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
call_turn_off = async_mock_service(hass, DOMAIN, "turn_off")
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1},
@ -178,7 +167,7 @@ async def test_light_brightness(hass, hk_driver, cls, events, driver):
== f"Set state to 1, brightness at 20{UNIT_PERCENTAGE}"
)
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1},
@ -201,7 +190,7 @@ async def test_light_brightness(hass, hk_driver, cls, events, driver):
== f"Set state to 1, brightness at 40{UNIT_PERCENTAGE}"
)
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1},
@ -247,7 +236,7 @@ async def test_light_brightness(hass, hk_driver, cls, events, driver):
assert acc.char_brightness.value == 1
async def test_light_color_temperature(hass, hk_driver, cls, events, driver):
async def test_light_color_temperature(hass, hk_driver, cls, events):
"""Test light with color temperature."""
entity_id = "light.demo"
@ -258,7 +247,7 @@ async def test_light_color_temperature(hass, hk_driver, cls, events, driver):
)
await hass.async_block_till_done()
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
assert acc.char_color_temperature.value == 190
@ -271,7 +260,7 @@ async def test_light_color_temperature(hass, hk_driver, cls, events, driver):
char_color_temperature_iid = acc.char_color_temperature.to_HAP()[HAP_REPR_IID]
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
@ -313,7 +302,7 @@ async def test_light_color_temperature_and_rgb_color(hass, hk_driver, cls, event
assert not hasattr(acc, "char_color_temperature")
async def test_light_rgb_color(hass, hk_driver, cls, events, driver):
async def test_light_rgb_color(hass, hk_driver, cls, events):
"""Test light with rgb_color."""
entity_id = "light.demo"
@ -324,7 +313,7 @@ async def test_light_rgb_color(hass, hk_driver, cls, events, driver):
)
await hass.async_block_till_done()
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
assert acc.char_hue.value == 260
assert acc.char_saturation.value == 90
@ -340,7 +329,7 @@ async def test_light_rgb_color(hass, hk_driver, cls, events, driver):
char_hue_iid = acc.char_hue.to_HAP()[HAP_REPR_IID]
char_saturation_iid = acc.char_saturation.to_HAP()[HAP_REPR_IID]
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
@ -365,7 +354,7 @@ async def test_light_rgb_color(hass, hk_driver, cls, events, driver):
assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)"
async def test_light_restore(hass, hk_driver, cls, events, driver):
async def test_light_restore(hass, hk_driver, cls, events):
"""Test setting up an entity from state in the event registry."""
hass.state = CoreState.not_running
@ -386,7 +375,7 @@ async def test_light_restore(hass, hk_driver, cls, events, driver):
await hass.async_block_till_done()
acc = cls.light(hass, hk_driver, "Light", "light.simple", 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
assert acc.category == 5 # Lightbulb
assert acc.chars == []
@ -398,7 +387,7 @@ async def test_light_restore(hass, hk_driver, cls, events, driver):
assert acc.char_on.value == 0
async def test_light_set_brightness_and_color(hass, hk_driver, cls, events, driver):
async def test_light_set_brightness_and_color(hass, hk_driver, cls, events):
"""Test light with all chars in one go."""
entity_id = "light.demo"
@ -412,7 +401,7 @@ async def test_light_set_brightness_and_color(hass, hk_driver, cls, events, driv
)
await hass.async_block_till_done()
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
# Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the
# brightness to 100 when turning on a light on a freshly booted up server.
@ -438,7 +427,7 @@ async def test_light_set_brightness_and_color(hass, hk_driver, cls, events, driv
# Set from HomeKit
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1},
@ -474,9 +463,7 @@ async def test_light_set_brightness_and_color(hass, hk_driver, cls, events, driv
)
async def test_light_set_brightness_and_color_temp(
hass, hk_driver, cls, events, driver
):
async def test_light_set_brightness_and_color_temp(hass, hk_driver, cls, events):
"""Test light with all chars in one go."""
entity_id = "light.demo"
@ -490,7 +477,7 @@ async def test_light_set_brightness_and_color_temp(
)
await hass.async_block_till_done()
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
# Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the
# brightness to 100 when turning on a light on a freshly booted up server.
@ -514,7 +501,7 @@ async def test_light_set_brightness_and_color_temp(
# Set from HomeKit
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1},

View File

@ -2,7 +2,6 @@
from collections import namedtuple
from unittest.mock import patch
from pyhap.accessory_driver import AccessoryDriver
from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE
import pytest
@ -62,15 +61,6 @@ from tests.common import async_mock_service
from tests.components.homekit.common import patch_debounce
@pytest.fixture
def driver():
"""Patch AccessoryDriver without zeroconf or HAPServer."""
with patch("pyhap.accessory_driver.HAPServer"), patch(
"pyhap.accessory_driver.Zeroconf"
), patch("pyhap.accessory_driver.AccessoryDriver.persist"):
yield AccessoryDriver()
@pytest.fixture(scope="module")
def cls():
"""Patch debounce decorator during import of type_thermostats."""
@ -85,7 +75,7 @@ def cls():
patcher.stop()
async def test_thermostat(hass, hk_driver, cls, events, driver):
async def test_thermostat(hass, hk_driver, cls, events):
"""Test if accessory and HA are updated accordingly."""
entity_id = "climate.test"
@ -106,7 +96,7 @@ async def test_thermostat(hass, hk_driver, cls, events, driver):
)
await hass.async_block_till_done()
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
await acc.run_handler()
await hass.async_block_till_done()
@ -293,7 +283,7 @@ async def test_thermostat(hass, hk_driver, cls, events, driver):
char_target_temp_iid = acc.char_target_temp.to_HAP()[HAP_REPR_IID]
char_heat_cool_iid = acc.char_target_heat_cool.to_HAP()[HAP_REPR_IID]
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
@ -313,7 +303,7 @@ async def test_thermostat(hass, hk_driver, cls, events, driver):
assert len(events) == 1
assert events[-1].data[ATTR_VALUE] == "TargetTemperature to 19.0°C"
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
@ -328,7 +318,7 @@ async def test_thermostat(hass, hk_driver, cls, events, driver):
await hass.async_block_till_done()
assert not call_set_hvac_mode
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
@ -348,7 +338,7 @@ async def test_thermostat(hass, hk_driver, cls, events, driver):
assert len(events) == 2
assert events[-1].data[ATTR_VALUE] == "TargetHeatingCoolingState to 1"
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
@ -369,7 +359,7 @@ async def test_thermostat(hass, hk_driver, cls, events, driver):
assert events[-1].data[ATTR_VALUE] == "TargetHeatingCoolingState to 3"
async def test_thermostat_auto(hass, hk_driver, cls, events, driver):
async def test_thermostat_auto(hass, hk_driver, cls, events):
"""Test if accessory and HA are updated accordingly."""
entity_id = "climate.test"
@ -384,7 +374,7 @@ async def test_thermostat_auto(hass, hk_driver, cls, events, driver):
)
await hass.async_block_till_done()
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
await acc.run_handler()
await hass.async_block_till_done()
@ -459,7 +449,7 @@ async def test_thermostat_auto(hass, hk_driver, cls, events, driver):
char_heating_thresh_temp_iid = acc.char_heating_thresh_temp.to_HAP()[HAP_REPR_IID]
char_cooling_thresh_temp_iid = acc.char_cooling_thresh_temp.to_HAP()[HAP_REPR_IID]
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
@ -491,7 +481,7 @@ async def test_thermostat_auto(hass, hk_driver, cls, events, driver):
)
async def test_thermostat_humidity(hass, hk_driver, cls, events, driver):
async def test_thermostat_humidity(hass, hk_driver, cls, events):
"""Test if accessory and HA are updated accordingly with humidity."""
entity_id = "climate.test"
@ -499,7 +489,7 @@ async def test_thermostat_humidity(hass, hk_driver, cls, events, driver):
hass.states.async_set(entity_id, HVAC_MODE_OFF, {ATTR_SUPPORTED_FEATURES: 4})
await hass.async_block_till_done()
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
await acc.run_handler()
await hass.async_block_till_done()
@ -528,7 +518,7 @@ async def test_thermostat_humidity(hass, hk_driver, cls, events, driver):
char_target_humidity_iid = acc.char_target_humidity.to_HAP()[HAP_REPR_IID]
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
@ -550,7 +540,7 @@ async def test_thermostat_humidity(hass, hk_driver, cls, events, driver):
assert events[-1].data[ATTR_VALUE] == "35%"
async def test_thermostat_power_state(hass, hk_driver, cls, events, driver):
async def test_thermostat_power_state(hass, hk_driver, cls, events):
"""Test if accessory and HA are updated accordingly."""
entity_id = "climate.test"
@ -574,7 +564,7 @@ async def test_thermostat_power_state(hass, hk_driver, cls, events, driver):
)
await hass.async_block_till_done()
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
await acc.run_handler()
await hass.async_block_till_done()
@ -627,7 +617,7 @@ async def test_thermostat_power_state(hass, hk_driver, cls, events, driver):
char_target_heat_cool_iid = acc.char_target_heat_cool.to_HAP()[HAP_REPR_IID]
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
@ -648,7 +638,7 @@ async def test_thermostat_power_state(hass, hk_driver, cls, events, driver):
assert len(events) == 1
assert events[-1].data[ATTR_VALUE] == "TargetHeatingCoolingState to 1"
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
@ -670,7 +660,7 @@ async def test_thermostat_power_state(hass, hk_driver, cls, events, driver):
assert acc.char_target_heat_cool.value == 2
async def test_thermostat_fahrenheit(hass, hk_driver, cls, events, driver):
async def test_thermostat_fahrenheit(hass, hk_driver, cls, events):
"""Test if accessory and HA are updated accordingly."""
entity_id = "climate.test"
@ -686,7 +676,7 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls, events, driver):
await hass.async_block_till_done()
with patch.object(hass.config.units, CONF_TEMPERATURE_UNIT, new=TEMP_FAHRENHEIT):
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
await acc.run_handler()
await hass.async_block_till_done()
@ -717,7 +707,7 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls, events, driver):
char_heating_thresh_temp_iid = acc.char_heating_thresh_temp.to_HAP()[HAP_REPR_IID]
char_target_temp_iid = acc.char_target_temp.to_HAP()[HAP_REPR_IID]
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
@ -738,7 +728,7 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls, events, driver):
assert len(events) == 1
assert events[-1].data[ATTR_VALUE] == "CoolingThresholdTemperature to 23°C"
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
@ -759,7 +749,7 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls, events, driver):
assert len(events) == 2
assert events[-1].data[ATTR_VALUE] == "HeatingThresholdTemperature to 22°C"
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
@ -779,7 +769,7 @@ async def test_thermostat_fahrenheit(hass, hk_driver, cls, events, driver):
assert events[-1].data[ATTR_VALUE] == "TargetTemperature to 24.0°C"
async def test_thermostat_get_temperature_range(hass, hk_driver, cls, driver):
async def test_thermostat_get_temperature_range(hass, hk_driver, cls):
"""Test if temperature range is evaluated correctly."""
entity_id = "climate.test"
@ -801,14 +791,14 @@ async def test_thermostat_get_temperature_range(hass, hk_driver, cls, driver):
assert acc.get_temperature_range() == (15.5, 21.0)
async def test_thermostat_temperature_step_whole(hass, hk_driver, cls, driver):
async def test_thermostat_temperature_step_whole(hass, hk_driver, cls):
"""Test climate device with single digit precision."""
entity_id = "climate.test"
hass.states.async_set(entity_id, HVAC_MODE_OFF, {ATTR_TARGET_TEMP_STEP: 1})
await hass.async_block_till_done()
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
await acc.run_handler()
await hass.async_block_till_done()
@ -861,7 +851,7 @@ async def test_thermostat_restore(hass, hk_driver, cls, events):
}
async def test_thermostat_hvac_modes(hass, hk_driver, cls, driver):
async def test_thermostat_hvac_modes(hass, hk_driver, cls):
"""Test if unsupported HVAC modes are deactivated in HomeKit."""
entity_id = "climate.test"
@ -871,7 +861,7 @@ async def test_thermostat_hvac_modes(hass, hk_driver, cls, driver):
await hass.async_block_till_done()
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
await acc.run_handler()
await hass.async_block_till_done()
@ -894,7 +884,7 @@ async def test_thermostat_hvac_modes(hass, hk_driver, cls, driver):
assert acc.char_target_heat_cool.value == 1
async def test_thermostat_hvac_modes_with_auto_heat_cool(hass, hk_driver, cls, driver):
async def test_thermostat_hvac_modes_with_auto_heat_cool(hass, hk_driver, cls):
"""Test we get heat cool over auto."""
entity_id = "climate.test"
@ -914,7 +904,7 @@ async def test_thermostat_hvac_modes_with_auto_heat_cool(hass, hk_driver, cls, d
await hass.async_block_till_done()
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
await acc.run_handler()
await hass.async_block_till_done()
@ -937,7 +927,7 @@ async def test_thermostat_hvac_modes_with_auto_heat_cool(hass, hk_driver, cls, d
char_target_heat_cool_iid = acc.char_target_heat_cool.to_HAP()[HAP_REPR_IID]
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
@ -957,9 +947,7 @@ async def test_thermostat_hvac_modes_with_auto_heat_cool(hass, hk_driver, cls, d
assert acc.char_target_heat_cool.value == 3
async def test_thermostat_hvac_modes_with_auto_no_heat_cool(
hass, hk_driver, cls, driver
):
async def test_thermostat_hvac_modes_with_auto_no_heat_cool(hass, hk_driver, cls):
"""Test we get auto when there is no heat cool."""
entity_id = "climate.test"
@ -972,7 +960,7 @@ async def test_thermostat_hvac_modes_with_auto_no_heat_cool(
await hass.async_block_till_done()
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
await acc.run_handler()
await hass.async_block_till_done()
@ -995,7 +983,7 @@ async def test_thermostat_hvac_modes_with_auto_no_heat_cool(
char_target_heat_cool_iid = acc.char_target_heat_cool.to_HAP()[HAP_REPR_IID]
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
@ -1015,7 +1003,7 @@ async def test_thermostat_hvac_modes_with_auto_no_heat_cool(
assert acc.char_target_heat_cool.value == 3
async def test_thermostat_hvac_modes_with_auto_only(hass, hk_driver, cls, driver):
async def test_thermostat_hvac_modes_with_auto_only(hass, hk_driver, cls):
"""Test if unsupported HVAC modes are deactivated in HomeKit."""
entity_id = "climate.test"
@ -1025,7 +1013,7 @@ async def test_thermostat_hvac_modes_with_auto_only(hass, hk_driver, cls, driver
await hass.async_block_till_done()
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
await acc.run_handler()
await hass.async_block_till_done()
@ -1048,7 +1036,7 @@ async def test_thermostat_hvac_modes_with_auto_only(hass, hk_driver, cls, driver
assert acc.char_target_heat_cool.value == 3
async def test_thermostat_hvac_modes_without_off(hass, hk_driver, cls, driver):
async def test_thermostat_hvac_modes_without_off(hass, hk_driver, cls):
"""Test a thermostat that has no off."""
entity_id = "climate.test"
@ -1058,7 +1046,7 @@ async def test_thermostat_hvac_modes_without_off(hass, hk_driver, cls, driver):
await hass.async_block_till_done()
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
await acc.run_handler()
await hass.async_block_till_done()
@ -1085,9 +1073,7 @@ async def test_thermostat_hvac_modes_without_off(hass, hk_driver, cls, driver):
assert acc.char_target_heat_cool.value == 1
async def test_thermostat_without_target_temp_only_range(
hass, hk_driver, cls, events, driver
):
async def test_thermostat_without_target_temp_only_range(hass, hk_driver, cls, events):
"""Test a thermostat that only supports a range."""
entity_id = "climate.test"
@ -1099,7 +1085,7 @@ async def test_thermostat_without_target_temp_only_range(
)
await hass.async_block_till_done()
acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
driver.add_accessory(acc)
hk_driver.add_accessory(acc)
await acc.run_handler()
await hass.async_block_till_done()
@ -1176,7 +1162,7 @@ async def test_thermostat_without_target_temp_only_range(
char_target_temp_iid = acc.char_target_temp.to_HAP()[HAP_REPR_IID]
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{
@ -1222,7 +1208,7 @@ async def test_thermostat_without_target_temp_only_range(
char_target_temp_iid = acc.char_target_temp.to_HAP()[HAP_REPR_IID]
driver.set_characteristics(
hk_driver.set_characteristics(
{
HAP_REPR_CHARS: [
{