Refactor HomeKit to allow supported features/device class to change (#101719)
This commit is contained in:
parent
f166e1cc1a
commit
7b4b8e7516
18 changed files with 662 additions and 452 deletions
|
@ -36,7 +36,13 @@ from homeassistant.components.homekit.const import (
|
|||
)
|
||||
from homeassistant.components.homekit.type_triggers import DeviceTriggerAccessory
|
||||
from homeassistant.components.homekit.util import get_persist_fullpath_for_entry_id
|
||||
from homeassistant.components.light import (
|
||||
ATTR_COLOR_MODE,
|
||||
ATTR_SUPPORTED_COLOR_MODES,
|
||||
ColorMode,
|
||||
)
|
||||
from homeassistant.components.sensor import SensorDeviceClass
|
||||
from homeassistant.components.switch import SwitchDeviceClass
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_ZEROCONF
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
|
@ -532,7 +538,7 @@ async def test_homekit_remove_accessory(
|
|||
acc_mock.stop = AsyncMock()
|
||||
homekit.bridge.accessories = {6: acc_mock}
|
||||
|
||||
acc = await homekit.async_remove_bridge_accessory(6)
|
||||
acc = homekit.async_remove_bridge_accessory(6)
|
||||
assert acc is acc_mock
|
||||
assert len(homekit.bridge.accessories) == 0
|
||||
|
||||
|
@ -876,6 +882,7 @@ async def test_homekit_stop(hass: HomeAssistant) -> None:
|
|||
|
||||
# Test if driver is started
|
||||
homekit.status = STATUS_RUNNING
|
||||
homekit._cancel_reload_dispatcher = lambda: None
|
||||
await homekit.async_stop()
|
||||
await hass.async_block_till_done()
|
||||
assert homekit.driver.async_stop.called is True
|
||||
|
@ -919,6 +926,120 @@ async def test_homekit_reset_accessories(
|
|||
await homekit.async_stop()
|
||||
|
||||
|
||||
async def test_homekit_reload_accessory_can_change_class(
|
||||
hass: HomeAssistant, mock_async_zeroconf: None, mock_hap
|
||||
) -> None:
|
||||
"""Test reloading a HomeKit Accessory in brdige mode.
|
||||
|
||||
This test ensure when device class changes the HomeKit class changes.
|
||||
"""
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
|
||||
)
|
||||
entity_id = "switch.outlet"
|
||||
hass.states.async_set(entity_id, "on", {ATTR_DEVICE_CLASS: None})
|
||||
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
|
||||
|
||||
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit):
|
||||
await async_init_entry(hass, entry)
|
||||
bridge: HomeBridge = homekit.driver.accessory
|
||||
await bridge.run()
|
||||
switch_accessory = next(iter(bridge.accessories.values()))
|
||||
assert type(switch_accessory).__name__ == "Switch"
|
||||
await hass.async_block_till_done()
|
||||
assert homekit.status == STATUS_RUNNING
|
||||
homekit.driver.aio_stop_event = MagicMock()
|
||||
hass.states.async_set(
|
||||
entity_id, "off", {ATTR_DEVICE_CLASS: SwitchDeviceClass.OUTLET}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
outlet_accessory = next(iter(bridge.accessories.values()))
|
||||
assert type(outlet_accessory).__name__ == "Outlet"
|
||||
|
||||
await homekit.async_stop()
|
||||
|
||||
|
||||
async def test_homekit_reload_accessory_in_accessory_mode(
|
||||
hass: HomeAssistant, mock_async_zeroconf: None, mock_hap
|
||||
) -> None:
|
||||
"""Test reloading a HomeKit Accessory in accessory mode.
|
||||
|
||||
This test ensure a device class changes can change the class of
|
||||
the accessory.
|
||||
"""
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
|
||||
)
|
||||
entity_id = "switch.outlet"
|
||||
hass.states.async_set(entity_id, "on", {ATTR_DEVICE_CLASS: None})
|
||||
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_ACCESSORY)
|
||||
|
||||
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit):
|
||||
await async_init_entry(hass, entry)
|
||||
primary_accessory = homekit.driver.accessory
|
||||
await primary_accessory.run()
|
||||
assert type(primary_accessory).__name__ == "Switch"
|
||||
await hass.async_block_till_done()
|
||||
assert homekit.status == STATUS_RUNNING
|
||||
homekit.driver.aio_stop_event = MagicMock()
|
||||
hass.states.async_set(
|
||||
entity_id, "off", {ATTR_DEVICE_CLASS: SwitchDeviceClass.OUTLET}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
primary_accessory = homekit.driver.accessory
|
||||
assert type(primary_accessory).__name__ == "Outlet"
|
||||
|
||||
await homekit.async_stop()
|
||||
|
||||
|
||||
async def test_homekit_reload_accessory_same_class(
|
||||
hass: HomeAssistant, mock_async_zeroconf: None, mock_hap
|
||||
) -> None:
|
||||
"""Test reloading a HomeKit Accessory in bridge mode.
|
||||
|
||||
The class of the accessory remains the same.
|
||||
"""
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
|
||||
)
|
||||
entity_id = "light.color"
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
"on",
|
||||
{ATTR_SUPPORTED_COLOR_MODES: [ColorMode.HS], ATTR_COLOR_MODE: ColorMode.HS},
|
||||
)
|
||||
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
|
||||
|
||||
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit):
|
||||
await async_init_entry(hass, entry)
|
||||
bridge: HomeBridge = homekit.driver.accessory
|
||||
await bridge.run()
|
||||
light_accessory_color = next(iter(bridge.accessories.values()))
|
||||
assert not hasattr(light_accessory_color, "char_color_temp")
|
||||
await hass.async_block_till_done()
|
||||
assert homekit.status == STATUS_RUNNING
|
||||
homekit.driver.aio_stop_event = MagicMock()
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
"on",
|
||||
{
|
||||
ATTR_SUPPORTED_COLOR_MODES: [ColorMode.HS, ColorMode.COLOR_TEMP],
|
||||
ATTR_COLOR_MODE: ColorMode.COLOR_TEMP,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
light_accessory_color_and_temp = next(iter(bridge.accessories.values()))
|
||||
assert hasattr(light_accessory_color_and_temp, "char_color_temp")
|
||||
|
||||
await homekit.async_stop()
|
||||
|
||||
|
||||
async def test_homekit_unpair(
|
||||
hass: HomeAssistant, device_registry: dr.DeviceRegistry, mock_async_zeroconf: None
|
||||
) -> None:
|
||||
|
@ -1076,8 +1197,8 @@ async def test_homekit_reset_accessories_not_supported(
|
|||
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_update_advertisement"
|
||||
) as hk_driver_async_update_advertisement, patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.async_start"
|
||||
), patch.object(
|
||||
homekit_base, "_HOMEKIT_CONFIG_UPDATE_TIME", 0
|
||||
|
@ -1101,7 +1222,7 @@ async def test_homekit_reset_accessories_not_supported(
|
|||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hk_driver_config_changed.call_count == 2
|
||||
assert hk_driver_async_update_advertisement.call_count == 1
|
||||
assert not mock_add_accessory.called
|
||||
assert len(homekit.bridge.accessories) == 0
|
||||
homekit.status = STATUS_STOPPED
|
||||
|
@ -1165,22 +1286,25 @@ async def test_homekit_reset_accessories_not_bridged(
|
|||
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_update_advertisement"
|
||||
) as hk_driver_async_update_advertisement, patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.async_start"
|
||||
), patch.object(
|
||||
homekit_base, "_HOMEKIT_CONFIG_UPDATE_TIME", 0
|
||||
):
|
||||
await async_init_entry(hass, entry)
|
||||
|
||||
assert hk_driver_async_update_advertisement.call_count == 0
|
||||
acc_mock = MagicMock()
|
||||
acc_mock.entity_id = entity_id
|
||||
acc_mock.stop = AsyncMock()
|
||||
acc_mock.to_HAP = lambda: {}
|
||||
|
||||
aid = homekit.aid_storage.get_or_allocate_aid_for_entity_id(entity_id)
|
||||
homekit.bridge.accessories = {aid: acc_mock}
|
||||
homekit.status = STATUS_RUNNING
|
||||
homekit.driver.aio_stop_event = MagicMock()
|
||||
assert hk_driver_async_update_advertisement.call_count == 0
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
|
@ -1190,7 +1314,7 @@ async def test_homekit_reset_accessories_not_bridged(
|
|||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hk_driver_config_changed.call_count == 0
|
||||
assert hk_driver_async_update_advertisement.call_count == 0
|
||||
assert not mock_add_accessory.called
|
||||
homekit.status = STATUS_STOPPED
|
||||
|
||||
|
@ -1208,8 +1332,8 @@ async def test_homekit_reset_single_accessory(
|
|||
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_update_advertisement"
|
||||
) as hk_driver_async_update_advertisement, patch(
|
||||
"pyhap.accessory_driver.AccessoryDriver.async_start"
|
||||
), patch(
|
||||
f"{PATH_HOMEKIT}.accessories.HomeAccessory.run"
|
||||
|
@ -1226,7 +1350,7 @@ async def test_homekit_reset_single_accessory(
|
|||
)
|
||||
await hass.async_block_till_done()
|
||||
assert mock_run.called
|
||||
assert hk_driver_config_changed.call_count == 1
|
||||
assert hk_driver_async_update_advertisement.call_count == 1
|
||||
homekit.status = STATUS_READY
|
||||
await homekit.async_stop()
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue