Resolve homekit cover adjustment slowness (#45730)

This commit is contained in:
J. Nick Koston 2021-01-31 10:39:35 -10:00 committed by GitHub
parent 8be357ff4f
commit dac9626112
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 72 additions and 220 deletions

View file

@ -1,7 +1,4 @@
"""Extend the basic Accessory and Bridge functions.""" """Extend the basic Accessory and Bridge functions."""
from datetime import timedelta
from functools import partial, wraps
from inspect import getmodule
import logging import logging
from pyhap.accessory import Accessory, Bridge from pyhap.accessory import Accessory, Bridge
@ -37,11 +34,7 @@ from homeassistant.const import (
__version__, __version__,
) )
from homeassistant.core import Context, callback as ha_callback, split_entity_id from homeassistant.core import Context, callback as ha_callback, split_entity_id
from homeassistant.helpers.event import ( from homeassistant.helpers.event import async_track_state_change_event
async_track_state_change_event,
track_point_in_utc_time,
)
from homeassistant.util import dt as dt_util
from homeassistant.util.decorator import Registry from homeassistant.util.decorator import Registry
from .const import ( from .const import (
@ -60,7 +53,6 @@ from .const import (
CONF_LINKED_BATTERY_CHARGING_SENSOR, CONF_LINKED_BATTERY_CHARGING_SENSOR,
CONF_LINKED_BATTERY_SENSOR, CONF_LINKED_BATTERY_SENSOR,
CONF_LOW_BATTERY_THRESHOLD, CONF_LOW_BATTERY_THRESHOLD,
DEBOUNCE_TIMEOUT,
DEFAULT_LOW_BATTERY_THRESHOLD, DEFAULT_LOW_BATTERY_THRESHOLD,
DEVICE_CLASS_CO, DEVICE_CLASS_CO,
DEVICE_CLASS_CO2, DEVICE_CLASS_CO2,
@ -98,37 +90,6 @@ SWITCH_TYPES = {
TYPES = Registry() TYPES = Registry()
def debounce(func):
"""Decorate function to debounce callbacks from HomeKit."""
@ha_callback
def call_later_listener(self, *args):
"""Handle call_later callback."""
debounce_params = self.debounce.pop(func.__name__, None)
if debounce_params:
self.hass.async_add_executor_job(func, self, *debounce_params[1:])
@wraps(func)
def wrapper(self, *args):
"""Start async timer."""
debounce_params = self.debounce.pop(func.__name__, None)
if debounce_params:
debounce_params[0]() # remove listener
remove_listener = track_point_in_utc_time(
self.hass,
partial(call_later_listener, self),
dt_util.utcnow() + timedelta(seconds=DEBOUNCE_TIMEOUT),
)
self.debounce[func.__name__] = (remove_listener, *args)
logger.debug(
"%s: Start %s timeout", self.entity_id, func.__name__.replace("set_", "")
)
name = getmodule(func).__name__
logger = logging.getLogger(name)
return wrapper
def get_accessory(hass, driver, state, aid, config): def get_accessory(hass, driver, state, aid, config):
"""Take state and return an accessory object if supported.""" """Take state and return an accessory object if supported."""
if not aid: if not aid:
@ -278,7 +239,6 @@ class HomeAccessory(Accessory):
self.category = category self.category = category
self.entity_id = entity_id self.entity_id = entity_id
self.hass = hass self.hass = hass
self.debounce = {}
self._subscriptions = [] self._subscriptions = []
self._char_battery = None self._char_battery = None
self._char_charging = None self._char_charging = None

View file

@ -33,7 +33,7 @@ from homeassistant.const import (
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.event import async_track_state_change_event
from .accessories import TYPES, HomeAccessory, debounce from .accessories import TYPES, HomeAccessory
from .const import ( from .const import (
ATTR_OBSTRUCTION_DETECTED, ATTR_OBSTRUCTION_DETECTED,
CHAR_CURRENT_DOOR_STATE, CHAR_CURRENT_DOOR_STATE,
@ -233,7 +233,6 @@ class OpeningDeviceBase(HomeAccessory):
return return
self.call_service(DOMAIN, SERVICE_STOP_COVER, {ATTR_ENTITY_ID: self.entity_id}) self.call_service(DOMAIN, SERVICE_STOP_COVER, {ATTR_ENTITY_ID: self.entity_id})
@debounce
def set_tilt(self, value): def set_tilt(self, value):
"""Set tilt to value if call came from HomeKit.""" """Set tilt to value if call came from HomeKit."""
_LOGGER.info("%s: Set tilt to %d", self.entity_id, value) _LOGGER.info("%s: Set tilt to %d", self.entity_id, value)
@ -284,7 +283,6 @@ class OpeningDevice(OpeningDeviceBase, HomeAccessory):
) )
self.async_update_state(state) self.async_update_state(state)
@debounce
def move_cover(self, value): def move_cover(self, value):
"""Move cover to value if call came from HomeKit.""" """Move cover to value if call came from HomeKit."""
_LOGGER.debug("%s: Set position to %d", self.entity_id, value) _LOGGER.debug("%s: Set position to %d", self.entity_id, value)
@ -360,7 +358,6 @@ class WindowCoveringBasic(OpeningDeviceBase, HomeAccessory):
) )
self.async_update_state(state) self.async_update_state(state)
@debounce
def move_cover(self, value): def move_cover(self, value):
"""Move cover to value if call came from HomeKit.""" """Move cover to value if call came from HomeKit."""
_LOGGER.debug("%s: Set position to %d", self.entity_id, value) _LOGGER.debug("%s: Set position to %d", self.entity_id, value)

View file

@ -1,17 +1,9 @@
"""Collection of fixtures and functions for the HomeKit tests.""" """Collection of fixtures and functions for the HomeKit tests."""
from unittest.mock import Mock, patch from unittest.mock import Mock
EMPTY_8_6_JPEG = b"empty_8_6" EMPTY_8_6_JPEG = b"empty_8_6"
def patch_debounce():
"""Return patch for debounce method."""
return patch(
"homeassistant.components.homekit.accessories.debounce",
lambda f: lambda *args, **kwargs: f(*args, **kwargs),
)
def mock_turbo_jpeg( def mock_turbo_jpeg(
first_width=None, second_width=None, first_height=None, second_height=None first_width=None, second_width=None, first_height=None, second_height=None
): ):

View file

@ -2,7 +2,6 @@
This includes tests for all mock object types. This includes tests for all mock object types.
""" """
from datetime import timedelta
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
import pytest import pytest
@ -11,7 +10,6 @@ from homeassistant.components.homekit.accessories import (
HomeAccessory, HomeAccessory,
HomeBridge, HomeBridge,
HomeDriver, HomeDriver,
debounce,
) )
from homeassistant.components.homekit.const import ( from homeassistant.components.homekit.const import (
ATTR_DISPLAY_NAME, ATTR_DISPLAY_NAME,
@ -45,41 +43,8 @@ from homeassistant.const import (
__version__, __version__,
) )
from homeassistant.helpers.event import TRACK_STATE_CHANGE_CALLBACKS from homeassistant.helpers.event import TRACK_STATE_CHANGE_CALLBACKS
import homeassistant.util.dt as dt_util
from tests.common import async_fire_time_changed, async_mock_service from tests.common import async_mock_service
async def test_debounce(hass):
"""Test add_timeout decorator function."""
def demo_func(*args):
nonlocal arguments, counter
counter += 1
arguments = args
arguments = None
counter = 0
mock = Mock(hass=hass, debounce={})
debounce_demo = debounce(demo_func)
assert debounce_demo.__name__ == "demo_func"
now = dt_util.utcnow()
with patch("homeassistant.util.dt.utcnow", return_value=now):
await hass.async_add_executor_job(debounce_demo, mock, "value")
async_fire_time_changed(hass, now + timedelta(seconds=3))
await hass.async_block_till_done()
assert counter == 1
assert len(arguments) == 2
with patch("homeassistant.util.dt.utcnow", return_value=now):
await hass.async_add_executor_job(debounce_demo, mock, "value")
await hass.async_add_executor_job(debounce_demo, mock, "value")
async_fire_time_changed(hass, now + timedelta(seconds=3))
await hass.async_block_till_done()
assert counter == 2
async def test_accessory_cancels_track_state_change_on_stop(hass, hk_driver): async def test_accessory_cancels_track_state_change_on_stop(hass, hk_driver):

View file

@ -67,7 +67,6 @@ from homeassistant.util import json as json_util
from .util import PATH_HOMEKIT, async_init_entry, async_init_integration from .util import PATH_HOMEKIT, async_init_entry, async_init_integration
from tests.common import MockConfigEntry, mock_device_registry, mock_registry from tests.common import MockConfigEntry, mock_device_registry, mock_registry
from tests.components.homekit.common import patch_debounce
IP_ADDRESS = "127.0.0.1" IP_ADDRESS = "127.0.0.1"
@ -89,14 +88,6 @@ def entity_reg_fixture(hass):
return mock_registry(hass) return mock_registry(hass)
@pytest.fixture(name="debounce_patcher", scope="module")
def debounce_patcher_fixture():
"""Patch debounce method."""
patcher = patch_debounce()
yield patcher.start()
patcher.stop()
async def test_setup_min(hass, mock_zeroconf): async def test_setup_min(hass, mock_zeroconf):
"""Test async_setup with min config options.""" """Test async_setup with min config options."""
entry = MockConfigEntry( entry = MockConfigEntry(
@ -485,7 +476,7 @@ async def test_homekit_entity_glob_filter(hass, mock_zeroconf):
mock_get_acc.reset_mock() mock_get_acc.reset_mock()
async def test_homekit_start(hass, hk_driver, device_reg, debounce_patcher): async def test_homekit_start(hass, hk_driver, device_reg):
"""Test HomeKit start method.""" """Test HomeKit start method."""
entry = await async_init_integration(hass) entry = await async_init_integration(hass)
@ -573,9 +564,7 @@ async def test_homekit_start(hass, hk_driver, device_reg, debounce_patcher):
assert len(device_reg.devices) == 1 assert len(device_reg.devices) == 1
async def test_homekit_start_with_a_broken_accessory( async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroconf):
hass, hk_driver, debounce_patcher, mock_zeroconf
):
"""Test HomeKit start method.""" """Test HomeKit start method."""
pin = b"123-45-678" pin = b"123-45-678"
entry = MockConfigEntry( entry = MockConfigEntry(
@ -754,7 +743,7 @@ async def test_homekit_too_many_accessories(hass, hk_driver, caplog, mock_zeroco
async def test_homekit_finds_linked_batteries( async def test_homekit_finds_linked_batteries(
hass, hk_driver, debounce_patcher, device_reg, entity_reg, mock_zeroconf hass, hk_driver, device_reg, entity_reg, mock_zeroconf
): ):
"""Test HomeKit start method.""" """Test HomeKit start method."""
entry = await async_init_integration(hass) entry = await async_init_integration(hass)
@ -840,7 +829,7 @@ async def test_homekit_finds_linked_batteries(
async def test_homekit_async_get_integration_fails( async def test_homekit_async_get_integration_fails(
hass, hk_driver, debounce_patcher, device_reg, entity_reg, mock_zeroconf hass, hk_driver, device_reg, entity_reg, mock_zeroconf
): ):
"""Test that we continue if async_get_integration fails.""" """Test that we continue if async_get_integration fails."""
entry = await async_init_integration(hass) entry = await async_init_integration(hass)
@ -1072,7 +1061,7 @@ def _write_data(path: str, data: Dict) -> None:
async def test_homekit_ignored_missing_devices( async def test_homekit_ignored_missing_devices(
hass, hk_driver, debounce_patcher, device_reg, entity_reg, mock_zeroconf hass, hk_driver, device_reg, entity_reg, mock_zeroconf
): ):
"""Test HomeKit handles a device in the entity registry but missing from the device registry.""" """Test HomeKit handles a device in the entity registry but missing from the device registry."""
entry = await async_init_integration(hass) entry = await async_init_integration(hass)
@ -1153,7 +1142,7 @@ async def test_homekit_ignored_missing_devices(
async def test_homekit_finds_linked_motion_sensors( async def test_homekit_finds_linked_motion_sensors(
hass, hk_driver, debounce_patcher, device_reg, entity_reg, mock_zeroconf hass, hk_driver, device_reg, entity_reg, mock_zeroconf
): ):
"""Test HomeKit start method.""" """Test HomeKit start method."""
entry = await async_init_integration(hass) entry = await async_init_integration(hass)
@ -1228,7 +1217,7 @@ async def test_homekit_finds_linked_motion_sensors(
async def test_homekit_finds_linked_humidity_sensors( async def test_homekit_finds_linked_humidity_sensors(
hass, hk_driver, debounce_patcher, device_reg, entity_reg, mock_zeroconf hass, hk_driver, device_reg, entity_reg, mock_zeroconf
): ):
"""Test HomeKit start method.""" """Test HomeKit start method."""
entry = await async_init_integration(hass) entry = await async_init_integration(hass)
@ -1376,9 +1365,7 @@ def _get_fixtures_base_path():
return os.path.dirname(os.path.dirname(os.path.dirname(__file__))) return os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
async def test_homekit_start_in_accessory_mode( async def test_homekit_start_in_accessory_mode(hass, hk_driver, device_reg):
hass, hk_driver, device_reg, debounce_patcher
):
"""Test HomeKit start method in accessory mode.""" """Test HomeKit start method in accessory mode."""
entry = await async_init_integration(hass) entry = await async_init_integration(hass)

View file

@ -1,7 +1,4 @@
"""Test different accessory types: Covers.""" """Test different accessory types: Covers."""
from collections import namedtuple
import pytest
from homeassistant.components.cover import ( from homeassistant.components.cover import (
ATTR_CURRENT_POSITION, ATTR_CURRENT_POSITION,
@ -22,6 +19,12 @@ from homeassistant.components.homekit.const import (
HK_DOOR_OPEN, HK_DOOR_OPEN,
HK_DOOR_OPENING, HK_DOOR_OPENING,
) )
from homeassistant.components.homekit.type_covers import (
GarageDoorOpener,
Window,
WindowCovering,
WindowCoveringBasic,
)
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES, ATTR_SUPPORTED_FEATURES,
@ -40,37 +43,15 @@ from homeassistant.core import CoreState
from homeassistant.helpers import entity_registry from homeassistant.helpers import entity_registry
from tests.common import async_mock_service from tests.common import async_mock_service
from tests.components.homekit.common import patch_debounce
@pytest.fixture(scope="module") async def test_garage_door_open_close(hass, hk_driver, events):
def cls():
"""Patch debounce decorator during import of type_covers."""
patcher = patch_debounce()
patcher.start()
_import = __import__(
"homeassistant.components.homekit.type_covers",
fromlist=["GarageDoorOpener", "WindowCovering", "WindowCoveringBasic"],
)
patcher_tuple = namedtuple(
"Cls", ["window", "windowcovering", "windowcovering_basic", "garage"]
)
yield patcher_tuple(
window=_import.Window,
windowcovering=_import.WindowCovering,
windowcovering_basic=_import.WindowCoveringBasic,
garage=_import.GarageDoorOpener,
)
patcher.stop()
async def test_garage_door_open_close(hass, hk_driver, cls, events):
"""Test if accessory and HA are updated accordingly.""" """Test if accessory and HA are updated accordingly."""
entity_id = "cover.garage_door" entity_id = "cover.garage_door"
hass.states.async_set(entity_id, None) hass.states.async_set(entity_id, None)
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.garage(hass, hk_driver, "Garage Door", entity_id, 2, None) acc = GarageDoorOpener(hass, hk_driver, "Garage Door", entity_id, 2, None)
await acc.run_handler() await acc.run_handler()
await hass.async_block_till_done() await hass.async_block_till_done()
@ -148,13 +129,13 @@ async def test_garage_door_open_close(hass, hk_driver, cls, events):
assert events[-1].data[ATTR_VALUE] is None assert events[-1].data[ATTR_VALUE] is None
async def test_windowcovering_set_cover_position(hass, hk_driver, cls, events): async def test_windowcovering_set_cover_position(hass, hk_driver, events):
"""Test if accessory and HA are updated accordingly.""" """Test if accessory and HA are updated accordingly."""
entity_id = "cover.window" entity_id = "cover.window"
hass.states.async_set(entity_id, None) hass.states.async_set(entity_id, None)
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.windowcovering(hass, hk_driver, "Cover", entity_id, 2, None) acc = WindowCovering(hass, hk_driver, "Cover", entity_id, 2, None)
await acc.run_handler() await acc.run_handler()
await hass.async_block_till_done() await hass.async_block_till_done()
@ -218,13 +199,13 @@ async def test_windowcovering_set_cover_position(hass, hk_driver, cls, events):
assert events[-1].data[ATTR_VALUE] == 75 assert events[-1].data[ATTR_VALUE] == 75
async def test_window_instantiate(hass, hk_driver, cls, events): async def test_window_instantiate(hass, hk_driver, events):
"""Test if Window accessory is instantiated correctly.""" """Test if Window accessory is instantiated correctly."""
entity_id = "cover.window" entity_id = "cover.window"
hass.states.async_set(entity_id, None) hass.states.async_set(entity_id, None)
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.window(hass, hk_driver, "Window", entity_id, 2, None) acc = Window(hass, hk_driver, "Window", entity_id, 2, None)
await acc.run_handler() await acc.run_handler()
await hass.async_block_till_done() await hass.async_block_till_done()
@ -235,7 +216,7 @@ async def test_window_instantiate(hass, hk_driver, cls, events):
assert acc.char_target_position.value == 0 assert acc.char_target_position.value == 0
async def test_windowcovering_cover_set_tilt(hass, hk_driver, cls, events): async def test_windowcovering_cover_set_tilt(hass, hk_driver, events):
"""Test if accessory and HA update slat tilt accordingly.""" """Test if accessory and HA update slat tilt accordingly."""
entity_id = "cover.window" entity_id = "cover.window"
@ -243,7 +224,7 @@ async def test_windowcovering_cover_set_tilt(hass, hk_driver, cls, events):
entity_id, STATE_UNKNOWN, {ATTR_SUPPORTED_FEATURES: SUPPORT_SET_TILT_POSITION} entity_id, STATE_UNKNOWN, {ATTR_SUPPORTED_FEATURES: SUPPORT_SET_TILT_POSITION}
) )
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.windowcovering(hass, hk_driver, "Cover", entity_id, 2, None) acc = WindowCovering(hass, hk_driver, "Cover", entity_id, 2, None)
await acc.run_handler() await acc.run_handler()
await hass.async_block_till_done() await hass.async_block_till_done()
@ -302,12 +283,12 @@ async def test_windowcovering_cover_set_tilt(hass, hk_driver, cls, events):
assert events[-1].data[ATTR_VALUE] == 75 assert events[-1].data[ATTR_VALUE] == 75
async def test_windowcovering_open_close(hass, hk_driver, cls, events): async def test_windowcovering_open_close(hass, hk_driver, events):
"""Test if accessory and HA are updated accordingly.""" """Test if accessory and HA are updated accordingly."""
entity_id = "cover.window" entity_id = "cover.window"
hass.states.async_set(entity_id, STATE_UNKNOWN, {ATTR_SUPPORTED_FEATURES: 0}) hass.states.async_set(entity_id, STATE_UNKNOWN, {ATTR_SUPPORTED_FEATURES: 0})
acc = cls.windowcovering_basic(hass, hk_driver, "Cover", entity_id, 2, None) acc = WindowCoveringBasic(hass, hk_driver, "Cover", entity_id, 2, None)
await acc.run_handler() await acc.run_handler()
await hass.async_block_till_done() await hass.async_block_till_done()
@ -383,14 +364,14 @@ async def test_windowcovering_open_close(hass, hk_driver, cls, events):
assert events[-1].data[ATTR_VALUE] is None assert events[-1].data[ATTR_VALUE] is None
async def test_windowcovering_open_close_stop(hass, hk_driver, cls, events): async def test_windowcovering_open_close_stop(hass, hk_driver, events):
"""Test if accessory and HA are updated accordingly.""" """Test if accessory and HA are updated accordingly."""
entity_id = "cover.window" entity_id = "cover.window"
hass.states.async_set( hass.states.async_set(
entity_id, STATE_UNKNOWN, {ATTR_SUPPORTED_FEATURES: SUPPORT_STOP} entity_id, STATE_UNKNOWN, {ATTR_SUPPORTED_FEATURES: SUPPORT_STOP}
) )
acc = cls.windowcovering_basic(hass, hk_driver, "Cover", entity_id, 2, None) acc = WindowCoveringBasic(hass, hk_driver, "Cover", entity_id, 2, None)
await acc.run_handler() await acc.run_handler()
await hass.async_block_till_done() await hass.async_block_till_done()
@ -431,7 +412,7 @@ async def test_windowcovering_open_close_stop(hass, hk_driver, cls, events):
async def test_windowcovering_open_close_with_position_and_stop( async def test_windowcovering_open_close_with_position_and_stop(
hass, hk_driver, cls, events hass, hk_driver, events
): ):
"""Test if accessory and HA are updated accordingly.""" """Test if accessory and HA are updated accordingly."""
entity_id = "cover.stop_window" entity_id = "cover.stop_window"
@ -441,7 +422,7 @@ async def test_windowcovering_open_close_with_position_and_stop(
STATE_UNKNOWN, STATE_UNKNOWN,
{ATTR_SUPPORTED_FEATURES: SUPPORT_STOP | SUPPORT_SET_POSITION}, {ATTR_SUPPORTED_FEATURES: SUPPORT_STOP | SUPPORT_SET_POSITION},
) )
acc = cls.windowcovering(hass, hk_driver, "Cover", entity_id, 2, None) acc = WindowCovering(hass, hk_driver, "Cover", entity_id, 2, None)
await acc.run_handler() await acc.run_handler()
await hass.async_block_till_done() await hass.async_block_till_done()
@ -461,7 +442,7 @@ async def test_windowcovering_open_close_with_position_and_stop(
assert events[-1].data[ATTR_VALUE] is None assert events[-1].data[ATTR_VALUE] is None
async def test_windowcovering_basic_restore(hass, hk_driver, cls, events): async def test_windowcovering_basic_restore(hass, hk_driver, events):
"""Test setting up an entity from state in the event registry.""" """Test setting up an entity from state in the event registry."""
hass.state = CoreState.not_running hass.state = CoreState.not_running
@ -486,22 +467,20 @@ async def test_windowcovering_basic_restore(hass, hk_driver, cls, events):
hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {}) hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {})
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.windowcovering_basic(hass, hk_driver, "Cover", "cover.simple", 2, None) acc = WindowCoveringBasic(hass, hk_driver, "Cover", "cover.simple", 2, None)
assert acc.category == 14 assert acc.category == 14
assert acc.char_current_position is not None assert acc.char_current_position is not None
assert acc.char_target_position is not None assert acc.char_target_position is not None
assert acc.char_position_state is not None assert acc.char_position_state is not None
acc = cls.windowcovering_basic( acc = WindowCoveringBasic(hass, hk_driver, "Cover", "cover.all_info_set", 2, None)
hass, hk_driver, "Cover", "cover.all_info_set", 2, None
)
assert acc.category == 14 assert acc.category == 14
assert acc.char_current_position is not None assert acc.char_current_position is not None
assert acc.char_target_position is not None assert acc.char_target_position is not None
assert acc.char_position_state is not None assert acc.char_position_state is not None
async def test_windowcovering_restore(hass, hk_driver, cls, events): async def test_windowcovering_restore(hass, hk_driver, events):
"""Test setting up an entity from state in the event registry.""" """Test setting up an entity from state in the event registry."""
hass.state = CoreState.not_running hass.state = CoreState.not_running
@ -526,20 +505,20 @@ async def test_windowcovering_restore(hass, hk_driver, cls, events):
hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {}) hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {})
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.windowcovering(hass, hk_driver, "Cover", "cover.simple", 2, None) acc = WindowCovering(hass, hk_driver, "Cover", "cover.simple", 2, None)
assert acc.category == 14 assert acc.category == 14
assert acc.char_current_position is not None assert acc.char_current_position is not None
assert acc.char_target_position is not None assert acc.char_target_position is not None
assert acc.char_position_state is not None assert acc.char_position_state is not None
acc = cls.windowcovering(hass, hk_driver, "Cover", "cover.all_info_set", 2, None) acc = WindowCovering(hass, hk_driver, "Cover", "cover.all_info_set", 2, None)
assert acc.category == 14 assert acc.category == 14
assert acc.char_current_position is not None assert acc.char_current_position is not None
assert acc.char_target_position is not None assert acc.char_target_position is not None
assert acc.char_position_state is not None assert acc.char_position_state is not None
async def test_garage_door_with_linked_obstruction_sensor(hass, hk_driver, cls, events): async def test_garage_door_with_linked_obstruction_sensor(hass, hk_driver, events):
"""Test if accessory and HA are updated accordingly with a linked obstruction sensor.""" """Test if accessory and HA are updated accordingly with a linked obstruction sensor."""
linked_obstruction_sensor_entity_id = "binary_sensor.obstruction" linked_obstruction_sensor_entity_id = "binary_sensor.obstruction"
entity_id = "cover.garage_door" entity_id = "cover.garage_door"
@ -547,7 +526,7 @@ async def test_garage_door_with_linked_obstruction_sensor(hass, hk_driver, cls,
hass.states.async_set(linked_obstruction_sensor_entity_id, STATE_OFF) hass.states.async_set(linked_obstruction_sensor_entity_id, STATE_OFF)
hass.states.async_set(entity_id, None) hass.states.async_set(entity_id, None)
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.garage( acc = GarageDoorOpener(
hass, hass,
hk_driver, hk_driver,
"Garage Door", "Garage Door",

View file

@ -1,8 +1,6 @@
"""Test different accessory types: Fans.""" """Test different accessory types: Fans."""
from collections import namedtuple
from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE
import pytest
from homeassistant.components.fan import ( from homeassistant.components.fan import (
ATTR_DIRECTION, ATTR_DIRECTION,
@ -16,6 +14,7 @@ from homeassistant.components.fan import (
SUPPORT_SET_SPEED, SUPPORT_SET_SPEED,
) )
from homeassistant.components.homekit.const import ATTR_VALUE from homeassistant.components.homekit.const import ATTR_VALUE
from homeassistant.components.homekit.type_fans import Fan
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES, ATTR_SUPPORTED_FEATURES,
@ -28,27 +27,15 @@ from homeassistant.core import CoreState
from homeassistant.helpers import entity_registry from homeassistant.helpers import entity_registry
from tests.common import async_mock_service from tests.common import async_mock_service
from tests.components.homekit.common import patch_debounce
@pytest.fixture(scope="module") async def test_fan_basic(hass, hk_driver, events):
def cls():
"""Patch debounce decorator during import of type_fans."""
patcher = patch_debounce()
patcher.start()
_import = __import__("homeassistant.components.homekit.type_fans", fromlist=["Fan"])
patcher_tuple = namedtuple("Cls", ["fan"])
yield patcher_tuple(fan=_import.Fan)
patcher.stop()
async def test_fan_basic(hass, hk_driver, cls, events):
"""Test fan with char state.""" """Test fan with char state."""
entity_id = "fan.demo" entity_id = "fan.demo"
hass.states.async_set(entity_id, STATE_ON, {ATTR_SUPPORTED_FEATURES: 0}) hass.states.async_set(entity_id, STATE_ON, {ATTR_SUPPORTED_FEATURES: 0})
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None) acc = Fan(hass, hk_driver, "Fan", entity_id, 1, None)
hk_driver.add_accessory(acc) hk_driver.add_accessory(acc)
assert acc.aid == 1 assert acc.aid == 1
@ -120,7 +107,7 @@ async def test_fan_basic(hass, hk_driver, cls, events):
assert events[-1].data[ATTR_VALUE] is None assert events[-1].data[ATTR_VALUE] is None
async def test_fan_direction(hass, hk_driver, cls, events): async def test_fan_direction(hass, hk_driver, events):
"""Test fan with direction.""" """Test fan with direction."""
entity_id = "fan.demo" entity_id = "fan.demo"
@ -130,7 +117,7 @@ async def test_fan_direction(hass, hk_driver, cls, events):
{ATTR_SUPPORTED_FEATURES: SUPPORT_DIRECTION, ATTR_DIRECTION: DIRECTION_FORWARD}, {ATTR_SUPPORTED_FEATURES: SUPPORT_DIRECTION, ATTR_DIRECTION: DIRECTION_FORWARD},
) )
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None) acc = Fan(hass, hk_driver, "Fan", entity_id, 1, None)
hk_driver.add_accessory(acc) hk_driver.add_accessory(acc)
assert acc.char_direction.value == 0 assert acc.char_direction.value == 0
@ -188,7 +175,7 @@ async def test_fan_direction(hass, hk_driver, cls, events):
assert events[-1].data[ATTR_VALUE] == DIRECTION_REVERSE assert events[-1].data[ATTR_VALUE] == DIRECTION_REVERSE
async def test_fan_oscillate(hass, hk_driver, cls, events): async def test_fan_oscillate(hass, hk_driver, events):
"""Test fan with oscillate.""" """Test fan with oscillate."""
entity_id = "fan.demo" entity_id = "fan.demo"
@ -198,7 +185,7 @@ async def test_fan_oscillate(hass, hk_driver, cls, events):
{ATTR_SUPPORTED_FEATURES: SUPPORT_OSCILLATE, ATTR_OSCILLATING: False}, {ATTR_SUPPORTED_FEATURES: SUPPORT_OSCILLATE, ATTR_OSCILLATING: False},
) )
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None) acc = Fan(hass, hk_driver, "Fan", entity_id, 1, None)
hk_driver.add_accessory(acc) hk_driver.add_accessory(acc)
assert acc.char_swing.value == 0 assert acc.char_swing.value == 0
@ -257,7 +244,7 @@ async def test_fan_oscillate(hass, hk_driver, cls, events):
assert events[-1].data[ATTR_VALUE] is True assert events[-1].data[ATTR_VALUE] is True
async def test_fan_speed(hass, hk_driver, cls, events): async def test_fan_speed(hass, hk_driver, events):
"""Test fan with speed.""" """Test fan with speed."""
entity_id = "fan.demo" entity_id = "fan.demo"
@ -270,7 +257,7 @@ async def test_fan_speed(hass, hk_driver, cls, events):
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None) acc = Fan(hass, hk_driver, "Fan", entity_id, 1, None)
hk_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 # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the
@ -336,7 +323,7 @@ async def test_fan_speed(hass, hk_driver, cls, events):
assert acc.char_active.value == 1 assert acc.char_active.value == 1
async def test_fan_set_all_one_shot(hass, hk_driver, cls, events): async def test_fan_set_all_one_shot(hass, hk_driver, events):
"""Test fan with speed.""" """Test fan with speed."""
entity_id = "fan.demo" entity_id = "fan.demo"
@ -353,7 +340,7 @@ async def test_fan_set_all_one_shot(hass, hk_driver, cls, events):
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None) acc = Fan(hass, hk_driver, "Fan", entity_id, 1, None)
hk_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 # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the
@ -529,7 +516,7 @@ async def test_fan_set_all_one_shot(hass, hk_driver, cls, events):
assert len(call_set_direction) == 2 assert len(call_set_direction) == 2
async def test_fan_restore(hass, hk_driver, cls, events): async def test_fan_restore(hass, hk_driver, events):
"""Test setting up an entity from state in the event registry.""" """Test setting up an entity from state in the event registry."""
hass.state = CoreState.not_running hass.state = CoreState.not_running
@ -554,14 +541,14 @@ async def test_fan_restore(hass, hk_driver, cls, events):
hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {}) hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {})
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.fan(hass, hk_driver, "Fan", "fan.simple", 2, None) acc = Fan(hass, hk_driver, "Fan", "fan.simple", 2, None)
assert acc.category == 3 assert acc.category == 3
assert acc.char_active is not None assert acc.char_active is not None
assert acc.char_direction is None assert acc.char_direction is None
assert acc.char_speed is None assert acc.char_speed is None
assert acc.char_swing is None assert acc.char_swing is None
acc = cls.fan(hass, hk_driver, "Fan", "fan.all_info_set", 2, None) acc = Fan(hass, hk_driver, "Fan", "fan.all_info_set", 2, None)
assert acc.category == 3 assert acc.category == 3
assert acc.char_active is not None assert acc.char_active is not None
assert acc.char_direction is not None assert acc.char_direction is not None

View file

@ -1,10 +1,9 @@
"""Test different accessory types: Lights.""" """Test different accessory types: Lights."""
from collections import namedtuple
from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE
import pytest
from homeassistant.components.homekit.const import ATTR_VALUE from homeassistant.components.homekit.const import ATTR_VALUE
from homeassistant.components.homekit.type_lights import Light
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,
ATTR_BRIGHTNESS_PCT, ATTR_BRIGHTNESS_PCT,
@ -28,29 +27,15 @@ from homeassistant.core import CoreState
from homeassistant.helpers import entity_registry from homeassistant.helpers import entity_registry
from tests.common import async_mock_service from tests.common import async_mock_service
from tests.components.homekit.common import patch_debounce
@pytest.fixture(scope="module") async def test_light_basic(hass, hk_driver, events):
def cls():
"""Patch debounce decorator during import of type_lights."""
patcher = patch_debounce()
patcher.start()
_import = __import__(
"homeassistant.components.homekit.type_lights", fromlist=["Light"]
)
patcher_tuple = namedtuple("Cls", ["light"])
yield patcher_tuple(light=_import.Light)
patcher.stop()
async def test_light_basic(hass, hk_driver, cls, events):
"""Test light with char state.""" """Test light with char state."""
entity_id = "light.demo" entity_id = "light.demo"
hass.states.async_set(entity_id, STATE_ON, {ATTR_SUPPORTED_FEATURES: 0}) hass.states.async_set(entity_id, STATE_ON, {ATTR_SUPPORTED_FEATURES: 0})
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) acc = Light(hass, hk_driver, "Light", entity_id, 1, None)
hk_driver.add_accessory(acc) hk_driver.add_accessory(acc)
assert acc.aid == 1 assert acc.aid == 1
@ -113,7 +98,7 @@ async def test_light_basic(hass, hk_driver, cls, events):
assert events[-1].data[ATTR_VALUE] == "Set state to 0" assert events[-1].data[ATTR_VALUE] == "Set state to 0"
async def test_light_brightness(hass, hk_driver, cls, events): async def test_light_brightness(hass, hk_driver, events):
"""Test light with brightness.""" """Test light with brightness."""
entity_id = "light.demo" entity_id = "light.demo"
@ -123,7 +108,7 @@ async def test_light_brightness(hass, hk_driver, cls, events):
{ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS, ATTR_BRIGHTNESS: 255}, {ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS, ATTR_BRIGHTNESS: 255},
) )
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) acc = Light(hass, hk_driver, "Light", entity_id, 1, None)
hk_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 # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the
@ -231,7 +216,7 @@ async def test_light_brightness(hass, hk_driver, cls, events):
assert acc.char_brightness.value == 1 assert acc.char_brightness.value == 1
async def test_light_color_temperature(hass, hk_driver, cls, events): async def test_light_color_temperature(hass, hk_driver, events):
"""Test light with color temperature.""" """Test light with color temperature."""
entity_id = "light.demo" entity_id = "light.demo"
@ -241,7 +226,7 @@ async def test_light_color_temperature(hass, hk_driver, cls, events):
{ATTR_SUPPORTED_FEATURES: SUPPORT_COLOR_TEMP, ATTR_COLOR_TEMP: 190}, {ATTR_SUPPORTED_FEATURES: SUPPORT_COLOR_TEMP, ATTR_COLOR_TEMP: 190},
) )
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) acc = Light(hass, hk_driver, "Light", entity_id, 1, None)
hk_driver.add_accessory(acc) hk_driver.add_accessory(acc)
assert acc.char_color_temperature.value == 190 assert acc.char_color_temperature.value == 190
@ -278,7 +263,7 @@ async def test_light_color_temperature(hass, hk_driver, cls, events):
assert events[-1].data[ATTR_VALUE] == "color temperature at 250" assert events[-1].data[ATTR_VALUE] == "color temperature at 250"
async def test_light_color_temperature_and_rgb_color(hass, hk_driver, cls, events): async def test_light_color_temperature_and_rgb_color(hass, hk_driver, events):
"""Test light with color temperature and rgb color not exposing temperature.""" """Test light with color temperature and rgb color not exposing temperature."""
entity_id = "light.demo" entity_id = "light.demo"
@ -292,7 +277,7 @@ async def test_light_color_temperature_and_rgb_color(hass, hk_driver, cls, event
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.light(hass, hk_driver, "Light", entity_id, 2, None) acc = Light(hass, hk_driver, "Light", entity_id, 2, None)
assert acc.char_hue.value == 260 assert acc.char_hue.value == 260
assert acc.char_saturation.value == 90 assert acc.char_saturation.value == 90
@ -313,7 +298,7 @@ async def test_light_color_temperature_and_rgb_color(hass, hk_driver, cls, event
assert acc.char_saturation.value == 61 assert acc.char_saturation.value == 61
async def test_light_rgb_color(hass, hk_driver, cls, events): async def test_light_rgb_color(hass, hk_driver, events):
"""Test light with rgb_color.""" """Test light with rgb_color."""
entity_id = "light.demo" entity_id = "light.demo"
@ -323,7 +308,7 @@ async def test_light_rgb_color(hass, hk_driver, cls, events):
{ATTR_SUPPORTED_FEATURES: SUPPORT_COLOR, ATTR_HS_COLOR: (260, 90)}, {ATTR_SUPPORTED_FEATURES: SUPPORT_COLOR, ATTR_HS_COLOR: (260, 90)},
) )
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) acc = Light(hass, hk_driver, "Light", entity_id, 1, None)
hk_driver.add_accessory(acc) hk_driver.add_accessory(acc)
assert acc.char_hue.value == 260 assert acc.char_hue.value == 260
@ -365,7 +350,7 @@ async def test_light_rgb_color(hass, hk_driver, cls, events):
assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)" assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)"
async def test_light_restore(hass, hk_driver, cls, events): async def test_light_restore(hass, hk_driver, events):
"""Test setting up an entity from state in the event registry.""" """Test setting up an entity from state in the event registry."""
hass.state = CoreState.not_running hass.state = CoreState.not_running
@ -385,20 +370,20 @@ async def test_light_restore(hass, hk_driver, cls, events):
hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {}) hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {})
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.light(hass, hk_driver, "Light", "light.simple", 1, None) acc = Light(hass, hk_driver, "Light", "light.simple", 1, None)
hk_driver.add_accessory(acc) hk_driver.add_accessory(acc)
assert acc.category == 5 # Lightbulb assert acc.category == 5 # Lightbulb
assert acc.chars == [] assert acc.chars == []
assert acc.char_on.value == 0 assert acc.char_on.value == 0
acc = cls.light(hass, hk_driver, "Light", "light.all_info_set", 2, None) acc = Light(hass, hk_driver, "Light", "light.all_info_set", 2, None)
assert acc.category == 5 # Lightbulb assert acc.category == 5 # Lightbulb
assert acc.chars == ["Brightness"] assert acc.chars == ["Brightness"]
assert acc.char_on.value == 0 assert acc.char_on.value == 0
async def test_light_set_brightness_and_color(hass, hk_driver, cls, events): async def test_light_set_brightness_and_color(hass, hk_driver, events):
"""Test light with all chars in one go.""" """Test light with all chars in one go."""
entity_id = "light.demo" entity_id = "light.demo"
@ -411,7 +396,7 @@ async def test_light_set_brightness_and_color(hass, hk_driver, cls, events):
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) acc = Light(hass, hk_driver, "Light", entity_id, 1, None)
hk_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 # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the
@ -474,7 +459,7 @@ async def test_light_set_brightness_and_color(hass, hk_driver, cls, events):
) )
async def test_light_set_brightness_and_color_temp(hass, hk_driver, cls, events): async def test_light_set_brightness_and_color_temp(hass, hk_driver, events):
"""Test light with all chars in one go.""" """Test light with all chars in one go."""
entity_id = "light.demo" entity_id = "light.demo"
@ -487,7 +472,7 @@ async def test_light_set_brightness_and_color_temp(hass, hk_driver, cls, events)
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) acc = Light(hass, hk_driver, "Light", entity_id, 1, None)
hk_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 # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the