Prepare for new aiohomekit lifecycle API (#66340)
This commit is contained in:
parent
2f220b27d4
commit
0daf20c0cc
11 changed files with 79 additions and 28 deletions
|
@ -94,6 +94,7 @@ homeassistant.components.homekit_controller.const
|
||||||
homeassistant.components.homekit_controller.lock
|
homeassistant.components.homekit_controller.lock
|
||||||
homeassistant.components.homekit_controller.select
|
homeassistant.components.homekit_controller.select
|
||||||
homeassistant.components.homekit_controller.storage
|
homeassistant.components.homekit_controller.storage
|
||||||
|
homeassistant.components.homekit_controller.utils
|
||||||
homeassistant.components.homewizard.*
|
homeassistant.components.homewizard.*
|
||||||
homeassistant.components.http.*
|
homeassistant.components.http.*
|
||||||
homeassistant.components.huawei_lte.*
|
homeassistant.components.huawei_lte.*
|
||||||
|
|
|
@ -14,7 +14,6 @@ from aiohomekit.model.characteristics import (
|
||||||
)
|
)
|
||||||
from aiohomekit.model.services import Service, ServicesTypes
|
from aiohomekit.model.services import Service, ServicesTypes
|
||||||
|
|
||||||
from homeassistant.components import zeroconf
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import Event, HomeAssistant
|
from homeassistant.core import Event, HomeAssistant
|
||||||
|
@ -24,8 +23,9 @@ from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from .config_flow import normalize_hkid
|
from .config_flow import normalize_hkid
|
||||||
from .connection import HKDevice, valid_serial_number
|
from .connection import HKDevice, valid_serial_number
|
||||||
from .const import CONTROLLER, ENTITY_MAP, KNOWN_DEVICES, TRIGGERS
|
from .const import ENTITY_MAP, KNOWN_DEVICES, TRIGGERS
|
||||||
from .storage import EntityMapStorage
|
from .storage import EntityMapStorage
|
||||||
|
from .utils import async_get_controller
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -208,10 +208,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
map_storage = hass.data[ENTITY_MAP] = EntityMapStorage(hass)
|
map_storage = hass.data[ENTITY_MAP] = EntityMapStorage(hass)
|
||||||
await map_storage.async_initialize()
|
await map_storage.async_initialize()
|
||||||
|
|
||||||
async_zeroconf_instance = await zeroconf.async_get_async_instance(hass)
|
await async_get_controller(hass)
|
||||||
hass.data[CONTROLLER] = aiohomekit.Controller(
|
|
||||||
async_zeroconf_instance=async_zeroconf_instance
|
|
||||||
)
|
|
||||||
hass.data[KNOWN_DEVICES] = {}
|
hass.data[KNOWN_DEVICES] = {}
|
||||||
hass.data[TRIGGERS] = {}
|
hass.data[TRIGGERS] = {}
|
||||||
|
|
||||||
|
@ -246,10 +244,10 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
# Remove cached type data from .storage/homekit_controller-entity-map
|
# Remove cached type data from .storage/homekit_controller-entity-map
|
||||||
hass.data[ENTITY_MAP].async_delete_map(hkid)
|
hass.data[ENTITY_MAP].async_delete_map(hkid)
|
||||||
|
|
||||||
|
controller = await async_get_controller(hass)
|
||||||
|
|
||||||
# Remove the pairing on the device, making the device discoverable again.
|
# Remove the pairing on the device, making the device discoverable again.
|
||||||
# Don't reuse any objects in hass.data as they are already unloaded
|
# Don't reuse any objects in hass.data as they are already unloaded
|
||||||
async_zeroconf_instance = await zeroconf.async_get_async_instance(hass)
|
|
||||||
controller = aiohomekit.Controller(async_zeroconf_instance=async_zeroconf_instance)
|
|
||||||
controller.load_pairing(hkid, dict(entry.data))
|
controller.load_pairing(hkid, dict(entry.data))
|
||||||
try:
|
try:
|
||||||
await controller.remove_pairing(hkid)
|
await controller.remove_pairing(hkid)
|
||||||
|
|
|
@ -20,6 +20,7 @@ from homeassistant.helpers.device_registry import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from .const import DOMAIN, KNOWN_DEVICES
|
from .const import DOMAIN, KNOWN_DEVICES
|
||||||
|
from .utils import async_get_controller
|
||||||
|
|
||||||
HOMEKIT_DIR = ".homekit"
|
HOMEKIT_DIR = ".homekit"
|
||||||
HOMEKIT_BRIDGE_DOMAIN = "homekit"
|
HOMEKIT_BRIDGE_DOMAIN = "homekit"
|
||||||
|
@ -104,10 +105,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
|
||||||
async def _async_setup_controller(self):
|
async def _async_setup_controller(self):
|
||||||
"""Create the controller."""
|
"""Create the controller."""
|
||||||
async_zeroconf_instance = await zeroconf.async_get_async_instance(self.hass)
|
self.controller = await async_get_controller(self.hass)
|
||||||
self.controller = aiohomekit.Controller(
|
|
||||||
async_zeroconf_instance=async_zeroconf_instance
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_step_user(self, user_input=None):
|
async def async_step_user(self, user_input=None):
|
||||||
"""Handle a flow start."""
|
"""Handle a flow start."""
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "HomeKit Controller",
|
"name": "HomeKit Controller",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
||||||
"requirements": ["aiohomekit==0.7.5"],
|
"requirements": ["aiohomekit==0.7.7"],
|
||||||
"zeroconf": ["_hap._tcp.local."],
|
"zeroconf": ["_hap._tcp.local."],
|
||||||
"after_dependencies": ["zeroconf"],
|
"after_dependencies": ["zeroconf"],
|
||||||
"codeowners": ["@Jc2k", "@bdraco"],
|
"codeowners": ["@Jc2k", "@bdraco"],
|
||||||
|
|
42
homeassistant/components/homekit_controller/utils.py
Normal file
42
homeassistant/components/homekit_controller/utils.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
"""Helper functions for the homekit_controller component."""
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
|
from aiohomekit import Controller
|
||||||
|
|
||||||
|
from homeassistant.components import zeroconf
|
||||||
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
|
from homeassistant.core import Event, HomeAssistant
|
||||||
|
|
||||||
|
from .const import CONTROLLER
|
||||||
|
|
||||||
|
|
||||||
|
async def async_get_controller(hass: HomeAssistant) -> Controller:
|
||||||
|
"""Get or create an aiohomekit Controller instance."""
|
||||||
|
if existing := hass.data.get(CONTROLLER):
|
||||||
|
return cast(Controller, existing)
|
||||||
|
|
||||||
|
async_zeroconf_instance = await zeroconf.async_get_async_instance(hass)
|
||||||
|
|
||||||
|
# In theory another call to async_get_controller could have run while we were
|
||||||
|
# trying to get the zeroconf instance. So we check again to make sure we
|
||||||
|
# don't leak a Controller instance here.
|
||||||
|
if existing := hass.data.get(CONTROLLER):
|
||||||
|
return cast(Controller, existing)
|
||||||
|
|
||||||
|
controller = Controller(async_zeroconf_instance=async_zeroconf_instance)
|
||||||
|
|
||||||
|
hass.data[CONTROLLER] = controller
|
||||||
|
|
||||||
|
async def _async_stop_homekit_controller(event: Event) -> None:
|
||||||
|
# Pop first so that in theory another controller /could/ start
|
||||||
|
# While this one was shutting down
|
||||||
|
hass.data.pop(CONTROLLER, None)
|
||||||
|
await controller.async_stop()
|
||||||
|
|
||||||
|
# Right now _async_stop_homekit_controller is only called on HA exiting
|
||||||
|
# So we don't have to worry about leaking a callback here.
|
||||||
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop_homekit_controller)
|
||||||
|
|
||||||
|
await controller.async_start()
|
||||||
|
|
||||||
|
return controller
|
11
mypy.ini
11
mypy.ini
|
@ -851,6 +851,17 @@ no_implicit_optional = true
|
||||||
warn_return_any = true
|
warn_return_any = true
|
||||||
warn_unreachable = true
|
warn_unreachable = true
|
||||||
|
|
||||||
|
[mypy-homeassistant.components.homekit_controller.utils]
|
||||||
|
check_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
disallow_subclassing_any = true
|
||||||
|
disallow_untyped_calls = true
|
||||||
|
disallow_untyped_decorators = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unreachable = true
|
||||||
|
|
||||||
[mypy-homeassistant.components.homewizard.*]
|
[mypy-homeassistant.components.homewizard.*]
|
||||||
check_untyped_defs = true
|
check_untyped_defs = true
|
||||||
disallow_incomplete_defs = true
|
disallow_incomplete_defs = true
|
||||||
|
|
|
@ -184,7 +184,7 @@ aioguardian==2021.11.0
|
||||||
aioharmony==0.2.9
|
aioharmony==0.2.9
|
||||||
|
|
||||||
# homeassistant.components.homekit_controller
|
# homeassistant.components.homekit_controller
|
||||||
aiohomekit==0.7.5
|
aiohomekit==0.7.7
|
||||||
|
|
||||||
# homeassistant.components.emulated_hue
|
# homeassistant.components.emulated_hue
|
||||||
# homeassistant.components.http
|
# homeassistant.components.http
|
||||||
|
|
|
@ -134,7 +134,7 @@ aioguardian==2021.11.0
|
||||||
aioharmony==0.2.9
|
aioharmony==0.2.9
|
||||||
|
|
||||||
# homeassistant.components.homekit_controller
|
# homeassistant.components.homekit_controller
|
||||||
aiohomekit==0.7.5
|
aiohomekit==0.7.7
|
||||||
|
|
||||||
# homeassistant.components.emulated_hue
|
# homeassistant.components.emulated_hue
|
||||||
# homeassistant.components.http
|
# homeassistant.components.http
|
||||||
|
|
|
@ -174,7 +174,9 @@ async def setup_platform(hass):
|
||||||
"""Load the platform but with a fake Controller API."""
|
"""Load the platform but with a fake Controller API."""
|
||||||
config = {"discovery": {}}
|
config = {"discovery": {}}
|
||||||
|
|
||||||
with mock.patch("aiohomekit.Controller") as controller:
|
with mock.patch(
|
||||||
|
"homeassistant.components.homekit_controller.utils.Controller"
|
||||||
|
) as controller:
|
||||||
fake_controller = controller.return_value = FakeController()
|
fake_controller = controller.return_value = FakeController()
|
||||||
await async_setup_component(hass, DOMAIN, config)
|
await async_setup_component(hass, DOMAIN, config)
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,10 @@ def utcnow(request):
|
||||||
def controller(hass):
|
def controller(hass):
|
||||||
"""Replace aiohomekit.Controller with an instance of aiohomekit.testing.FakeController."""
|
"""Replace aiohomekit.Controller with an instance of aiohomekit.testing.FakeController."""
|
||||||
instance = FakeController()
|
instance = FakeController()
|
||||||
with unittest.mock.patch("aiohomekit.Controller", return_value=instance):
|
with unittest.mock.patch(
|
||||||
|
"homeassistant.components.homekit_controller.utils.Controller",
|
||||||
|
return_value=instance,
|
||||||
|
):
|
||||||
yield instance
|
yield instance
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ from unittest.mock import patch
|
||||||
|
|
||||||
from aiohomekit.model.characteristics import CharacteristicsTypes
|
from aiohomekit.model.characteristics import CharacteristicsTypes
|
||||||
from aiohomekit.model.services import ServicesTypes
|
from aiohomekit.model.services import ServicesTypes
|
||||||
from aiohomekit.testing import FakeController
|
|
||||||
|
|
||||||
from homeassistant.components.homekit_controller.const import ENTITY_MAP
|
from homeassistant.components.homekit_controller.const import ENTITY_MAP
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
|
@ -35,19 +34,16 @@ async def test_unload_on_stop(hass, utcnow):
|
||||||
async def test_async_remove_entry(hass: HomeAssistant):
|
async def test_async_remove_entry(hass: HomeAssistant):
|
||||||
"""Test unpairing a component."""
|
"""Test unpairing a component."""
|
||||||
helper = await setup_test_component(hass, create_motion_sensor_service)
|
helper = await setup_test_component(hass, create_motion_sensor_service)
|
||||||
|
controller = helper.pairing.controller
|
||||||
|
|
||||||
hkid = "00:00:00:00:00:00"
|
hkid = "00:00:00:00:00:00"
|
||||||
|
|
||||||
with patch("aiohomekit.Controller") as controller_cls:
|
assert len(controller.pairings) == 1
|
||||||
# Setup a fake controller with 1 pairing
|
|
||||||
controller = controller_cls.return_value = FakeController()
|
|
||||||
await controller.add_paired_device([helper.accessory], hkid)
|
|
||||||
assert len(controller.pairings) == 1
|
|
||||||
|
|
||||||
assert hkid in hass.data[ENTITY_MAP].storage_data
|
assert hkid in hass.data[ENTITY_MAP].storage_data
|
||||||
|
|
||||||
# Remove it via config entry and number of pairings should go down
|
# Remove it via config entry and number of pairings should go down
|
||||||
await helper.config_entry.async_remove(hass)
|
await helper.config_entry.async_remove(hass)
|
||||||
assert len(controller.pairings) == 0
|
assert len(controller.pairings) == 0
|
||||||
|
|
||||||
assert hkid not in hass.data[ENTITY_MAP].storage_data
|
assert hkid not in hass.data[ENTITY_MAP].storage_data
|
||||||
|
|
Loading…
Add table
Reference in a new issue