mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 17:27:52 +00:00
Cleanup homekit and remove aid storage from hass.data (#47488)
This commit is contained in:
parent
33c4eb3434
commit
f4b775b125
@ -34,7 +34,7 @@ from homeassistant.const import (
|
||||
SERVICE_RELOAD,
|
||||
)
|
||||
from homeassistant.core import CoreState, HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady, Unauthorized
|
||||
from homeassistant.exceptions import Unauthorized
|
||||
from homeassistant.helpers import device_registry, entity_registry
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entityfilter import BASE_FILTER_SCHEMA, FILTER_SCHEMA
|
||||
@ -58,7 +58,6 @@ from . import ( # noqa: F401
|
||||
from .accessories import HomeBridge, HomeDriver, get_accessory
|
||||
from .aidmanager import AccessoryAidStorage
|
||||
from .const import (
|
||||
AID_STORAGE,
|
||||
ATTR_INTERGRATION,
|
||||
ATTR_MANUFACTURER,
|
||||
ATTR_MODEL,
|
||||
@ -241,9 +240,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
port = conf[CONF_PORT]
|
||||
_LOGGER.debug("Begin setup HomeKit for %s", name)
|
||||
|
||||
aid_storage = AccessoryAidStorage(hass, entry.entry_id)
|
||||
|
||||
await aid_storage.async_initialize()
|
||||
# ip_address and advertise_ip are yaml only
|
||||
ip_address = conf.get(CONF_IP_ADDRESS)
|
||||
advertise_ip = conf.get(CONF_ADVERTISE_IP)
|
||||
@ -276,26 +272,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
entry.entry_id,
|
||||
entry.title,
|
||||
)
|
||||
zeroconf_instance = await zeroconf.async_get_instance(hass)
|
||||
|
||||
# If the previous instance hasn't cleaned up yet
|
||||
# we need to wait a bit
|
||||
try:
|
||||
await hass.async_add_executor_job(homekit.setup, zeroconf_instance)
|
||||
except (OSError, AttributeError) as ex:
|
||||
_LOGGER.warning(
|
||||
"%s could not be setup because the local port %s is in use", name, port
|
||||
)
|
||||
raise ConfigEntryNotReady from ex
|
||||
|
||||
undo_listener = entry.add_update_listener(_async_update_listener)
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = {
|
||||
AID_STORAGE: aid_storage,
|
||||
HOMEKIT: homekit,
|
||||
UNDO_UPDATE_LISTENER: undo_listener,
|
||||
UNDO_UPDATE_LISTENER: entry.add_update_listener(_async_update_listener),
|
||||
}
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, homekit.async_stop)
|
||||
|
||||
if hass.state == CoreState.running:
|
||||
await homekit.async_start()
|
||||
elif auto_start:
|
||||
@ -463,6 +447,7 @@ class HomeKit:
|
||||
self._entry_id = entry_id
|
||||
self._entry_title = entry_title
|
||||
self._homekit_mode = homekit_mode
|
||||
self.aid_storage = None
|
||||
self.status = STATUS_READY
|
||||
|
||||
self.bridge = None
|
||||
@ -470,7 +455,6 @@ class HomeKit:
|
||||
|
||||
def setup(self, zeroconf_instance):
|
||||
"""Set up bridge and accessory driver."""
|
||||
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop)
|
||||
ip_addr = self._ip_address or get_local_ip()
|
||||
persist_file = get_persist_fullpath_for_entry_id(self.hass, self._entry_id)
|
||||
|
||||
@ -503,10 +487,9 @@ class HomeKit:
|
||||
self.driver.config_changed()
|
||||
return
|
||||
|
||||
aid_storage = self.hass.data[DOMAIN][self._entry_id][AID_STORAGE]
|
||||
removed = []
|
||||
for entity_id in entity_ids:
|
||||
aid = aid_storage.get_or_allocate_aid_for_entity_id(entity_id)
|
||||
aid = self.aid_storage.get_or_allocate_aid_for_entity_id(entity_id)
|
||||
if aid not in self.bridge.accessories:
|
||||
continue
|
||||
|
||||
@ -531,9 +514,6 @@ class HomeKit:
|
||||
|
||||
def add_bridge_accessory(self, state):
|
||||
"""Try adding accessory to bridge if configured beforehand."""
|
||||
if not self._filter(state.entity_id):
|
||||
return
|
||||
|
||||
# The bridge itself counts as an accessory
|
||||
if len(self.bridge.accessories) + 1 >= MAX_DEVICES:
|
||||
_LOGGER.warning(
|
||||
@ -555,9 +535,7 @@ class HomeKit:
|
||||
state.entity_id,
|
||||
)
|
||||
|
||||
aid = self.hass.data[DOMAIN][self._entry_id][
|
||||
AID_STORAGE
|
||||
].get_or_allocate_aid_for_entity_id(state.entity_id)
|
||||
aid = self.aid_storage.get_or_allocate_aid_for_entity_id(state.entity_id)
|
||||
conf = self._config.pop(state.entity_id, {})
|
||||
# If an accessory cannot be created or added due to an exception
|
||||
# of any kind (usually in pyhap) it should not prevent
|
||||
@ -578,15 +556,10 @@ class HomeKit:
|
||||
acc = self.bridge.accessories.pop(aid)
|
||||
return acc
|
||||
|
||||
async def async_start(self, *args):
|
||||
"""Start the accessory driver."""
|
||||
if self.status != STATUS_READY:
|
||||
return
|
||||
self.status = STATUS_WAIT
|
||||
|
||||
ent_reg = await entity_registry.async_get_registry(self.hass)
|
||||
dev_reg = await device_registry.async_get_registry(self.hass)
|
||||
|
||||
async def async_configure_accessories(self):
|
||||
"""Configure accessories for the included states."""
|
||||
dev_reg = device_registry.async_get(self.hass)
|
||||
ent_reg = entity_registry.async_get(self.hass)
|
||||
device_lookup = ent_reg.async_get_device_class_lookup(
|
||||
{
|
||||
(BINARY_SENSOR_DOMAIN, DEVICE_CLASS_BATTERY_CHARGING),
|
||||
@ -597,10 +570,9 @@ class HomeKit:
|
||||
}
|
||||
)
|
||||
|
||||
bridged_states = []
|
||||
entity_states = []
|
||||
for state in self.hass.states.async_all():
|
||||
entity_id = state.entity_id
|
||||
|
||||
if not self._filter(entity_id):
|
||||
continue
|
||||
|
||||
@ -611,17 +583,40 @@ class HomeKit:
|
||||
)
|
||||
self._async_configure_linked_sensors(ent_reg_ent, device_lookup, state)
|
||||
|
||||
bridged_states.append(state)
|
||||
entity_states.append(state)
|
||||
|
||||
self._async_register_bridge(dev_reg)
|
||||
await self._async_start(bridged_states)
|
||||
return entity_states
|
||||
|
||||
async def async_start(self, *args):
|
||||
"""Load storage and start."""
|
||||
if self.status != STATUS_READY:
|
||||
return
|
||||
self.status = STATUS_WAIT
|
||||
zc_instance = await zeroconf.async_get_instance(self.hass)
|
||||
await self.hass.async_add_executor_job(self.setup, zc_instance)
|
||||
self.aid_storage = AccessoryAidStorage(self.hass, self._entry_id)
|
||||
await self.aid_storage.async_initialize()
|
||||
await self._async_create_accessories()
|
||||
self._async_register_bridge()
|
||||
_LOGGER.debug("Driver start for %s", self._name)
|
||||
await self.driver.async_start()
|
||||
self.status = STATUS_RUNNING
|
||||
|
||||
if self.driver.state.paired:
|
||||
return
|
||||
|
||||
show_setup_message(
|
||||
self.hass,
|
||||
self._entry_id,
|
||||
accessory_friendly_name(self._entry_title, self.driver.accessory),
|
||||
self.driver.state.pincode,
|
||||
self.driver.accessory.xhm_uri(),
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_register_bridge(self, dev_reg):
|
||||
def _async_register_bridge(self):
|
||||
"""Register the bridge as a device so homekit_controller and exclude it from discovery."""
|
||||
dev_reg = device_registry.async_get(self.hass)
|
||||
formatted_mac = device_registry.format_mac(self.driver.state.mac)
|
||||
# Connections and identifiers are both used here.
|
||||
#
|
||||
@ -645,8 +640,9 @@ class HomeKit:
|
||||
identifiers={identifier},
|
||||
connections={connection},
|
||||
manufacturer=MANUFACTURER,
|
||||
name=self._name,
|
||||
model=f"Home Assistant HomeKit {hk_mode_name}",
|
||||
name=accessory_friendly_name(self._entry_title, self.driver.accessory),
|
||||
model=f"HomeKit {hk_mode_name}",
|
||||
entry_type="service",
|
||||
)
|
||||
|
||||
@callback
|
||||
@ -663,14 +659,13 @@ class HomeKit:
|
||||
for device_id in devices_to_purge:
|
||||
dev_reg.async_remove_device(device_id)
|
||||
|
||||
async def _async_start(self, entity_states):
|
||||
"""Start the accessory."""
|
||||
async def _async_create_accessories(self):
|
||||
"""Create the accessories."""
|
||||
entity_states = await self.async_configure_accessories()
|
||||
if self._homekit_mode == HOMEKIT_MODE_ACCESSORY:
|
||||
state = entity_states[0]
|
||||
conf = self._config.pop(state.entity_id, {})
|
||||
acc = get_accessory(self.hass, self.driver, state, STANDALONE_AID, conf)
|
||||
|
||||
self.driver.add_accessory(acc)
|
||||
else:
|
||||
self.bridge = HomeBridge(self.hass, self.driver, self._name)
|
||||
for state in entity_states:
|
||||
@ -679,15 +674,6 @@ class HomeKit:
|
||||
|
||||
await self.hass.async_add_executor_job(self.driver.add_accessory, acc)
|
||||
|
||||
if not self.driver.state.paired:
|
||||
show_setup_message(
|
||||
self.hass,
|
||||
self._entry_id,
|
||||
accessory_friendly_name(self._entry_title, self.driver.accessory),
|
||||
self.driver.state.pincode,
|
||||
self.driver.accessory.xhm_uri(),
|
||||
)
|
||||
|
||||
async def async_stop(self, *args):
|
||||
"""Stop the accessory driver."""
|
||||
if self.status != STATUS_RUNNING:
|
||||
|
@ -5,7 +5,6 @@ DEBOUNCE_TIMEOUT = 0.5
|
||||
DEVICE_PRECISION_LEEWAY = 6
|
||||
DOMAIN = "homekit"
|
||||
HOMEKIT_FILE = ".homekit.state"
|
||||
AID_STORAGE = "homekit-aid-allocations"
|
||||
HOMEKIT_PAIRING_QR = "homekit-pairing-qr"
|
||||
HOMEKIT_PAIRING_QR_SECRET = "homekit-pairing-qr-secret"
|
||||
HOMEKIT = "homekit"
|
||||
|
@ -487,8 +487,10 @@ def accessory_friendly_name(hass_name, accessory):
|
||||
see both to identify the accessory.
|
||||
"""
|
||||
accessory_mdns_name = accessory.display_name
|
||||
if hass_name.startswith(accessory_mdns_name):
|
||||
if hass_name.casefold().startswith(accessory_mdns_name.casefold()):
|
||||
return hass_name
|
||||
if accessory_mdns_name.casefold().startswith(hass_name.casefold()):
|
||||
return accessory_mdns_name
|
||||
return f"{hass_name} ({accessory_mdns_name})"
|
||||
|
||||
|
||||
|
@ -14,7 +14,9 @@ def hk_driver(loop):
|
||||
"""Return a custom AccessoryDriver instance for HomeKit accessory init."""
|
||||
with patch("pyhap.accessory_driver.Zeroconf"), patch(
|
||||
"pyhap.accessory_driver.AccessoryEncoder"
|
||||
), patch("pyhap.accessory_driver.HAPServer"), patch(
|
||||
), patch("pyhap.accessory_driver.HAPServer.async_stop"), patch(
|
||||
"pyhap.accessory_driver.HAPServer.async_start"
|
||||
), patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.publish"
|
||||
), patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.persist"
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""Tests for the HomeKit component."""
|
||||
import asyncio
|
||||
import os
|
||||
from typing import Dict
|
||||
from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch
|
||||
@ -23,7 +24,6 @@ from homeassistant.components.homekit import (
|
||||
)
|
||||
from homeassistant.components.homekit.accessories import HomeBridge
|
||||
from homeassistant.components.homekit.const import (
|
||||
AID_STORAGE,
|
||||
BRIDGE_NAME,
|
||||
BRIDGE_SERIAL_NUMBER,
|
||||
CONF_AUTO_START,
|
||||
@ -47,7 +47,6 @@ from homeassistant.const import (
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
EVENT_HOMEASSISTANT_STARTED,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
PERCENTAGE,
|
||||
SERVICE_RELOAD,
|
||||
STATE_ON,
|
||||
@ -98,8 +97,28 @@ def _mock_homekit(hass, entry, homekit_mode, entity_filter=None):
|
||||
)
|
||||
|
||||
|
||||
def _mock_homekit_bridge(hass, entry):
|
||||
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
|
||||
homekit.driver = MagicMock()
|
||||
return homekit
|
||||
|
||||
|
||||
def _mock_accessories(accessory_count):
|
||||
accessories = {}
|
||||
for idx in range(accessory_count + 1):
|
||||
accessories[idx + 1000] = MagicMock(async_stop=AsyncMock())
|
||||
return accessories
|
||||
|
||||
|
||||
def _mock_pyhap_bridge():
|
||||
return MagicMock(
|
||||
aid=1, accessories=_mock_accessories(10), display_name="HomeKit Bridge"
|
||||
)
|
||||
|
||||
|
||||
async def test_setup_min(hass, mock_zeroconf):
|
||||
"""Test async_setup with min config options."""
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_NAME: BRIDGE_NAME, CONF_PORT: DEFAULT_PORT},
|
||||
@ -126,18 +145,16 @@ async def test_setup_min(hass, mock_zeroconf):
|
||||
entry.entry_id,
|
||||
entry.title,
|
||||
)
|
||||
assert mock_homekit().setup.called is True
|
||||
|
||||
# Test auto start enabled
|
||||
mock_homekit.reset_mock()
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_homekit().async_start.assert_called()
|
||||
assert mock_homekit().async_start.called is True
|
||||
|
||||
|
||||
async def test_setup_auto_start_disabled(hass, mock_zeroconf):
|
||||
"""Test async_setup with auto start disabled and test service calls."""
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_NAME: "Test Name", CONF_PORT: 11111, CONF_IP_ADDRESS: "172.0.0.0"},
|
||||
@ -164,7 +181,6 @@ async def test_setup_auto_start_disabled(hass, mock_zeroconf):
|
||||
entry.entry_id,
|
||||
entry.title,
|
||||
)
|
||||
assert mock_homekit().setup.called is True
|
||||
|
||||
# Test auto_start disabled
|
||||
homekit.reset_mock()
|
||||
@ -237,9 +253,6 @@ async def test_homekit_setup(hass, hk_driver, mock_zeroconf):
|
||||
)
|
||||
assert homekit.driver.safe_mode is False
|
||||
|
||||
# Test if stop listener is setup
|
||||
assert hass.bus.async_listeners().get(EVENT_HOMEASSISTANT_STOP) == 1
|
||||
|
||||
|
||||
async def test_homekit_setup_ip_address(hass, hk_driver, mock_zeroconf):
|
||||
"""Test setup with given IP address."""
|
||||
@ -321,40 +334,37 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver, mock_zeroconf):
|
||||
|
||||
async def test_homekit_add_accessory(hass, mock_zeroconf):
|
||||
"""Add accessory if config exists and get_acc returns an accessory."""
|
||||
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
|
||||
homekit.driver = "driver"
|
||||
homekit.bridge = mock_bridge = Mock()
|
||||
homekit.bridge.accessories = range(10)
|
||||
homekit.async_start = AsyncMock()
|
||||
homekit = _mock_homekit_bridge(hass, entry)
|
||||
mock_acc = Mock(category="any")
|
||||
|
||||
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit):
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_acc = Mock(category="any")
|
||||
homekit.bridge = _mock_pyhap_bridge()
|
||||
|
||||
with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc:
|
||||
mock_get_acc.side_effect = [None, mock_acc, None]
|
||||
state = State("light.demo", "on")
|
||||
homekit.add_bridge_accessory(state)
|
||||
mock_get_acc.assert_called_with(hass, ANY, ANY, 1403373688, {})
|
||||
assert not mock_bridge.add_accessory.called
|
||||
assert not homekit.bridge.add_accessory.called
|
||||
|
||||
state = State("demo.test", "on")
|
||||
homekit.add_bridge_accessory(state)
|
||||
mock_get_acc.assert_called_with(hass, ANY, ANY, 600325356, {})
|
||||
assert mock_bridge.add_accessory.called
|
||||
assert homekit.bridge.add_accessory.called
|
||||
|
||||
state = State("demo.test_2", "on")
|
||||
homekit.add_bridge_accessory(state)
|
||||
mock_get_acc.assert_called_with(hass, ANY, ANY, 1467253281, {})
|
||||
assert mock_bridge.add_accessory.called
|
||||
assert homekit.bridge.add_accessory.called
|
||||
|
||||
|
||||
@pytest.mark.parametrize("acc_category", [CATEGORY_TELEVISION, CATEGORY_CAMERA])
|
||||
@ -362,29 +372,27 @@ async def test_homekit_warn_add_accessory_bridge(
|
||||
hass, acc_category, mock_zeroconf, caplog
|
||||
):
|
||||
"""Test we warn when adding cameras or tvs to a bridge."""
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
|
||||
homekit.driver = "driver"
|
||||
homekit.bridge = mock_bridge = Mock()
|
||||
homekit.bridge.accessories = range(10)
|
||||
homekit.async_start = AsyncMock()
|
||||
homekit = _mock_homekit_bridge(hass, entry)
|
||||
|
||||
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit):
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_camera_acc = Mock(category=acc_category)
|
||||
homekit.bridge = _mock_pyhap_bridge()
|
||||
|
||||
with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc:
|
||||
mock_get_acc.side_effect = [None, mock_camera_acc, None]
|
||||
state = State("camera.test", "on")
|
||||
homekit.add_bridge_accessory(state)
|
||||
mock_get_acc.assert_called_with(hass, ANY, ANY, 1508819236, {})
|
||||
assert not mock_bridge.add_accessory.called
|
||||
assert not homekit.bridge.add_accessory.called
|
||||
|
||||
assert "accessory mode" in caplog.text
|
||||
|
||||
@ -396,12 +404,12 @@ async def test_homekit_remove_accessory(hass, mock_zeroconf):
|
||||
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
|
||||
|
||||
homekit.driver = "driver"
|
||||
homekit.bridge = mock_bridge = Mock()
|
||||
mock_bridge.accessories = {"light.demo": "acc"}
|
||||
homekit.bridge = _mock_pyhap_bridge()
|
||||
homekit.bridge.accessories = {"light.demo": "acc"}
|
||||
|
||||
acc = homekit.remove_bridge_accessory("light.demo")
|
||||
assert acc == "acc"
|
||||
assert len(mock_bridge.accessories) == 0
|
||||
assert len(homekit.bridge.accessories) == 0
|
||||
|
||||
|
||||
async def test_homekit_entity_filter(hass, mock_zeroconf):
|
||||
@ -413,20 +421,14 @@ async def test_homekit_entity_filter(hass, mock_zeroconf):
|
||||
|
||||
homekit.bridge = Mock()
|
||||
homekit.bridge.accessories = {}
|
||||
hass.states.async_set("cover.test", "open")
|
||||
hass.states.async_set("demo.test", "on")
|
||||
hass.states.async_set("light.demo", "on")
|
||||
|
||||
with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc:
|
||||
mock_get_acc.return_value = None
|
||||
|
||||
homekit.add_bridge_accessory(State("cover.test", "open"))
|
||||
assert mock_get_acc.called is True
|
||||
mock_get_acc.reset_mock()
|
||||
|
||||
homekit.add_bridge_accessory(State("demo.test", "on"))
|
||||
assert mock_get_acc.called is True
|
||||
mock_get_acc.reset_mock()
|
||||
|
||||
homekit.add_bridge_accessory(State("light.demo", "light"))
|
||||
assert mock_get_acc.called is False
|
||||
filtered_states = await homekit.async_configure_accessories()
|
||||
assert hass.states.get("cover.test") in filtered_states
|
||||
assert hass.states.get("demo.test") in filtered_states
|
||||
assert hass.states.get("light.demo") not in filtered_states
|
||||
|
||||
|
||||
async def test_homekit_entity_glob_filter(hass, mock_zeroconf):
|
||||
@ -441,39 +443,29 @@ async def test_homekit_entity_glob_filter(hass, mock_zeroconf):
|
||||
homekit.bridge = Mock()
|
||||
homekit.bridge.accessories = {}
|
||||
|
||||
with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc:
|
||||
mock_get_acc.return_value = None
|
||||
hass.states.async_set("cover.test", "open")
|
||||
hass.states.async_set("demo.test", "on")
|
||||
hass.states.async_set("cover.excluded_test", "open")
|
||||
hass.states.async_set("light.included_test", "on")
|
||||
|
||||
homekit.add_bridge_accessory(State("cover.test", "open"))
|
||||
assert mock_get_acc.called is True
|
||||
mock_get_acc.reset_mock()
|
||||
|
||||
homekit.add_bridge_accessory(State("demo.test", "on"))
|
||||
assert mock_get_acc.called is True
|
||||
mock_get_acc.reset_mock()
|
||||
|
||||
homekit.add_bridge_accessory(State("cover.excluded_test", "open"))
|
||||
assert mock_get_acc.called is False
|
||||
mock_get_acc.reset_mock()
|
||||
|
||||
homekit.add_bridge_accessory(State("light.included_test", "light"))
|
||||
assert mock_get_acc.called is True
|
||||
mock_get_acc.reset_mock()
|
||||
filtered_states = await homekit.async_configure_accessories()
|
||||
assert hass.states.get("cover.test") in filtered_states
|
||||
assert hass.states.get("demo.test") in filtered_states
|
||||
assert hass.states.get("cover.excluded_test") not in filtered_states
|
||||
assert hass.states.get("light.included_test") in filtered_states
|
||||
|
||||
|
||||
async def test_homekit_start(hass, hk_driver, device_reg):
|
||||
async def test_homekit_start(hass, hk_driver, mock_zeroconf, device_reg):
|
||||
"""Test HomeKit start method."""
|
||||
entry = await async_init_integration(hass)
|
||||
|
||||
pin = b"123-45-678"
|
||||
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
|
||||
|
||||
homekit.bridge = Mock()
|
||||
homekit.bridge.accessories = []
|
||||
homekit.driver = hk_driver
|
||||
# pylint: disable=protected-access
|
||||
homekit._filter = Mock(return_value=True)
|
||||
homekit.driver.accessory = Accessory(hk_driver, "any")
|
||||
acc = Accessory(hk_driver, "any")
|
||||
homekit.driver.accessory = acc
|
||||
|
||||
connection = (device_registry.CONNECTION_NETWORK_MAC, "AA:BB:CC:DD:EE:FF")
|
||||
bridge_with_wrong_mac = device_reg.async_get_or_create(
|
||||
@ -491,8 +483,6 @@ async def test_homekit_start(hass, hk_driver, device_reg):
|
||||
with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch(
|
||||
f"{PATH_HOMEKIT}.show_setup_message"
|
||||
) as mock_setup_msg, patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.add_accessory"
|
||||
) as hk_driver_add_acc, patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.async_start"
|
||||
) as hk_driver_start:
|
||||
await homekit.async_start()
|
||||
@ -500,9 +490,8 @@ async def test_homekit_start(hass, hk_driver, device_reg):
|
||||
await hass.async_block_till_done()
|
||||
mock_add_acc.assert_any_call(state)
|
||||
mock_setup_msg.assert_called_with(
|
||||
hass, entry.entry_id, "Mock Title (any)", pin, ANY
|
||||
hass, entry.entry_id, "Mock Title (Home Assistant Bridge)", ANY, ANY
|
||||
)
|
||||
hk_driver_add_acc.assert_called_with(homekit.bridge)
|
||||
assert hk_driver_start.called
|
||||
assert homekit.status == STATUS_RUNNING
|
||||
|
||||
@ -526,8 +515,6 @@ async def test_homekit_start(hass, hk_driver, device_reg):
|
||||
with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch(
|
||||
f"{PATH_HOMEKIT}.show_setup_message"
|
||||
) as mock_setup_msg, patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.add_accessory"
|
||||
) as hk_driver_add_acc, patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.async_start"
|
||||
) as hk_driver_start:
|
||||
await homekit.async_start()
|
||||
@ -545,7 +532,6 @@ async def test_homekit_start(hass, hk_driver, device_reg):
|
||||
|
||||
async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroconf):
|
||||
"""Test HomeKit start method."""
|
||||
pin = b"123-45-678"
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
|
||||
)
|
||||
@ -565,17 +551,14 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroc
|
||||
with patch(f"{PATH_HOMEKIT}.get_accessory", side_effect=Exception), patch(
|
||||
f"{PATH_HOMEKIT}.show_setup_message"
|
||||
) as mock_setup_msg, patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.add_accessory",
|
||||
) as hk_driver_add_acc, patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.async_start"
|
||||
) as hk_driver_start:
|
||||
await homekit.async_start()
|
||||
|
||||
await hass.async_block_till_done()
|
||||
mock_setup_msg.assert_called_with(
|
||||
hass, entry.entry_id, "Mock Title (any)", pin, ANY
|
||||
hass, entry.entry_id, "Mock Title (Home Assistant Bridge)", ANY, ANY
|
||||
)
|
||||
hk_driver_add_acc.assert_called_with(homekit.bridge)
|
||||
assert hk_driver_start.called
|
||||
assert homekit.status == STATUS_RUNNING
|
||||
|
||||
@ -616,27 +599,23 @@ async def test_homekit_stop(hass):
|
||||
|
||||
async def test_homekit_reset_accessories(hass, mock_zeroconf):
|
||||
"""Test adding too many accessories to HomeKit."""
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
|
||||
)
|
||||
entity_id = "light.demo"
|
||||
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
|
||||
|
||||
homekit.bridge = Mock()
|
||||
homekit.bridge.accessories = {}
|
||||
|
||||
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), patch(
|
||||
f"{PATH_HOMEKIT}.HomeKit.setup"
|
||||
), patch("pyhap.accessory.Bridge.add_accessory") as mock_add_accessory, patch(
|
||||
"pyhap.accessory.Bridge.add_accessory"
|
||||
) as mock_add_accessory, patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.config_changed"
|
||||
) as hk_driver_config_changed, patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.async_start"
|
||||
):
|
||||
await async_init_entry(hass, entry)
|
||||
|
||||
aid = hass.data[DOMAIN][entry.entry_id][
|
||||
AID_STORAGE
|
||||
].get_or_allocate_aid_for_entity_id(entity_id)
|
||||
aid = homekit.aid_storage.get_or_allocate_aid_for_entity_id(entity_id)
|
||||
homekit.bridge.accessories = {aid: "acc"}
|
||||
homekit.status = STATUS_RUNNING
|
||||
|
||||
@ -675,10 +654,8 @@ async def test_homekit_too_many_accessories(hass, hk_driver, caplog, mock_zeroco
|
||||
hass.states.async_set("light.demo3", "on")
|
||||
|
||||
with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.add_accessory"
|
||||
), patch(f"{PATH_HOMEKIT}.show_setup_message"), patch(
|
||||
f"{PATH_HOMEKIT}.HomeBridge", _mock_bridge
|
||||
):
|
||||
f"{PATH_HOMEKIT}.show_setup_message"
|
||||
), patch(f"{PATH_HOMEKIT}.HomeBridge", _mock_bridge):
|
||||
await homekit.async_start()
|
||||
await hass.async_block_till_done()
|
||||
assert "would exceed" in caplog.text
|
||||
@ -693,9 +670,7 @@ async def test_homekit_finds_linked_batteries(
|
||||
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
|
||||
|
||||
homekit.driver = hk_driver
|
||||
# pylint: disable=protected-access
|
||||
homekit._filter = Mock(return_value=True)
|
||||
homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge")
|
||||
homekit.bridge = MagicMock()
|
||||
|
||||
config_entry = MockConfigEntry(domain="test", data={})
|
||||
config_entry.add_to_hass(hass)
|
||||
@ -735,17 +710,15 @@ async def test_homekit_finds_linked_batteries(
|
||||
)
|
||||
hass.states.async_set(light.entity_id, STATE_ON)
|
||||
|
||||
with patch.object(homekit.bridge, "add_accessory"), patch(
|
||||
f"{PATH_HOMEKIT}.show_setup_message"
|
||||
), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.async_start"
|
||||
):
|
||||
with patch(f"{PATH_HOMEKIT}.show_setup_message"), patch(
|
||||
f"{PATH_HOMEKIT}.get_accessory"
|
||||
) as mock_get_acc, patch("pyhap.accessory_driver.AccessoryDriver.async_start"):
|
||||
await homekit.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_get_acc.assert_called_with(
|
||||
hass,
|
||||
hk_driver,
|
||||
ANY,
|
||||
ANY,
|
||||
ANY,
|
||||
{
|
||||
@ -766,8 +739,6 @@ async def test_homekit_async_get_integration_fails(
|
||||
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
|
||||
|
||||
homekit.driver = hk_driver
|
||||
# pylint: disable=protected-access
|
||||
homekit._filter = Mock(return_value=True)
|
||||
homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge")
|
||||
|
||||
config_entry = MockConfigEntry(domain="test", data={})
|
||||
@ -817,7 +788,7 @@ async def test_homekit_async_get_integration_fails(
|
||||
|
||||
mock_get_acc.assert_called_with(
|
||||
hass,
|
||||
hk_driver,
|
||||
ANY,
|
||||
ANY,
|
||||
ANY,
|
||||
{
|
||||
@ -832,6 +803,7 @@ async def test_homekit_async_get_integration_fails(
|
||||
|
||||
async def test_yaml_updates_update_config_entry_for_name(hass, mock_zeroconf):
|
||||
"""Test async_setup with imported config."""
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
source=SOURCE_IMPORT,
|
||||
@ -861,7 +833,6 @@ async def test_yaml_updates_update_config_entry_for_name(hass, mock_zeroconf):
|
||||
entry.entry_id,
|
||||
entry.title,
|
||||
)
|
||||
assert mock_homekit().setup.called is True
|
||||
|
||||
# Test auto start enabled
|
||||
mock_homekit.reset_mock()
|
||||
@ -871,20 +842,6 @@ async def test_yaml_updates_update_config_entry_for_name(hass, mock_zeroconf):
|
||||
mock_homekit().async_start.assert_called()
|
||||
|
||||
|
||||
async def test_raise_config_entry_not_ready(hass, mock_zeroconf):
|
||||
"""Test async_setup when the port is not available."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_NAME: BRIDGE_NAME, CONF_PORT: DEFAULT_PORT},
|
||||
options={},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch(f"{PATH_HOMEKIT}.HomeKit.setup", side_effect=OSError):
|
||||
assert not await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def test_homekit_uses_system_zeroconf(hass, hk_driver, mock_zeroconf):
|
||||
"""Test HomeKit uses system zeroconf."""
|
||||
entry = MockConfigEntry(
|
||||
@ -917,13 +874,12 @@ async def test_homekit_ignored_missing_devices(
|
||||
hass, hk_driver, device_reg, entity_reg, mock_zeroconf
|
||||
):
|
||||
"""Test HomeKit handles a device in the entity registry but missing from the device registry."""
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
entry = await async_init_integration(hass)
|
||||
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
|
||||
|
||||
homekit.driver = hk_driver
|
||||
# pylint: disable=protected-access
|
||||
homekit._filter = Mock(return_value=True)
|
||||
homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge")
|
||||
homekit.bridge = _mock_pyhap_bridge()
|
||||
|
||||
config_entry = MockConfigEntry(domain="test", data={})
|
||||
config_entry.add_to_hass(hass)
|
||||
@ -952,25 +908,28 @@ async def test_homekit_ignored_missing_devices(
|
||||
light = entity_reg.async_get_or_create(
|
||||
"light", "powerwall", "demo", device_id=device_entry.id
|
||||
)
|
||||
|
||||
before_removal = entity_reg.entities.copy()
|
||||
# Delete the device to make sure we fallback
|
||||
# to using the platform
|
||||
device_reg.async_remove_device(device_entry.id)
|
||||
# Wait for the entities to be removed
|
||||
await asyncio.sleep(0)
|
||||
await asyncio.sleep(0)
|
||||
# Restore the registry
|
||||
entity_reg.entities = before_removal
|
||||
|
||||
hass.states.async_set(light.entity_id, STATE_ON)
|
||||
hass.states.async_set("light.two", STATE_ON)
|
||||
|
||||
with patch.object(homekit.bridge, "add_accessory"), patch(
|
||||
f"{PATH_HOMEKIT}.show_setup_message"
|
||||
), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.async_start"
|
||||
):
|
||||
with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch(
|
||||
f"{PATH_HOMEKIT}.HomeBridge", return_value=homekit.bridge
|
||||
), patch("pyhap.accessory_driver.AccessoryDriver.async_start"):
|
||||
await homekit.async_start()
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_get_acc.assert_any_call(
|
||||
hass,
|
||||
hk_driver,
|
||||
ANY,
|
||||
ANY,
|
||||
ANY,
|
||||
{
|
||||
@ -990,8 +949,6 @@ async def test_homekit_finds_linked_motion_sensors(
|
||||
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
|
||||
|
||||
homekit.driver = hk_driver
|
||||
# pylint: disable=protected-access
|
||||
homekit._filter = Mock(return_value=True)
|
||||
homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge")
|
||||
|
||||
config_entry = MockConfigEntry(domain="test", data={})
|
||||
@ -1032,7 +989,7 @@ async def test_homekit_finds_linked_motion_sensors(
|
||||
|
||||
mock_get_acc.assert_called_with(
|
||||
hass,
|
||||
hk_driver,
|
||||
ANY,
|
||||
ANY,
|
||||
ANY,
|
||||
{
|
||||
@ -1053,7 +1010,6 @@ async def test_homekit_finds_linked_humidity_sensors(
|
||||
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
|
||||
|
||||
homekit.driver = hk_driver
|
||||
homekit._filter = Mock(return_value=True)
|
||||
homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge")
|
||||
|
||||
config_entry = MockConfigEntry(domain="test", data={})
|
||||
@ -1097,7 +1053,7 @@ async def test_homekit_finds_linked_humidity_sensors(
|
||||
|
||||
mock_get_acc.assert_called_with(
|
||||
hass,
|
||||
hk_driver,
|
||||
ANY,
|
||||
ANY,
|
||||
ANY,
|
||||
{
|
||||
@ -1111,6 +1067,7 @@ async def test_homekit_finds_linked_humidity_sensors(
|
||||
|
||||
async def test_reload(hass, mock_zeroconf):
|
||||
"""Test we can reload from yaml."""
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
source=SOURCE_IMPORT,
|
||||
@ -1121,7 +1078,6 @@ async def test_reload(hass, mock_zeroconf):
|
||||
|
||||
with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit:
|
||||
mock_homekit.return_value = homekit = Mock()
|
||||
type(homekit).async_start = AsyncMock()
|
||||
assert await async_setup_component(
|
||||
hass, "homekit", {"homekit": {CONF_NAME: "reloadable", CONF_PORT: 12345}}
|
||||
)
|
||||
@ -1140,7 +1096,6 @@ async def test_reload(hass, mock_zeroconf):
|
||||
entry.entry_id,
|
||||
entry.title,
|
||||
)
|
||||
assert mock_homekit().setup.called is True
|
||||
yaml_path = os.path.join(
|
||||
_get_fixtures_base_path(),
|
||||
"fixtures",
|
||||
@ -1156,7 +1111,6 @@ async def test_reload(hass, mock_zeroconf):
|
||||
"pyhap.accessory_driver.AccessoryDriver.async_start"
|
||||
):
|
||||
mock_homekit2.return_value = homekit = Mock()
|
||||
type(homekit).async_start = AsyncMock()
|
||||
await hass.services.async_call(
|
||||
"homekit",
|
||||
SERVICE_RELOAD,
|
||||
@ -1178,33 +1132,30 @@ async def test_reload(hass, mock_zeroconf):
|
||||
entry.entry_id,
|
||||
entry.title,
|
||||
)
|
||||
assert mock_homekit2().setup.called is True
|
||||
|
||||
|
||||
def _get_fixtures_base_path():
|
||||
return os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
|
||||
async def test_homekit_start_in_accessory_mode(hass, hk_driver, device_reg):
|
||||
async def test_homekit_start_in_accessory_mode(
|
||||
hass, hk_driver, mock_zeroconf, device_reg
|
||||
):
|
||||
"""Test HomeKit start method in accessory mode."""
|
||||
entry = await async_init_integration(hass)
|
||||
|
||||
pin = b"123-45-678"
|
||||
|
||||
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_ACCESSORY)
|
||||
|
||||
homekit.bridge = Mock()
|
||||
homekit.bridge.accessories = []
|
||||
homekit.driver = hk_driver
|
||||
# pylint: disable=protected-access
|
||||
homekit._filter = Mock(return_value=True)
|
||||
homekit.driver.accessory = Accessory(hk_driver, "any")
|
||||
|
||||
hass.states.async_set("light.demo", "on")
|
||||
|
||||
with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.add_accessory"
|
||||
), patch(f"{PATH_HOMEKIT}.show_setup_message") as mock_setup_msg, patch(
|
||||
f"{PATH_HOMEKIT}.show_setup_message"
|
||||
) as mock_setup_msg, patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.async_start"
|
||||
) as hk_driver_start:
|
||||
await homekit.async_start()
|
||||
@ -1212,7 +1163,7 @@ async def test_homekit_start_in_accessory_mode(hass, hk_driver, device_reg):
|
||||
await hass.async_block_till_done()
|
||||
mock_add_acc.assert_not_called()
|
||||
mock_setup_msg.assert_called_with(
|
||||
hass, entry.entry_id, "Mock Title (any)", pin, ANY
|
||||
hass, entry.entry_id, "Mock Title (demo)", ANY, ANY
|
||||
)
|
||||
assert hk_driver_start.called
|
||||
assert homekit.status == STATUS_RUNNING
|
||||
|
@ -294,5 +294,7 @@ async def test_accessory_friendly_name():
|
||||
|
||||
accessory = Mock()
|
||||
accessory.display_name = "same"
|
||||
assert accessory_friendly_name("same", accessory) == "same"
|
||||
assert accessory_friendly_name("Same", accessory) == "Same"
|
||||
assert accessory_friendly_name("hass title", accessory) == "hass title (same)"
|
||||
accessory.display_name = "Hass title 123"
|
||||
assert accessory_friendly_name("hass title", accessory) == "Hass title 123"
|
||||
|
Loading…
x
Reference in New Issue
Block a user