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

@ -1,8 +1,8 @@
"""Support for Homekit device discovery.""" """Support for Homekit device discovery."""
import logging import logging
import homekit import aiohomekit
from homekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import CharacteristicsTypes
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
@ -94,7 +94,8 @@ class HomeKitEntity(Entity):
def _setup_characteristic(self, char): def _setup_characteristic(self, char):
"""Configure an entity based on a HomeKit characteristics metadata.""" """Configure an entity based on a HomeKit characteristics metadata."""
# Build up a list of (aid, iid) tuples to poll on update() # Build up a list of (aid, iid) tuples to poll on update()
self.pollable_characteristics.append((self._aid, char["iid"])) if "pr" in char["perms"]:
self.pollable_characteristics.append((self._aid, char["iid"]))
# Build a map of ctype -> iid # Build a map of ctype -> iid
short_name = CharacteristicsTypes.get_short(char["type"]) short_name = CharacteristicsTypes.get_short(char["type"])
@ -223,7 +224,7 @@ async def async_setup(hass, config):
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()
hass.data[CONTROLLER] = homekit.Controller() hass.data[CONTROLLER] = aiohomekit.Controller()
hass.data[KNOWN_DEVICES] = {} hass.data[KNOWN_DEVICES] = {}
return True return True

View file

@ -1,5 +1,5 @@
"""Support for HomeKit Controller air quality sensors.""" """Support for HomeKit Controller air quality sensors."""
from homekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import CharacteristicsTypes
from homeassistant.components.air_quality import AirQualityEntity from homeassistant.components.air_quality import AirQualityEntity
from homeassistant.core import callback from homeassistant.core import callback

View file

@ -1,7 +1,7 @@
"""Support for Homekit Alarm Control Panel.""" """Support for Homekit Alarm Control Panel."""
import logging import logging
from homekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import CharacteristicsTypes
from homeassistant.components.alarm_control_panel import AlarmControlPanel from homeassistant.components.alarm_control_panel import AlarmControlPanel
from homeassistant.components.alarm_control_panel.const import ( from homeassistant.components.alarm_control_panel.const import (

View file

@ -1,7 +1,7 @@
"""Support for Homekit motion sensors.""" """Support for Homekit motion sensors."""
import logging import logging
from homekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import CharacteristicsTypes
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
DEVICE_CLASS_SMOKE, DEVICE_CLASS_SMOKE,

View file

@ -1,7 +1,7 @@
"""Support for Homekit climate devices.""" """Support for Homekit climate devices."""
import logging import logging
from homekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import CharacteristicsTypes
from homeassistant.components.climate import ( from homeassistant.components.climate import (
DEFAULT_MAX_HUMIDITY, DEFAULT_MAX_HUMIDITY,

View file

@ -4,8 +4,9 @@ import logging
import os import os
import re import re
import homekit import aiohomekit
from homekit.controller.ip_implementation import IpPairing from aiohomekit import Controller
from aiohomekit.controller.ip import IpPairing
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
@ -72,7 +73,7 @@ def ensure_pin_format(pin):
""" """
match = PIN_FORMAT.search(pin) match = PIN_FORMAT.search(pin)
if not match: if not match:
raise homekit.exceptions.MalformedPinError(f"Invalid PIN code f{pin}") raise aiohomekit.exceptions.MalformedPinError(f"Invalid PIN code f{pin}")
return "{}-{}-{}".format(*match.groups()) return "{}-{}-{}".format(*match.groups())
@ -88,7 +89,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow):
self.model = None self.model = None
self.hkid = None self.hkid = None
self.devices = {} self.devices = {}
self.controller = homekit.Controller() self.controller = Controller()
self.finish_pairing = None self.finish_pairing = None
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None):
@ -97,22 +98,22 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow):
if user_input is not None: if user_input is not None:
key = user_input["device"] key = user_input["device"]
self.hkid = self.devices[key]["id"] self.hkid = self.devices[key].device_id
self.model = self.devices[key]["md"] self.model = self.devices[key].info["md"]
await self.async_set_unique_id( await self.async_set_unique_id(
normalize_hkid(self.hkid), raise_on_progress=False normalize_hkid(self.hkid), raise_on_progress=False
) )
return await self.async_step_pair() return await self.async_step_pair()
all_hosts = await self.hass.async_add_executor_job(self.controller.discover, 5) all_hosts = await self.controller.discover_ip()
self.devices = {} self.devices = {}
for host in all_hosts: for host in all_hosts:
status_flags = int(host["sf"]) status_flags = int(host.info["sf"])
paired = not status_flags & 0x01 paired = not status_flags & 0x01
if paired: if paired:
continue continue
self.devices[host["name"]] = host self.devices[host.info["name"]] = host
if not self.devices: if not self.devices:
return self.async_abort(reason="no_devices") return self.async_abort(reason="no_devices")
@ -130,10 +131,11 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow):
unique_id = user_input["unique_id"] unique_id = user_input["unique_id"]
await self.async_set_unique_id(unique_id) await self.async_set_unique_id(unique_id)
records = await self.hass.async_add_executor_job(self.controller.discover, 5) devices = await self.controller.discover_ip(5)
for record in records: for device in devices:
if normalize_hkid(record["id"]) != unique_id: if normalize_hkid(device.device_id) != unique_id:
continue continue
record = device.info
return await self.async_step_zeroconf( return await self.async_step_zeroconf(
{ {
"host": record["address"], "host": record["address"],
@ -295,55 +297,49 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow):
code = pair_info["pairing_code"] code = pair_info["pairing_code"]
try: try:
code = ensure_pin_format(code) code = ensure_pin_format(code)
pairing = await self.finish_pairing(code)
await self.hass.async_add_executor_job(self.finish_pairing, code) return await self._entry_from_accessory(pairing)
except aiohomekit.exceptions.MalformedPinError:
pairing = self.controller.pairings.get(self.hkid)
if pairing:
return await self._entry_from_accessory(pairing)
errors["pairing_code"] = "unable_to_pair"
except homekit.exceptions.MalformedPinError:
# Library claimed pin was invalid before even making an API call # Library claimed pin was invalid before even making an API call
errors["pairing_code"] = "authentication_error" errors["pairing_code"] = "authentication_error"
except homekit.AuthenticationError: except aiohomekit.AuthenticationError:
# PairSetup M4 - SRP proof failed # PairSetup M4 - SRP proof failed
# PairSetup M6 - Ed25519 signature verification failed # PairSetup M6 - Ed25519 signature verification failed
# PairVerify M4 - Decryption failed # PairVerify M4 - Decryption failed
# PairVerify M4 - Device not recognised # PairVerify M4 - Device not recognised
# PairVerify M4 - Ed25519 signature verification failed # PairVerify M4 - Ed25519 signature verification failed
errors["pairing_code"] = "authentication_error" errors["pairing_code"] = "authentication_error"
except homekit.UnknownError: except aiohomekit.UnknownError:
# An error occurred on the device whilst performing this # An error occurred on the device whilst performing this
# operation. # operation.
errors["pairing_code"] = "unknown_error" errors["pairing_code"] = "unknown_error"
except homekit.MaxPeersError: except aiohomekit.MaxPeersError:
# The device can't pair with any more accessories. # The device can't pair with any more accessories.
errors["pairing_code"] = "max_peers_error" errors["pairing_code"] = "max_peers_error"
except homekit.AccessoryNotFoundError: except aiohomekit.AccessoryNotFoundError:
# Can no longer find the device on the network # Can no longer find the device on the network
return self.async_abort(reason="accessory_not_found_error") return self.async_abort(reason="accessory_not_found_error")
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception("Pairing attempt failed with an unhandled exception") _LOGGER.exception("Pairing attempt failed with an unhandled exception")
errors["pairing_code"] = "pairing_failed" errors["pairing_code"] = "pairing_failed"
start_pairing = self.controller.start_pairing discovery = await self.controller.find_ip_by_device_id(self.hkid)
try: try:
self.finish_pairing = await self.hass.async_add_executor_job( self.finish_pairing = await discovery.start_pairing(self.hkid)
start_pairing, self.hkid, self.hkid
) except aiohomekit.BusyError:
except homekit.BusyError:
# Already performing a pair setup operation with a different # Already performing a pair setup operation with a different
# controller # controller
errors["pairing_code"] = "busy_error" errors["pairing_code"] = "busy_error"
except homekit.MaxTriesError: except aiohomekit.MaxTriesError:
# The accessory has received more than 100 unsuccessful auth # The accessory has received more than 100 unsuccessful auth
# attempts. # attempts.
errors["pairing_code"] = "max_tries_error" errors["pairing_code"] = "max_tries_error"
except homekit.UnavailableError: except aiohomekit.UnavailableError:
# The accessory is already paired - cannot try to pair again. # The accessory is already paired - cannot try to pair again.
return self.async_abort(reason="already_paired") return self.async_abort(reason="already_paired")
except homekit.AccessoryNotFoundError: except aiohomekit.AccessoryNotFoundError:
# Can no longer find the device on the network # Can no longer find the device on the network
return self.async_abort(reason="accessory_not_found_error") return self.async_abort(reason="accessory_not_found_error")
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
@ -376,9 +372,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow):
# the same time. # the same time.
accessories = pairing_data.pop("accessories", None) accessories = pairing_data.pop("accessories", None)
if not accessories: if not accessories:
accessories = await self.hass.async_add_executor_job( accessories = await pairing.list_accessories_and_characteristics()
pairing.list_accessories_and_characteristics
)
bridge_info = get_bridge_information(accessories) bridge_info = get_bridge_information(accessories)
name = get_accessory_name(bridge_info) name = get_accessory_name(bridge_info)

View file

@ -3,14 +3,14 @@ import asyncio
import datetime import datetime
import logging import logging
from homekit.controller.ip_implementation import IpPairing from aiohomekit.controller.ip import IpPairing
from homekit.exceptions import ( from aiohomekit.exceptions import (
AccessoryDisconnectedError, AccessoryDisconnectedError,
AccessoryNotFoundError, AccessoryNotFoundError,
EncryptionError, EncryptionError,
) )
from homekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import CharacteristicsTypes
from homekit.model.services import ServicesTypes from aiohomekit.model.services import ServicesTypes
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
@ -186,10 +186,7 @@ class HKDevice:
async def async_refresh_entity_map(self, config_num): async def async_refresh_entity_map(self, config_num):
"""Handle setup of a HomeKit accessory.""" """Handle setup of a HomeKit accessory."""
try: try:
async with self.pairing_lock: self.accessories = await self.pairing.list_accessories_and_characteristics()
self.accessories = await self.hass.async_add_executor_job(
self.pairing.list_accessories_and_characteristics
)
except AccessoryDisconnectedError: except AccessoryDisconnectedError:
# If we fail to refresh this data then we will naturally retry # If we fail to refresh this data then we will naturally retry
# later when Bonjour spots c# is still not up to date. # later when Bonjour spots c# is still not up to date.
@ -305,10 +302,7 @@ class HKDevice:
async def get_characteristics(self, *args, **kwargs): async def get_characteristics(self, *args, **kwargs):
"""Read latest state from homekit accessory.""" """Read latest state from homekit accessory."""
async with self.pairing_lock: async with self.pairing_lock:
chars = await self.hass.async_add_executor_job( return await self.pairing.get_characteristics(*args, **kwargs)
self.pairing.get_characteristics, *args, **kwargs
)
return chars
async def put_characteristics(self, characteristics): async def put_characteristics(self, characteristics):
"""Control a HomeKit device state from Home Assistant.""" """Control a HomeKit device state from Home Assistant."""
@ -317,9 +311,7 @@ class HKDevice:
chars.append((row["aid"], row["iid"], row["value"])) chars.append((row["aid"], row["iid"], row["value"]))
async with self.pairing_lock: async with self.pairing_lock:
results = await self.hass.async_add_executor_job( results = await self.pairing.put_characteristics(chars)
self.pairing.put_characteristics, chars
)
# Feed characteristics back into HA and update the current state # Feed characteristics back into HA and update the current state
# results will only contain failures, so anythin in characteristics # results will only contain failures, so anythin in characteristics

View file

@ -1,7 +1,7 @@
"""Support for Homekit covers.""" """Support for Homekit covers."""
import logging import logging
from homekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import CharacteristicsTypes
from homeassistant.components.cover import ( from homeassistant.components.cover import (
ATTR_POSITION, ATTR_POSITION,

View file

@ -1,7 +1,7 @@
"""Support for Homekit fans.""" """Support for Homekit fans."""
import logging import logging
from homekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import CharacteristicsTypes
from homeassistant.components.fan import ( from homeassistant.components.fan import (
DIRECTION_FORWARD, DIRECTION_FORWARD,

View file

@ -1,7 +1,7 @@
"""Support for Homekit lights.""" """Support for Homekit lights."""
import logging import logging
from homekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import CharacteristicsTypes
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,

View file

@ -1,7 +1,7 @@
"""Support for HomeKit Controller locks.""" """Support for HomeKit Controller locks."""
import logging import logging
from homekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import CharacteristicsTypes
from homeassistant.components.lock import LockDevice from homeassistant.components.lock import LockDevice
from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_LOCKED, STATE_UNLOCKED from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_LOCKED, STATE_UNLOCKED

View file

@ -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": ["homekit[IP]==0.15.0"], "requirements": ["aiohomekit[IP]==0.2.10"],
"dependencies": [], "dependencies": [],
"zeroconf": ["_hap._tcp.local."], "zeroconf": ["_hap._tcp.local."],
"codeowners": ["@Jc2k"] "codeowners": ["@Jc2k"]

View file

@ -1,5 +1,5 @@
"""Support for Homekit sensors.""" """Support for Homekit sensors."""
from homekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import CharacteristicsTypes
from homeassistant.const import DEVICE_CLASS_BATTERY, TEMP_CELSIUS from homeassistant.const import DEVICE_CLASS_BATTERY, TEMP_CELSIUS
from homeassistant.core import callback from homeassistant.core import callback

View file

@ -1,7 +1,7 @@
"""Support for Homekit switches.""" """Support for Homekit switches."""
import logging import logging
from homekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import CharacteristicsTypes
from homeassistant.components.switch import SwitchDevice from homeassistant.components.switch import SwitchDevice
from homeassistant.core import callback from homeassistant.core import callback

View file

@ -161,6 +161,9 @@ aioftp==0.12.0
# homeassistant.components.harmony # homeassistant.components.harmony
aioharmony==0.1.13 aioharmony==0.1.13
# homeassistant.components.homekit_controller
aiohomekit[IP]==0.2.10
# homeassistant.components.emulated_hue # homeassistant.components.emulated_hue
# homeassistant.components.http # homeassistant.components.http
aiohttp_cors==0.7.0 aiohttp_cors==0.7.0
@ -688,9 +691,6 @@ home-assistant-frontend==20200220.1
# homeassistant.components.zwave # homeassistant.components.zwave
homeassistant-pyozw==0.1.8 homeassistant-pyozw==0.1.8
# homeassistant.components.homekit_controller
homekit[IP]==0.15.0
# homeassistant.components.homematicip_cloud # homeassistant.components.homematicip_cloud
homematicip==0.10.17 homematicip==0.10.17

View file

@ -61,6 +61,9 @@ aiobotocore==0.11.1
# homeassistant.components.esphome # homeassistant.components.esphome
aioesphomeapi==2.6.1 aioesphomeapi==2.6.1
# homeassistant.components.homekit_controller
aiohomekit[IP]==0.2.10
# homeassistant.components.emulated_hue # homeassistant.components.emulated_hue
# homeassistant.components.http # homeassistant.components.http
aiohttp_cors==0.7.0 aiohttp_cors==0.7.0
@ -259,9 +262,6 @@ home-assistant-frontend==20200220.1
# homeassistant.components.zwave # homeassistant.components.zwave
homeassistant-pyozw==0.1.8 homeassistant-pyozw==0.1.8
# homeassistant.components.homekit_controller
homekit[IP]==0.15.0
# homeassistant.components.homematicip_cloud # homeassistant.components.homematicip_cloud
homematicip==0.10.17 homematicip==0.10.17

View file

@ -4,14 +4,10 @@ import json
import os import os
from unittest import mock from unittest import mock
from homekit.exceptions import AccessoryNotFoundError from aiohomekit.exceptions import AccessoryNotFoundError
from homekit.model import Accessory, get_id from aiohomekit.model import Accessory
from homekit.model.characteristics import ( from aiohomekit.model.characteristics import CharacteristicsTypes
AbstractCharacteristic, from aiohomekit.model.services import ServicesTypes
CharacteristicPermissions,
CharacteristicsTypes,
)
from homekit.model.services import AbstractService, ServicesTypes
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.homekit_controller import config_flow from homeassistant.components.homekit_controller import config_flow
@ -40,14 +36,14 @@ class FakePairing:
self.pairing_data = {} self.pairing_data = {}
self.available = True self.available = True
def list_accessories_and_characteristics(self): async def list_accessories_and_characteristics(self):
"""Fake implementation of list_accessories_and_characteristics.""" """Fake implementation of list_accessories_and_characteristics."""
accessories = [a.to_accessory_and_service_list() for a in self.accessories] accessories = [a.to_accessory_and_service_list() for a in self.accessories]
# replicate what happens upstream right now # replicate what happens upstream right now
self.pairing_data["accessories"] = accessories self.pairing_data["accessories"] = accessories
return accessories return accessories
def get_characteristics(self, characteristics): async def get_characteristics(self, characteristics):
"""Fake implementation of get_characteristics.""" """Fake implementation of get_characteristics."""
if not self.available: if not self.available:
raise AccessoryNotFoundError("Accessory not found") raise AccessoryNotFoundError("Accessory not found")
@ -64,7 +60,7 @@ class FakePairing:
results[(aid, cid)] = {"value": char.get_value()} results[(aid, cid)] = {"value": char.get_value()}
return results return results
def put_characteristics(self, characteristics): async def put_characteristics(self, characteristics):
"""Fake implementation of put_characteristics.""" """Fake implementation of put_characteristics."""
for aid, cid, new_val in characteristics: for aid, cid, new_val in characteristics:
for accessory in self.accessories: for accessory in self.accessories:
@ -124,45 +120,6 @@ class Helper:
return state 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): async def time_changed(hass, seconds):
"""Trigger time changed.""" """Trigger time changed."""
next_update = dt_util.utcnow() + timedelta(seconds) 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) load_fixture, os.path.join("homekit_controller", path)
) )
accessories_json = json.loads(accessories_fixture) accessories_json = json.loads(accessories_fixture)
accessories = Accessory.setup_accessories_from_list(accessories_json)
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)
return accessories return accessories
@ -217,7 +141,7 @@ 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("homekit.Controller") as controller: with mock.patch("aiohomekit.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)
@ -293,15 +217,18 @@ async def device_config_changed(hass, accessories):
await hass.async_block_till_done() 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. """Load a fake homekit accessory based on a homekit accessory model.
If capitalize is True, property names will be in upper case. If capitalize is True, property names will be in upper case.
If suffix is set, entityId will include the suffix If suffix is set, entityId will include the suffix
""" """
accessory = Accessory("TestDevice", "example.com", "Test", "0001", "0.1")
setup_accessory(accessory)
domain = None domain = None
for service in services: for service in accessory.services:
service_name = ServicesTypes.get_short(service.type) service_name = ServicesTypes.get_short(service.type)
if service_name in HOMEKIT_ACCESSORY_DISPATCH: if service_name in HOMEKIT_ACCESSORY_DISPATCH:
domain = HOMEKIT_ACCESSORY_DISPATCH[service_name] 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" 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]) config_entry, pairing = await setup_test_accessories(hass, [accessory])
entity = "testdevice" if suffix is None else "testdevice_{}".format(suffix) entity = "testdevice" if suffix is None else "testdevice_{}".format(suffix)
return Helper(hass, ".".join((domain, entity)), pairing, accessory, config_entry) return Helper(hass, ".".join((domain, entity)), pairing, accessory, config_entry)

View file

@ -6,7 +6,7 @@ https://github.com/home-assistant/home-assistant/issues/15336
from unittest import mock from unittest import mock
from homekit import AccessoryDisconnectedError from aiohomekit import AccessoryDisconnectedError
from homeassistant.components.climate.const import ( from homeassistant.components.climate.const import (
SUPPORT_TARGET_HUMIDITY, SUPPORT_TARGET_HUMIDITY,

View file

@ -3,7 +3,7 @@
from datetime import timedelta from datetime import timedelta
from unittest import mock from unittest import mock
from homekit.exceptions import AccessoryDisconnectedError, EncryptionError from aiohomekit.exceptions import AccessoryDisconnectedError, EncryptionError
import pytest import pytest
from homeassistant.components.light import SUPPORT_BRIGHTNESS, SUPPORT_COLOR from homeassistant.components.light import SUPPORT_BRIGHTNESS, SUPPORT_COLOR

View file

@ -1,39 +1,39 @@
"""Basic checks for HomeKit air quality sensor.""" """Basic checks for HomeKit air quality sensor."""
from tests.components.homekit_controller.common import FakeService, setup_test_component from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes
from tests.components.homekit_controller.common import setup_test_component
def create_air_quality_sensor_service(): def create_air_quality_sensor_service(accessory):
"""Define temperature characteristics.""" """Define temperature characteristics."""
service = FakeService("public.hap.service.sensor.air-quality") service = accessory.add_service(ServicesTypes.AIR_QUALITY_SENSOR)
cur_state = service.add_characteristic("air-quality") cur_state = service.add_char(CharacteristicsTypes.AIR_QUALITY)
cur_state.value = 5 cur_state.value = 5
cur_state = service.add_characteristic("density.ozone") cur_state = service.add_char(CharacteristicsTypes.DENSITY_OZONE)
cur_state.value = 1111 cur_state.value = 1111
cur_state = service.add_characteristic("density.no2") cur_state = service.add_char(CharacteristicsTypes.DENSITY_NO2)
cur_state.value = 2222 cur_state.value = 2222
cur_state = service.add_characteristic("density.so2") cur_state = service.add_char(CharacteristicsTypes.DENSITY_SO2)
cur_state.value = 3333 cur_state.value = 3333
cur_state = service.add_characteristic("density.pm25") cur_state = service.add_char(CharacteristicsTypes.DENSITY_PM25)
cur_state.value = 4444 cur_state.value = 4444
cur_state = service.add_characteristic("density.pm10") cur_state = service.add_char(CharacteristicsTypes.DENSITY_PM10)
cur_state.value = 5555 cur_state.value = 5555
cur_state = service.add_characteristic("density.voc") cur_state = service.add_char(CharacteristicsTypes.DENSITY_VOC)
cur_state.value = 6666 cur_state.value = 6666
return service
async def test_air_quality_sensor_read_state(hass, utcnow): async def test_air_quality_sensor_read_state(hass, utcnow):
"""Test reading the state of a HomeKit temperature sensor accessory.""" """Test reading the state of a HomeKit temperature sensor accessory."""
sensor = create_air_quality_sensor_service() helper = await setup_test_component(hass, create_air_quality_sensor_service)
helper = await setup_test_component(hass, [sensor])
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
assert state.state == "4444" assert state.state == "4444"

View file

@ -1,34 +1,34 @@
"""Basic checks for HomeKitalarm_control_panel.""" """Basic checks for HomeKitalarm_control_panel."""
from tests.components.homekit_controller.common import FakeService, setup_test_component from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes
from tests.components.homekit_controller.common import setup_test_component
CURRENT_STATE = ("security-system", "security-system-state.current") CURRENT_STATE = ("security-system", "security-system-state.current")
TARGET_STATE = ("security-system", "security-system-state.target") TARGET_STATE = ("security-system", "security-system-state.target")
def create_security_system_service(): def create_security_system_service(accessory):
"""Define a security-system characteristics as per page 219 of HAP spec.""" """Define a security-system characteristics as per page 219 of HAP spec."""
service = FakeService("public.hap.service.security-system") service = accessory.add_service(ServicesTypes.SECURITY_SYSTEM)
cur_state = service.add_characteristic("security-system-state.current") cur_state = service.add_char(CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT)
cur_state.value = 0 cur_state.value = 0
targ_state = service.add_characteristic("security-system-state.target") targ_state = service.add_char(CharacteristicsTypes.SECURITY_SYSTEM_STATE_TARGET)
targ_state.value = 0 targ_state.value = 0
# According to the spec, a battery-level characteristic is normally # According to the spec, a battery-level characteristic is normally
# part of a separate service. However as the code was written (which # part of a separate service. However as the code was written (which
# predates this test) the battery level would have to be part of the lock # predates this test) the battery level would have to be part of the lock
# service as it is here. # service as it is here.
targ_state = service.add_characteristic("battery-level") targ_state = service.add_char(CharacteristicsTypes.BATTERY_LEVEL)
targ_state.value = 50 targ_state.value = 50
return service
async def test_switch_change_alarm_state(hass, utcnow): async def test_switch_change_alarm_state(hass, utcnow):
"""Test that we can turn a HomeKit alarm on and off again.""" """Test that we can turn a HomeKit alarm on and off again."""
alarm_control_panel = create_security_system_service() helper = await setup_test_component(hass, create_security_system_service)
helper = await setup_test_component(hass, [alarm_control_panel])
await hass.services.async_call( await hass.services.async_call(
"alarm_control_panel", "alarm_control_panel",
@ -65,8 +65,7 @@ async def test_switch_change_alarm_state(hass, utcnow):
async def test_switch_read_alarm_state(hass, utcnow): async def test_switch_read_alarm_state(hass, utcnow):
"""Test that we can read the state of a HomeKit alarm accessory.""" """Test that we can read the state of a HomeKit alarm accessory."""
alarm_control_panel = create_security_system_service() helper = await setup_test_component(hass, create_security_system_service)
helper = await setup_test_component(hass, [alarm_control_panel])
helper.characteristics[CURRENT_STATE].value = 0 helper.characteristics[CURRENT_STATE].value = 0
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()

View file

@ -1,25 +1,25 @@
"""Basic checks for HomeKit motion sensors and contact sensors.""" """Basic checks for HomeKit motion sensors and contact sensors."""
from tests.components.homekit_controller.common import FakeService, setup_test_component from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes
from tests.components.homekit_controller.common import setup_test_component
MOTION_DETECTED = ("motion", "motion-detected") MOTION_DETECTED = ("motion", "motion-detected")
CONTACT_STATE = ("contact", "contact-state") CONTACT_STATE = ("contact", "contact-state")
SMOKE_DETECTED = ("smoke", "smoke-detected") SMOKE_DETECTED = ("smoke", "smoke-detected")
def create_motion_sensor_service(): def create_motion_sensor_service(accessory):
"""Define motion characteristics as per page 225 of HAP spec.""" """Define motion characteristics as per page 225 of HAP spec."""
service = FakeService("public.hap.service.sensor.motion") service = accessory.add_service(ServicesTypes.MOTION_SENSOR)
cur_state = service.add_characteristic("motion-detected") cur_state = service.add_char(CharacteristicsTypes.MOTION_DETECTED)
cur_state.value = 0 cur_state.value = 0
return service
async def test_motion_sensor_read_state(hass, utcnow): async def test_motion_sensor_read_state(hass, utcnow):
"""Test that we can read the state of a HomeKit motion sensor accessory.""" """Test that we can read the state of a HomeKit motion sensor accessory."""
sensor = create_motion_sensor_service() helper = await setup_test_component(hass, create_motion_sensor_service)
helper = await setup_test_component(hass, [sensor])
helper.characteristics[MOTION_DETECTED].value = False helper.characteristics[MOTION_DETECTED].value = False
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
@ -30,20 +30,17 @@ async def test_motion_sensor_read_state(hass, utcnow):
assert state.state == "on" assert state.state == "on"
def create_contact_sensor_service(): def create_contact_sensor_service(accessory):
"""Define contact characteristics.""" """Define contact characteristics."""
service = FakeService("public.hap.service.sensor.contact") service = accessory.add_service(ServicesTypes.CONTACT_SENSOR)
cur_state = service.add_characteristic("contact-state") cur_state = service.add_char(CharacteristicsTypes.CONTACT_STATE)
cur_state.value = 0 cur_state.value = 0
return service
async def test_contact_sensor_read_state(hass, utcnow): async def test_contact_sensor_read_state(hass, utcnow):
"""Test that we can read the state of a HomeKit contact accessory.""" """Test that we can read the state of a HomeKit contact accessory."""
sensor = create_contact_sensor_service() helper = await setup_test_component(hass, create_contact_sensor_service)
helper = await setup_test_component(hass, [sensor])
helper.characteristics[CONTACT_STATE].value = 0 helper.characteristics[CONTACT_STATE].value = 0
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
@ -54,20 +51,17 @@ async def test_contact_sensor_read_state(hass, utcnow):
assert state.state == "on" assert state.state == "on"
def create_smoke_sensor_service(): def create_smoke_sensor_service(accessory):
"""Define smoke sensor characteristics.""" """Define smoke sensor characteristics."""
service = FakeService("public.hap.service.sensor.smoke") service = accessory.add_service(ServicesTypes.SMOKE_SENSOR)
cur_state = service.add_characteristic("smoke-detected") cur_state = service.add_char(CharacteristicsTypes.SMOKE_DETECTED)
cur_state.value = 0 cur_state.value = 0
return service
async def test_smoke_sensor_read_state(hass, utcnow): async def test_smoke_sensor_read_state(hass, utcnow):
"""Test that we can read the state of a HomeKit contact accessory.""" """Test that we can read the state of a HomeKit contact accessory."""
sensor = create_smoke_sensor_service() helper = await setup_test_component(hass, create_smoke_sensor_service)
helper = await setup_test_component(hass, [sensor])
helper.characteristics[SMOKE_DETECTED].value = 0 helper.characteristics[SMOKE_DETECTED].value = 0
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()

View file

@ -1,4 +1,7 @@
"""Basic checks for HomeKitclimate.""" """Basic checks for HomeKitclimate."""
from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes
from homeassistant.components.climate.const import ( from homeassistant.components.climate.const import (
DOMAIN, DOMAIN,
HVAC_MODE_COOL, HVAC_MODE_COOL,
@ -10,7 +13,7 @@ from homeassistant.components.climate.const import (
SERVICE_SET_TEMPERATURE, SERVICE_SET_TEMPERATURE,
) )
from tests.components.homekit_controller.common import FakeService, setup_test_component from tests.components.homekit_controller.common import setup_test_component
HEATING_COOLING_TARGET = ("thermostat", "heating-cooling.target") HEATING_COOLING_TARGET = ("thermostat", "heating-cooling.target")
HEATING_COOLING_CURRENT = ("thermostat", "heating-cooling.current") HEATING_COOLING_CURRENT = ("thermostat", "heating-cooling.current")
@ -20,63 +23,65 @@ HUMIDITY_TARGET = ("thermostat", "relative-humidity.target")
HUMIDITY_CURRENT = ("thermostat", "relative-humidity.current") HUMIDITY_CURRENT = ("thermostat", "relative-humidity.current")
def create_thermostat_service(): def create_thermostat_service(accessory):
"""Define thermostat characteristics.""" """Define thermostat characteristics."""
service = FakeService("public.hap.service.thermostat") service = accessory.add_service(ServicesTypes.THERMOSTAT)
char = service.add_characteristic("heating-cooling.target") char = service.add_char(CharacteristicsTypes.HEATING_COOLING_TARGET)
char.value = 0 char.value = 0
char = service.add_characteristic("heating-cooling.current") char = service.add_char(CharacteristicsTypes.HEATING_COOLING_CURRENT)
char.value = 0 char.value = 0
char = service.add_characteristic("temperature.target") char = service.add_char(CharacteristicsTypes.TEMPERATURE_TARGET)
char.minValue = 7
char.maxValue = 35
char.value = 0 char.value = 0
char = service.add_characteristic("temperature.current") char = service.add_char(CharacteristicsTypes.TEMPERATURE_CURRENT)
char.value = 0 char.value = 0
char = service.add_characteristic("relative-humidity.target") char = service.add_char(CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET)
char.value = 0 char.value = 0
char = service.add_characteristic("relative-humidity.current") char = service.add_char(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT)
char.value = 0 char.value = 0
return service
def create_thermostat_service_min_max(accessory):
async def test_climate_respect_supported_op_modes_1(hass, utcnow): """Define thermostat characteristics."""
"""Test that climate respects minValue/maxValue hints.""" service = accessory.add_service(ServicesTypes.THERMOSTAT)
service = FakeService("public.hap.service.thermostat") char = service.add_char(CharacteristicsTypes.HEATING_COOLING_TARGET)
char = service.add_characteristic("heating-cooling.target")
char.value = 0 char.value = 0
char.minValue = 0 char.minValue = 0
char.maxValue = 1 char.maxValue = 1
helper = await setup_test_component(hass, [service])
async def test_climate_respect_supported_op_modes_1(hass, utcnow):
"""Test that climate respects minValue/maxValue hints."""
helper = await setup_test_component(hass, create_thermostat_service_min_max)
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
assert state.attributes["hvac_modes"] == ["off", "heat"] assert state.attributes["hvac_modes"] == ["off", "heat"]
async def test_climate_respect_supported_op_modes_2(hass, utcnow): def create_thermostat_service_valid_vals(accessory):
"""Test that climate respects validValue hints.""" """Define thermostat characteristics."""
service = FakeService("public.hap.service.thermostat") service = accessory.add_service(ServicesTypes.THERMOSTAT)
char = service.add_characteristic("heating-cooling.target") char = service.add_char(CharacteristicsTypes.HEATING_COOLING_TARGET)
char.value = 0 char.value = 0
char.valid_values = [0, 1, 2] char.valid_values = [0, 1, 2]
helper = await setup_test_component(hass, [service])
async def test_climate_respect_supported_op_modes_2(hass, utcnow):
"""Test that climate respects validValue hints."""
helper = await setup_test_component(hass, create_thermostat_service_valid_vals)
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
assert state.attributes["hvac_modes"] == ["off", "heat", "cool"] assert state.attributes["hvac_modes"] == ["off", "heat", "cool"]
async def test_climate_change_thermostat_state(hass, utcnow): async def test_climate_change_thermostat_state(hass, utcnow):
"""Test that we can turn a HomeKit thermostat on and off again.""" """Test that we can turn a HomeKit thermostat on and off again."""
from homekit.model.services import ThermostatService helper = await setup_test_component(hass, create_thermostat_service)
helper = await setup_test_component(hass, [ThermostatService()])
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
@ -114,9 +119,7 @@ async def test_climate_change_thermostat_state(hass, utcnow):
async def test_climate_change_thermostat_temperature(hass, utcnow): async def test_climate_change_thermostat_temperature(hass, utcnow):
"""Test that we can turn a HomeKit thermostat on and off again.""" """Test that we can turn a HomeKit thermostat on and off again."""
from homekit.model.services import ThermostatService helper = await setup_test_component(hass, create_thermostat_service)
helper = await setup_test_component(hass, [ThermostatService()])
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
@ -137,7 +140,7 @@ async def test_climate_change_thermostat_temperature(hass, utcnow):
async def test_climate_change_thermostat_humidity(hass, utcnow): async def test_climate_change_thermostat_humidity(hass, utcnow):
"""Test that we can turn a HomeKit thermostat on and off again.""" """Test that we can turn a HomeKit thermostat on and off again."""
helper = await setup_test_component(hass, [create_thermostat_service()]) helper = await setup_test_component(hass, create_thermostat_service)
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
@ -158,7 +161,7 @@ async def test_climate_change_thermostat_humidity(hass, utcnow):
async def test_climate_read_thermostat_state(hass, utcnow): async def test_climate_read_thermostat_state(hass, utcnow):
"""Test that we can read the state of a HomeKit thermostat accessory.""" """Test that we can read the state of a HomeKit thermostat accessory."""
helper = await setup_test_component(hass, [create_thermostat_service()]) helper = await setup_test_component(hass, create_thermostat_service)
# Simulate that heating is on # Simulate that heating is on
helper.characteristics[TEMPERATURE_CURRENT].value = 19 helper.characteristics[TEMPERATURE_CURRENT].value = 19
@ -200,7 +203,7 @@ async def test_climate_read_thermostat_state(hass, utcnow):
async def test_hvac_mode_vs_hvac_action(hass, utcnow): async def test_hvac_mode_vs_hvac_action(hass, utcnow):
"""Check that we haven't conflated hvac_mode and hvac_action.""" """Check that we haven't conflated hvac_mode and hvac_action."""
helper = await setup_test_component(hass, [create_thermostat_service()]) helper = await setup_test_component(hass, create_thermostat_service)
# Simulate that current temperature is above target temp # Simulate that current temperature is above target temp
# Heating might be on, but hvac_action currently 'off' # Heating might be on, but hvac_action currently 'off'

View file

@ -2,40 +2,39 @@
import json import json
from unittest import mock from unittest import mock
import homekit import aiohomekit
from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes
import asynctest
import pytest import pytest
from homeassistant.components.homekit_controller import config_flow from homeassistant.components.homekit_controller import config_flow
from homeassistant.components.homekit_controller.const import KNOWN_DEVICES from homeassistant.components.homekit_controller.const import KNOWN_DEVICES
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.homekit_controller.common import ( from tests.components.homekit_controller.common import Accessory, setup_platform
Accessory,
FakeService,
setup_platform,
)
PAIRING_START_FORM_ERRORS = [ PAIRING_START_FORM_ERRORS = [
(homekit.BusyError, "busy_error"), (aiohomekit.BusyError, "busy_error"),
(homekit.MaxTriesError, "max_tries_error"), (aiohomekit.MaxTriesError, "max_tries_error"),
(KeyError, "pairing_failed"), (KeyError, "pairing_failed"),
] ]
PAIRING_START_ABORT_ERRORS = [ PAIRING_START_ABORT_ERRORS = [
(homekit.AccessoryNotFoundError, "accessory_not_found_error"), (aiohomekit.AccessoryNotFoundError, "accessory_not_found_error"),
(homekit.UnavailableError, "already_paired"), (aiohomekit.UnavailableError, "already_paired"),
] ]
PAIRING_FINISH_FORM_ERRORS = [ PAIRING_FINISH_FORM_ERRORS = [
(homekit.exceptions.MalformedPinError, "authentication_error"), (aiohomekit.exceptions.MalformedPinError, "authentication_error"),
(homekit.MaxPeersError, "max_peers_error"), (aiohomekit.MaxPeersError, "max_peers_error"),
(homekit.AuthenticationError, "authentication_error"), (aiohomekit.AuthenticationError, "authentication_error"),
(homekit.UnknownError, "unknown_error"), (aiohomekit.UnknownError, "unknown_error"),
(KeyError, "pairing_failed"), (KeyError, "pairing_failed"),
] ]
PAIRING_FINISH_ABORT_ERRORS = [ PAIRING_FINISH_ABORT_ERRORS = [
(homekit.AccessoryNotFoundError, "accessory_not_found_error") (aiohomekit.AccessoryNotFoundError, "accessory_not_found_error")
] ]
INVALID_PAIRING_CODES = [ INVALID_PAIRING_CODES = [
@ -60,13 +59,22 @@ VALID_PAIRING_CODES = [
] ]
def _setup_flow_handler(hass): def _setup_flow_handler(hass, pairing=None):
flow = config_flow.HomekitControllerFlowHandler() flow = config_flow.HomekitControllerFlowHandler()
flow.hass = hass flow.hass = hass
flow.context = {} flow.context = {}
finish_pairing = asynctest.CoroutineMock(return_value=pairing)
discovery = mock.Mock()
discovery.device_id = "00:00:00:00:00:00"
discovery.start_pairing = asynctest.CoroutineMock(return_value=finish_pairing)
flow.controller = mock.Mock() flow.controller = mock.Mock()
flow.controller.pairings = {} flow.controller.pairings = {}
flow.controller.find_ip_by_device_id = asynctest.CoroutineMock(
return_value=discovery
)
return flow return flow
@ -81,7 +89,7 @@ async def _setup_flow_zeroconf(hass, discovery_info):
@pytest.mark.parametrize("pairing_code", INVALID_PAIRING_CODES) @pytest.mark.parametrize("pairing_code", INVALID_PAIRING_CODES)
def test_invalid_pairing_codes(pairing_code): def test_invalid_pairing_codes(pairing_code):
"""Test ensure_pin_format raises for an invalid pin code.""" """Test ensure_pin_format raises for an invalid pin code."""
with pytest.raises(homekit.exceptions.MalformedPinError): with pytest.raises(aiohomekit.exceptions.MalformedPinError):
config_flow.ensure_pin_format(pairing_code) config_flow.ensure_pin_format(pairing_code)
@ -106,6 +114,15 @@ async def test_discovery_works(hass):
flow = _setup_flow_handler(hass) flow = _setup_flow_handler(hass)
finish_pairing = asynctest.CoroutineMock()
discovery = mock.Mock()
discovery.start_pairing = asynctest.CoroutineMock(return_value=finish_pairing)
flow.controller.find_ip_by_device_id = asynctest.CoroutineMock(
return_value=discovery
)
# Device is discovered # Device is discovered
result = await flow.async_step_zeroconf(discovery_info) result = await flow.async_step_zeroconf(discovery_info)
assert result["type"] == "form" assert result["type"] == "form"
@ -120,21 +137,27 @@ async def test_discovery_works(hass):
result = await flow.async_step_pair({}) result = await flow.async_step_pair({})
assert result["type"] == "form" assert result["type"] == "form"
assert result["step_id"] == "pair" assert result["step_id"] == "pair"
assert flow.controller.start_pairing.call_count == 1 assert discovery.start_pairing.call_count == 1
pairing = mock.Mock(pairing_data={"AccessoryPairingID": "00:00:00:00:00:00"}) pairing = mock.Mock(pairing_data={"AccessoryPairingID": "00:00:00:00:00:00"})
pairing.list_accessories_and_characteristics.return_value = [ pairing.list_accessories_and_characteristics = asynctest.CoroutineMock(
{ return_value=[
"aid": 1, {
"services": [ "aid": 1,
{ "services": [
"characteristics": [{"type": "23", "value": "Koogeek-LS1-20833F"}], {
"type": "3e", "characteristics": [
} {"type": "23", "value": "Koogeek-LS1-20833F"}
], ],
} "type": "3e",
] }
],
}
]
)
finish_pairing.return_value = pairing
# Pairing doesn't error error and pairing results # Pairing doesn't error error and pairing results
flow.controller.pairings = {"00:00:00:00:00:00": pairing} flow.controller.pairings = {"00:00:00:00:00:00": pairing}
@ -155,6 +178,15 @@ async def test_discovery_works_upper_case(hass):
flow = _setup_flow_handler(hass) flow = _setup_flow_handler(hass)
finish_pairing = asynctest.CoroutineMock()
discovery = mock.Mock()
discovery.start_pairing = asynctest.CoroutineMock(return_value=finish_pairing)
flow.controller.find_ip_by_device_id = asynctest.CoroutineMock(
return_value=discovery
)
# Device is discovered # Device is discovered
result = await flow.async_step_zeroconf(discovery_info) result = await flow.async_step_zeroconf(discovery_info)
assert result["type"] == "form" assert result["type"] == "form"
@ -169,21 +201,27 @@ async def test_discovery_works_upper_case(hass):
result = await flow.async_step_pair({}) result = await flow.async_step_pair({})
assert result["type"] == "form" assert result["type"] == "form"
assert result["step_id"] == "pair" assert result["step_id"] == "pair"
assert flow.controller.start_pairing.call_count == 1 assert discovery.start_pairing.call_count == 1
pairing = mock.Mock(pairing_data={"AccessoryPairingID": "00:00:00:00:00:00"}) pairing = mock.Mock(pairing_data={"AccessoryPairingID": "00:00:00:00:00:00"})
pairing.list_accessories_and_characteristics.return_value = [ pairing.list_accessories_and_characteristics = asynctest.CoroutineMock(
{ return_value=[
"aid": 1, {
"services": [ "aid": 1,
{ "services": [
"characteristics": [{"type": "23", "value": "Koogeek-LS1-20833F"}], {
"type": "3e", "characteristics": [
} {"type": "23", "value": "Koogeek-LS1-20833F"}
], ],
} "type": "3e",
] }
],
}
]
)
finish_pairing.return_value = pairing
flow.controller.pairings = {"00:00:00:00:00:00": pairing} flow.controller.pairings = {"00:00:00:00:00:00": pairing}
result = await flow.async_step_pair({"pairing_code": "111-22-333"}) result = await flow.async_step_pair({"pairing_code": "111-22-333"})
@ -203,6 +241,15 @@ async def test_discovery_works_missing_csharp(hass):
flow = _setup_flow_handler(hass) flow = _setup_flow_handler(hass)
finish_pairing = asynctest.CoroutineMock()
discovery = mock.Mock()
discovery.start_pairing = asynctest.CoroutineMock(return_value=finish_pairing)
flow.controller.find_ip_by_device_id = asynctest.CoroutineMock(
return_value=discovery
)
# Device is discovered # Device is discovered
result = await flow.async_step_zeroconf(discovery_info) result = await flow.async_step_zeroconf(discovery_info)
assert result["type"] == "form" assert result["type"] == "form"
@ -217,21 +264,27 @@ async def test_discovery_works_missing_csharp(hass):
result = await flow.async_step_pair({}) result = await flow.async_step_pair({})
assert result["type"] == "form" assert result["type"] == "form"
assert result["step_id"] == "pair" assert result["step_id"] == "pair"
assert flow.controller.start_pairing.call_count == 1 assert discovery.start_pairing.call_count == 1
pairing = mock.Mock(pairing_data={"AccessoryPairingID": "00:00:00:00:00:00"}) pairing = mock.Mock(pairing_data={"AccessoryPairingID": "00:00:00:00:00:00"})
pairing.list_accessories_and_characteristics.return_value = [ pairing.list_accessories_and_characteristics = asynctest.CoroutineMock(
{ return_value=[
"aid": 1, {
"services": [ "aid": 1,
{ "services": [
"characteristics": [{"type": "23", "value": "Koogeek-LS1-20833F"}], {
"type": "3e", "characteristics": [
} {"type": "23", "value": "Koogeek-LS1-20833F"}
], ],
} "type": "3e",
] }
],
}
]
)
finish_pairing.return_value = pairing
flow.controller.pairings = {"00:00:00:00:00:00": pairing} flow.controller.pairings = {"00:00:00:00:00:00": pairing}
@ -390,39 +443,6 @@ async def test_discovery_already_configured_config_change(hass):
assert conn.async_refresh_entity_map.call_args == mock.call(2) assert conn.async_refresh_entity_map.call_args == mock.call(2)
async def test_pair_unable_to_pair(hass):
"""Pairing completed without exception, but didn't create a pairing."""
discovery_info = {
"name": "TestDevice",
"host": "127.0.0.1",
"port": 8080,
"properties": {"md": "TestDevice", "id": "00:00:00:00:00:00", "c#": 1, "sf": 1},
}
flow = _setup_flow_handler(hass)
# Device is discovered
result = await flow.async_step_zeroconf(discovery_info)
assert result["type"] == "form"
assert result["step_id"] == "pair"
assert flow.context == {
"hkid": "00:00:00:00:00:00",
"title_placeholders": {"name": "TestDevice"},
"unique_id": "00:00:00:00:00:00",
}
# User initiates pairing - device enters pairing mode and displays code
result = await flow.async_step_pair({})
assert result["type"] == "form"
assert result["step_id"] == "pair"
assert flow.controller.start_pairing.call_count == 1
# Pairing doesn't error but no pairing object is generated
result = await flow.async_step_pair({"pairing_code": "111-22-333"})
assert result["type"] == "form"
assert result["errors"]["pairing_code"] == "unable_to_pair"
@pytest.mark.parametrize("exception,expected", PAIRING_START_ABORT_ERRORS) @pytest.mark.parametrize("exception,expected", PAIRING_START_ABORT_ERRORS)
async def test_pair_abort_errors_on_start(hass, exception, expected): async def test_pair_abort_errors_on_start(hass, exception, expected):
"""Test various pairing errors.""" """Test various pairing errors."""
@ -435,6 +455,13 @@ async def test_pair_abort_errors_on_start(hass, exception, expected):
flow = _setup_flow_handler(hass) flow = _setup_flow_handler(hass)
discovery = mock.Mock()
discovery.start_pairing = asynctest.CoroutineMock(side_effect=exception("error"))
flow.controller.find_ip_by_device_id = asynctest.CoroutineMock(
return_value=discovery
)
# Device is discovered # Device is discovered
result = await flow.async_step_zeroconf(discovery_info) result = await flow.async_step_zeroconf(discovery_info)
assert result["type"] == "form" assert result["type"] == "form"
@ -446,10 +473,7 @@ async def test_pair_abort_errors_on_start(hass, exception, expected):
} }
# User initiates pairing - device refuses to enter pairing mode # User initiates pairing - device refuses to enter pairing mode
with mock.patch.object(flow.controller, "start_pairing") as start_pairing: result = await flow.async_step_pair({})
start_pairing.side_effect = exception("error")
result = await flow.async_step_pair({})
assert result["type"] == "abort" assert result["type"] == "abort"
assert result["reason"] == expected assert result["reason"] == expected
assert flow.context == { assert flow.context == {
@ -471,6 +495,13 @@ async def test_pair_form_errors_on_start(hass, exception, expected):
flow = _setup_flow_handler(hass) flow = _setup_flow_handler(hass)
discovery = mock.Mock()
discovery.start_pairing = asynctest.CoroutineMock(side_effect=exception("error"))
flow.controller.find_ip_by_device_id = asynctest.CoroutineMock(
return_value=discovery
)
# Device is discovered # Device is discovered
result = await flow.async_step_zeroconf(discovery_info) result = await flow.async_step_zeroconf(discovery_info)
assert result["type"] == "form" assert result["type"] == "form"
@ -482,10 +513,7 @@ async def test_pair_form_errors_on_start(hass, exception, expected):
} }
# User initiates pairing - device refuses to enter pairing mode # User initiates pairing - device refuses to enter pairing mode
with mock.patch.object(flow.controller, "start_pairing") as start_pairing: result = await flow.async_step_pair({})
start_pairing.side_effect = exception("error")
result = await flow.async_step_pair({})
assert result["type"] == "form" assert result["type"] == "form"
assert result["errors"]["pairing_code"] == expected assert result["errors"]["pairing_code"] == expected
assert flow.context == { assert flow.context == {
@ -507,6 +535,15 @@ async def test_pair_abort_errors_on_finish(hass, exception, expected):
flow = _setup_flow_handler(hass) flow = _setup_flow_handler(hass)
finish_pairing = asynctest.CoroutineMock(side_effect=exception("error"))
discovery = mock.Mock()
discovery.start_pairing = asynctest.CoroutineMock(return_value=finish_pairing)
flow.controller.find_ip_by_device_id = asynctest.CoroutineMock(
return_value=discovery
)
# Device is discovered # Device is discovered
result = await flow.async_step_zeroconf(discovery_info) result = await flow.async_step_zeroconf(discovery_info)
assert result["type"] == "form" assert result["type"] == "form"
@ -521,10 +558,9 @@ async def test_pair_abort_errors_on_finish(hass, exception, expected):
result = await flow.async_step_pair({}) result = await flow.async_step_pair({})
assert result["type"] == "form" assert result["type"] == "form"
assert result["step_id"] == "pair" assert result["step_id"] == "pair"
assert flow.controller.start_pairing.call_count == 1 assert discovery.start_pairing.call_count == 1
# User submits code - pairing fails but can be retried # User submits code - pairing fails but can be retried
flow.finish_pairing.side_effect = exception("error")
result = await flow.async_step_pair({"pairing_code": "111-22-333"}) result = await flow.async_step_pair({"pairing_code": "111-22-333"})
assert result["type"] == "abort" assert result["type"] == "abort"
assert result["reason"] == expected assert result["reason"] == expected
@ -547,6 +583,15 @@ async def test_pair_form_errors_on_finish(hass, exception, expected):
flow = _setup_flow_handler(hass) flow = _setup_flow_handler(hass)
finish_pairing = asynctest.CoroutineMock(side_effect=exception("error"))
discovery = mock.Mock()
discovery.start_pairing = asynctest.CoroutineMock(return_value=finish_pairing)
flow.controller.find_ip_by_device_id = asynctest.CoroutineMock(
return_value=discovery
)
# Device is discovered # Device is discovered
result = await flow.async_step_zeroconf(discovery_info) result = await flow.async_step_zeroconf(discovery_info)
assert result["type"] == "form" assert result["type"] == "form"
@ -561,10 +606,9 @@ async def test_pair_form_errors_on_finish(hass, exception, expected):
result = await flow.async_step_pair({}) result = await flow.async_step_pair({})
assert result["type"] == "form" assert result["type"] == "form"
assert result["step_id"] == "pair" assert result["step_id"] == "pair"
assert flow.controller.start_pairing.call_count == 1 assert discovery.start_pairing.call_count == 1
# User submits code - pairing fails but can be retried # User submits code - pairing fails but can be retried
flow.finish_pairing.side_effect = exception("error")
result = await flow.async_step_pair({"pairing_code": "111-22-333"}) result = await flow.async_step_pair({"pairing_code": "111-22-333"})
assert result["type"] == "form" assert result["type"] == "form"
assert result["errors"]["pairing_code"] == expected assert result["errors"]["pairing_code"] == expected
@ -588,17 +632,21 @@ async def test_import_works(hass):
pairing = mock.Mock(pairing_data={"AccessoryPairingID": "00:00:00:00:00:00"}) pairing = mock.Mock(pairing_data={"AccessoryPairingID": "00:00:00:00:00:00"})
pairing.list_accessories_and_characteristics.return_value = [ pairing.list_accessories_and_characteristics = asynctest.CoroutineMock(
{ return_value=[
"aid": 1, {
"services": [ "aid": 1,
{ "services": [
"characteristics": [{"type": "23", "value": "Koogeek-LS1-20833F"}], {
"type": "3e", "characteristics": [
} {"type": "23", "value": "Koogeek-LS1-20833F"}
], ],
} "type": "3e",
] }
],
}
]
)
flow = _setup_flow_handler(hass) flow = _setup_flow_handler(hass)
@ -653,22 +701,35 @@ async def test_user_works(hass):
} }
pairing = mock.Mock(pairing_data={"AccessoryPairingID": "00:00:00:00:00:00"}) pairing = mock.Mock(pairing_data={"AccessoryPairingID": "00:00:00:00:00:00"})
pairing.list_accessories_and_characteristics.return_value = [ pairing.list_accessories_and_characteristics = asynctest.CoroutineMock(
{ return_value=[
"aid": 1, {
"services": [ "aid": 1,
{ "services": [
"characteristics": [{"type": "23", "value": "Koogeek-LS1-20833F"}], {
"type": "3e", "characteristics": [
} {"type": "23", "value": "Koogeek-LS1-20833F"}
], ],
} "type": "3e",
] }
],
}
]
)
flow = _setup_flow_handler(hass) flow = _setup_flow_handler(hass)
finish_pairing = asynctest.CoroutineMock(return_value=pairing)
discovery = mock.Mock()
discovery.info = discovery_info
discovery.start_pairing = asynctest.CoroutineMock(return_value=finish_pairing)
flow.controller.pairings = {"00:00:00:00:00:00": pairing} flow.controller.pairings = {"00:00:00:00:00:00": pairing}
flow.controller.discover.return_value = [discovery_info] flow.controller.discover_ip = asynctest.CoroutineMock(return_value=[discovery])
flow.controller.find_ip_by_device_id = asynctest.CoroutineMock(
return_value=discovery
)
result = await flow.async_step_user() result = await flow.async_step_user()
assert result["type"] == "form" assert result["type"] == "form"
@ -688,7 +749,7 @@ async def test_user_no_devices(hass):
"""Test user initiated pairing where no devices discovered.""" """Test user initiated pairing where no devices discovered."""
flow = _setup_flow_handler(hass) flow = _setup_flow_handler(hass)
flow.controller.discover.return_value = [] flow.controller.discover_ip = asynctest.CoroutineMock(return_value=[])
result = await flow.async_step_user() result = await flow.async_step_user()
assert result["type"] == "abort" assert result["type"] == "abort"
@ -709,7 +770,10 @@ async def test_user_no_unpaired_devices(hass):
"sf": 0, "sf": 0,
} }
flow.controller.discover.return_value = [discovery_info] discovery = mock.Mock()
discovery.info = discovery_info
flow.controller.discover_ip = asynctest.CoroutineMock(return_value=[discovery])
result = await flow.async_step_user() result = await flow.async_step_user()
assert result["type"] == "abort" assert result["type"] == "abort"
@ -718,12 +782,10 @@ async def test_user_no_unpaired_devices(hass):
async def test_parse_new_homekit_json(hass): async def test_parse_new_homekit_json(hass):
"""Test migrating recent .homekit/pairings.json files.""" """Test migrating recent .homekit/pairings.json files."""
service = FakeService("public.hap.service.lightbulb")
on_char = service.add_characteristic("on")
on_char.value = 1
accessory = Accessory("TestDevice", "example.com", "Test", "0001", "0.1") accessory = Accessory("TestDevice", "example.com", "Test", "0001", "0.1")
accessory.services.append(service) service = accessory.add_service(ServicesTypes.LIGHTBULB)
on_char = service.add_char(CharacteristicsTypes.ON)
on_char.value = 0
fake_controller = await setup_platform(hass) fake_controller = await setup_platform(hass)
pairing = fake_controller.add([accessory]) pairing = fake_controller.add([accessory])
@ -766,12 +828,10 @@ async def test_parse_new_homekit_json(hass):
async def test_parse_old_homekit_json(hass): async def test_parse_old_homekit_json(hass):
"""Test migrating original .homekit/hk-00:00:00:00:00:00 files.""" """Test migrating original .homekit/hk-00:00:00:00:00:00 files."""
service = FakeService("public.hap.service.lightbulb")
on_char = service.add_characteristic("on")
on_char.value = 1
accessory = Accessory("TestDevice", "example.com", "Test", "0001", "0.1") accessory = Accessory("TestDevice", "example.com", "Test", "0001", "0.1")
accessory.services.append(service) service = accessory.add_service(ServicesTypes.LIGHTBULB)
on_char = service.add_char(CharacteristicsTypes.ON)
on_char.value = 0
fake_controller = await setup_platform(hass) fake_controller = await setup_platform(hass)
pairing = fake_controller.add([accessory]) pairing = fake_controller.add([accessory])
@ -818,12 +878,10 @@ async def test_parse_old_homekit_json(hass):
async def test_parse_overlapping_homekit_json(hass): async def test_parse_overlapping_homekit_json(hass):
"""Test migrating .homekit/pairings.json files when hk- exists too.""" """Test migrating .homekit/pairings.json files when hk- exists too."""
service = FakeService("public.hap.service.lightbulb")
on_char = service.add_characteristic("on")
on_char.value = 1
accessory = Accessory("TestDevice", "example.com", "Test", "0001", "0.1") accessory = Accessory("TestDevice", "example.com", "Test", "0001", "0.1")
accessory.services.append(service) service = accessory.add_service(ServicesTypes.LIGHTBULB)
on_char = service.add_char(CharacteristicsTypes.ON)
on_char.value = 0
fake_controller = await setup_platform(hass) fake_controller = await setup_platform(hass)
pairing = fake_controller.add([accessory]) pairing = fake_controller.add([accessory])
@ -857,7 +915,6 @@ async def test_parse_overlapping_homekit_json(hass):
pairing_cls_imp = ( pairing_cls_imp = (
"homeassistant.components.homekit_controller.config_flow.IpPairing" "homeassistant.components.homekit_controller.config_flow.IpPairing"
) )
with mock.patch(pairing_cls_imp) as pairing_cls: with mock.patch(pairing_cls_imp) as pairing_cls:
pairing_cls.return_value = pairing pairing_cls.return_value = pairing
with mock.patch("builtins.open", side_effect=side_effects): with mock.patch("builtins.open", side_effect=side_effects):
@ -894,22 +951,39 @@ async def test_unignore_works(hass):
} }
pairing = mock.Mock(pairing_data={"AccessoryPairingID": "00:00:00:00:00:00"}) pairing = mock.Mock(pairing_data={"AccessoryPairingID": "00:00:00:00:00:00"})
pairing.list_accessories_and_characteristics.return_value = [ pairing.list_accessories_and_characteristics = asynctest.CoroutineMock(
{ return_value=[
"aid": 1, {
"services": [ "aid": 1,
{ "services": [
"characteristics": [{"type": "23", "value": "Koogeek-LS1-20833F"}], {
"type": "3e", "characteristics": [
} {"type": "23", "value": "Koogeek-LS1-20833F"}
], ],
} "type": "3e",
] }
],
}
]
)
finish_pairing = asynctest.CoroutineMock()
discovery = mock.Mock()
discovery.device_id = "00:00:00:00:00:00"
discovery.info = discovery_info
discovery.start_pairing = asynctest.CoroutineMock(return_value=finish_pairing)
finish_pairing.return_value = pairing
flow = _setup_flow_handler(hass) flow = _setup_flow_handler(hass)
flow.controller.pairings = {"00:00:00:00:00:00": pairing} flow.controller.pairings = {"00:00:00:00:00:00": pairing}
flow.controller.discover.return_value = [discovery_info] flow.controller.discover_ip = asynctest.CoroutineMock(return_value=[discovery])
flow.controller.find_ip_by_device_id = asynctest.CoroutineMock(
return_value=discovery
)
result = await flow.async_step_unignore({"unique_id": "00:00:00:00:00:00"}) result = await flow.async_step_unignore({"unique_id": "00:00:00:00:00:00"})
assert result["type"] == "form" assert result["type"] == "form"
@ -924,7 +998,6 @@ async def test_unignore_works(hass):
result = await flow.async_step_pair({}) result = await flow.async_step_pair({})
assert result["type"] == "form" assert result["type"] == "form"
assert result["step_id"] == "pair" assert result["step_id"] == "pair"
assert flow.controller.start_pairing.call_count == 1
# Pairing finalized # Pairing finalized
result = await flow.async_step_pair({"pairing_code": "111-22-333"}) result = await flow.async_step_pair({"pairing_code": "111-22-333"})
@ -949,8 +1022,12 @@ async def test_unignore_ignores_missing_devices(hass):
"sf": 1, "sf": 1,
} }
discovery = mock.Mock()
discovery.device_id = "00:00:00:00:00:00"
discovery.info = discovery_info
flow = _setup_flow_handler(hass) flow = _setup_flow_handler(hass)
flow.controller.discover.return_value = [discovery_info] flow.controller.discover_ip = asynctest.CoroutineMock(return_value=[discovery])
result = await flow.async_step_unignore({"unique_id": "00:00:00:00:00:01"}) result = await flow.async_step_unignore({"unique_id": "00:00:00:00:00:01"})
assert result["type"] == "abort" assert result["type"] == "abort"

View file

@ -1,5 +1,8 @@
"""Basic checks for HomeKitalarm_control_panel.""" """Basic checks for HomeKitalarm_control_panel."""
from tests.components.homekit_controller.common import FakeService, setup_test_component from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes
from tests.components.homekit_controller.common import setup_test_component
POSITION_STATE = ("window-covering", "position.state") POSITION_STATE = ("window-covering", "position.state")
POSITION_CURRENT = ("window-covering", "position.current") POSITION_CURRENT = ("window-covering", "position.current")
@ -19,61 +22,56 @@ DOOR_TARGET = ("garage-door-opener", "door-state.target")
DOOR_OBSTRUCTION = ("garage-door-opener", "obstruction-detected") DOOR_OBSTRUCTION = ("garage-door-opener", "obstruction-detected")
def create_window_covering_service(): def create_window_covering_service(accessory):
"""Define a window-covering characteristics as per page 219 of HAP spec.""" """Define a window-covering characteristics as per page 219 of HAP spec."""
service = FakeService("public.hap.service.window-covering") service = accessory.add_service(ServicesTypes.WINDOW_COVERING)
cur_state = service.add_characteristic("position.current") cur_state = service.add_char(CharacteristicsTypes.POSITION_CURRENT)
cur_state.value = 0 cur_state.value = 0
targ_state = service.add_characteristic("position.target") targ_state = service.add_char(CharacteristicsTypes.POSITION_TARGET)
targ_state.value = 0 targ_state.value = 0
position_state = service.add_characteristic("position.state") position_state = service.add_char(CharacteristicsTypes.POSITION_STATE)
position_state.value = 0 position_state.value = 0
position_hold = service.add_characteristic("position.hold") position_hold = service.add_char(CharacteristicsTypes.POSITION_HOLD)
position_hold.value = 0 position_hold.value = 0
obstruction = service.add_characteristic("obstruction-detected") obstruction = service.add_char(CharacteristicsTypes.OBSTRUCTION_DETECTED)
obstruction.value = False obstruction.value = False
name = service.add_characteristic("name") name = service.add_char(CharacteristicsTypes.NAME)
name.value = "testdevice" name.value = "testdevice"
return service return service
def create_window_covering_service_with_h_tilt(): def create_window_covering_service_with_h_tilt(accessory):
"""Define a window-covering characteristics as per page 219 of HAP spec.""" """Define a window-covering characteristics as per page 219 of HAP spec."""
service = create_window_covering_service() service = create_window_covering_service(accessory)
tilt_current = service.add_characteristic("horizontal-tilt.current") tilt_current = service.add_char(CharacteristicsTypes.HORIZONTAL_TILT_CURRENT)
tilt_current.value = 0 tilt_current.value = 0
tilt_target = service.add_characteristic("horizontal-tilt.target") tilt_target = service.add_char(CharacteristicsTypes.HORIZONTAL_TILT_TARGET)
tilt_target.value = 0 tilt_target.value = 0
return service
def create_window_covering_service_with_v_tilt(accessory):
def create_window_covering_service_with_v_tilt():
"""Define a window-covering characteristics as per page 219 of HAP spec.""" """Define a window-covering characteristics as per page 219 of HAP spec."""
service = create_window_covering_service() service = create_window_covering_service(accessory)
tilt_current = service.add_characteristic("vertical-tilt.current") tilt_current = service.add_char(CharacteristicsTypes.VERTICAL_TILT_CURRENT)
tilt_current.value = 0 tilt_current.value = 0
tilt_target = service.add_characteristic("vertical-tilt.target") tilt_target = service.add_char(CharacteristicsTypes.VERTICAL_TILT_TARGET)
tilt_target.value = 0 tilt_target.value = 0
return service
async def test_change_window_cover_state(hass, utcnow): async def test_change_window_cover_state(hass, utcnow):
"""Test that we can turn a HomeKit alarm on and off again.""" """Test that we can turn a HomeKit alarm on and off again."""
window_cover = create_window_covering_service() helper = await setup_test_component(hass, create_window_covering_service)
helper = await setup_test_component(hass, [window_cover])
await hass.services.async_call( await hass.services.async_call(
"cover", "open_cover", {"entity_id": helper.entity_id}, blocking=True "cover", "open_cover", {"entity_id": helper.entity_id}, blocking=True
@ -88,8 +86,7 @@ async def test_change_window_cover_state(hass, utcnow):
async def test_read_window_cover_state(hass, utcnow): async def test_read_window_cover_state(hass, utcnow):
"""Test that we can read the state of a HomeKit alarm accessory.""" """Test that we can read the state of a HomeKit alarm accessory."""
window_cover = create_window_covering_service() helper = await setup_test_component(hass, create_window_covering_service)
helper = await setup_test_component(hass, [window_cover])
helper.characteristics[POSITION_STATE].value = 0 helper.characteristics[POSITION_STATE].value = 0
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
@ -110,8 +107,9 @@ async def test_read_window_cover_state(hass, utcnow):
async def test_read_window_cover_tilt_horizontal(hass, utcnow): async def test_read_window_cover_tilt_horizontal(hass, utcnow):
"""Test that horizontal tilt is handled correctly.""" """Test that horizontal tilt is handled correctly."""
window_cover = create_window_covering_service_with_h_tilt() helper = await setup_test_component(
helper = await setup_test_component(hass, [window_cover]) hass, create_window_covering_service_with_h_tilt
)
helper.characteristics[H_TILT_CURRENT].value = 75 helper.characteristics[H_TILT_CURRENT].value = 75
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
@ -120,8 +118,9 @@ async def test_read_window_cover_tilt_horizontal(hass, utcnow):
async def test_read_window_cover_tilt_vertical(hass, utcnow): async def test_read_window_cover_tilt_vertical(hass, utcnow):
"""Test that vertical tilt is handled correctly.""" """Test that vertical tilt is handled correctly."""
window_cover = create_window_covering_service_with_v_tilt() helper = await setup_test_component(
helper = await setup_test_component(hass, [window_cover]) hass, create_window_covering_service_with_v_tilt
)
helper.characteristics[V_TILT_CURRENT].value = 75 helper.characteristics[V_TILT_CURRENT].value = 75
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
@ -130,8 +129,9 @@ async def test_read_window_cover_tilt_vertical(hass, utcnow):
async def test_write_window_cover_tilt_horizontal(hass, utcnow): async def test_write_window_cover_tilt_horizontal(hass, utcnow):
"""Test that horizontal tilt is written correctly.""" """Test that horizontal tilt is written correctly."""
window_cover = create_window_covering_service_with_h_tilt() helper = await setup_test_component(
helper = await setup_test_component(hass, [window_cover]) hass, create_window_covering_service_with_h_tilt
)
await hass.services.async_call( await hass.services.async_call(
"cover", "cover",
@ -144,8 +144,9 @@ async def test_write_window_cover_tilt_horizontal(hass, utcnow):
async def test_write_window_cover_tilt_vertical(hass, utcnow): async def test_write_window_cover_tilt_vertical(hass, utcnow):
"""Test that vertical tilt is written correctly.""" """Test that vertical tilt is written correctly."""
window_cover = create_window_covering_service_with_v_tilt() helper = await setup_test_component(
helper = await setup_test_component(hass, [window_cover]) hass, create_window_covering_service_with_v_tilt
)
await hass.services.async_call( await hass.services.async_call(
"cover", "cover",
@ -158,8 +159,9 @@ async def test_write_window_cover_tilt_vertical(hass, utcnow):
async def test_window_cover_stop(hass, utcnow): async def test_window_cover_stop(hass, utcnow):
"""Test that vertical tilt is written correctly.""" """Test that vertical tilt is written correctly."""
window_cover = create_window_covering_service_with_v_tilt() helper = await setup_test_component(
helper = await setup_test_component(hass, [window_cover]) hass, create_window_covering_service_with_v_tilt
)
await hass.services.async_call( await hass.services.async_call(
"cover", "stop_cover", {"entity_id": helper.entity_id}, blocking=True "cover", "stop_cover", {"entity_id": helper.entity_id}, blocking=True
@ -167,20 +169,20 @@ async def test_window_cover_stop(hass, utcnow):
assert helper.characteristics[POSITION_HOLD].value == 1 assert helper.characteristics[POSITION_HOLD].value == 1
def create_garage_door_opener_service(): def create_garage_door_opener_service(accessory):
"""Define a garage-door-opener chars as per page 217 of HAP spec.""" """Define a garage-door-opener chars as per page 217 of HAP spec."""
service = FakeService("public.hap.service.garage-door-opener") service = accessory.add_service(ServicesTypes.GARAGE_DOOR_OPENER)
cur_state = service.add_characteristic("door-state.current") cur_state = service.add_char(CharacteristicsTypes.DOOR_STATE_CURRENT)
cur_state.value = 0 cur_state.value = 0
targ_state = service.add_characteristic("door-state.target") cur_state = service.add_char(CharacteristicsTypes.DOOR_STATE_TARGET)
targ_state.value = 0 cur_state.value = 0
obstruction = service.add_characteristic("obstruction-detected") obstruction = service.add_char(CharacteristicsTypes.OBSTRUCTION_DETECTED)
obstruction.value = False obstruction.value = False
name = service.add_characteristic("name") name = service.add_char(CharacteristicsTypes.NAME)
name.value = "testdevice" name.value = "testdevice"
return service return service
@ -188,8 +190,7 @@ def create_garage_door_opener_service():
async def test_change_door_state(hass, utcnow): async def test_change_door_state(hass, utcnow):
"""Test that we can turn open and close a HomeKit garage door.""" """Test that we can turn open and close a HomeKit garage door."""
door = create_garage_door_opener_service() helper = await setup_test_component(hass, create_garage_door_opener_service)
helper = await setup_test_component(hass, [door])
await hass.services.async_call( await hass.services.async_call(
"cover", "open_cover", {"entity_id": helper.entity_id}, blocking=True "cover", "open_cover", {"entity_id": helper.entity_id}, blocking=True
@ -204,8 +205,7 @@ async def test_change_door_state(hass, utcnow):
async def test_read_door_state(hass, utcnow): async def test_read_door_state(hass, utcnow):
"""Test that we can read the state of a HomeKit garage door.""" """Test that we can read the state of a HomeKit garage door."""
door = create_garage_door_opener_service() helper = await setup_test_component(hass, create_garage_door_opener_service)
helper = await setup_test_component(hass, [door])
helper.characteristics[DOOR_CURRENT].value = 0 helper.characteristics[DOOR_CURRENT].value = 0
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()

View file

@ -1,5 +1,8 @@
"""Basic checks for HomeKit motion sensors and contact sensors.""" """Basic checks for HomeKit motion sensors and contact sensors."""
from tests.components.homekit_controller.common import FakeService, setup_test_component from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes
from tests.components.homekit_controller.common import setup_test_component
V1_ON = ("fan", "on") V1_ON = ("fan", "on")
V1_ROTATION_DIRECTION = ("fan", "rotation.direction") V1_ROTATION_DIRECTION = ("fan", "rotation.direction")
@ -11,50 +14,45 @@ V2_ROTATION_SPEED = ("fanv2", "rotation.speed")
V2_SWING_MODE = ("fanv2", "swing-mode") V2_SWING_MODE = ("fanv2", "swing-mode")
def create_fan_service(): def create_fan_service(accessory):
""" """
Define fan v1 characteristics as per HAP spec. Define fan v1 characteristics as per HAP spec.
This service is no longer documented in R2 of the public HAP spec but existing This service is no longer documented in R2 of the public HAP spec but existing
devices out there use it (like the SIMPLEconnect fan) devices out there use it (like the SIMPLEconnect fan)
""" """
service = FakeService("public.hap.service.fan") service = accessory.add_service(ServicesTypes.FAN)
cur_state = service.add_characteristic("on") cur_state = service.add_char(CharacteristicsTypes.ON)
cur_state.value = 0 cur_state.value = 0
cur_state = service.add_characteristic("rotation.direction") direction = service.add_char(CharacteristicsTypes.ROTATION_DIRECTION)
cur_state.value = 0 direction.value = 0
cur_state = service.add_characteristic("rotation.speed") speed = service.add_char(CharacteristicsTypes.ROTATION_SPEED)
cur_state.value = 0 speed.value = 0
return service
def create_fanv2_service(): def create_fanv2_service(accessory):
"""Define fan v2 characteristics as per HAP spec.""" """Define fan v2 characteristics as per HAP spec."""
service = FakeService("public.hap.service.fanv2") service = accessory.add_service(ServicesTypes.FAN_V2)
cur_state = service.add_characteristic("active") cur_state = service.add_char(CharacteristicsTypes.ACTIVE)
cur_state.value = 0 cur_state.value = 0
cur_state = service.add_characteristic("rotation.direction") direction = service.add_char(CharacteristicsTypes.ROTATION_DIRECTION)
cur_state.value = 0 direction.value = 0
cur_state = service.add_characteristic("rotation.speed") speed = service.add_char(CharacteristicsTypes.ROTATION_SPEED)
cur_state.value = 0 speed.value = 0
cur_state = service.add_characteristic("swing-mode") swing_mode = service.add_char(CharacteristicsTypes.SWING_MODE)
cur_state.value = 0 swing_mode.value = 0
return service
async def test_fan_read_state(hass, utcnow): async def test_fan_read_state(hass, utcnow):
"""Test that we can read the state of a HomeKit fan accessory.""" """Test that we can read the state of a HomeKit fan accessory."""
sensor = create_fan_service() helper = await setup_test_component(hass, create_fan_service)
helper = await setup_test_component(hass, [sensor])
helper.characteristics[V1_ON].value = False helper.characteristics[V1_ON].value = False
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
@ -67,8 +65,7 @@ async def test_fan_read_state(hass, utcnow):
async def test_turn_on(hass, utcnow): async def test_turn_on(hass, utcnow):
"""Test that we can turn a fan on.""" """Test that we can turn a fan on."""
fan = create_fan_service() helper = await setup_test_component(hass, create_fan_service)
helper = await setup_test_component(hass, [fan])
await hass.services.async_call( await hass.services.async_call(
"fan", "fan",
@ -100,8 +97,7 @@ async def test_turn_on(hass, utcnow):
async def test_turn_off(hass, utcnow): async def test_turn_off(hass, utcnow):
"""Test that we can turn a fan off.""" """Test that we can turn a fan off."""
fan = create_fan_service() helper = await setup_test_component(hass, create_fan_service)
helper = await setup_test_component(hass, [fan])
helper.characteristics[V1_ON].value = 1 helper.characteristics[V1_ON].value = 1
@ -113,8 +109,7 @@ async def test_turn_off(hass, utcnow):
async def test_set_speed(hass, utcnow): async def test_set_speed(hass, utcnow):
"""Test that we set fan speed.""" """Test that we set fan speed."""
fan = create_fan_service() helper = await setup_test_component(hass, create_fan_service)
helper = await setup_test_component(hass, [fan])
helper.characteristics[V1_ON].value = 1 helper.characteristics[V1_ON].value = 1
@ -153,8 +148,7 @@ async def test_set_speed(hass, utcnow):
async def test_speed_read(hass, utcnow): async def test_speed_read(hass, utcnow):
"""Test that we can read a fans oscillation.""" """Test that we can read a fans oscillation."""
fan = create_fan_service() helper = await setup_test_component(hass, create_fan_service)
helper = await setup_test_component(hass, [fan])
helper.characteristics[V1_ON].value = 1 helper.characteristics[V1_ON].value = 1
helper.characteristics[V1_ROTATION_SPEED].value = 100 helper.characteristics[V1_ROTATION_SPEED].value = 100
@ -177,8 +171,7 @@ async def test_speed_read(hass, utcnow):
async def test_set_direction(hass, utcnow): async def test_set_direction(hass, utcnow):
"""Test that we can set fan spin direction.""" """Test that we can set fan spin direction."""
fan = create_fan_service() helper = await setup_test_component(hass, create_fan_service)
helper = await setup_test_component(hass, [fan])
await hass.services.async_call( await hass.services.async_call(
"fan", "fan",
@ -199,8 +192,7 @@ async def test_set_direction(hass, utcnow):
async def test_direction_read(hass, utcnow): async def test_direction_read(hass, utcnow):
"""Test that we can read a fans oscillation.""" """Test that we can read a fans oscillation."""
fan = create_fan_service() helper = await setup_test_component(hass, create_fan_service)
helper = await setup_test_component(hass, [fan])
helper.characteristics[V1_ROTATION_DIRECTION].value = 0 helper.characteristics[V1_ROTATION_DIRECTION].value = 0
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
@ -213,8 +205,7 @@ async def test_direction_read(hass, utcnow):
async def test_fanv2_read_state(hass, utcnow): async def test_fanv2_read_state(hass, utcnow):
"""Test that we can read the state of a HomeKit fan accessory.""" """Test that we can read the state of a HomeKit fan accessory."""
sensor = create_fanv2_service() helper = await setup_test_component(hass, create_fanv2_service)
helper = await setup_test_component(hass, [sensor])
helper.characteristics[V2_ACTIVE].value = False helper.characteristics[V2_ACTIVE].value = False
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
@ -227,8 +218,7 @@ async def test_fanv2_read_state(hass, utcnow):
async def test_v2_turn_on(hass, utcnow): async def test_v2_turn_on(hass, utcnow):
"""Test that we can turn a fan on.""" """Test that we can turn a fan on."""
fan = create_fanv2_service() helper = await setup_test_component(hass, create_fanv2_service)
helper = await setup_test_component(hass, [fan])
await hass.services.async_call( await hass.services.async_call(
"fan", "fan",
@ -260,8 +250,7 @@ async def test_v2_turn_on(hass, utcnow):
async def test_v2_turn_off(hass, utcnow): async def test_v2_turn_off(hass, utcnow):
"""Test that we can turn a fan off.""" """Test that we can turn a fan off."""
fan = create_fanv2_service() helper = await setup_test_component(hass, create_fanv2_service)
helper = await setup_test_component(hass, [fan])
helper.characteristics[V2_ACTIVE].value = 1 helper.characteristics[V2_ACTIVE].value = 1
@ -273,8 +262,7 @@ async def test_v2_turn_off(hass, utcnow):
async def test_v2_set_speed(hass, utcnow): async def test_v2_set_speed(hass, utcnow):
"""Test that we set fan speed.""" """Test that we set fan speed."""
fan = create_fanv2_service() helper = await setup_test_component(hass, create_fanv2_service)
helper = await setup_test_component(hass, [fan])
helper.characteristics[V2_ACTIVE].value = 1 helper.characteristics[V2_ACTIVE].value = 1
@ -313,8 +301,7 @@ async def test_v2_set_speed(hass, utcnow):
async def test_v2_speed_read(hass, utcnow): async def test_v2_speed_read(hass, utcnow):
"""Test that we can read a fans oscillation.""" """Test that we can read a fans oscillation."""
fan = create_fanv2_service() helper = await setup_test_component(hass, create_fanv2_service)
helper = await setup_test_component(hass, [fan])
helper.characteristics[V2_ACTIVE].value = 1 helper.characteristics[V2_ACTIVE].value = 1
helper.characteristics[V2_ROTATION_SPEED].value = 100 helper.characteristics[V2_ROTATION_SPEED].value = 100
@ -337,8 +324,7 @@ async def test_v2_speed_read(hass, utcnow):
async def test_v2_set_direction(hass, utcnow): async def test_v2_set_direction(hass, utcnow):
"""Test that we can set fan spin direction.""" """Test that we can set fan spin direction."""
fan = create_fanv2_service() helper = await setup_test_component(hass, create_fanv2_service)
helper = await setup_test_component(hass, [fan])
await hass.services.async_call( await hass.services.async_call(
"fan", "fan",
@ -359,8 +345,7 @@ async def test_v2_set_direction(hass, utcnow):
async def test_v2_direction_read(hass, utcnow): async def test_v2_direction_read(hass, utcnow):
"""Test that we can read a fans oscillation.""" """Test that we can read a fans oscillation."""
fan = create_fanv2_service() helper = await setup_test_component(hass, create_fanv2_service)
helper = await setup_test_component(hass, [fan])
helper.characteristics[V2_ROTATION_DIRECTION].value = 0 helper.characteristics[V2_ROTATION_DIRECTION].value = 0
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
@ -373,8 +358,7 @@ async def test_v2_direction_read(hass, utcnow):
async def test_v2_oscillate(hass, utcnow): async def test_v2_oscillate(hass, utcnow):
"""Test that we can control a fans oscillation.""" """Test that we can control a fans oscillation."""
fan = create_fanv2_service() helper = await setup_test_component(hass, create_fanv2_service)
helper = await setup_test_component(hass, [fan])
await hass.services.async_call( await hass.services.async_call(
"fan", "fan",
@ -395,8 +379,7 @@ async def test_v2_oscillate(hass, utcnow):
async def test_v2_oscillate_read(hass, utcnow): async def test_v2_oscillate_read(hass, utcnow):
"""Test that we can read a fans oscillation.""" """Test that we can read a fans oscillation."""
fan = create_fanv2_service() helper = await setup_test_component(hass, create_fanv2_service)
helper = await setup_test_component(hass, [fan])
helper.characteristics[V2_SWING_MODE].value = 0 helper.characteristics[V2_SWING_MODE].value = 0
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()

View file

@ -1,7 +1,10 @@
"""Basic checks for HomeKitSwitch.""" """Basic checks for HomeKitSwitch."""
from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes
from homeassistant.components.homekit_controller.const import KNOWN_DEVICES from homeassistant.components.homekit_controller.const import KNOWN_DEVICES
from tests.components.homekit_controller.common import FakeService, setup_test_component from tests.components.homekit_controller.common import setup_test_component
LIGHT_ON = ("lightbulb", "on") LIGHT_ON = ("lightbulb", "on")
LIGHT_BRIGHTNESS = ("lightbulb", "brightness") LIGHT_BRIGHTNESS = ("lightbulb", "brightness")
@ -10,37 +13,37 @@ LIGHT_SATURATION = ("lightbulb", "saturation")
LIGHT_COLOR_TEMP = ("lightbulb", "color-temperature") LIGHT_COLOR_TEMP = ("lightbulb", "color-temperature")
def create_lightbulb_service(): def create_lightbulb_service(accessory):
"""Define lightbulb characteristics.""" """Define lightbulb characteristics."""
service = FakeService("public.hap.service.lightbulb") service = accessory.add_service(ServicesTypes.LIGHTBULB)
on_char = service.add_characteristic("on") on_char = service.add_char(CharacteristicsTypes.ON)
on_char.value = 0 on_char.value = 0
brightness = service.add_characteristic("brightness") brightness = service.add_char(CharacteristicsTypes.BRIGHTNESS)
brightness.value = 0 brightness.value = 0
return service return service
def create_lightbulb_service_with_hs(): def create_lightbulb_service_with_hs(accessory):
"""Define a lightbulb service with hue + saturation.""" """Define a lightbulb service with hue + saturation."""
service = create_lightbulb_service() service = create_lightbulb_service(accessory)
hue = service.add_characteristic("hue") hue = service.add_char(CharacteristicsTypes.HUE)
hue.value = 0 hue.value = 0
saturation = service.add_characteristic("saturation") saturation = service.add_char(CharacteristicsTypes.SATURATION)
saturation.value = 0 saturation.value = 0
return service return service
def create_lightbulb_service_with_color_temp(): def create_lightbulb_service_with_color_temp(accessory):
"""Define a lightbulb service with color temp.""" """Define a lightbulb service with color temp."""
service = create_lightbulb_service() service = create_lightbulb_service(accessory)
color_temp = service.add_characteristic("color-temperature") color_temp = service.add_char(CharacteristicsTypes.COLOR_TEMPERATURE)
color_temp.value = 0 color_temp.value = 0
return service return service
@ -48,8 +51,7 @@ def create_lightbulb_service_with_color_temp():
async def test_switch_change_light_state(hass, utcnow): async def test_switch_change_light_state(hass, utcnow):
"""Test that we can turn a HomeKit light on and off again.""" """Test that we can turn a HomeKit light on and off again."""
bulb = create_lightbulb_service_with_hs() helper = await setup_test_component(hass, create_lightbulb_service_with_hs)
helper = await setup_test_component(hass, [bulb])
await hass.services.async_call( await hass.services.async_call(
"light", "light",
@ -71,8 +73,7 @@ async def test_switch_change_light_state(hass, utcnow):
async def test_switch_change_light_state_color_temp(hass, utcnow): async def test_switch_change_light_state_color_temp(hass, utcnow):
"""Test that we can turn change color_temp.""" """Test that we can turn change color_temp."""
bulb = create_lightbulb_service_with_color_temp() helper = await setup_test_component(hass, create_lightbulb_service_with_color_temp)
helper = await setup_test_component(hass, [bulb])
await hass.services.async_call( await hass.services.async_call(
"light", "light",
@ -87,8 +88,7 @@ async def test_switch_change_light_state_color_temp(hass, utcnow):
async def test_switch_read_light_state(hass, utcnow): async def test_switch_read_light_state(hass, utcnow):
"""Test that we can read the state of a HomeKit light accessory.""" """Test that we can read the state of a HomeKit light accessory."""
bulb = create_lightbulb_service_with_hs() helper = await setup_test_component(hass, create_lightbulb_service_with_hs)
helper = await setup_test_component(hass, [bulb])
# Initial state is that the light is off # Initial state is that the light is off
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
@ -112,8 +112,7 @@ async def test_switch_read_light_state(hass, utcnow):
async def test_switch_read_light_state_color_temp(hass, utcnow): async def test_switch_read_light_state_color_temp(hass, utcnow):
"""Test that we can read the color_temp of a light accessory.""" """Test that we can read the color_temp of a light accessory."""
bulb = create_lightbulb_service_with_color_temp() helper = await setup_test_component(hass, create_lightbulb_service_with_color_temp)
helper = await setup_test_component(hass, [bulb])
# Initial state is that the light is off # Initial state is that the light is off
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
@ -132,8 +131,7 @@ async def test_switch_read_light_state_color_temp(hass, utcnow):
async def test_light_becomes_unavailable_but_recovers(hass, utcnow): async def test_light_becomes_unavailable_but_recovers(hass, utcnow):
"""Test transition to and from unavailable state.""" """Test transition to and from unavailable state."""
bulb = create_lightbulb_service_with_color_temp() helper = await setup_test_component(hass, create_lightbulb_service_with_color_temp)
helper = await setup_test_component(hass, [bulb])
# Initial state is that the light is off # Initial state is that the light is off
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
@ -158,8 +156,7 @@ async def test_light_becomes_unavailable_but_recovers(hass, utcnow):
async def test_light_unloaded(hass, utcnow): async def test_light_unloaded(hass, utcnow):
"""Test entity and HKDevice are correctly unloaded.""" """Test entity and HKDevice are correctly unloaded."""
bulb = create_lightbulb_service_with_color_temp() helper = await setup_test_component(hass, create_lightbulb_service_with_color_temp)
helper = await setup_test_component(hass, [bulb])
# Initial state is that the light is off # Initial state is that the light is off
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()

View file

@ -1,25 +1,28 @@
"""Basic checks for HomeKitLock.""" """Basic checks for HomeKitLock."""
from tests.components.homekit_controller.common import FakeService, setup_test_component from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes
from tests.components.homekit_controller.common import setup_test_component
LOCK_CURRENT_STATE = ("lock-mechanism", "lock-mechanism.current-state") LOCK_CURRENT_STATE = ("lock-mechanism", "lock-mechanism.current-state")
LOCK_TARGET_STATE = ("lock-mechanism", "lock-mechanism.target-state") LOCK_TARGET_STATE = ("lock-mechanism", "lock-mechanism.target-state")
def create_lock_service(): def create_lock_service(accessory):
"""Define a lock characteristics as per page 219 of HAP spec.""" """Define a lock characteristics as per page 219 of HAP spec."""
service = FakeService("public.hap.service.lock-mechanism") service = accessory.add_service(ServicesTypes.LOCK_MECHANISM)
cur_state = service.add_characteristic("lock-mechanism.current-state") cur_state = service.add_char(CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE)
cur_state.value = 0 cur_state.value = 0
targ_state = service.add_characteristic("lock-mechanism.target-state") targ_state = service.add_char(CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE)
targ_state.value = 0 targ_state.value = 0
# According to the spec, a battery-level characteristic is normally # According to the spec, a battery-level characteristic is normally
# part of a separate service. However as the code was written (which # part of a separate service. However as the code was written (which
# predates this test) the battery level would have to be part of the lock # predates this test) the battery level would have to be part of the lock
# service as it is here. # service as it is here.
targ_state = service.add_characteristic("battery-level") targ_state = service.add_char(CharacteristicsTypes.BATTERY_LEVEL)
targ_state.value = 50 targ_state.value = 50
return service return service
@ -27,8 +30,7 @@ def create_lock_service():
async def test_switch_change_lock_state(hass, utcnow): async def test_switch_change_lock_state(hass, utcnow):
"""Test that we can turn a HomeKit lock on and off again.""" """Test that we can turn a HomeKit lock on and off again."""
lock = create_lock_service() helper = await setup_test_component(hass, create_lock_service)
helper = await setup_test_component(hass, [lock])
await hass.services.async_call( await hass.services.async_call(
"lock", "lock", {"entity_id": "lock.testdevice"}, blocking=True "lock", "lock", {"entity_id": "lock.testdevice"}, blocking=True
@ -43,8 +45,7 @@ async def test_switch_change_lock_state(hass, utcnow):
async def test_switch_read_lock_state(hass, utcnow): async def test_switch_read_lock_state(hass, utcnow):
"""Test that we can read the state of a HomeKit lock accessory.""" """Test that we can read the state of a HomeKit lock accessory."""
lock = create_lock_service() helper = await setup_test_component(hass, create_lock_service)
helper = await setup_test_component(hass, [lock])
helper.characteristics[LOCK_CURRENT_STATE].value = 0 helper.characteristics[LOCK_CURRENT_STATE].value = 0
helper.characteristics[LOCK_TARGET_STATE].value = 0 helper.characteristics[LOCK_TARGET_STATE].value = 0

View file

@ -1,5 +1,8 @@
"""Basic checks for HomeKit sensor.""" """Basic checks for HomeKit sensor."""
from tests.components.homekit_controller.common import FakeService, setup_test_component from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes
from tests.components.homekit_controller.common import setup_test_component
TEMPERATURE = ("temperature", "temperature.current") TEMPERATURE = ("temperature", "temperature.current")
HUMIDITY = ("humidity", "relative-humidity.current") HUMIDITY = ("humidity", "relative-humidity.current")
@ -10,57 +13,49 @@ CHARGING_STATE = ("battery", "charging-state")
LO_BATT = ("battery", "status-lo-batt") LO_BATT = ("battery", "status-lo-batt")
def create_temperature_sensor_service(): def create_temperature_sensor_service(accessory):
"""Define temperature characteristics.""" """Define temperature characteristics."""
service = FakeService("public.hap.service.sensor.temperature") service = accessory.add_service(ServicesTypes.TEMPERATURE_SENSOR)
cur_state = service.add_characteristic("temperature.current") cur_state = service.add_char(CharacteristicsTypes.TEMPERATURE_CURRENT)
cur_state.value = 0 cur_state.value = 0
return service
def create_humidity_sensor_service(accessory):
def create_humidity_sensor_service():
"""Define humidity characteristics.""" """Define humidity characteristics."""
service = FakeService("public.hap.service.sensor.humidity") service = accessory.add_service(ServicesTypes.HUMIDITY_SENSOR)
cur_state = service.add_characteristic("relative-humidity.current") cur_state = service.add_char(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT)
cur_state.value = 0 cur_state.value = 0
return service
def create_light_level_sensor_service(accessory):
def create_light_level_sensor_service():
"""Define light level characteristics.""" """Define light level characteristics."""
service = FakeService("public.hap.service.sensor.light") service = accessory.add_service(ServicesTypes.LIGHT_SENSOR)
cur_state = service.add_characteristic("light-level.current") cur_state = service.add_char(CharacteristicsTypes.LIGHT_LEVEL_CURRENT)
cur_state.value = 0 cur_state.value = 0
return service
def create_carbon_dioxide_level_sensor_service(accessory):
def create_carbon_dioxide_level_sensor_service():
"""Define carbon dioxide level characteristics.""" """Define carbon dioxide level characteristics."""
service = FakeService("public.hap.service.sensor.carbon-dioxide") service = accessory.add_service(ServicesTypes.CARBON_DIOXIDE_SENSOR)
cur_state = service.add_characteristic("carbon-dioxide.level") cur_state = service.add_char(CharacteristicsTypes.CARBON_DIOXIDE_LEVEL)
cur_state.value = 0 cur_state.value = 0
return service
def create_battery_level_sensor(accessory):
def create_battery_level_sensor():
"""Define battery level characteristics.""" """Define battery level characteristics."""
service = FakeService("public.hap.service.battery") service = accessory.add_service(ServicesTypes.BATTERY_SERVICE)
cur_state = service.add_characteristic("battery-level") cur_state = service.add_char(CharacteristicsTypes.BATTERY_LEVEL)
cur_state.value = 100 cur_state.value = 100
low_battery = service.add_characteristic("status-lo-batt") low_battery = service.add_char(CharacteristicsTypes.STATUS_LO_BATT)
low_battery.value = 0 low_battery.value = 0
charging_state = service.add_characteristic("charging-state") charging_state = service.add_char(CharacteristicsTypes.CHARGING_STATE)
charging_state.value = 0 charging_state.value = 0
return service return service
@ -68,8 +63,9 @@ def create_battery_level_sensor():
async def test_temperature_sensor_read_state(hass, utcnow): async def test_temperature_sensor_read_state(hass, utcnow):
"""Test reading the state of a HomeKit temperature sensor accessory.""" """Test reading the state of a HomeKit temperature sensor accessory."""
sensor = create_temperature_sensor_service() helper = await setup_test_component(
helper = await setup_test_component(hass, [sensor], suffix="temperature") hass, create_temperature_sensor_service, suffix="temperature"
)
helper.characteristics[TEMPERATURE].value = 10 helper.characteristics[TEMPERATURE].value = 10
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
@ -82,8 +78,9 @@ async def test_temperature_sensor_read_state(hass, utcnow):
async def test_humidity_sensor_read_state(hass, utcnow): async def test_humidity_sensor_read_state(hass, utcnow):
"""Test reading the state of a HomeKit humidity sensor accessory.""" """Test reading the state of a HomeKit humidity sensor accessory."""
sensor = create_humidity_sensor_service() helper = await setup_test_component(
helper = await setup_test_component(hass, [sensor], suffix="humidity") hass, create_humidity_sensor_service, suffix="humidity"
)
helper.characteristics[HUMIDITY].value = 10 helper.characteristics[HUMIDITY].value = 10
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
@ -96,8 +93,9 @@ async def test_humidity_sensor_read_state(hass, utcnow):
async def test_light_level_sensor_read_state(hass, utcnow): async def test_light_level_sensor_read_state(hass, utcnow):
"""Test reading the state of a HomeKit temperature sensor accessory.""" """Test reading the state of a HomeKit temperature sensor accessory."""
sensor = create_light_level_sensor_service() helper = await setup_test_component(
helper = await setup_test_component(hass, [sensor], suffix="light_level") hass, create_light_level_sensor_service, suffix="light_level"
)
helper.characteristics[LIGHT_LEVEL].value = 10 helper.characteristics[LIGHT_LEVEL].value = 10
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
@ -110,8 +108,9 @@ async def test_light_level_sensor_read_state(hass, utcnow):
async def test_carbon_dioxide_level_sensor_read_state(hass, utcnow): async def test_carbon_dioxide_level_sensor_read_state(hass, utcnow):
"""Test reading the state of a HomeKit carbon dioxide sensor accessory.""" """Test reading the state of a HomeKit carbon dioxide sensor accessory."""
sensor = create_carbon_dioxide_level_sensor_service() helper = await setup_test_component(
helper = await setup_test_component(hass, [sensor], suffix="co2") hass, create_carbon_dioxide_level_sensor_service, suffix="co2"
)
helper.characteristics[CARBON_DIOXIDE_LEVEL].value = 10 helper.characteristics[CARBON_DIOXIDE_LEVEL].value = 10
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
@ -124,8 +123,9 @@ async def test_carbon_dioxide_level_sensor_read_state(hass, utcnow):
async def test_battery_level_sensor(hass, utcnow): async def test_battery_level_sensor(hass, utcnow):
"""Test reading the state of a HomeKit battery level sensor.""" """Test reading the state of a HomeKit battery level sensor."""
sensor = create_battery_level_sensor() helper = await setup_test_component(
helper = await setup_test_component(hass, [sensor], suffix="battery") hass, create_battery_level_sensor, suffix="battery"
)
helper.characteristics[BATTERY_LEVEL].value = 100 helper.characteristics[BATTERY_LEVEL].value = 100
state = await helper.poll_and_get_state() state = await helper.poll_and_get_state()
@ -140,8 +140,9 @@ async def test_battery_level_sensor(hass, utcnow):
async def test_battery_charging(hass, utcnow): async def test_battery_charging(hass, utcnow):
"""Test reading the state of a HomeKit battery's charging state.""" """Test reading the state of a HomeKit battery's charging state."""
sensor = create_battery_level_sensor() helper = await setup_test_component(
helper = await setup_test_component(hass, [sensor], suffix="battery") hass, create_battery_level_sensor, suffix="battery"
)
helper.characteristics[BATTERY_LEVEL].value = 0 helper.characteristics[BATTERY_LEVEL].value = 0
helper.characteristics[CHARGING_STATE].value = 1 helper.characteristics[CHARGING_STATE].value = 1
@ -155,8 +156,9 @@ async def test_battery_charging(hass, utcnow):
async def test_battery_low(hass, utcnow): async def test_battery_low(hass, utcnow):
"""Test reading the state of a HomeKit battery's low state.""" """Test reading the state of a HomeKit battery's low state."""
sensor = create_battery_level_sensor() helper = await setup_test_component(
helper = await setup_test_component(hass, [sensor], suffix="battery") hass, create_battery_level_sensor, suffix="battery"
)
helper.characteristics[LO_BATT].value = 0 helper.characteristics[LO_BATT].value = 0
helper.characteristics[BATTERY_LEVEL].value = 1 helper.characteristics[BATTERY_LEVEL].value = 1

View file

@ -1,11 +1,13 @@
"""Basic checks for entity map storage.""" """Basic checks for entity map storage."""
from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.homekit_controller import async_remove_entry from homeassistant.components.homekit_controller import async_remove_entry
from homeassistant.components.homekit_controller.const import ENTITY_MAP from homeassistant.components.homekit_controller.const import ENTITY_MAP
from tests.common import flush_store from tests.common import flush_store
from tests.components.homekit_controller.common import ( from tests.components.homekit_controller.common import (
FakeService,
setup_platform, setup_platform,
setup_test_component, setup_test_component,
) )
@ -57,18 +59,16 @@ async def test_storage_is_removed_idempotent(hass):
assert hkid not in entity_map.storage_data assert hkid not in entity_map.storage_data
def create_lightbulb_service(): def create_lightbulb_service(accessory):
"""Define lightbulb characteristics.""" """Define lightbulb characteristics."""
service = FakeService("public.hap.service.lightbulb") service = accessory.add_service(ServicesTypes.LIGHTBULB)
on_char = service.add_characteristic("on") on_char = service.add_char(CharacteristicsTypes.ON)
on_char.value = 0 on_char.value = 0
return service
async def test_storage_is_updated_on_add(hass, hass_storage, utcnow): async def test_storage_is_updated_on_add(hass, hass_storage, utcnow):
"""Test entity map storage is cleaned up on adding an accessory.""" """Test entity map storage is cleaned up on adding an accessory."""
bulb = create_lightbulb_service() await setup_test_component(hass, create_lightbulb_service)
await setup_test_component(hass, [bulb])
entity_map = hass.data[ENTITY_MAP] entity_map = hass.data[ENTITY_MAP]
hkid = "00:00:00:00:00:00" hkid = "00:00:00:00:00:00"
@ -83,8 +83,7 @@ async def test_storage_is_updated_on_add(hass, hass_storage, utcnow):
async def test_storage_is_removed_on_config_entry_removal(hass, utcnow): async def test_storage_is_removed_on_config_entry_removal(hass, utcnow):
"""Test entity map storage is cleaned up on config entry removal.""" """Test entity map storage is cleaned up on config entry removal."""
bulb = create_lightbulb_service() await setup_test_component(hass, create_lightbulb_service)
await setup_test_component(hass, [bulb])
hkid = "00:00:00:00:00:00" hkid = "00:00:00:00:00:00"

View file

@ -1,12 +1,25 @@
"""Basic checks for HomeKitSwitch.""" """Basic checks for HomeKitSwitch."""
from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes
from tests.components.homekit_controller.common import setup_test_component from tests.components.homekit_controller.common import setup_test_component
def create_switch_service(accessory):
"""Define outlet characteristics."""
service = accessory.add_service(ServicesTypes.OUTLET)
on_char = service.add_char(CharacteristicsTypes.ON)
on_char.value = False
outlet_in_use = service.add_char(CharacteristicsTypes.OUTLET_IN_USE)
outlet_in_use.value = False
async def test_switch_change_outlet_state(hass, utcnow): async def test_switch_change_outlet_state(hass, utcnow):
"""Test that we can turn a HomeKit outlet on and off again.""" """Test that we can turn a HomeKit outlet on and off again."""
from homekit.model.services import OutletService helper = await setup_test_component(hass, create_switch_service)
helper = await setup_test_component(hass, [OutletService()])
await hass.services.async_call( await hass.services.async_call(
"switch", "turn_on", {"entity_id": "switch.testdevice"}, blocking=True "switch", "turn_on", {"entity_id": "switch.testdevice"}, blocking=True
@ -21,9 +34,7 @@ async def test_switch_change_outlet_state(hass, utcnow):
async def test_switch_read_outlet_state(hass, utcnow): async def test_switch_read_outlet_state(hass, utcnow):
"""Test that we can read the state of a HomeKit outlet accessory.""" """Test that we can read the state of a HomeKit outlet accessory."""
from homekit.model.services import OutletService helper = await setup_test_component(hass, create_switch_service)
helper = await setup_test_component(hass, [OutletService()])
# Initial state is that the switch is off and the outlet isn't in use # Initial state is that the switch is off and the outlet isn't in use
switch_1 = await helper.poll_and_get_state() switch_1 = await helper.poll_and_get_state()