mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Recreate HomeKit accessories when calling the reset_accessory service (#53199)
This commit is contained in:
parent
4d122fc366
commit
2a65c5f93c
@ -120,6 +120,10 @@ PORT_CLEANUP_CHECK_INTERVAL_SECS = 1
|
|||||||
|
|
||||||
MDNS_TARGET_IP = "224.0.0.251"
|
MDNS_TARGET_IP = "224.0.0.251"
|
||||||
|
|
||||||
|
_HOMEKIT_CONFIG_UPDATE_TIME = (
|
||||||
|
5 # number of seconds to wait for homekit to see the c# change
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _has_all_unique_names_and_ports(bridges):
|
def _has_all_unique_names_and_ports(bridges):
|
||||||
"""Validate that each homekit bridge configured has a unique name."""
|
"""Validate that each homekit bridge configured has a unique name."""
|
||||||
@ -351,7 +355,7 @@ def _async_register_events_and_services(hass: HomeAssistant):
|
|||||||
"""Register events and services for HomeKit."""
|
"""Register events and services for HomeKit."""
|
||||||
hass.http.register_view(HomeKitPairingQRView)
|
hass.http.register_view(HomeKitPairingQRView)
|
||||||
|
|
||||||
def handle_homekit_reset_accessory(service):
|
async def async_handle_homekit_reset_accessory(service):
|
||||||
"""Handle start HomeKit service call."""
|
"""Handle start HomeKit service call."""
|
||||||
for entry_id in hass.data[DOMAIN]:
|
for entry_id in hass.data[DOMAIN]:
|
||||||
if HOMEKIT not in hass.data[DOMAIN][entry_id]:
|
if HOMEKIT not in hass.data[DOMAIN][entry_id]:
|
||||||
@ -365,12 +369,12 @@ def _async_register_events_and_services(hass: HomeAssistant):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
entity_ids = service.data.get("entity_id")
|
entity_ids = service.data.get("entity_id")
|
||||||
homekit.reset_accessories(entity_ids)
|
await homekit.async_reset_accessories(entity_ids)
|
||||||
|
|
||||||
hass.services.async_register(
|
hass.services.async_register(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_HOMEKIT_RESET_ACCESSORY,
|
SERVICE_HOMEKIT_RESET_ACCESSORY,
|
||||||
handle_homekit_reset_accessory,
|
async_handle_homekit_reset_accessory,
|
||||||
schema=RESET_ACCESSORY_SERVICE_SCHEMA,
|
schema=RESET_ACCESSORY_SERVICE_SCHEMA,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -486,36 +490,61 @@ class HomeKit:
|
|||||||
|
|
||||||
self.driver.persist()
|
self.driver.persist()
|
||||||
|
|
||||||
def reset_accessories(self, entity_ids):
|
async def async_reset_accessories(self, entity_ids):
|
||||||
"""Reset the accessory to load the latest configuration."""
|
"""Reset the accessory to load the latest configuration."""
|
||||||
if not self.bridge:
|
if not self.bridge:
|
||||||
self.driver.config_changed()
|
await self.async_reset_accessories_in_accessory_mode(entity_ids)
|
||||||
return
|
return
|
||||||
|
await self.async_reset_accessories_in_bridge_mode(entity_ids)
|
||||||
|
|
||||||
removed = []
|
async def async_reset_accessories_in_accessory_mode(self, entity_ids):
|
||||||
|
"""Reset accessories in accessory mode."""
|
||||||
|
acc = self.driver.accessory
|
||||||
|
if acc.entity_id not in entity_ids:
|
||||||
|
return
|
||||||
|
acc.async_stop()
|
||||||
|
if not (state := self.hass.states.get(acc.entity_id)):
|
||||||
|
_LOGGER.warning(
|
||||||
|
"The underlying entity %s disappeared during reset", acc.entity
|
||||||
|
)
|
||||||
|
return
|
||||||
|
if new_acc := self._async_create_single_accessory([state]):
|
||||||
|
self.driver.accessory = new_acc
|
||||||
|
await self.async_config_changed()
|
||||||
|
|
||||||
|
async def async_reset_accessories_in_bridge_mode(self, entity_ids):
|
||||||
|
"""Reset accessories in bridge mode."""
|
||||||
|
new = []
|
||||||
for entity_id in entity_ids:
|
for entity_id in entity_ids:
|
||||||
aid = self.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:
|
if aid not in self.bridge.accessories:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"HomeKit Bridge %s will reset accessory with linked entity_id %s",
|
"HomeKit Bridge %s will reset accessory with linked entity_id %s",
|
||||||
self._name,
|
self._name,
|
||||||
entity_id,
|
entity_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
acc = self.remove_bridge_accessory(aid)
|
acc = self.remove_bridge_accessory(aid)
|
||||||
removed.append(acc)
|
if state := self.hass.states.get(acc.entity_id):
|
||||||
|
new.append(state)
|
||||||
|
else:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"The underlying entity %s disappeared during reset", acc.entity
|
||||||
|
)
|
||||||
|
|
||||||
if not removed:
|
if not new:
|
||||||
# No matched accessories, probably on another bridge
|
# No matched accessories, probably on another bridge
|
||||||
return
|
return
|
||||||
|
|
||||||
self.driver.config_changed()
|
await self.async_config_changed()
|
||||||
|
await asyncio.sleep(_HOMEKIT_CONFIG_UPDATE_TIME)
|
||||||
|
for state in new:
|
||||||
|
self.add_bridge_accessory(state)
|
||||||
|
await self.async_config_changed()
|
||||||
|
|
||||||
for acc in removed:
|
async def async_config_changed(self):
|
||||||
self.bridge.add_accessory(acc)
|
"""Call config changed which writes out the new config to disk."""
|
||||||
self.driver.config_changed()
|
await self.hass.async_add_executor_job(self.driver.config_changed)
|
||||||
|
|
||||||
def add_bridge_accessory(self, state):
|
def add_bridge_accessory(self, state):
|
||||||
"""Try adding accessory to bridge if configured beforehand."""
|
"""Try adding accessory to bridge if configured beforehand."""
|
||||||
@ -541,7 +570,7 @@ class HomeKit:
|
|||||||
)
|
)
|
||||||
|
|
||||||
aid = self.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, {})
|
conf = self._config.get(state.entity_id, {}).copy()
|
||||||
# If an accessory cannot be created or added due to an exception
|
# If an accessory cannot be created or added due to an exception
|
||||||
# of any kind (usually in pyhap) it should not prevent
|
# of any kind (usually in pyhap) it should not prevent
|
||||||
# the rest of the accessories from being created
|
# the rest of the accessories from being created
|
||||||
@ -556,9 +585,9 @@ class HomeKit:
|
|||||||
|
|
||||||
def remove_bridge_accessory(self, aid):
|
def remove_bridge_accessory(self, aid):
|
||||||
"""Try adding accessory to bridge if configured beforehand."""
|
"""Try adding accessory to bridge if configured beforehand."""
|
||||||
acc = None
|
acc = self.bridge.accessories.pop(aid, None)
|
||||||
if aid in self.bridge.accessories:
|
if acc:
|
||||||
acc = self.bridge.accessories.pop(aid)
|
acc.async_stop()
|
||||||
return acc
|
return acc
|
||||||
|
|
||||||
async def async_configure_accessories(self):
|
async def async_configure_accessories(self):
|
||||||
@ -665,33 +694,45 @@ class HomeKit:
|
|||||||
for device_id in devices_to_purge:
|
for device_id in devices_to_purge:
|
||||||
dev_reg.async_remove_device(device_id)
|
dev_reg.async_remove_device(device_id)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_create_single_accessory(self, entity_states):
|
||||||
|
"""Create a single HomeKit accessory (accessory mode)."""
|
||||||
|
if not entity_states:
|
||||||
|
_LOGGER.error(
|
||||||
|
"HomeKit %s cannot startup: entity not available: %s",
|
||||||
|
self._name,
|
||||||
|
self._filter.config,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
state = entity_states[0]
|
||||||
|
conf = self._config.get(state.entity_id, {}).copy()
|
||||||
|
acc = get_accessory(self.hass, self.driver, state, STANDALONE_AID, conf)
|
||||||
|
if acc is None:
|
||||||
|
_LOGGER.error(
|
||||||
|
"HomeKit %s cannot startup: entity not supported: %s",
|
||||||
|
self._name,
|
||||||
|
self._filter.config,
|
||||||
|
)
|
||||||
|
return acc
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_create_bridge_accessory(self, entity_states):
|
||||||
|
"""Create a HomeKit bridge with accessories. (bridge mode)."""
|
||||||
|
self.bridge = HomeBridge(self.hass, self.driver, self._name)
|
||||||
|
for state in entity_states:
|
||||||
|
self.add_bridge_accessory(state)
|
||||||
|
return self.bridge
|
||||||
|
|
||||||
async def _async_create_accessories(self):
|
async def _async_create_accessories(self):
|
||||||
"""Create the accessories."""
|
"""Create the accessories."""
|
||||||
entity_states = await self.async_configure_accessories()
|
entity_states = await self.async_configure_accessories()
|
||||||
if self._homekit_mode == HOMEKIT_MODE_ACCESSORY:
|
if self._homekit_mode == HOMEKIT_MODE_ACCESSORY:
|
||||||
if not entity_states:
|
acc = self._async_create_single_accessory(entity_states)
|
||||||
_LOGGER.error(
|
|
||||||
"HomeKit %s cannot startup: entity not available: %s",
|
|
||||||
self._name,
|
|
||||||
self._filter.config,
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
state = entity_states[0]
|
|
||||||
conf = self._config.pop(state.entity_id, {})
|
|
||||||
acc = get_accessory(self.hass, self.driver, state, STANDALONE_AID, conf)
|
|
||||||
if acc is None:
|
|
||||||
_LOGGER.error(
|
|
||||||
"HomeKit %s cannot startup: entity not supported: %s",
|
|
||||||
self._name,
|
|
||||||
self._filter.config,
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
else:
|
else:
|
||||||
self.bridge = HomeBridge(self.hass, self.driver, self._name)
|
acc = self._async_create_bridge_accessory(entity_states)
|
||||||
for state in entity_states:
|
|
||||||
self.add_bridge_accessory(state)
|
|
||||||
acc = self.bridge
|
|
||||||
|
|
||||||
|
if acc is None:
|
||||||
|
return False
|
||||||
# No need to load/persist as we do it in setup
|
# No need to load/persist as we do it in setup
|
||||||
self.driver.accessory = acc
|
self.driver.accessory = acc
|
||||||
return True
|
return True
|
||||||
|
@ -434,10 +434,12 @@ async def test_homekit_remove_accessory(hass, mock_zeroconf):
|
|||||||
|
|
||||||
homekit.driver = "driver"
|
homekit.driver = "driver"
|
||||||
homekit.bridge = _mock_pyhap_bridge()
|
homekit.bridge = _mock_pyhap_bridge()
|
||||||
homekit.bridge.accessories = {"light.demo": "acc"}
|
acc_mock = MagicMock()
|
||||||
|
homekit.bridge.accessories = {6: acc_mock}
|
||||||
|
|
||||||
acc = homekit.remove_bridge_accessory("light.demo")
|
acc = homekit.remove_bridge_accessory(6)
|
||||||
assert acc == "acc"
|
assert acc is acc_mock
|
||||||
|
assert acc_mock.async_stop.called
|
||||||
assert len(homekit.bridge.accessories) == 0
|
assert len(homekit.bridge.accessories) == 0
|
||||||
|
|
||||||
|
|
||||||
@ -627,12 +629,13 @@ async def test_homekit_stop(hass):
|
|||||||
|
|
||||||
|
|
||||||
async def test_homekit_reset_accessories(hass, mock_zeroconf):
|
async def test_homekit_reset_accessories(hass, mock_zeroconf):
|
||||||
"""Test adding too many accessories to HomeKit."""
|
"""Test resetting HomeKit accessories."""
|
||||||
await async_setup_component(hass, "persistent_notification", {})
|
await async_setup_component(hass, "persistent_notification", {})
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
|
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
|
||||||
)
|
)
|
||||||
entity_id = "light.demo"
|
entity_id = "light.demo"
|
||||||
|
hass.states.async_set("light.demo", "on")
|
||||||
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
|
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
|
||||||
|
|
||||||
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), patch(
|
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), patch(
|
||||||
@ -641,11 +644,15 @@ async def test_homekit_reset_accessories(hass, mock_zeroconf):
|
|||||||
"pyhap.accessory_driver.AccessoryDriver.config_changed"
|
"pyhap.accessory_driver.AccessoryDriver.config_changed"
|
||||||
) as hk_driver_config_changed, patch(
|
) as hk_driver_config_changed, patch(
|
||||||
"pyhap.accessory_driver.AccessoryDriver.async_start"
|
"pyhap.accessory_driver.AccessoryDriver.async_start"
|
||||||
|
), patch.object(
|
||||||
|
homekit_base, "_HOMEKIT_CONFIG_UPDATE_TIME", 0
|
||||||
):
|
):
|
||||||
await async_init_entry(hass, entry)
|
await async_init_entry(hass, entry)
|
||||||
|
|
||||||
|
acc_mock = MagicMock()
|
||||||
|
acc_mock.entity_id = entity_id
|
||||||
aid = homekit.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.bridge.accessories = {aid: acc_mock}
|
||||||
homekit.status = STATUS_RUNNING
|
homekit.status = STATUS_RUNNING
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -661,6 +668,259 @@ async def test_homekit_reset_accessories(hass, mock_zeroconf):
|
|||||||
homekit.status = STATUS_READY
|
homekit.status = STATUS_READY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_homekit_reset_accessories_not_supported(hass, mock_zeroconf):
|
||||||
|
"""Test resetting HomeKit accessories with an unsupported entity."""
|
||||||
|
await async_setup_component(hass, "persistent_notification", {})
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
|
||||||
|
)
|
||||||
|
entity_id = "not_supported.demo"
|
||||||
|
hass.states.async_set("not_supported.demo", "on")
|
||||||
|
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
|
||||||
|
|
||||||
|
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), 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"
|
||||||
|
), patch.object(
|
||||||
|
homekit_base, "_HOMEKIT_CONFIG_UPDATE_TIME", 0
|
||||||
|
):
|
||||||
|
await async_init_entry(hass, entry)
|
||||||
|
|
||||||
|
acc_mock = MagicMock()
|
||||||
|
acc_mock.entity_id = entity_id
|
||||||
|
aid = homekit.aid_storage.get_or_allocate_aid_for_entity_id(entity_id)
|
||||||
|
homekit.bridge.accessories = {aid: acc_mock}
|
||||||
|
homekit.status = STATUS_RUNNING
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_HOMEKIT_RESET_ACCESSORY,
|
||||||
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert hk_driver_config_changed.call_count == 2
|
||||||
|
assert not mock_add_accessory.called
|
||||||
|
assert len(homekit.bridge.accessories) == 0
|
||||||
|
homekit.status = STATUS_STOPPED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_homekit_reset_accessories_state_missing(hass, mock_zeroconf):
|
||||||
|
"""Test resetting HomeKit accessories when the state goes missing."""
|
||||||
|
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)
|
||||||
|
|
||||||
|
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), 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"
|
||||||
|
), patch.object(
|
||||||
|
homekit_base, "_HOMEKIT_CONFIG_UPDATE_TIME", 0
|
||||||
|
):
|
||||||
|
await async_init_entry(hass, entry)
|
||||||
|
|
||||||
|
acc_mock = MagicMock()
|
||||||
|
acc_mock.entity_id = entity_id
|
||||||
|
aid = homekit.aid_storage.get_or_allocate_aid_for_entity_id(entity_id)
|
||||||
|
homekit.bridge.accessories = {aid: acc_mock}
|
||||||
|
homekit.status = STATUS_RUNNING
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_HOMEKIT_RESET_ACCESSORY,
|
||||||
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert hk_driver_config_changed.call_count == 0
|
||||||
|
assert not mock_add_accessory.called
|
||||||
|
homekit.status = STATUS_STOPPED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_homekit_reset_accessories_not_bridged(hass, mock_zeroconf):
|
||||||
|
"""Test resetting HomeKit accessories when the state is not bridged."""
|
||||||
|
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)
|
||||||
|
|
||||||
|
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), 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"
|
||||||
|
), patch.object(
|
||||||
|
homekit_base, "_HOMEKIT_CONFIG_UPDATE_TIME", 0
|
||||||
|
):
|
||||||
|
await async_init_entry(hass, entry)
|
||||||
|
|
||||||
|
acc_mock = MagicMock()
|
||||||
|
acc_mock.entity_id = entity_id
|
||||||
|
aid = homekit.aid_storage.get_or_allocate_aid_for_entity_id(entity_id)
|
||||||
|
homekit.bridge.accessories = {aid: acc_mock}
|
||||||
|
homekit.status = STATUS_RUNNING
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_HOMEKIT_RESET_ACCESSORY,
|
||||||
|
{ATTR_ENTITY_ID: "light.not_bridged"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert hk_driver_config_changed.call_count == 0
|
||||||
|
assert not mock_add_accessory.called
|
||||||
|
homekit.status = STATUS_STOPPED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_homekit_reset_single_accessory(hass, mock_zeroconf):
|
||||||
|
"""Test resetting HomeKit single accessory."""
|
||||||
|
await async_setup_component(hass, "persistent_notification", {})
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
|
||||||
|
)
|
||||||
|
entity_id = "light.demo"
|
||||||
|
hass.states.async_set("light.demo", "on")
|
||||||
|
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_ACCESSORY)
|
||||||
|
|
||||||
|
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), 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)
|
||||||
|
|
||||||
|
homekit.status = STATUS_RUNNING
|
||||||
|
acc_mock = MagicMock()
|
||||||
|
acc_mock.entity_id = entity_id
|
||||||
|
homekit.driver.accessory = acc_mock
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_HOMEKIT_RESET_ACCESSORY,
|
||||||
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert hk_driver_config_changed.call_count == 1
|
||||||
|
homekit.status = STATUS_READY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_homekit_reset_single_accessory_unsupported(hass, mock_zeroconf):
|
||||||
|
"""Test resetting HomeKit single accessory with an unsupported entity."""
|
||||||
|
await async_setup_component(hass, "persistent_notification", {})
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
|
||||||
|
)
|
||||||
|
entity_id = "not_supported.demo"
|
||||||
|
hass.states.async_set("not_supported.demo", "on")
|
||||||
|
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_ACCESSORY)
|
||||||
|
|
||||||
|
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), 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)
|
||||||
|
|
||||||
|
homekit.status = STATUS_RUNNING
|
||||||
|
acc_mock = MagicMock()
|
||||||
|
acc_mock.entity_id = entity_id
|
||||||
|
homekit.driver.accessory = acc_mock
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_HOMEKIT_RESET_ACCESSORY,
|
||||||
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert hk_driver_config_changed.call_count == 0
|
||||||
|
homekit.status = STATUS_STOPPED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_homekit_reset_single_accessory_state_missing(hass, mock_zeroconf):
|
||||||
|
"""Test resetting HomeKit single accessory when the state goes missing."""
|
||||||
|
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_ACCESSORY)
|
||||||
|
|
||||||
|
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), 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)
|
||||||
|
|
||||||
|
homekit.status = STATUS_RUNNING
|
||||||
|
acc_mock = MagicMock()
|
||||||
|
acc_mock.entity_id = entity_id
|
||||||
|
homekit.driver.accessory = acc_mock
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_HOMEKIT_RESET_ACCESSORY,
|
||||||
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert hk_driver_config_changed.call_count == 0
|
||||||
|
homekit.status = STATUS_STOPPED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_homekit_reset_single_accessory_no_match(hass, mock_zeroconf):
|
||||||
|
"""Test resetting HomeKit single accessory when the entity id does not match."""
|
||||||
|
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_ACCESSORY)
|
||||||
|
|
||||||
|
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), 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)
|
||||||
|
|
||||||
|
homekit.status = STATUS_RUNNING
|
||||||
|
acc_mock = MagicMock()
|
||||||
|
acc_mock.entity_id = entity_id
|
||||||
|
homekit.driver.accessory = acc_mock
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_HOMEKIT_RESET_ACCESSORY,
|
||||||
|
{ATTR_ENTITY_ID: "light.no_match"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert hk_driver_config_changed.call_count == 0
|
||||||
|
homekit.status = STATUS_STOPPED
|
||||||
|
|
||||||
|
|
||||||
async def test_homekit_too_many_accessories(hass, hk_driver, caplog, mock_zeroconf):
|
async def test_homekit_too_many_accessories(hass, hk_driver, caplog, mock_zeroconf):
|
||||||
"""Test adding too many accessories to HomeKit."""
|
"""Test adding too many accessories to HomeKit."""
|
||||||
entry = await async_init_integration(hass)
|
entry = await async_init_integration(hass)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user