Refactor homekit_controller to be fully asynchronous (#32111)

* Port homekit_controller to aiohomekit

* Remove succeed() test helper

* Remove fail() test helper
This commit is contained in:
Jc2k 2020-02-24 09:55:33 +00:00 committed by GitHub
parent a1a835cf54
commit df9363610c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 560 additions and 583 deletions

View file

@ -4,14 +4,10 @@ import json
import os
from unittest import mock
from homekit.exceptions import AccessoryNotFoundError
from homekit.model import Accessory, get_id
from homekit.model.characteristics import (
AbstractCharacteristic,
CharacteristicPermissions,
CharacteristicsTypes,
)
from homekit.model.services import AbstractService, ServicesTypes
from aiohomekit.exceptions import AccessoryNotFoundError
from aiohomekit.model import Accessory
from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes
from homeassistant import config_entries
from homeassistant.components.homekit_controller import config_flow
@ -40,14 +36,14 @@ class FakePairing:
self.pairing_data = {}
self.available = True
def list_accessories_and_characteristics(self):
async def list_accessories_and_characteristics(self):
"""Fake implementation of list_accessories_and_characteristics."""
accessories = [a.to_accessory_and_service_list() for a in self.accessories]
# replicate what happens upstream right now
self.pairing_data["accessories"] = accessories
return accessories
def get_characteristics(self, characteristics):
async def get_characteristics(self, characteristics):
"""Fake implementation of get_characteristics."""
if not self.available:
raise AccessoryNotFoundError("Accessory not found")
@ -64,7 +60,7 @@ class FakePairing:
results[(aid, cid)] = {"value": char.get_value()}
return results
def put_characteristics(self, characteristics):
async def put_characteristics(self, characteristics):
"""Fake implementation of put_characteristics."""
for aid, cid, new_val in characteristics:
for accessory in self.accessories:
@ -124,45 +120,6 @@ class Helper:
return state
class FakeCharacteristic(AbstractCharacteristic):
"""
A model of a generic HomeKit characteristic.
Base is abstract and can't be instanced directly so this subclass is
needed even though it doesn't add any methods.
"""
def to_accessory_and_service_list(self):
"""Serialize the characteristic."""
# Upstream doesn't correctly serialize valid_values
# This fix will be upstreamed and this function removed when it
# is fixed.
record = super().to_accessory_and_service_list()
if self.valid_values:
record["valid-values"] = self.valid_values
return record
class FakeService(AbstractService):
"""A model of a generic HomeKit service."""
def __init__(self, service_name):
"""Create a fake service by its short form HAP spec name."""
char_type = ServicesTypes.get_uuid(service_name)
super().__init__(char_type, get_id())
def add_characteristic(self, name):
"""Add a characteristic to this service by name."""
full_name = "public.hap.characteristic." + name
char = FakeCharacteristic(get_id(), full_name, None)
char.perms = [
CharacteristicPermissions.paired_read,
CharacteristicPermissions.paired_write,
]
self.characteristics.append(char)
return char
async def time_changed(hass, seconds):
"""Trigger time changed."""
next_update = dt_util.utcnow() + timedelta(seconds)
@ -176,40 +133,7 @@ async def setup_accessories_from_file(hass, path):
load_fixture, os.path.join("homekit_controller", path)
)
accessories_json = json.loads(accessories_fixture)
accessories = []
for accessory_data in accessories_json:
accessory = Accessory("Name", "Mfr", "Model", "0001", "0.1")
accessory.services = []
accessory.aid = accessory_data["aid"]
for service_data in accessory_data["services"]:
service = FakeService("public.hap.service.accessory-information")
service.type = service_data["type"]
service.iid = service_data["iid"]
for char_data in service_data["characteristics"]:
char = FakeCharacteristic(1, "23", None)
char.type = char_data["type"]
char.iid = char_data["iid"]
char.perms = char_data["perms"]
char.format = char_data["format"]
if "description" in char_data:
char.description = char_data["description"]
if "value" in char_data:
char.value = char_data["value"]
if "minValue" in char_data:
char.minValue = char_data["minValue"]
if "maxValue" in char_data:
char.maxValue = char_data["maxValue"]
if "valid-values" in char_data:
char.valid_values = char_data["valid-values"]
service.characteristics.append(char)
accessory.services.append(service)
accessories.append(accessory)
accessories = Accessory.setup_accessories_from_list(accessories_json)
return accessories
@ -217,7 +141,7 @@ async def setup_platform(hass):
"""Load the platform but with a fake Controller API."""
config = {"discovery": {}}
with mock.patch("homekit.Controller") as controller:
with mock.patch("aiohomekit.Controller") as controller:
fake_controller = controller.return_value = FakeController()
await async_setup_component(hass, DOMAIN, config)
@ -293,15 +217,18 @@ async def device_config_changed(hass, accessories):
await hass.async_block_till_done()
async def setup_test_component(hass, services, capitalize=False, suffix=None):
async def setup_test_component(hass, setup_accessory, capitalize=False, suffix=None):
"""Load a fake homekit accessory based on a homekit accessory model.
If capitalize is True, property names will be in upper case.
If suffix is set, entityId will include the suffix
"""
accessory = Accessory("TestDevice", "example.com", "Test", "0001", "0.1")
setup_accessory(accessory)
domain = None
for service in services:
for service in accessory.services:
service_name = ServicesTypes.get_short(service.type)
if service_name in HOMEKIT_ACCESSORY_DISPATCH:
domain = HOMEKIT_ACCESSORY_DISPATCH[service_name]
@ -309,9 +236,6 @@ async def setup_test_component(hass, services, capitalize=False, suffix=None):
assert domain, "Cannot map test homekit services to Home Assistant domain"
accessory = Accessory("TestDevice", "example.com", "Test", "0001", "0.1")
accessory.services.extend(services)
config_entry, pairing = await setup_test_accessories(hass, [accessory])
entity = "testdevice" if suffix is None else "testdevice_{}".format(suffix)
return Helper(hass, ".".join((domain, entity)), pairing, accessory, config_entry)