HomeKit Restructure (new config options) (#12997)

* Restructure
* Pincode will now be autogenerated and display using a persistence notification
* Added 'homekit.start' service
* Added config options
* Renamed files for types
* Improved tests
* Changes (based on feedback)
* Removed CONF_PIN_CODE
* Added services.yaml
* Service will only be registered if auto_start=False
* Bugfix names, changed default port
* Generate aids with zlib.adler32
* Added entity filter, minor changes
* Small changes
This commit is contained in:
cdce8p 2018-03-15 02:48:21 +01:00 committed by GitHub
parent 64f18c62f4
commit d348f09d3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1038 additions and 715 deletions

View File

@ -3,154 +3,199 @@
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/homekit/ https://home-assistant.io/components/homekit/
""" """
import asyncio
import logging import logging
import re from zlib import adler32
import voluptuous as vol import voluptuous as vol
from homeassistant.const import (
ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, CONF_PORT,
TEMP_CELSIUS, TEMP_FAHRENHEIT,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
from homeassistant.components.climate import ( from homeassistant.components.climate import (
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW) SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
from homeassistant.components.cover import SUPPORT_SET_POSITION
from homeassistant.const import (
ATTR_CODE, ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT,
CONF_PORT, TEMP_CELSIUS, TEMP_FAHRENHEIT,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entityfilter import FILTER_SCHEMA
from homeassistant.util import get_local_ip from homeassistant.util import get_local_ip
from homeassistant.util.decorator import Registry from homeassistant.util.decorator import Registry
from .const import (
DOMAIN, HOMEKIT_FILE, CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FILTER,
DEFAULT_PORT, DEFAULT_AUTO_START, SERVICE_HOMEKIT_START)
from .util import (
validate_entity_config, show_setup_message)
TYPES = Registry() TYPES = Registry()
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_RE_VALID_PINCODE = r"^(\d{3}-\d{2}-\d{3})$"
DOMAIN = 'homekit'
REQUIREMENTS = ['HAP-python==1.1.7'] REQUIREMENTS = ['HAP-python==1.1.7']
BRIDGE_NAME = 'Home Assistant'
CONF_PIN_CODE = 'pincode'
HOMEKIT_FILE = '.homekit.state'
def valid_pin(value):
"""Validate pin code value."""
match = re.match(_RE_VALID_PINCODE, str(value).strip())
if not match:
raise vol.Invalid("Pin must be in the format: '123-45-678'")
return match.group(0)
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All({ DOMAIN: vol.All({
vol.Optional(CONF_PORT, default=51826): vol.Coerce(int), vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_PIN_CODE, default='123-45-678'): valid_pin, vol.Optional(CONF_AUTO_START, default=DEFAULT_AUTO_START): cv.boolean,
vol.Optional(CONF_FILTER, default={}): FILTER_SCHEMA,
vol.Optional(CONF_ENTITY_CONFIG, default={}): validate_entity_config,
}) })
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
@asyncio.coroutine async def async_setup(hass, config):
def async_setup(hass, config):
"""Setup the HomeKit component.""" """Setup the HomeKit component."""
_LOGGER.debug("Begin setup HomeKit") _LOGGER.debug('Begin setup HomeKit')
conf = config[DOMAIN] conf = config[DOMAIN]
port = conf.get(CONF_PORT) port = conf[CONF_PORT]
pin = str.encode(conf.get(CONF_PIN_CODE)) auto_start = conf[CONF_AUTO_START]
entity_filter = conf[CONF_FILTER]
entity_config = conf[CONF_ENTITY_CONFIG]
homekit = HomeKit(hass, port) homekit = HomeKit(hass, port, entity_filter, entity_config)
homekit.setup_bridge(pin) homekit.setup()
if auto_start:
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, homekit.start)
return True
def handle_homekit_service_start(service):
"""Handle start HomeKit service call."""
if homekit.started:
_LOGGER.warning('HomeKit is already running')
return
homekit.start()
hass.services.async_register(DOMAIN, SERVICE_HOMEKIT_START,
handle_homekit_service_start)
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, homekit.start_driver)
return True return True
def import_types(): def get_accessory(hass, state, aid, config):
"""Import all types from files in the HomeKit directory."""
_LOGGER.debug("Import type files.")
# pylint: disable=unused-variable
from . import ( # noqa F401
covers, security_systems, sensors, switches, thermostats)
def get_accessory(hass, state):
"""Take state and return an accessory object if supported.""" """Take state and return an accessory object if supported."""
_LOGGER.debug('%s: <aid=%d config=%s>')
if not aid:
_LOGGER.warning('The entitiy "%s" is not supported, since it '
'generates an invalid aid, please change it.',
state.entity_id)
return None
if state.domain == 'sensor': if state.domain == 'sensor':
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
if unit == TEMP_CELSIUS or unit == TEMP_FAHRENHEIT: if unit == TEMP_CELSIUS or unit == TEMP_FAHRENHEIT:
_LOGGER.debug("Add \"%s\" as \"%s\"", _LOGGER.debug('Add "%s" as "%s"',
state.entity_id, 'TemperatureSensor') state.entity_id, 'TemperatureSensor')
return TYPES['TemperatureSensor'](hass, state.entity_id, return TYPES['TemperatureSensor'](hass, state.entity_id,
state.name) state.name, aid=aid)
elif state.domain == 'cover': elif state.domain == 'cover':
# Only add covers that support set_cover_position # Only add covers that support set_cover_position
if state.attributes.get(ATTR_SUPPORTED_FEATURES) & 4: features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
_LOGGER.debug("Add \"%s\" as \"%s\"", if features & SUPPORT_SET_POSITION:
state.entity_id, 'Window') _LOGGER.debug('Add "%s" as "%s"',
return TYPES['Window'](hass, state.entity_id, state.name) state.entity_id, 'WindowCovering')
return TYPES['WindowCovering'](hass, state.entity_id, state.name,
aid=aid)
elif state.domain == 'alarm_control_panel': elif state.domain == 'alarm_control_panel':
_LOGGER.debug("Add \"%s\" as \"%s\"", state.entity_id, _LOGGER.debug('Add "%s" as "%s"', state.entity_id,
'SecuritySystem') 'SecuritySystem')
return TYPES['SecuritySystem'](hass, state.entity_id, state.name) return TYPES['SecuritySystem'](hass, state.entity_id, state.name,
alarm_code=config[ATTR_CODE], aid=aid)
elif state.domain == 'climate': elif state.domain == 'climate':
support_auto = False features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
features = state.attributes.get(ATTR_SUPPORTED_FEATURES) support_temp_range = SUPPORT_TARGET_TEMPERATURE_LOW | \
SUPPORT_TARGET_TEMPERATURE_HIGH
# Check if climate device supports auto mode # Check if climate device supports auto mode
if (features & SUPPORT_TARGET_TEMPERATURE_HIGH) \ support_auto = bool(features & support_temp_range)
and (features & SUPPORT_TARGET_TEMPERATURE_LOW):
support_auto = True _LOGGER.debug('Add "%s" as "%s"', state.entity_id, 'Thermostat')
_LOGGER.debug("Add \"%s\" as \"%s\"", state.entity_id, 'Thermostat')
return TYPES['Thermostat'](hass, state.entity_id, return TYPES['Thermostat'](hass, state.entity_id,
state.name, support_auto) state.name, support_auto, aid=aid)
elif state.domain == 'switch' or state.domain == 'remote' \ elif state.domain == 'switch' or state.domain == 'remote' \
or state.domain == 'input_boolean': or state.domain == 'input_boolean':
_LOGGER.debug("Add \"%s\" as \"%s\"", state.entity_id, 'Switch') _LOGGER.debug('Add "%s" as "%s"', state.entity_id, 'Switch')
return TYPES['Switch'](hass, state.entity_id, state.name) return TYPES['Switch'](hass, state.entity_id, state.name, aid=aid)
_LOGGER.warning('The entity "%s" is not supported yet',
state.entity_id)
return None return None
def generate_aid(entity_id):
"""Generate accessory aid with zlib adler32."""
aid = adler32(entity_id.encode('utf-8'))
if aid == 0 or aid == 1:
return None
return aid
class HomeKit(): class HomeKit():
"""Class to handle all actions between HomeKit and Home Assistant.""" """Class to handle all actions between HomeKit and Home Assistant."""
def __init__(self, hass, port): def __init__(self, hass, port, entity_filter, entity_config):
"""Initialize a HomeKit object.""" """Initialize a HomeKit object."""
self._hass = hass self._hass = hass
self._port = port self._port = port
self._filter = entity_filter
self._config = entity_config
self.started = False
self.bridge = None self.bridge = None
self.driver = None self.driver = None
def setup_bridge(self, pin): def setup(self):
"""Setup the bridge component to track all accessories.""" """Setup bridge and accessory driver."""
from .accessories import HomeBridge from .accessories import HomeBridge, HomeDriver
self.bridge = HomeBridge(BRIDGE_NAME, 'homekit.bridge', pin)
def start_driver(self, event): self._hass.bus.async_listen_once(
"""Start the accessory driver.""" EVENT_HOMEASSISTANT_STOP, self.stop)
from pyhap.accessory_driver import AccessoryDriver
self._hass.bus.listen_once(
EVENT_HOMEASSISTANT_STOP, self.stop_driver)
import_types() path = self._hass.config.path(HOMEKIT_FILE)
_LOGGER.debug("Start adding accessories.") self.bridge = HomeBridge(self._hass)
for state in self._hass.states.all(): self.driver = HomeDriver(self.bridge, self._port, get_local_ip(), path)
acc = get_accessory(self._hass, state)
def add_bridge_accessory(self, state):
"""Try adding accessory to bridge if configured beforehand."""
if not state or not self._filter(state.entity_id):
return
aid = generate_aid(state.entity_id)
conf = self._config.pop(state.entity_id, {})
acc = get_accessory(self._hass, state, aid, conf)
if acc is not None: if acc is not None:
self.bridge.add_accessory(acc) self.bridge.add_accessory(acc)
ip_address = get_local_ip() def start(self, *args):
path = self._hass.config.path(HOMEKIT_FILE) """Start the accessory driver."""
self.driver = AccessoryDriver(self.bridge, self._port, if self.started:
ip_address, path) return
_LOGGER.debug("Driver started") self.started = True
# pylint: disable=unused-variable
from . import ( # noqa F401
type_covers, type_security_systems, type_sensors,
type_switches, type_thermostats)
for state in self._hass.states.all():
self.add_bridge_accessory(state)
for entity_id in self._config:
_LOGGER.warning('The entity "%s" was not setup when HomeKit '
'was started', entity_id)
self.bridge.set_broker(self.driver)
if not self.bridge.paired:
show_setup_message(self.bridge, self._hass)
_LOGGER.debug('Driver start')
self.driver.start() self.driver.start()
def stop_driver(self, event): def stop(self, *args):
"""Stop the accessory driver.""" """Stop the accessory driver."""
_LOGGER.debug("Driver stop") if not self.started:
if self.driver is not None: return
_LOGGER.debug('Driver stop')
if self.driver and self.driver.run_sentinel:
self.driver.stop() self.driver.stop()

View File

@ -2,15 +2,31 @@
import logging import logging
from pyhap.accessory import Accessory, Bridge, Category from pyhap.accessory import Accessory, Bridge, Category
from pyhap.accessory_driver import AccessoryDriver
from .const import ( from .const import (
SERV_ACCESSORY_INFO, SERV_BRIDGING_STATE, MANUFACTURER, ACCESSORY_MODEL, ACCESSORY_NAME, BRIDGE_MODEL, BRIDGE_NAME,
CHAR_MODEL, CHAR_MANUFACTURER, CHAR_NAME, CHAR_SERIAL_NUMBER) MANUFACTURER, SERV_ACCESSORY_INFO, SERV_BRIDGING_STATE,
CHAR_MANUFACTURER, CHAR_MODEL, CHAR_NAME, CHAR_SERIAL_NUMBER)
from .util import (
show_setup_message, dismiss_setup_message)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def add_preload_service(acc, service, chars=None):
"""Define and return a service to be available for the accessory."""
from pyhap.loader import get_serv_loader, get_char_loader
service = get_serv_loader().get(service)
if chars:
chars = chars if isinstance(chars, list) else [chars]
for char_name in chars:
char = get_char_loader().get(char_name)
service.add_characteristic(char)
acc.add_service(service)
return service
def set_accessory_info(acc, name, model, manufacturer=MANUFACTURER, def set_accessory_info(acc, name, model, manufacturer=MANUFACTURER,
serial_number='0000'): serial_number='0000'):
"""Set the default accessory information.""" """Set the default accessory information."""
@ -21,36 +37,23 @@ def set_accessory_info(acc, name, model, manufacturer=MANUFACTURER,
service.get_characteristic(CHAR_SERIAL_NUMBER).set_value(serial_number) service.get_characteristic(CHAR_SERIAL_NUMBER).set_value(serial_number)
def add_preload_service(acc, service, chars=None, opt_chars=None): def override_properties(char, properties=None, valid_values=None):
"""Define and return a service to be available for the accessory.""" """Override characteristic property values and valid values."""
from pyhap.loader import get_serv_loader, get_char_loader if properties:
service = get_serv_loader().get(service) char.properties.update(properties)
if chars:
chars = chars if isinstance(chars, list) else [chars]
for char_name in chars:
char = get_char_loader().get(char_name)
service.add_characteristic(char)
if opt_chars:
opt_chars = opt_chars if isinstance(opt_chars, list) else [opt_chars]
for opt_char_name in opt_chars:
opt_char = get_char_loader().get(opt_char_name)
service.add_opt_characteristic(opt_char)
acc.add_service(service)
return service
if valid_values:
def override_properties(char, new_properties): char.properties['ValidValues'].update(valid_values)
"""Override characteristic property values."""
char.properties.update(new_properties)
class HomeAccessory(Accessory): class HomeAccessory(Accessory):
"""Class to extend the Accessory class.""" """Adapter class for Accessory."""
def __init__(self, display_name, model, category='OTHER', **kwargs): def __init__(self, name=ACCESSORY_NAME, model=ACCESSORY_MODEL,
category='OTHER', **kwargs):
"""Initialize a Accessory object.""" """Initialize a Accessory object."""
super().__init__(display_name, **kwargs) super().__init__(name, **kwargs)
set_accessory_info(self, display_name, model) set_accessory_info(self, name, model)
self.category = getattr(Category, category, Category.OTHER) self.category = getattr(Category, category, Category.OTHER)
def _set_services(self): def _set_services(self):
@ -58,13 +61,37 @@ class HomeAccessory(Accessory):
class HomeBridge(Bridge): class HomeBridge(Bridge):
"""Class to extend the Bridge class.""" """Adapter class for Bridge."""
def __init__(self, display_name, model, pincode, **kwargs): def __init__(self, hass, name=BRIDGE_NAME,
model=BRIDGE_MODEL, **kwargs):
"""Initialize a Bridge object.""" """Initialize a Bridge object."""
super().__init__(display_name, pincode=pincode, **kwargs) super().__init__(name, **kwargs)
set_accessory_info(self, display_name, model) set_accessory_info(self, name, model)
self._hass = hass
def _set_services(self): def _set_services(self):
add_preload_service(self, SERV_ACCESSORY_INFO) add_preload_service(self, SERV_ACCESSORY_INFO)
add_preload_service(self, SERV_BRIDGING_STATE) add_preload_service(self, SERV_BRIDGING_STATE)
def setup_message(self):
"""Prevent print of pyhap setup message to terminal."""
pass
def add_paired_client(self, client_uuid, client_public):
"""Override super function to dismiss setup message if paired."""
super().add_paired_client(client_uuid, client_public)
dismiss_setup_message(self._hass)
def remove_paired_client(self, client_uuid):
"""Override super function to show setup message if unpaired."""
super().remove_paired_client(client_uuid)
show_setup_message(self, self._hass)
class HomeDriver(AccessoryDriver):
"""Adapter class for AccessoryDriver."""
def __init__(self, *args, **kwargs):
"""Initialize a AccessoryDriver object."""
super().__init__(*args, **kwargs)

View File

@ -1,7 +1,30 @@
"""Constants used be the HomeKit component.""" """Constants used be the HomeKit component."""
# #### MISC ####
DOMAIN = 'homekit'
HOMEKIT_FILE = '.homekit.state'
HOMEKIT_NOTIFY_ID = 4663548
# #### CONFIG ####
CONF_AUTO_START = 'auto_start'
CONF_ENTITY_CONFIG = 'entity_config'
CONF_FILTER = 'filter'
# #### CONFIG DEFAULTS ####
DEFAULT_AUTO_START = True
DEFAULT_PORT = 51827
# #### HOMEKIT COMPONENT SERVICES ####
SERVICE_HOMEKIT_START = 'start'
# #### STRING CONSTANTS ####
ACCESSORY_MODEL = 'homekit.accessory'
ACCESSORY_NAME = 'Home Accessory'
BRIDGE_MODEL = 'homekit.bridge'
BRIDGE_NAME = 'Home Assistant'
MANUFACTURER = 'HomeAssistant' MANUFACTURER = 'HomeAssistant'
# Services
# #### Services ####
SERV_ACCESSORY_INFO = 'AccessoryInformation' SERV_ACCESSORY_INFO = 'AccessoryInformation'
SERV_BRIDGING_STATE = 'BridgingState' SERV_BRIDGING_STATE = 'BridgingState'
SERV_SECURITY_SYSTEM = 'SecuritySystem' SERV_SECURITY_SYSTEM = 'SecuritySystem'
@ -10,7 +33,8 @@ SERV_TEMPERATURE_SENSOR = 'TemperatureSensor'
SERV_THERMOSTAT = 'Thermostat' SERV_THERMOSTAT = 'Thermostat'
SERV_WINDOW_COVERING = 'WindowCovering' SERV_WINDOW_COVERING = 'WindowCovering'
# Characteristics
# #### Characteristics ####
CHAR_ACC_IDENTIFIER = 'AccessoryIdentifier' CHAR_ACC_IDENTIFIER = 'AccessoryIdentifier'
CHAR_CATEGORY = 'Category' CHAR_CATEGORY = 'Category'
CHAR_COOLING_THRESHOLD_TEMPERATURE = 'CoolingThresholdTemperature' CHAR_COOLING_THRESHOLD_TEMPERATURE = 'CoolingThresholdTemperature'
@ -33,5 +57,5 @@ CHAR_TARGET_SECURITY_STATE = 'SecuritySystemTargetState'
CHAR_TARGET_TEMPERATURE = 'TargetTemperature' CHAR_TARGET_TEMPERATURE = 'TargetTemperature'
CHAR_TEMP_DISPLAY_UNITS = 'TemperatureDisplayUnits' CHAR_TEMP_DISPLAY_UNITS = 'TemperatureDisplayUnits'
# Properties # #### Properties ####
PROP_CELSIUS = {'minValue': -273, 'maxValue': 999} PROP_CELSIUS = {'minValue': -273, 'maxValue': 999}

View File

@ -0,0 +1,4 @@
# Describes the format for available HomeKit services
start:
description: Starts the HomeKit component driver.

View File

@ -14,16 +14,17 @@ from .const import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@TYPES.register('Window') @TYPES.register('WindowCovering')
class Window(HomeAccessory): class WindowCovering(HomeAccessory):
"""Generate a Window accessory for a cover entity. """Generate a Window accessory for a cover entity.
The cover entity must support: set_cover_position. The cover entity must support: set_cover_position.
""" """
def __init__(self, hass, entity_id, display_name): def __init__(self, hass, entity_id, display_name, *args, **kwargs):
"""Initialize a Window accessory object.""" """Initialize a Window accessory object."""
super().__init__(display_name, entity_id, 'WINDOW') super().__init__(display_name, entity_id, 'WINDOW_COVERING',
*args, **kwargs)
self._hass = hass self._hass = hass
self._entity_id = entity_id self._entity_id = entity_id
@ -31,12 +32,12 @@ class Window(HomeAccessory):
self.current_position = None self.current_position = None
self.homekit_target = None self.homekit_target = None
self.serv_cover = add_preload_service(self, SERV_WINDOW_COVERING) serv_cover = add_preload_service(self, SERV_WINDOW_COVERING)
self.char_current_position = self.serv_cover. \ self.char_current_position = serv_cover. \
get_characteristic(CHAR_CURRENT_POSITION) get_characteristic(CHAR_CURRENT_POSITION)
self.char_target_position = self.serv_cover. \ self.char_target_position = serv_cover. \
get_characteristic(CHAR_TARGET_POSITION) get_characteristic(CHAR_TARGET_POSITION)
self.char_position_state = self.serv_cover. \ self.char_position_state = serv_cover. \
get_characteristic(CHAR_POSITION_STATE) get_characteristic(CHAR_POSITION_STATE)
self.char_current_position.value = 0 self.char_current_position.value = 0
self.char_target_position.value = 0 self.char_target_position.value = 0
@ -55,15 +56,14 @@ class Window(HomeAccessory):
def move_cover(self, value): def move_cover(self, value):
"""Move cover to value if call came from HomeKit.""" """Move cover to value if call came from HomeKit."""
if value != self.current_position: if value != self.current_position:
_LOGGER.debug("%s: Set position to %d", self._entity_id, value) _LOGGER.debug('%s: Set position to %d', self._entity_id, value)
self.homekit_target = value self.homekit_target = value
if value > self.current_position: if value > self.current_position:
self.char_position_state.set_value(1) self.char_position_state.set_value(1)
elif value < self.current_position: elif value < self.current_position:
self.char_position_state.set_value(0) self.char_position_state.set_value(0)
self._hass.services.call( self._hass.components.cover.set_cover_position(
'cover', 'set_cover_position', value, self._entity_id)
{'entity_id': self._entity_id, 'position': value})
def update_cover_position(self, entity_id=None, old_state=None, def update_cover_position(self, entity_id=None, old_state=None,
new_state=None): new_state=None):
@ -71,9 +71,10 @@ class Window(HomeAccessory):
if new_state is None: if new_state is None:
return return
current_position = new_state.attributes[ATTR_CURRENT_POSITION] current_position = new_state.attributes.get(ATTR_CURRENT_POSITION)
if current_position is None: if current_position is None:
return return
self.current_position = int(current_position) self.current_position = int(current_position)
self.char_current_position.set_value(self.current_position) self.char_current_position.set_value(self.current_position)

View File

@ -28,9 +28,11 @@ STATE_TO_SERVICE = {STATE_ALARM_DISARMED: 'alarm_disarm',
class SecuritySystem(HomeAccessory): class SecuritySystem(HomeAccessory):
"""Generate an SecuritySystem accessory for an alarm control panel.""" """Generate an SecuritySystem accessory for an alarm control panel."""
def __init__(self, hass, entity_id, display_name, alarm_code=None): def __init__(self, hass, entity_id, display_name,
alarm_code, *args, **kwargs):
"""Initialize a SecuritySystem accessory object.""" """Initialize a SecuritySystem accessory object."""
super().__init__(display_name, entity_id, 'ALARM_SYSTEM') super().__init__(display_name, entity_id, 'ALARM_SYSTEM',
*args, **kwargs)
self._hass = hass self._hass = hass
self._entity_id = entity_id self._entity_id = entity_id
@ -38,11 +40,11 @@ class SecuritySystem(HomeAccessory):
self.flag_target_state = False self.flag_target_state = False
self.service_alarm = add_preload_service(self, SERV_SECURITY_SYSTEM) serv_alarm = add_preload_service(self, SERV_SECURITY_SYSTEM)
self.char_current_state = self.service_alarm. \ self.char_current_state = serv_alarm. \
get_characteristic(CHAR_CURRENT_SECURITY_STATE) get_characteristic(CHAR_CURRENT_SECURITY_STATE)
self.char_current_state.value = 3 self.char_current_state.value = 3
self.char_target_state = self.service_alarm. \ self.char_target_state = serv_alarm. \
get_characteristic(CHAR_TARGET_SECURITY_STATE) get_characteristic(CHAR_TARGET_SECURITY_STATE)
self.char_target_state.value = 3 self.char_target_state.value = 3
@ -58,15 +60,13 @@ class SecuritySystem(HomeAccessory):
def set_security_state(self, value): def set_security_state(self, value):
"""Move security state to value if call came from HomeKit.""" """Move security state to value if call came from HomeKit."""
_LOGGER.debug("%s: Set security state to %d", _LOGGER.debug('%s: Set security state to %d',
self._entity_id, value) self._entity_id, value)
self.flag_target_state = True self.flag_target_state = True
hass_value = HOMEKIT_TO_HASS[value] hass_value = HOMEKIT_TO_HASS[value]
service = STATE_TO_SERVICE[hass_value] service = STATE_TO_SERVICE[hass_value]
params = {ATTR_ENTITY_ID: self._entity_id} params = {ATTR_ENTITY_ID: self._entity_id, ATTR_CODE: self._alarm_code}
if self._alarm_code is not None:
params[ATTR_CODE] = self._alarm_code
self._hass.services.call('alarm_control_panel', service, params) self._hass.services.call('alarm_control_panel', service, params)
def update_security_state(self, entity_id=None, def update_security_state(self, entity_id=None,
@ -78,15 +78,15 @@ class SecuritySystem(HomeAccessory):
hass_state = new_state.state hass_state = new_state.state
if hass_state not in HASS_TO_HOMEKIT: if hass_state not in HASS_TO_HOMEKIT:
return return
current_security_state = HASS_TO_HOMEKIT[hass_state] current_security_state = HASS_TO_HOMEKIT[hass_state]
self.char_current_state.set_value(current_security_state) self.char_current_state.set_value(current_security_state,
_LOGGER.debug("%s: Updated current state to %s (%d)", should_callback=False)
self._entity_id, hass_state, _LOGGER.debug('%s: Updated current state to %s (%d)',
current_security_state) self._entity_id, hass_state, current_security_state)
if not self.flag_target_state: if not self.flag_target_state:
self.char_target_state.set_value(current_security_state, self.char_target_state.set_value(current_security_state,
should_callback=False) should_callback=False)
elif self.char_target_state.get_value() \ if self.char_target_state.value == self.char_current_state.value:
== self.char_current_state.get_value():
self.flag_target_state = False self.flag_target_state = False

View File

@ -36,16 +36,15 @@ class TemperatureSensor(HomeAccessory):
Sensor entity must return temperature in °C, °F. Sensor entity must return temperature in °C, °F.
""" """
def __init__(self, hass, entity_id, display_name): def __init__(self, hass, entity_id, display_name, *args, **kwargs):
"""Initialize a TemperatureSensor accessory object.""" """Initialize a TemperatureSensor accessory object."""
super().__init__(display_name, entity_id, 'SENSOR') super().__init__(display_name, entity_id, 'SENSOR', *args, **kwargs)
self._hass = hass self._hass = hass
self._entity_id = entity_id self._entity_id = entity_id
self.serv_temp = add_preload_service(self, SERV_TEMPERATURE_SENSOR) serv_temp = add_preload_service(self, SERV_TEMPERATURE_SENSOR)
self.char_temp = self.serv_temp. \ self.char_temp = serv_temp.get_characteristic(CHAR_CURRENT_TEMPERATURE)
get_characteristic(CHAR_CURRENT_TEMPERATURE)
override_properties(self.char_temp, PROP_CELSIUS) override_properties(self.char_temp, PROP_CELSIUS)
self.char_temp.value = 0 self.char_temp.value = 0
self.unit = None self.unit = None
@ -68,5 +67,5 @@ class TemperatureSensor(HomeAccessory):
temperature = calc_temperature(new_state.state, unit) temperature = calc_temperature(new_state.state, unit)
if temperature is not None: if temperature is not None:
self.char_temp.set_value(temperature) self.char_temp.set_value(temperature)
_LOGGER.debug("%s: Current temperature set to %d°C", _LOGGER.debug('%s: Current temperature set to %d°C',
self._entity_id, temperature) self._entity_id, temperature)

View File

@ -1,7 +1,8 @@
"""Class to hold all switch accessories.""" """Class to hold all switch accessories."""
import logging import logging
from homeassistant.const import ATTR_ENTITY_ID from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_ON)
from homeassistant.core import split_entity_id from homeassistant.core import split_entity_id
from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.event import async_track_state_change
@ -16,9 +17,9 @@ _LOGGER = logging.getLogger(__name__)
class Switch(HomeAccessory): class Switch(HomeAccessory):
"""Generate a Switch accessory.""" """Generate a Switch accessory."""
def __init__(self, hass, entity_id, display_name): def __init__(self, hass, entity_id, display_name, *args, **kwargs):
"""Initialize a Switch accessory object to represent a remote.""" """Initialize a Switch accessory object to represent a remote."""
super().__init__(display_name, entity_id, 'SWITCH') super().__init__(display_name, entity_id, 'SWITCH', *args, **kwargs)
self._hass = hass self._hass = hass
self._entity_id = entity_id self._entity_id = entity_id
@ -26,8 +27,8 @@ class Switch(HomeAccessory):
self.flag_target_state = False self.flag_target_state = False
self.service_switch = add_preload_service(self, SERV_SWITCH) serv_switch = add_preload_service(self, SERV_SWITCH)
self.char_on = self.service_switch.get_characteristic(CHAR_ON) self.char_on = serv_switch.get_characteristic(CHAR_ON)
self.char_on.value = False self.char_on.value = False
self.char_on.setter_callback = self.set_state self.char_on.setter_callback = self.set_state
@ -41,10 +42,10 @@ class Switch(HomeAccessory):
def set_state(self, value): def set_state(self, value):
"""Move switch state to value if call came from HomeKit.""" """Move switch state to value if call came from HomeKit."""
_LOGGER.debug("%s: Set switch state to %s", _LOGGER.debug('%s: Set switch state to %s',
self._entity_id, value) self._entity_id, value)
self.flag_target_state = True self.flag_target_state = True
service = 'turn_on' if value else 'turn_off' service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
self._hass.services.call(self._domain, service, self._hass.services.call(self._domain, service,
{ATTR_ENTITY_ID: self._entity_id}) {ATTR_ENTITY_ID: self._entity_id})
@ -53,10 +54,10 @@ class Switch(HomeAccessory):
if new_state is None: if new_state is None:
return return
current_state = (new_state.state == 'on') current_state = (new_state.state == STATE_ON)
if not self.flag_target_state: if not self.flag_target_state:
_LOGGER.debug("%s: Set current state to %s", _LOGGER.debug('%s: Set current state to %s',
self._entity_id, current_state) self._entity_id, current_state)
self.char_on.set_value(current_state, should_callback=False) self.char_on.set_value(current_state, should_callback=False)
else:
self.flag_target_state = False self.flag_target_state = False

View File

@ -7,8 +7,7 @@ from homeassistant.components.climate import (
ATTR_OPERATION_MODE, ATTR_OPERATION_LIST, ATTR_OPERATION_MODE, ATTR_OPERATION_LIST,
STATE_HEAT, STATE_COOL, STATE_AUTO) STATE_HEAT, STATE_COOL, STATE_AUTO)
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, TEMP_FAHRENHEIT)
TEMP_CELSIUS, TEMP_FAHRENHEIT)
from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.event import async_track_state_change
from . import TYPES from . import TYPES
@ -33,9 +32,11 @@ HC_HOMEKIT_TO_HASS = {c: s for s, c in HC_HASS_TO_HOMEKIT.items()}
class Thermostat(HomeAccessory): class Thermostat(HomeAccessory):
"""Generate a Thermostat accessory for a climate.""" """Generate a Thermostat accessory for a climate."""
def __init__(self, hass, entity_id, display_name, support_auto=False): def __init__(self, hass, entity_id, display_name,
support_auto, *args, **kwargs):
"""Initialize a Thermostat accessory object.""" """Initialize a Thermostat accessory object."""
super().__init__(display_name, entity_id, 'THERMOSTAT') super().__init__(display_name, entity_id, 'THERMOSTAT',
*args, **kwargs)
self._hass = hass self._hass = hass
self._entity_id = entity_id self._entity_id = entity_id
@ -46,48 +47,47 @@ class Thermostat(HomeAccessory):
self.coolingthresh_flag_target_state = False self.coolingthresh_flag_target_state = False
self.heatingthresh_flag_target_state = False self.heatingthresh_flag_target_state = False
extra_chars = None
# Add additional characteristics if auto mode is supported # Add additional characteristics if auto mode is supported
if support_auto: extra_chars = [
extra_chars = [CHAR_COOLING_THRESHOLD_TEMPERATURE, CHAR_COOLING_THRESHOLD_TEMPERATURE,
CHAR_HEATING_THRESHOLD_TEMPERATURE] CHAR_HEATING_THRESHOLD_TEMPERATURE] if support_auto else None
# Preload the thermostat service # Preload the thermostat service
self.service_thermostat = add_preload_service(self, SERV_THERMOSTAT, serv_thermostat = add_preload_service(self, SERV_THERMOSTAT,
extra_chars) extra_chars)
# Current and target mode characteristics # Current and target mode characteristics
self.char_current_heat_cool = self.service_thermostat. \ self.char_current_heat_cool = serv_thermostat. \
get_characteristic(CHAR_CURRENT_HEATING_COOLING) get_characteristic(CHAR_CURRENT_HEATING_COOLING)
self.char_current_heat_cool.value = 0 self.char_current_heat_cool.value = 0
self.char_target_heat_cool = self.service_thermostat. \ self.char_target_heat_cool = serv_thermostat. \
get_characteristic(CHAR_TARGET_HEATING_COOLING) get_characteristic(CHAR_TARGET_HEATING_COOLING)
self.char_target_heat_cool.value = 0 self.char_target_heat_cool.value = 0
self.char_target_heat_cool.setter_callback = self.set_heat_cool self.char_target_heat_cool.setter_callback = self.set_heat_cool
# Current and target temperature characteristics # Current and target temperature characteristics
self.char_current_temp = self.service_thermostat. \ self.char_current_temp = serv_thermostat. \
get_characteristic(CHAR_CURRENT_TEMPERATURE) get_characteristic(CHAR_CURRENT_TEMPERATURE)
self.char_current_temp.value = 21.0 self.char_current_temp.value = 21.0
self.char_target_temp = self.service_thermostat. \ self.char_target_temp = serv_thermostat. \
get_characteristic(CHAR_TARGET_TEMPERATURE) get_characteristic(CHAR_TARGET_TEMPERATURE)
self.char_target_temp.value = 21.0 self.char_target_temp.value = 21.0
self.char_target_temp.setter_callback = self.set_target_temperature self.char_target_temp.setter_callback = self.set_target_temperature
# Display units characteristic # Display units characteristic
self.char_display_units = self.service_thermostat. \ self.char_display_units = serv_thermostat. \
get_characteristic(CHAR_TEMP_DISPLAY_UNITS) get_characteristic(CHAR_TEMP_DISPLAY_UNITS)
self.char_display_units.value = 0 self.char_display_units.value = 0
# If the device supports it: high and low temperature characteristics # If the device supports it: high and low temperature characteristics
if support_auto: if support_auto:
self.char_cooling_thresh_temp = self.service_thermostat. \ self.char_cooling_thresh_temp = serv_thermostat. \
get_characteristic(CHAR_COOLING_THRESHOLD_TEMPERATURE) get_characteristic(CHAR_COOLING_THRESHOLD_TEMPERATURE)
self.char_cooling_thresh_temp.value = 23.0 self.char_cooling_thresh_temp.value = 23.0
self.char_cooling_thresh_temp.setter_callback = \ self.char_cooling_thresh_temp.setter_callback = \
self.set_cooling_threshold self.set_cooling_threshold
self.char_heating_thresh_temp = self.service_thermostat. \ self.char_heating_thresh_temp = serv_thermostat. \
get_characteristic(CHAR_HEATING_THRESHOLD_TEMPERATURE) get_characteristic(CHAR_HEATING_THRESHOLD_TEMPERATURE)
self.char_heating_thresh_temp.value = 19.0 self.char_heating_thresh_temp.value = 19.0
self.char_heating_thresh_temp.setter_callback = \ self.char_heating_thresh_temp.setter_callback = \
@ -107,47 +107,40 @@ class Thermostat(HomeAccessory):
def set_heat_cool(self, value): def set_heat_cool(self, value):
"""Move operation mode to value if call came from HomeKit.""" """Move operation mode to value if call came from HomeKit."""
if value in HC_HOMEKIT_TO_HASS: if value in HC_HOMEKIT_TO_HASS:
_LOGGER.debug("%s: Set heat-cool to %d", self._entity_id, value) _LOGGER.debug('%s: Set heat-cool to %d', self._entity_id, value)
self.heat_cool_flag_target_state = True self.heat_cool_flag_target_state = True
hass_value = HC_HOMEKIT_TO_HASS[value] hass_value = HC_HOMEKIT_TO_HASS[value]
self._hass.services.call('climate', 'set_operation_mode', self._hass.components.climate.set_operation_mode(
{ATTR_ENTITY_ID: self._entity_id, operation_mode=hass_value, entity_id=self._entity_id)
ATTR_OPERATION_MODE: hass_value})
def set_cooling_threshold(self, value): def set_cooling_threshold(self, value):
"""Set cooling threshold temp to value if call came from HomeKit.""" """Set cooling threshold temp to value if call came from HomeKit."""
_LOGGER.debug("%s: Set cooling threshold temperature to %.2f", _LOGGER.debug('%s: Set cooling threshold temperature to %.2f',
self._entity_id, value) self._entity_id, value)
self.coolingthresh_flag_target_state = True self.coolingthresh_flag_target_state = True
low = self.char_heating_thresh_temp.get_value() low = self.char_heating_thresh_temp.value
self._hass.services.call( self._hass.components.climate.set_temperature(
'climate', 'set_temperature', entity_id=self._entity_id, target_temp_high=value,
{ATTR_ENTITY_ID: self._entity_id, target_temp_low=low)
ATTR_TARGET_TEMP_HIGH: value,
ATTR_TARGET_TEMP_LOW: low})
def set_heating_threshold(self, value): def set_heating_threshold(self, value):
"""Set heating threshold temp to value if call came from HomeKit.""" """Set heating threshold temp to value if call came from HomeKit."""
_LOGGER.debug("%s: Set heating threshold temperature to %.2f", _LOGGER.debug('%s: Set heating threshold temperature to %.2f',
self._entity_id, value) self._entity_id, value)
self.heatingthresh_flag_target_state = True self.heatingthresh_flag_target_state = True
# Home assistant always wants to set low and high at the same time # Home assistant always wants to set low and high at the same time
high = self.char_cooling_thresh_temp.get_value() high = self.char_cooling_thresh_temp.value
self._hass.services.call( self._hass.components.climate.set_temperature(
'climate', 'set_temperature', entity_id=self._entity_id, target_temp_high=high,
{ATTR_ENTITY_ID: self._entity_id, target_temp_low=value)
ATTR_TARGET_TEMP_LOW: value,
ATTR_TARGET_TEMP_HIGH: high})
def set_target_temperature(self, value): def set_target_temperature(self, value):
"""Set target temperature to value if call came from HomeKit.""" """Set target temperature to value if call came from HomeKit."""
_LOGGER.debug("%s: Set target temperature to %.2f", _LOGGER.debug('%s: Set target temperature to %.2f',
self._entity_id, value) self._entity_id, value)
self.temperature_flag_target_state = True self.temperature_flag_target_state = True
self._hass.services.call( self._hass.components.climate.set_temperature(
'climate', 'set_temperature', temperature=value, entity_id=self._entity_id)
{ATTR_ENTITY_ID: self._entity_id,
ATTR_TEMPERATURE: value})
def update_thermostat(self, entity_id=None, def update_thermostat(self, entity_id=None,
old_state=None, new_state=None): old_state=None, new_state=None):
@ -166,62 +159,58 @@ class Thermostat(HomeAccessory):
if not self.temperature_flag_target_state: if not self.temperature_flag_target_state:
self.char_target_temp.set_value(target_temp, self.char_target_temp.set_value(target_temp,
should_callback=False) should_callback=False)
else:
self.temperature_flag_target_state = False self.temperature_flag_target_state = False
# Update cooling threshold temperature if characteristic exists # Update cooling threshold temperature if characteristic exists
if self.char_cooling_thresh_temp is not None: if self.char_cooling_thresh_temp:
cooling_thresh = new_state.attributes.get(ATTR_TARGET_TEMP_HIGH) cooling_thresh = new_state.attributes.get(ATTR_TARGET_TEMP_HIGH)
if cooling_thresh is not None: if cooling_thresh:
if not self.coolingthresh_flag_target_state: if not self.coolingthresh_flag_target_state:
self.char_cooling_thresh_temp.set_value( self.char_cooling_thresh_temp.set_value(
cooling_thresh, should_callback=False) cooling_thresh, should_callback=False)
else:
self.coolingthresh_flag_target_state = False self.coolingthresh_flag_target_state = False
# Update heating threshold temperature if characteristic exists # Update heating threshold temperature if characteristic exists
if self.char_heating_thresh_temp is not None: if self.char_heating_thresh_temp:
heating_thresh = new_state.attributes.get(ATTR_TARGET_TEMP_LOW) heating_thresh = new_state.attributes.get(ATTR_TARGET_TEMP_LOW)
if heating_thresh is not None: if heating_thresh:
if not self.heatingthresh_flag_target_state: if not self.heatingthresh_flag_target_state:
self.char_heating_thresh_temp.set_value( self.char_heating_thresh_temp.set_value(
heating_thresh, should_callback=False) heating_thresh, should_callback=False)
else:
self.heatingthresh_flag_target_state = False self.heatingthresh_flag_target_state = False
# Update display units # Update display units
display_units = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) display_units = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
if display_units is not None \ if display_units \
and display_units in UNIT_HASS_TO_HOMEKIT: and display_units in UNIT_HASS_TO_HOMEKIT:
self.char_display_units.set_value( self.char_display_units.set_value(
UNIT_HASS_TO_HOMEKIT[display_units]) UNIT_HASS_TO_HOMEKIT[display_units])
# Update target operation mode # Update target operation mode
operation_mode = new_state.attributes.get(ATTR_OPERATION_MODE) operation_mode = new_state.attributes.get(ATTR_OPERATION_MODE)
if operation_mode is not None \ if operation_mode \
and operation_mode in HC_HASS_TO_HOMEKIT: and operation_mode in HC_HASS_TO_HOMEKIT:
if not self.heat_cool_flag_target_state: if not self.heat_cool_flag_target_state:
self.char_target_heat_cool.set_value( self.char_target_heat_cool.set_value(
HC_HASS_TO_HOMEKIT[operation_mode], should_callback=False) HC_HASS_TO_HOMEKIT[operation_mode], should_callback=False)
else:
self.heat_cool_flag_target_state = False self.heat_cool_flag_target_state = False
# Set current operation mode based on temperatures and target mode # Set current operation mode based on temperatures and target mode
if operation_mode == STATE_HEAT: if operation_mode == STATE_HEAT:
if current_temp < target_temp: if isinstance(target_temp, float) and current_temp < target_temp:
current_operation_mode = STATE_HEAT current_operation_mode = STATE_HEAT
else: else:
current_operation_mode = STATE_OFF current_operation_mode = STATE_OFF
elif operation_mode == STATE_COOL: elif operation_mode == STATE_COOL:
if current_temp > target_temp: if isinstance(target_temp, float) and current_temp > target_temp:
current_operation_mode = STATE_COOL current_operation_mode = STATE_COOL
else: else:
current_operation_mode = STATE_OFF current_operation_mode = STATE_OFF
elif operation_mode == STATE_AUTO: elif operation_mode == STATE_AUTO:
# Check if auto is supported # Check if auto is supported
if self.char_cooling_thresh_temp is not None: if self.char_cooling_thresh_temp:
lower_temp = self.char_heating_thresh_temp.get_value() lower_temp = self.char_heating_thresh_temp.value
upper_temp = self.char_cooling_thresh_temp.get_value() upper_temp = self.char_cooling_thresh_temp.value
if current_temp < lower_temp: if current_temp < lower_temp:
current_operation_mode = STATE_HEAT current_operation_mode = STATE_HEAT
elif current_temp > upper_temp: elif current_temp > upper_temp:
@ -232,9 +221,11 @@ class Thermostat(HomeAccessory):
# Check if heating or cooling are supported # Check if heating or cooling are supported
heat = STATE_HEAT in new_state.attributes[ATTR_OPERATION_LIST] heat = STATE_HEAT in new_state.attributes[ATTR_OPERATION_LIST]
cool = STATE_COOL in new_state.attributes[ATTR_OPERATION_LIST] cool = STATE_COOL in new_state.attributes[ATTR_OPERATION_LIST]
if current_temp < target_temp and heat: if isinstance(target_temp, float) and \
current_temp < target_temp and heat:
current_operation_mode = STATE_HEAT current_operation_mode = STATE_HEAT
elif current_temp > target_temp and cool: elif isinstance(target_temp, float) and \
current_temp > target_temp and cool:
current_operation_mode = STATE_COOL current_operation_mode = STATE_COOL
else: else:
current_operation_mode = STATE_OFF current_operation_mode = STATE_OFF

View File

@ -0,0 +1,46 @@
"""Collection of useful functions for the HomeKit component."""
import logging
import voluptuous as vol
from homeassistant.core import split_entity_id
from homeassistant.const import (
ATTR_CODE)
import homeassistant.helpers.config_validation as cv
from .const import HOMEKIT_NOTIFY_ID
_LOGGER = logging.getLogger(__name__)
def validate_entity_config(values):
"""Validate config entry for CONF_ENTITY."""
entities = {}
for key, config in values.items():
entity = cv.entity_id(key)
params = {}
if not isinstance(config, dict):
raise vol.Invalid('The configuration for "{}" must be '
' an dictionary.'.format(entity))
domain, _ = split_entity_id(entity)
if domain == 'alarm_control_panel':
code = config.get(ATTR_CODE)
params[ATTR_CODE] = cv.string(code) if code else None
entities[entity] = params
return entities
def show_setup_message(bridge, hass):
"""Display persistent notification with setup information."""
pin = bridge.pincode.decode()
message = 'To setup Home Assistant in the Home App, enter the ' \
'following code:\n### {}'.format(pin)
hass.components.persistent_notification.create(
message, 'HomeKit Setup', HOMEKIT_NOTIFY_ID)
def dismiss_setup_message(hass):
"""Dismiss persistent notification and remove QR code."""
hass.components.persistent_notification.dismiss(HOMEKIT_NOTIFY_ID)

View File

@ -1 +0,0 @@
"""The tests for the homekit component."""

View File

@ -2,166 +2,164 @@
This includes tests for all mock object types. This includes tests for all mock object types.
""" """
import unittest
from unittest.mock import patch from unittest.mock import call, patch, Mock
# pylint: disable=unused-import
from pyhap.loader import get_serv_loader, get_char_loader # noqa F401
from homeassistant.components.homekit.accessories import ( from homeassistant.components.homekit.accessories import (
set_accessory_info, add_preload_service, override_properties, add_preload_service, set_accessory_info, override_properties,
HomeAccessory, HomeBridge) HomeAccessory, HomeBridge, HomeDriver)
from homeassistant.components.homekit.const import ( from homeassistant.components.homekit.const import (
ACCESSORY_MODEL, ACCESSORY_NAME, BRIDGE_MODEL, BRIDGE_NAME,
SERV_ACCESSORY_INFO, SERV_BRIDGING_STATE, SERV_ACCESSORY_INFO, SERV_BRIDGING_STATE,
CHAR_MODEL, CHAR_MANUFACTURER, CHAR_NAME, CHAR_SERIAL_NUMBER) CHAR_MANUFACTURER, CHAR_MODEL, CHAR_NAME, CHAR_SERIAL_NUMBER)
from tests.mock.homekit import (
get_patch_paths, mock_preload_service,
MockTypeLoader, MockAccessory, MockService, MockChar)
PATH_SERV = 'pyhap.loader.get_serv_loader'
PATH_CHAR = 'pyhap.loader.get_char_loader'
PATH_ACC, _ = get_patch_paths()
@patch(PATH_CHAR, return_value=MockTypeLoader('char')) class TestAccessories(unittest.TestCase):
@patch(PATH_SERV, return_value=MockTypeLoader('service')) """Test pyhap adapter methods."""
def test_add_preload_service(mock_serv, mock_char):
"""Test method add_preload_service.
The methods 'get_serv_loader' and 'get_char_loader' are mocked. def test_add_preload_service(self):
""" """Test add_preload_service without additional characteristics."""
acc = MockAccessory('Accessory') acc = Mock()
serv = add_preload_service(acc, 'TestService', serv = add_preload_service(acc, 'AirPurifier')
['TestChar', 'TestChar2'], self.assertEqual(acc.mock_calls, [call.add_service(serv)])
['TestOptChar', 'TestOptChar2']) with self.assertRaises(AssertionError):
serv.get_characteristic('Name')
assert serv.display_name == 'TestService' # Test with typo in service name
assert len(serv.characteristics) == 2 with self.assertRaises(KeyError):
assert len(serv.opt_characteristics) == 2 add_preload_service(Mock(), 'AirPurifierTypo')
acc.services = [] # Test adding additional characteristic as string
serv = add_preload_service(acc, 'TestService') serv = add_preload_service(Mock(), 'AirPurifier', 'Name')
serv.get_characteristic('Name')
assert not serv.characteristics # Test adding additional characteristics as list
assert not serv.opt_characteristics serv = add_preload_service(Mock(), 'AirPurifier',
['Name', 'RotationSpeed'])
serv.get_characteristic('Name')
serv.get_characteristic('RotationSpeed')
acc.services = [] # Test adding additional characteristic with typo
serv = add_preload_service(acc, 'TestService', with self.assertRaises(KeyError):
'TestChar', 'TestOptChar') add_preload_service(Mock(), 'AirPurifier', 'NameTypo')
assert len(serv.characteristics) == 1 def test_set_accessory_info(self):
assert len(serv.opt_characteristics) == 1 """Test setting the basic accessory information."""
# Test HomeAccessory
assert serv.characteristics[0].display_name == 'TestChar' acc = HomeAccessory()
assert serv.opt_characteristics[0].display_name == 'TestOptChar'
def test_override_properties():
"""Test override of characteristic properties with MockChar."""
char = MockChar('TestChar')
new_prop = {1: 'Test', 2: 'Demo'}
override_properties(char, new_prop)
assert char.properties == new_prop
def test_set_accessory_info():
"""Test setting of basic accessory information with MockAccessory."""
acc = MockAccessory('Accessory')
set_accessory_info(acc, 'name', 'model', 'manufacturer', '0000') set_accessory_info(acc, 'name', 'model', 'manufacturer', '0000')
assert len(acc.services) == 1 serv = acc.get_service(SERV_ACCESSORY_INFO)
serv = acc.services[0] self.assertEqual(serv.get_characteristic(CHAR_NAME).value, 'name')
self.assertEqual(serv.get_characteristic(CHAR_MODEL).value, 'model')
self.assertEqual(
serv.get_characteristic(CHAR_MANUFACTURER).value, 'manufacturer')
self.assertEqual(
serv.get_characteristic(CHAR_SERIAL_NUMBER).value, '0000')
assert serv.display_name == SERV_ACCESSORY_INFO # Test HomeBridge
assert len(serv.characteristics) == 4 acc = HomeBridge(None)
chars = serv.characteristics set_accessory_info(acc, 'name', 'model', 'manufacturer', '0000')
assert chars[0].display_name == CHAR_NAME serv = acc.get_service(SERV_ACCESSORY_INFO)
assert chars[0].value == 'name' self.assertEqual(serv.get_characteristic(CHAR_MODEL).value, 'model')
assert chars[1].display_name == CHAR_MODEL self.assertEqual(
assert chars[1].value == 'model' serv.get_characteristic(CHAR_MANUFACTURER).value, 'manufacturer')
assert chars[2].display_name == CHAR_MANUFACTURER self.assertEqual(
assert chars[2].value == 'manufacturer' serv.get_characteristic(CHAR_SERIAL_NUMBER).value, '0000')
assert chars[3].display_name == CHAR_SERIAL_NUMBER
assert chars[3].value == '0000'
def test_override_properties(self):
"""Test overriding property values."""
serv = add_preload_service(Mock(), 'AirPurifier', 'RotationSpeed')
@patch(PATH_ACC, side_effect=mock_preload_service) char_active = serv.get_characteristic('Active')
def test_home_accessory(mock_pre_serv): char_rotation_speed = serv.get_characteristic('RotationSpeed')
"""Test initializing a HomeAccessory object."""
acc = HomeAccessory('TestAccessory', 'test.accessory', 'WINDOW')
assert acc.display_name == 'TestAccessory' self.assertTrue(
assert acc.category == 13 # Category.WINDOW char_active.properties['ValidValues'].get('State') is None)
assert len(acc.services) == 1 self.assertEqual(char_rotation_speed.properties['maxValue'], 100)
serv = acc.services[0] override_properties(char_active, valid_values={'State': 'On'})
assert serv.display_name == SERV_ACCESSORY_INFO override_properties(char_rotation_speed, properties={'maxValue': 200})
char_model = serv.get_characteristic(CHAR_MODEL)
assert char_model.get_value() == 'test.accessory'
self.assertFalse(
char_active.properties['ValidValues'].get('State') is None)
self.assertEqual(char_rotation_speed.properties['maxValue'], 200)
@patch(PATH_ACC, side_effect=mock_preload_service) def test_home_accessory(self):
def test_home_bridge(mock_pre_serv): """Test HomeAccessory class."""
"""Test initializing a HomeBridge object.""" acc = HomeAccessory()
bridge = HomeBridge('TestBridge', 'test.bridge', b'123-45-678') self.assertEqual(acc.display_name, ACCESSORY_NAME)
self.assertEqual(acc.category, 1) # Category.OTHER
self.assertEqual(len(acc.services), 1)
serv = acc.services[0] # SERV_ACCESSORY_INFO
self.assertEqual(
serv.get_characteristic(CHAR_MODEL).value, ACCESSORY_MODEL)
assert bridge.display_name == 'TestBridge' acc = HomeAccessory('test_name', 'test_model', 'FAN', aid=2)
assert bridge.pincode == b'123-45-678' self.assertEqual(acc.display_name, 'test_name')
assert len(bridge.services) == 2 self.assertEqual(acc.category, 3) # Category.FAN
self.assertEqual(acc.aid, 2)
self.assertEqual(len(acc.services), 1)
serv = acc.services[0] # SERV_ACCESSORY_INFO
self.assertEqual(
serv.get_characteristic(CHAR_MODEL).value, 'test_model')
assert bridge.services[0].display_name == SERV_ACCESSORY_INFO def test_home_bridge(self):
assert bridge.services[1].display_name == SERV_BRIDGING_STATE """Test HomeBridge class."""
bridge = HomeBridge(None)
self.assertEqual(bridge.display_name, BRIDGE_NAME)
self.assertEqual(bridge.category, 2) # Category.BRIDGE
self.assertEqual(len(bridge.services), 2)
serv = bridge.services[0] # SERV_ACCESSORY_INFO
self.assertEqual(serv.display_name, SERV_ACCESSORY_INFO)
self.assertEqual(
serv.get_characteristic(CHAR_MODEL).value, BRIDGE_MODEL)
serv = bridge.services[1] # SERV_BRIDGING_STATE
self.assertEqual(serv.display_name, SERV_BRIDGING_STATE)
char_model = bridge.services[0].get_characteristic(CHAR_MODEL) bridge = HomeBridge('hass', 'test_name', 'test_model')
assert char_model.get_value() == 'test.bridge' self.assertEqual(bridge.display_name, 'test_name')
self.assertEqual(len(bridge.services), 2)
serv = bridge.services[0] # SERV_ACCESSORY_INFO
self.assertEqual(
serv.get_characteristic(CHAR_MODEL).value, 'test_model')
# setup_message
bridge.setup_message()
def test_mock_accessory(): # add_paired_client
"""Test attributes and functions of a MockAccessory.""" with patch('pyhap.accessory.Accessory.add_paired_client') \
acc = MockAccessory('TestAcc') as mock_add_paired_client, \
serv = MockService('TestServ') patch('homeassistant.components.homekit.accessories.'
acc.add_service(serv) 'dismiss_setup_message') as mock_dissmiss_msg:
bridge.add_paired_client('client_uuid', 'client_public')
assert acc.display_name == 'TestAcc' self.assertEqual(mock_add_paired_client.call_args,
assert len(acc.services) == 1 call('client_uuid', 'client_public'))
self.assertEqual(mock_dissmiss_msg.call_args, call('hass'))
assert acc.get_service('TestServ') == serv # remove_paired_client
assert acc.get_service('NewServ').display_name == 'NewServ' with patch('pyhap.accessory.Accessory.remove_paired_client') \
assert len(acc.services) == 2 as mock_remove_paired_client, \
patch('homeassistant.components.homekit.accessories.'
'show_setup_message') as mock_show_msg:
bridge.remove_paired_client('client_uuid')
self.assertEqual(
mock_remove_paired_client.call_args, call('client_uuid'))
self.assertEqual(mock_show_msg.call_args, call(bridge, 'hass'))
def test_mock_service(): def test_home_driver(self):
"""Test attributes and functions of a MockService.""" """Test HomeDriver class."""
serv = MockService('TestServ') bridge = HomeBridge(None)
char = MockChar('TestChar') ip_adress = '127.0.0.1'
opt_char = MockChar('TestOptChar') port = 51826
serv.add_characteristic(char) path = '.homekit.state'
serv.add_opt_characteristic(opt_char)
assert serv.display_name == 'TestServ' with patch('pyhap.accessory_driver.AccessoryDriver.__init__') \
assert len(serv.characteristics) == 1 as mock_driver:
assert len(serv.opt_characteristics) == 1 HomeDriver(bridge, ip_adress, port, path)
assert serv.get_characteristic('TestChar') == char self.assertEqual(
assert serv.get_characteristic('TestOptChar') == opt_char mock_driver.call_args, call(bridge, ip_adress, port, path))
assert serv.get_characteristic('NewChar').display_name == 'NewChar'
assert len(serv.characteristics) == 2
def test_mock_char():
"""Test attributes and functions of a MockChar."""
def callback_method(value):
"""Provide a callback options for 'set_value' method."""
assert value == 'With callback'
char = MockChar('TestChar')
char.set_value('Value')
assert char.display_name == 'TestChar'
assert char.get_value() == 'Value'
char.setter_callback = callback_method
char.set_value('With callback')

View File

@ -1,57 +1,113 @@
"""Package to test the get_accessory method.""" """Package to test the get_accessory method."""
from unittest.mock import patch, MagicMock import logging
import unittest
from unittest.mock import patch, Mock
from homeassistant.core import State from homeassistant.core import State
from homeassistant.components.homekit import ( from homeassistant.components.climate import (
TYPES, get_accessory, import_types) SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
from homeassistant.components.homekit import get_accessory, TYPES
from homeassistant.const import ( from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, ATTR_SUPPORTED_FEATURES, ATTR_CODE, ATTR_UNIT_OF_MEASUREMENT, ATTR_SUPPORTED_FEATURES,
TEMP_CELSIUS, TEMP_FAHRENHEIT, STATE_UNKNOWN) TEMP_CELSIUS, TEMP_FAHRENHEIT)
_LOGGER = logging.getLogger(__name__)
CONFIG = {}
def test_import_types(): def test_get_accessory_invalid(caplog):
"""Test if all type files are imported correctly."""
try:
import_types()
assert True
# pylint: disable=broad-except
except Exception:
assert False
def test_component_not_supported():
"""Test with unsupported component.""" """Test with unsupported component."""
state = State('demo.unsupported', STATE_UNKNOWN) assert get_accessory(None, State('test.unsupported', 'on'), 2, None) \
is None
assert caplog.records[1].levelname == 'WARNING'
assert True if get_accessory(None, state) is None else False assert get_accessory(None, State('test.test', 'on'), None, None) \
is None
assert caplog.records[3].levelname == 'WARNING'
def test_sensor_temperature_celsius(): class TestGetAccessories(unittest.TestCase):
"""Methods to test the get_accessory method."""
def setUp(self):
"""Setup Mock type."""
self.mock_type = Mock()
def tearDown(self):
"""Test if mock type was called."""
self.assertTrue(self.mock_type.called)
def test_sensor_temperature_celsius(self):
"""Test temperature sensor with Celsius as unit.""" """Test temperature sensor with Celsius as unit."""
mock_type = MagicMock() with patch.dict(TYPES, {'TemperatureSensor': self.mock_type}):
with patch.dict(TYPES, {'TemperatureSensor': mock_type}):
state = State('sensor.temperature', '23', state = State('sensor.temperature', '23',
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
get_accessory(None, state) get_accessory(None, state, 2, {})
assert len(mock_type.mock_calls) == 1
# pylint: disable=invalid-name # pylint: disable=invalid-name
def test_sensor_temperature_fahrenheit(): def test_sensor_temperature_fahrenheit(self):
"""Test temperature sensor with Fahrenheit as unit.""" """Test temperature sensor with Fahrenheit as unit."""
mock_type = MagicMock() with patch.dict(TYPES, {'TemperatureSensor': self.mock_type}):
with patch.dict(TYPES, {'TemperatureSensor': mock_type}):
state = State('sensor.temperature', '74', state = State('sensor.temperature', '74',
{ATTR_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT}) {ATTR_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT})
get_accessory(None, state) get_accessory(None, state, 2, {})
assert len(mock_type.mock_calls) == 1
def test_cover_set_position(self):
def test_cover_set_position():
"""Test cover with support for set_cover_position.""" """Test cover with support for set_cover_position."""
mock_type = MagicMock() with patch.dict(TYPES, {'WindowCovering': self.mock_type}):
with patch.dict(TYPES, {'Window': mock_type}):
state = State('cover.set_position', 'open', state = State('cover.set_position', 'open',
{ATTR_SUPPORTED_FEATURES: 4}) {ATTR_SUPPORTED_FEATURES: 4})
get_accessory(None, state) get_accessory(None, state, 2, {})
assert len(mock_type.mock_calls) == 1
def test_alarm_control_panel(self):
"""Test alarm control panel."""
config = {ATTR_CODE: '1234'}
with patch.dict(TYPES, {'SecuritySystem': self.mock_type}):
state = State('alarm_control_panel.test', 'armed')
get_accessory(None, state, 2, config)
# pylint: disable=unsubscriptable-object
self.assertEqual(
self.mock_type.call_args[1].get('alarm_code'), '1234')
def test_climate(self):
"""Test climate devices."""
with patch.dict(TYPES, {'Thermostat': self.mock_type}):
state = State('climate.test', 'auto')
get_accessory(None, state, 2, {})
# pylint: disable=unsubscriptable-object
self.assertEqual(
self.mock_type.call_args[0][-1], False) # support_auto
def test_climate_support_auto(self):
"""Test climate devices with support for auto mode."""
with patch.dict(TYPES, {'Thermostat': self.mock_type}):
state = State('climate.test', 'auto', {
ATTR_SUPPORTED_FEATURES:
SUPPORT_TARGET_TEMPERATURE_LOW |
SUPPORT_TARGET_TEMPERATURE_HIGH})
get_accessory(None, state, 2, {})
# pylint: disable=unsubscriptable-object
self.assertEqual(
self.mock_type.call_args[0][-1], True) # support_auto
def test_switch(self):
"""Test switch."""
with patch.dict(TYPES, {'Switch': self.mock_type}):
state = State('switch.test', 'on')
get_accessory(None, state, 2, {})
def test_remote(self):
"""Test remote."""
with patch.dict(TYPES, {'Switch': self.mock_type}):
state = State('remote.test', 'on')
get_accessory(None, state, 2, {})
def test_input_boolean(self):
"""Test input_boolean."""
with patch.dict(TYPES, {'Switch': self.mock_type}):
state = State('input_boolean.test', 'on')
get_accessory(None, state, 2, {})

View File

@ -1,33 +1,22 @@
"""Tests for the HomeKit component.""" """Tests for the HomeKit component."""
import unittest import unittest
from unittest.mock import call, patch, ANY from unittest.mock import call, patch, ANY, Mock
import voluptuous as vol
# pylint: disable=unused-import
from pyhap.accessory_driver import AccessoryDriver # noqa F401
from homeassistant import setup from homeassistant import setup
from homeassistant.core import Event from homeassistant.core import State
from homeassistant.components.homekit import ( from homeassistant.components.homekit import HomeKit, generate_aid
CONF_PIN_CODE, HOMEKIT_FILE, HomeKit, valid_pin) from homeassistant.components.homekit.accessories import HomeBridge
from homeassistant.components.homekit.const import (
DOMAIN, HOMEKIT_FILE, CONF_AUTO_START,
DEFAULT_PORT, SERVICE_HOMEKIT_START)
from homeassistant.helpers.entityfilter import generate_filter
from homeassistant.const import ( from homeassistant.const import (
CONF_PORT, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) CONF_PORT, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
from tests.common import get_test_home_assistant from tests.common import get_test_home_assistant
from tests.mock.homekit import get_patch_paths, PATH_HOMEKIT
PATH_ACC, _ = get_patch_paths()
IP_ADDRESS = '127.0.0.1' IP_ADDRESS = '127.0.0.1'
PATH_HOMEKIT = 'homeassistant.components.homekit'
CONFIG_MIN = {'homekit': {}}
CONFIG = {
'homekit': {
CONF_PORT: 11111,
CONF_PIN_CODE: '987-65-432',
}
}
class TestHomeKit(unittest.TestCase): class TestHomeKit(unittest.TestCase):
@ -41,75 +30,162 @@ class TestHomeKit(unittest.TestCase):
"""Stop down everything that was started.""" """Stop down everything that was started."""
self.hass.stop() self.hass.stop()
def test_validate_pincode(self): def test_generate_aid(self):
"""Test async_setup with invalid config option.""" """Test generate aid method."""
schema = vol.Schema(valid_pin) aid = generate_aid('demo.entity')
self.assertIsInstance(aid, int)
self.assertTrue(aid >= 2 and aid <= 18446744073709551615)
for value in ('', '123-456-78', 'a23-45-678', '12345678', 1234): with patch(PATH_HOMEKIT + '.adler32') as mock_adler32:
with self.assertRaises(vol.MultipleInvalid): mock_adler32.side_effect = [0, 1]
schema(value) self.assertIsNone(generate_aid('demo.entity'))
for value in ('123-45-678', '234-56-789'):
self.assertTrue(schema(value))
@patch(PATH_HOMEKIT + '.HomeKit') @patch(PATH_HOMEKIT + '.HomeKit')
def test_setup_min(self, mock_homekit): def test_setup_min(self, mock_homekit):
"""Test async_setup with minimal config option.""" """Test async_setup with min config options."""
self.assertTrue(setup.setup_component( self.assertTrue(setup.setup_component(
self.hass, 'homekit', CONFIG_MIN)) self.hass, DOMAIN, {DOMAIN: {}}))
self.assertEqual(mock_homekit.mock_calls, self.assertEqual(mock_homekit.mock_calls, [
[call(self.hass, 51826), call(self.hass, DEFAULT_PORT, ANY, {}),
call().setup_bridge(b'123-45-678')]) call().setup()])
# Test auto start enabled
mock_homekit.reset_mock() mock_homekit.reset_mock()
self.hass.bus.fire(EVENT_HOMEASSISTANT_START)
self.hass.block_till_done()
self.assertEqual(mock_homekit.mock_calls, [call().start(ANY)])
@patch(PATH_HOMEKIT + '.HomeKit')
def test_setup_auto_start_disabled(self, mock_homekit):
"""Test async_setup with auto start disabled and test service calls."""
mock_homekit.return_value = homekit = Mock()
config = {DOMAIN: {CONF_AUTO_START: False, CONF_PORT: 11111}}
self.assertTrue(setup.setup_component(
self.hass, DOMAIN, config))
self.hass.bus.fire(EVENT_HOMEASSISTANT_START) self.hass.bus.fire(EVENT_HOMEASSISTANT_START)
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual(mock_homekit.mock_calls, self.assertEqual(mock_homekit.mock_calls, [
[call().start_driver(ANY)]) call(self.hass, 11111, ANY, {}),
call().setup()])
@patch(PATH_HOMEKIT + '.HomeKit') # Test start call with driver stopped.
def test_setup_parameters(self, mock_homekit): homekit.reset_mock()
"""Test async_setup with full config option.""" homekit.configure_mock(**{'started': False})
self.assertTrue(setup.setup_component(
self.hass, 'homekit', CONFIG))
self.assertEqual(mock_homekit.mock_calls, self.hass.services.call('homekit', 'start')
[call(self.hass, 11111), self.assertEqual(homekit.mock_calls, [call.start()])
call().setup_bridge(b'987-65-432')])
@patch('pyhap.accessory_driver.AccessoryDriver') # Test start call with driver started.
def test_homekit_class(self, mock_acc_driver): homekit.reset_mock()
"""Test interaction between the HomeKit class and pyhap.""" homekit.configure_mock(**{'started': True})
with patch(PATH_HOMEKIT + '.accessories.HomeBridge') as mock_bridge:
homekit = HomeKit(self.hass, 51826)
homekit.setup_bridge(b'123-45-678')
mock_bridge.reset_mock() self.hass.services.call(DOMAIN, SERVICE_HOMEKIT_START)
self.hass.states.set('demo.demo1', 'on') self.assertEqual(homekit.mock_calls, [])
self.hass.states.set('demo.demo2', 'off')
with patch(PATH_HOMEKIT + '.get_accessory') as mock_get_acc, \ def test_homekit_setup(self):
patch(PATH_HOMEKIT + '.import_types') as mock_import_types, \ """Test setup of bridge and driver."""
homekit = HomeKit(self.hass, DEFAULT_PORT, {}, {})
with patch(PATH_HOMEKIT + '.accessories.HomeDriver') as mock_driver, \
patch('homeassistant.util.get_local_ip') as mock_ip: patch('homeassistant.util.get_local_ip') as mock_ip:
mock_get_acc.side_effect = ['TempSensor', 'Window']
mock_ip.return_value = IP_ADDRESS mock_ip.return_value = IP_ADDRESS
homekit.start_driver(Event(EVENT_HOMEASSISTANT_START)) homekit.setup()
path = self.hass.config.path(HOMEKIT_FILE) path = self.hass.config.path(HOMEKIT_FILE)
self.assertTrue(isinstance(homekit.bridge, HomeBridge))
self.assertEqual(mock_driver.mock_calls, [
call(homekit.bridge, DEFAULT_PORT, IP_ADDRESS, path)])
self.assertEqual(mock_import_types.call_count, 1) # Test if stop listener is setup
self.assertEqual(mock_get_acc.call_count, 2) self.assertEqual(
self.assertEqual(mock_bridge.mock_calls, self.hass.bus.listeners.get(EVENT_HOMEASSISTANT_STOP), 1)
[call().add_accessory('TempSensor'),
call().add_accessory('Window')])
self.assertEqual(mock_acc_driver.mock_calls,
[call(homekit.bridge, 51826, IP_ADDRESS, path),
call().start()])
mock_acc_driver.reset_mock()
self.hass.bus.fire(EVENT_HOMEASSISTANT_STOP) def test_homekit_add_accessory(self):
self.hass.block_till_done() """Add accessory if config exists and get_acc returns an accessory."""
homekit = HomeKit(self.hass, None, lambda entity_id: True, {})
homekit.bridge = HomeBridge(self.hass)
self.assertEqual(mock_acc_driver.mock_calls, [call().stop()]) with patch(PATH_HOMEKIT + '.accessories.HomeBridge.add_accessory') \
as mock_add_acc, \
patch(PATH_HOMEKIT + '.get_accessory') as mock_get_acc:
mock_get_acc.side_effect = [None, 'acc', None]
homekit.add_bridge_accessory(State('light.demo', 'on'))
self.assertEqual(mock_get_acc.call_args,
call(self.hass, ANY, 363398124, {}))
self.assertFalse(mock_add_acc.called)
homekit.add_bridge_accessory(State('demo.test', 'on'))
self.assertEqual(mock_get_acc.call_args,
call(self.hass, ANY, 294192020, {}))
self.assertTrue(mock_add_acc.called)
homekit.add_bridge_accessory(State('demo.test_2', 'on'))
self.assertEqual(mock_get_acc.call_args,
call(self.hass, ANY, 429982757, {}))
self.assertEqual(mock_add_acc.mock_calls, [call('acc')])
def test_homekit_entity_filter(self):
"""Test the entity filter."""
entity_filter = generate_filter(['cover'], ['demo.test'], [], [])
homekit = HomeKit(self.hass, None, entity_filter, {})
with patch(PATH_HOMEKIT + '.get_accessory') as mock_get_acc:
mock_get_acc.return_value = None
homekit.add_bridge_accessory(State('cover.test', 'open'))
self.assertTrue(mock_get_acc.called)
mock_get_acc.reset_mock()
homekit.add_bridge_accessory(State('demo.test', 'on'))
self.assertTrue(mock_get_acc.called)
mock_get_acc.reset_mock()
homekit.add_bridge_accessory(State('light.demo', 'light'))
self.assertFalse(mock_get_acc.called)
@patch(PATH_HOMEKIT + '.show_setup_message')
@patch(PATH_HOMEKIT + '.HomeKit.add_bridge_accessory')
def test_homekit_start(self, mock_add_bridge_acc, mock_show_setup_msg):
"""Test HomeKit start method."""
homekit = HomeKit(self.hass, None, {}, {'cover.demo': {}})
homekit.bridge = HomeBridge(self.hass)
homekit.driver = Mock()
self.hass.states.set('light.demo', 'on')
state = self.hass.states.all()[0]
homekit.start()
self.assertEqual(mock_add_bridge_acc.mock_calls, [call(state)])
self.assertEqual(mock_show_setup_msg.mock_calls, [
call(homekit.bridge, self.hass)])
self.assertEqual(homekit.driver.mock_calls, [call.start()])
self.assertTrue(homekit.started)
# Test start() if already started
homekit.driver.reset_mock()
homekit.start()
self.assertEqual(homekit.driver.mock_calls, [])
def test_homekit_stop(self):
"""Test HomeKit stop method."""
homekit = HomeKit(None, None, None, None)
homekit.driver = Mock()
# Test if started = False
homekit.stop()
self.assertFalse(homekit.driver.stop.called)
# Test if driver not started
homekit.started = True
homekit.driver.configure_mock(**{'run_sentinel': None})
homekit.stop()
self.assertFalse(homekit.driver.stop.called)
# Test if driver is started
homekit.driver.configure_mock(**{'run_sentinel': 'sentinel'})
homekit.stop()
self.assertTrue(homekit.driver.stop.called)

View File

@ -1,64 +0,0 @@
"""Test different accessory types: Switches."""
import unittest
from unittest.mock import patch
from homeassistant.core import callback
from homeassistant.components.homekit.switches import Switch
from homeassistant.const import ATTR_SERVICE, EVENT_CALL_SERVICE
from tests.common import get_test_home_assistant
from tests.mock.homekit import get_patch_paths, mock_preload_service
PATH_ACC, PATH_FILE = get_patch_paths('switches')
class TestHomekitSwitches(unittest.TestCase):
"""Test class for all accessory types regarding switches."""
def setUp(self):
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.events = []
@callback
def record_event(event):
"""Track called event."""
self.events.append(event)
self.hass.bus.listen(EVENT_CALL_SERVICE, record_event)
def tearDown(self):
"""Stop down everything that was started."""
self.hass.stop()
def test_switch_set_state(self):
"""Test if accessory and HA are updated accordingly."""
switch = 'switch.testswitch'
with patch(PATH_ACC, side_effect=mock_preload_service):
with patch(PATH_FILE, side_effect=mock_preload_service):
acc = Switch(self.hass, switch, 'Switch')
acc.run()
self.assertEqual(acc.char_on.value, False)
self.hass.states.set(switch, 'on')
self.hass.block_till_done()
self.assertEqual(acc.char_on.value, True)
self.hass.states.set(switch, 'off')
self.hass.block_till_done()
self.assertEqual(acc.char_on.value, False)
# Set from HomeKit
acc.char_on.set_value(True)
self.hass.block_till_done()
self.assertEqual(
self.events[0].data[ATTR_SERVICE], 'turn_on')
self.assertEqual(acc.char_on.value, True)
acc.char_on.set_value(False)
self.hass.block_till_done()
self.assertEqual(
self.events[1].data[ATTR_SERVICE], 'turn_off')
self.assertEqual(acc.char_on.value, False)

View File

@ -1,19 +1,15 @@
"""Test different accessory types: Covers.""" """Test different accessory types: Covers."""
import unittest import unittest
from unittest.mock import patch
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.components.cover import ( from homeassistant.components.cover import (
ATTR_POSITION, ATTR_CURRENT_POSITION) ATTR_POSITION, ATTR_CURRENT_POSITION)
from homeassistant.components.homekit.covers import Window from homeassistant.components.homekit.type_covers import WindowCovering
from homeassistant.const import ( from homeassistant.const import (
STATE_UNKNOWN, STATE_OPEN, STATE_UNKNOWN, STATE_OPEN,
ATTR_SERVICE, ATTR_SERVICE_DATA, EVENT_CALL_SERVICE) ATTR_SERVICE, ATTR_SERVICE_DATA, EVENT_CALL_SERVICE)
from tests.common import get_test_home_assistant from tests.common import get_test_home_assistant
from tests.mock.homekit import get_patch_paths, mock_preload_service
PATH_ACC, PATH_FILE = get_patch_paths('covers')
class TestHomekitSensors(unittest.TestCase): class TestHomekitSensors(unittest.TestCase):
@ -39,11 +35,12 @@ class TestHomekitSensors(unittest.TestCase):
"""Test if accessory and HA are updated accordingly.""" """Test if accessory and HA are updated accordingly."""
window_cover = 'cover.window' window_cover = 'cover.window'
with patch(PATH_ACC, side_effect=mock_preload_service): acc = WindowCovering(self.hass, window_cover, 'Cover', aid=2)
with patch(PATH_FILE, side_effect=mock_preload_service):
acc = Window(self.hass, window_cover, 'Cover')
acc.run() acc.run()
self.assertEqual(acc.aid, 2)
self.assertEqual(acc.category, 14) # WindowCovering
self.assertEqual(acc.char_current_position.value, 0) self.assertEqual(acc.char_current_position.value, 0)
self.assertEqual(acc.char_target_position.value, 0) self.assertEqual(acc.char_target_position.value, 0)
self.assertEqual(acc.char_position_state.value, 0) self.assertEqual(acc.char_position_state.value, 0)

View File

@ -1,18 +1,15 @@
"""Test different accessory types: Security Systems.""" """Test different accessory types: Security Systems."""
import unittest import unittest
from unittest.mock import patch
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.components.homekit.security_systems import SecuritySystem from homeassistant.components.homekit.type_security_systems import (
SecuritySystem)
from homeassistant.const import ( from homeassistant.const import (
ATTR_SERVICE, EVENT_CALL_SERVICE, ATTR_CODE, ATTR_SERVICE, ATTR_SERVICE_DATA, EVENT_CALL_SERVICE,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED) STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED, STATE_UNKNOWN)
from tests.common import get_test_home_assistant from tests.common import get_test_home_assistant
from tests.mock.homekit import get_patch_paths, mock_preload_service
PATH_ACC, PATH_FILE = get_patch_paths('security_systems')
class TestHomekitSecuritySystems(unittest.TestCase): class TestHomekitSecuritySystems(unittest.TestCase):
@ -36,13 +33,15 @@ class TestHomekitSecuritySystems(unittest.TestCase):
def test_switch_set_state(self): def test_switch_set_state(self):
"""Test if accessory and HA are updated accordingly.""" """Test if accessory and HA are updated accordingly."""
acp = 'alarm_control_panel.testsecurity' acp = 'alarm_control_panel.test'
with patch(PATH_ACC, side_effect=mock_preload_service): acc = SecuritySystem(self.hass, acp, 'SecuritySystem',
with patch(PATH_FILE, side_effect=mock_preload_service): alarm_code='1234', aid=2)
acc = SecuritySystem(self.hass, acp, 'SecuritySystem')
acc.run() acc.run()
self.assertEqual(acc.aid, 2)
self.assertEqual(acc.category, 11) # AlarmSystem
self.assertEqual(acc.char_current_state.value, 3) self.assertEqual(acc.char_current_state.value, 3)
self.assertEqual(acc.char_target_state.value, 3) self.assertEqual(acc.char_target_state.value, 3)
@ -66,27 +65,40 @@ class TestHomekitSecuritySystems(unittest.TestCase):
self.assertEqual(acc.char_target_state.value, 3) self.assertEqual(acc.char_target_state.value, 3)
self.assertEqual(acc.char_current_state.value, 3) self.assertEqual(acc.char_current_state.value, 3)
self.hass.states.set(acp, STATE_UNKNOWN)
self.hass.block_till_done()
self.assertEqual(acc.char_target_state.value, 3)
self.assertEqual(acc.char_current_state.value, 3)
# Set from HomeKit # Set from HomeKit
acc.char_target_state.set_value(0) acc.char_target_state.set_value(0)
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual( self.assertEqual(
self.events[0].data[ATTR_SERVICE], 'alarm_arm_home') self.events[0].data[ATTR_SERVICE], 'alarm_arm_home')
self.assertEqual(
self.events[0].data[ATTR_SERVICE_DATA][ATTR_CODE], '1234')
self.assertEqual(acc.char_target_state.value, 0) self.assertEqual(acc.char_target_state.value, 0)
acc.char_target_state.set_value(1) acc.char_target_state.set_value(1)
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual( self.assertEqual(
self.events[1].data[ATTR_SERVICE], 'alarm_arm_away') self.events[1].data[ATTR_SERVICE], 'alarm_arm_away')
self.assertEqual(
self.events[0].data[ATTR_SERVICE_DATA][ATTR_CODE], '1234')
self.assertEqual(acc.char_target_state.value, 1) self.assertEqual(acc.char_target_state.value, 1)
acc.char_target_state.set_value(2) acc.char_target_state.set_value(2)
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual( self.assertEqual(
self.events[2].data[ATTR_SERVICE], 'alarm_arm_night') self.events[2].data[ATTR_SERVICE], 'alarm_arm_night')
self.assertEqual(
self.events[0].data[ATTR_SERVICE_DATA][ATTR_CODE], '1234')
self.assertEqual(acc.char_target_state.value, 2) self.assertEqual(acc.char_target_state.value, 2)
acc.char_target_state.set_value(3) acc.char_target_state.set_value(3)
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual( self.assertEqual(
self.events[3].data[ATTR_SERVICE], 'alarm_disarm') self.events[3].data[ATTR_SERVICE], 'alarm_disarm')
self.assertEqual(
self.events[0].data[ATTR_SERVICE_DATA][ATTR_CODE], '1234')
self.assertEqual(acc.char_target_state.value, 3) self.assertEqual(acc.char_target_state.value, 3)

View File

@ -1,17 +1,13 @@
"""Test different accessory types: Sensors.""" """Test different accessory types: Sensors."""
import unittest import unittest
from unittest.mock import patch
from homeassistant.components.homekit.const import PROP_CELSIUS from homeassistant.components.homekit.const import PROP_CELSIUS
from homeassistant.components.homekit.sensors import ( from homeassistant.components.homekit.type_sensors import (
TemperatureSensor, calc_temperature) TemperatureSensor, calc_temperature)
from homeassistant.const import ( from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, TEMP_FAHRENHEIT, STATE_UNKNOWN) ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT)
from tests.common import get_test_home_assistant from tests.common import get_test_home_assistant
from tests.mock.homekit import get_patch_paths, mock_preload_service
PATH_ACC, PATH_FILE = get_patch_paths('sensors')
def test_calc_temperature(): def test_calc_temperature():
@ -32,7 +28,6 @@ class TestHomekitSensors(unittest.TestCase):
def setUp(self): def setUp(self):
"""Setup things to be run when tests are started.""" """Setup things to be run when tests are started."""
self.hass = get_test_home_assistant() self.hass = get_test_home_assistant()
get_patch_paths('sensors')
def tearDown(self): def tearDown(self):
"""Stop down everything that was started.""" """Stop down everything that was started."""
@ -40,27 +35,28 @@ class TestHomekitSensors(unittest.TestCase):
def test_temperature(self): def test_temperature(self):
"""Test if accessory is updated after state change.""" """Test if accessory is updated after state change."""
temperature_sensor = 'sensor.temperature' entity_id = 'sensor.temperature'
with patch(PATH_ACC, side_effect=mock_preload_service): acc = TemperatureSensor(self.hass, entity_id, 'Temperature', aid=2)
with patch(PATH_FILE, side_effect=mock_preload_service):
acc = TemperatureSensor(self.hass, temperature_sensor,
'Temperature')
acc.run() acc.run()
self.assertEqual(acc.char_temp.value, 0.0) self.assertEqual(acc.aid, 2)
self.assertEqual(acc.char_temp.properties, PROP_CELSIUS) self.assertEqual(acc.category, 10) # Sensor
self.hass.states.set(temperature_sensor, STATE_UNKNOWN, self.assertEqual(acc.char_temp.value, 0.0)
for key, value in PROP_CELSIUS.items():
self.assertEqual(acc.char_temp.properties[key], value)
self.hass.states.set(entity_id, STATE_UNKNOWN,
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
self.hass.block_till_done() self.hass.block_till_done()
self.hass.states.set(temperature_sensor, '20', self.hass.states.set(entity_id, '20',
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual(acc.char_temp.value, 20) self.assertEqual(acc.char_temp.value, 20)
self.hass.states.set(temperature_sensor, '75.2', self.hass.states.set(entity_id, '75.2',
{ATTR_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT}) {ATTR_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT})
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual(acc.char_temp.value, 24) self.assertEqual(acc.char_temp.value, 24)

View File

@ -0,0 +1,104 @@
"""Test different accessory types: Switches."""
import unittest
from homeassistant.core import callback, split_entity_id
from homeassistant.components.homekit.type_switches import Switch
from homeassistant.const import (
ATTR_DOMAIN, ATTR_SERVICE, EVENT_CALL_SERVICE,
SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_ON, STATE_OFF)
from tests.common import get_test_home_assistant
class TestHomekitSwitches(unittest.TestCase):
"""Test class for all accessory types regarding switches."""
def setUp(self):
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.events = []
@callback
def record_event(event):
"""Track called event."""
self.events.append(event)
self.hass.bus.listen(EVENT_CALL_SERVICE, record_event)
def tearDown(self):
"""Stop down everything that was started."""
self.hass.stop()
def test_switch_set_state(self):
"""Test if accessory and HA are updated accordingly."""
entity_id = 'switch.test'
domain = split_entity_id(entity_id)[0]
acc = Switch(self.hass, entity_id, 'Switch', aid=2)
acc.run()
self.assertEqual(acc.aid, 2)
self.assertEqual(acc.category, 8) # Switch
self.assertEqual(acc.char_on.value, False)
self.hass.states.set(entity_id, STATE_ON)
self.hass.block_till_done()
self.assertEqual(acc.char_on.value, True)
self.hass.states.set(entity_id, STATE_OFF)
self.hass.block_till_done()
self.assertEqual(acc.char_on.value, False)
# Set from HomeKit
acc.char_on.set_value(True)
self.hass.block_till_done()
self.assertEqual(
self.events[0].data[ATTR_DOMAIN], domain)
self.assertEqual(
self.events[0].data[ATTR_SERVICE], SERVICE_TURN_ON)
acc.char_on.set_value(False)
self.hass.block_till_done()
self.assertEqual(
self.events[1].data[ATTR_DOMAIN], domain)
self.assertEqual(
self.events[1].data[ATTR_SERVICE], SERVICE_TURN_OFF)
def test_remote_set_state(self):
"""Test service call for remote as domain."""
entity_id = 'remote.test'
domain = split_entity_id(entity_id)[0]
acc = Switch(self.hass, entity_id, 'Switch', aid=2)
acc.run()
self.assertEqual(acc.char_on.value, False)
# Set from HomeKit
acc.char_on.set_value(True)
self.hass.block_till_done()
self.assertEqual(
self.events[0].data[ATTR_DOMAIN], domain)
self.assertEqual(
self.events[0].data[ATTR_SERVICE], SERVICE_TURN_ON)
self.assertEqual(acc.char_on.value, True)
def test_input_boolean_set_state(self):
"""Test service call for remote as domain."""
entity_id = 'input_boolean.test'
domain = split_entity_id(entity_id)[0]
acc = Switch(self.hass, entity_id, 'Switch', aid=2)
acc.run()
self.assertEqual(acc.char_on.value, False)
# Set from HomeKit
acc.char_on.set_value(True)
self.hass.block_till_done()
self.assertEqual(
self.events[0].data[ATTR_DOMAIN], domain)
self.assertEqual(
self.events[0].data[ATTR_SERVICE], SERVICE_TURN_ON)
self.assertEqual(acc.char_on.value, True)

View File

@ -1,21 +1,18 @@
"""Test different accessory types: Thermostats.""" """Test different accessory types: Thermostats."""
import unittest import unittest
from unittest.mock import patch
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ATTR_CURRENT_TEMPERATURE, ATTR_TEMPERATURE, ATTR_CURRENT_TEMPERATURE, ATTR_TEMPERATURE,
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, ATTR_OPERATION_MODE,
ATTR_OPERATION_MODE, STATE_HEAT, STATE_AUTO) ATTR_OPERATION_LIST, STATE_COOL, STATE_HEAT, STATE_AUTO)
from homeassistant.components.homekit.thermostats import Thermostat, STATE_OFF from homeassistant.components.homekit.type_thermostats import (
Thermostat, STATE_OFF)
from homeassistant.const import ( from homeassistant.const import (
ATTR_SERVICE, EVENT_CALL_SERVICE, ATTR_SERVICE_DATA, ATTR_SERVICE, EVENT_CALL_SERVICE, ATTR_SERVICE_DATA,
ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS) ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS)
from tests.common import get_test_home_assistant from tests.common import get_test_home_assistant
from tests.mock.homekit import get_patch_paths, mock_preload_service
PATH_ACC, PATH_FILE = get_patch_paths('thermostats')
class TestHomekitThermostats(unittest.TestCase): class TestHomekitThermostats(unittest.TestCase):
@ -39,13 +36,14 @@ class TestHomekitThermostats(unittest.TestCase):
def test_default_thermostat(self): def test_default_thermostat(self):
"""Test if accessory and HA are updated accordingly.""" """Test if accessory and HA are updated accordingly."""
climate = 'climate.testclimate' climate = 'climate.test'
with patch(PATH_ACC, side_effect=mock_preload_service): acc = Thermostat(self.hass, climate, 'Climate', False, aid=2)
with patch(PATH_FILE, side_effect=mock_preload_service):
acc = Thermostat(self.hass, climate, 'Climate', False)
acc.run() acc.run()
self.assertEqual(acc.aid, 2)
self.assertEqual(acc.category, 9) # Thermostat
self.assertEqual(acc.char_current_heat_cool.value, 0) self.assertEqual(acc.char_current_heat_cool.value, 0)
self.assertEqual(acc.char_target_heat_cool.value, 0) self.assertEqual(acc.char_target_heat_cool.value, 0)
self.assertEqual(acc.char_current_temp.value, 21.0) self.assertEqual(acc.char_current_temp.value, 21.0)
@ -78,6 +76,30 @@ class TestHomekitThermostats(unittest.TestCase):
self.assertEqual(acc.char_current_temp.value, 23.0) self.assertEqual(acc.char_current_temp.value, 23.0)
self.assertEqual(acc.char_display_units.value, 0) self.assertEqual(acc.char_display_units.value, 0)
self.hass.states.set(climate, STATE_COOL,
{ATTR_OPERATION_MODE: STATE_COOL,
ATTR_TEMPERATURE: 20.0,
ATTR_CURRENT_TEMPERATURE: 25.0,
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
self.hass.block_till_done()
self.assertEqual(acc.char_target_temp.value, 20.0)
self.assertEqual(acc.char_current_heat_cool.value, 2)
self.assertEqual(acc.char_target_heat_cool.value, 2)
self.assertEqual(acc.char_current_temp.value, 25.0)
self.assertEqual(acc.char_display_units.value, 0)
self.hass.states.set(climate, STATE_COOL,
{ATTR_OPERATION_MODE: STATE_COOL,
ATTR_TEMPERATURE: 20.0,
ATTR_CURRENT_TEMPERATURE: 19.0,
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
self.hass.block_till_done()
self.assertEqual(acc.char_target_temp.value, 20.0)
self.assertEqual(acc.char_current_heat_cool.value, 0)
self.assertEqual(acc.char_target_heat_cool.value, 2)
self.assertEqual(acc.char_current_temp.value, 19.0)
self.assertEqual(acc.char_display_units.value, 0)
self.hass.states.set(climate, STATE_OFF, self.hass.states.set(climate, STATE_OFF,
{ATTR_OPERATION_MODE: STATE_OFF, {ATTR_OPERATION_MODE: STATE_OFF,
ATTR_TEMPERATURE: 22.0, ATTR_TEMPERATURE: 22.0,
@ -90,6 +112,45 @@ class TestHomekitThermostats(unittest.TestCase):
self.assertEqual(acc.char_current_temp.value, 18.0) self.assertEqual(acc.char_current_temp.value, 18.0)
self.assertEqual(acc.char_display_units.value, 0) self.assertEqual(acc.char_display_units.value, 0)
self.hass.states.set(climate, STATE_AUTO,
{ATTR_OPERATION_MODE: STATE_AUTO,
ATTR_OPERATION_LIST: [STATE_HEAT, STATE_COOL],
ATTR_TEMPERATURE: 22.0,
ATTR_CURRENT_TEMPERATURE: 18.0,
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
self.hass.block_till_done()
self.assertEqual(acc.char_target_temp.value, 22.0)
self.assertEqual(acc.char_current_heat_cool.value, 1)
self.assertEqual(acc.char_target_heat_cool.value, 3)
self.assertEqual(acc.char_current_temp.value, 18.0)
self.assertEqual(acc.char_display_units.value, 0)
self.hass.states.set(climate, STATE_AUTO,
{ATTR_OPERATION_MODE: STATE_AUTO,
ATTR_OPERATION_LIST: [STATE_HEAT, STATE_COOL],
ATTR_TEMPERATURE: 22.0,
ATTR_CURRENT_TEMPERATURE: 25.0,
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
self.hass.block_till_done()
self.assertEqual(acc.char_target_temp.value, 22.0)
self.assertEqual(acc.char_current_heat_cool.value, 2)
self.assertEqual(acc.char_target_heat_cool.value, 3)
self.assertEqual(acc.char_current_temp.value, 25.0)
self.assertEqual(acc.char_display_units.value, 0)
self.hass.states.set(climate, STATE_AUTO,
{ATTR_OPERATION_MODE: STATE_AUTO,
ATTR_OPERATION_LIST: [STATE_HEAT, STATE_COOL],
ATTR_TEMPERATURE: 22.0,
ATTR_CURRENT_TEMPERATURE: 22.0,
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
self.hass.block_till_done()
self.assertEqual(acc.char_target_temp.value, 22.0)
self.assertEqual(acc.char_current_heat_cool.value, 0)
self.assertEqual(acc.char_target_heat_cool.value, 3)
self.assertEqual(acc.char_current_temp.value, 22.0)
self.assertEqual(acc.char_display_units.value, 0)
# Set from HomeKit # Set from HomeKit
acc.char_target_temp.set_value(19.0) acc.char_target_temp.set_value(19.0)
self.hass.block_till_done() self.hass.block_till_done()
@ -110,7 +171,7 @@ class TestHomekitThermostats(unittest.TestCase):
def test_auto_thermostat(self): def test_auto_thermostat(self):
"""Test if accessory and HA are updated accordingly.""" """Test if accessory and HA are updated accordingly."""
climate = 'climate.testclimate' climate = 'climate.test'
acc = Thermostat(self.hass, climate, 'Climate', True) acc = Thermostat(self.hass, climate, 'Climate', True)
acc.run() acc.run()

View File

@ -0,0 +1,83 @@
"""Test HomeKit util module."""
import unittest
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components.homekit.accessories import HomeBridge
from homeassistant.components.homekit.const import HOMEKIT_NOTIFY_ID
from homeassistant.components.homekit.util import (
show_setup_message, dismiss_setup_message, ATTR_CODE)
from homeassistant.components.homekit.util import validate_entity_config \
as vec
from homeassistant.components.persistent_notification import (
SERVICE_CREATE, SERVICE_DISMISS, ATTR_NOTIFICATION_ID)
from homeassistant.const import (
EVENT_CALL_SERVICE, ATTR_DOMAIN, ATTR_SERVICE, ATTR_SERVICE_DATA)
from tests.common import get_test_home_assistant
class TestUtil(unittest.TestCase):
"""Test all HomeKit util methods."""
def setUp(self):
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.events = []
@callback
def record_event(event):
"""Track called event."""
self.events.append(event)
self.hass.bus.listen(EVENT_CALL_SERVICE, record_event)
def tearDown(self):
"""Stop down everything that was started."""
self.hass.stop()
def test_validate_entity_config(self):
"""Test validate entities."""
configs = [{'invalid_entity_id': {}}, {'demo.test': 1},
{'demo.test': 'test'}, {'demo.test': [1, 2]},
{'demo.test': None}]
for conf in configs:
with self.assertRaises(vol.Invalid):
vec(conf)
self.assertEqual(vec({}), {})
self.assertEqual(
vec({'alarm_control_panel.demo': {ATTR_CODE: '1234'}}),
{'alarm_control_panel.demo': {ATTR_CODE: '1234'}})
def test_show_setup_msg(self):
"""Test show setup message as persistence notification."""
bridge = HomeBridge(self.hass)
show_setup_message(bridge, self.hass)
self.hass.block_till_done()
data = self.events[0].data
self.assertEqual(
data.get(ATTR_DOMAIN, None), 'persistent_notification')
self.assertEqual(data.get(ATTR_SERVICE, None), SERVICE_CREATE)
self.assertNotEqual(data.get(ATTR_SERVICE_DATA, None), None)
self.assertEqual(
data[ATTR_SERVICE_DATA].get(ATTR_NOTIFICATION_ID, None),
HOMEKIT_NOTIFY_ID)
def test_dismiss_setup_msg(self):
"""Test dismiss setup message."""
dismiss_setup_message(self.hass)
self.hass.block_till_done()
data = self.events[0].data
self.assertEqual(
data.get(ATTR_DOMAIN, None), 'persistent_notification')
self.assertEqual(data.get(ATTR_SERVICE, None), SERVICE_DISMISS)
self.assertNotEqual(data.get(ATTR_SERVICE_DATA, None), None)
self.assertEqual(
data[ATTR_SERVICE_DATA].get(ATTR_NOTIFICATION_ID, None),
HOMEKIT_NOTIFY_ID)

View File

@ -1,133 +0,0 @@
"""Basic mock functions and objects related to the HomeKit component."""
PATH_HOMEKIT = 'homeassistant.components.homekit'
def get_patch_paths(name=None):
"""Return paths to mock 'add_preload_service'."""
path_acc = PATH_HOMEKIT + '.accessories.add_preload_service'
path_file = PATH_HOMEKIT + '.' + str(name) + '.add_preload_service'
return (path_acc, path_file)
def mock_preload_service(acc, service, chars=None, opt_chars=None):
"""Mock alternative for function 'add_preload_service'."""
service = MockService(service)
if chars:
chars = chars if isinstance(chars, list) else [chars]
for char_name in chars:
service.add_characteristic(char_name)
if opt_chars:
opt_chars = opt_chars if isinstance(opt_chars, list) else [opt_chars]
for opt_char_name in opt_chars:
service.add_characteristic(opt_char_name)
acc.add_service(service)
return service
class MockAccessory():
"""Define all attributes and methods for a MockAccessory."""
def __init__(self, name):
"""Initialize a MockAccessory object."""
self.display_name = name
self.services = []
def __repr__(self):
"""Return a representation of a MockAccessory. Use for debugging."""
serv_list = [serv.display_name for serv in self.services]
return "<accessory \"{}\", services={}>".format(
self.display_name, serv_list)
def add_service(self, service):
"""Add service to list of services."""
self.services.append(service)
def get_service(self, name):
"""Retrieve service from service list or return new MockService."""
for serv in self.services:
if serv.display_name == name:
return serv
serv = MockService(name)
self.add_service(serv)
return serv
class MockService():
"""Define all attributes and methods for a MockService."""
def __init__(self, name):
"""Initialize a MockService object."""
self.characteristics = []
self.opt_characteristics = []
self.display_name = name
def __repr__(self):
"""Return a representation of a MockService. Use for debugging."""
char_list = [char.display_name for char in self.characteristics]
opt_char_list = [
char.display_name for char in self.opt_characteristics]
return "<service \"{}\", chars={}, opt_chars={}>".format(
self.display_name, char_list, opt_char_list)
def add_characteristic(self, char):
"""Add characteristic to char list."""
self.characteristics.append(char)
def add_opt_characteristic(self, char):
"""Add characteristic to opt_char list."""
self.opt_characteristics.append(char)
def get_characteristic(self, name):
"""Get char for char lists or return new MockChar."""
for char in self.characteristics:
if char.display_name == name:
return char
for char in self.opt_characteristics:
if char.display_name == name:
return char
char = MockChar(name)
self.add_characteristic(char)
return char
class MockChar():
"""Define all attributes and methods for a MockChar."""
def __init__(self, name):
"""Initialize a MockChar object."""
self.display_name = name
self.properties = {}
self.value = None
self.type_id = None
self.setter_callback = None
def __repr__(self):
"""Return a representation of a MockChar. Use for debugging."""
return "<char \"{}\", value={}>".format(
self.display_name, self.value)
def set_value(self, value, should_notify=True, should_callback=True):
"""Set value of char."""
self.value = value
if self.setter_callback is not None and should_callback:
# pylint: disable=not-callable
self.setter_callback(value)
def get_value(self):
"""Get char value."""
return self.value
class MockTypeLoader():
"""Define all attributes and methods for a MockTypeLoader."""
def __init__(self, class_type):
"""Initialize a MockTypeLoader object."""
self.class_type = class_type
def get(self, name):
"""Return a MockService or MockChar object."""
if self.class_type == 'service':
return MockService(name)
elif self.class_type == 'char':
return MockChar(name)