diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 4984cfee959..080dd2a7cbd 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -30,7 +30,13 @@ from .util import ( TYPES = Registry() _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['HAP-python==1.1.9'] +REQUIREMENTS = ['HAP-python==2.0.0'] + +# #### Driver Status #### +STATUS_READY = 0 +STATUS_RUNNING = 1 +STATUS_STOPPED = 2 +STATUS_WAIT = 3 CONFIG_SCHEMA = vol.Schema({ @@ -57,7 +63,7 @@ async def async_setup(hass, config): entity_config = conf[CONF_ENTITY_CONFIG] homekit = HomeKit(hass, port, ip_address, entity_filter, entity_config) - homekit.setup() + await hass.async_add_job(homekit.setup) if auto_start: hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, homekit.start) @@ -65,8 +71,10 @@ async def async_setup(hass, config): def handle_homekit_service_start(service): """Handle start HomeKit service call.""" - if homekit.started: - _LOGGER.warning('HomeKit is already running') + if homekit.status != STATUS_READY: + _LOGGER.warning( + 'HomeKit is not ready. Either it is already running or has ' + 'been stopped.') return homekit.start() @@ -162,7 +170,7 @@ class HomeKit(): self._ip_address = ip_address self._filter = entity_filter self._config = entity_config - self.started = False + self.status = STATUS_READY self.bridge = None self.driver = None @@ -191,9 +199,9 @@ class HomeKit(): def start(self, *args): """Start the accessory driver.""" - if self.started: + if self.status != STATUS_READY: return - self.started = True + self.status = STATUS_WAIT # pylint: disable=unused-variable from . import ( # noqa F401 @@ -202,19 +210,20 @@ class HomeKit(): for state in self.hass.states.all(): self.add_bridge_accessory(state) - self.bridge.set_broker(self.driver) + self.bridge.set_driver(self.driver) if not self.bridge.paired: show_setup_message(self.hass, self.bridge) _LOGGER.debug('Driver start') - self.driver.start() + self.hass.add_job(self.driver.start) + self.status = STATUS_RUNNING def stop(self, *args): """Stop the accessory driver.""" - if not self.started: + if self.status != STATUS_RUNNING: return + self.status = STATUS_STOPPED _LOGGER.debug('Driver stop') - if self.driver and self.driver.run_sentinel: - self.driver.stop() + self.hass.add_job(self.driver.stop) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index c7703b221d8..c47c3f8fbe7 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -4,8 +4,9 @@ from functools import wraps from inspect import getmodule import logging -from pyhap.accessory import Accessory, Bridge, Category +from pyhap.accessory import Accessory, Bridge from pyhap.accessory_driver import AccessoryDriver +from pyhap.const import CATEGORY_OTHER from homeassistant.const import __version__ from homeassistant.core import callback as ha_callback @@ -15,9 +16,8 @@ from homeassistant.helpers.event import ( from homeassistant.util import dt as dt_util from .const import ( - DEBOUNCE_TIMEOUT, BRIDGE_MODEL, BRIDGE_NAME, BRIDGE_SERIAL_NUMBER, - MANUFACTURER, SERV_ACCESSORY_INFO, CHAR_FIRMWARE_REVISION, - CHAR_MANUFACTURER, CHAR_MODEL, CHAR_NAME, CHAR_SERIAL_NUMBER) + DEBOUNCE_TIMEOUT, BRIDGE_MODEL, BRIDGE_NAME, + BRIDGE_SERIAL_NUMBER, MANUFACTURER) from .util import ( show_setup_message, dismiss_setup_message) @@ -61,59 +61,20 @@ def debounce(func): return wrapper -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 setup_char(char_name, service, value=None, properties=None, callback=None): - """Helper function to return fully configured characteristic.""" - char = service.get_characteristic(char_name) - if value: - char.value = value - if properties: - char.override_properties(properties) - if callback: - char.setter_callback = callback - return char - - -def set_accessory_info(acc, name, model, serial_number, - manufacturer=MANUFACTURER, - firmware_revision=__version__): - """Set the default accessory information.""" - service = acc.get_service(SERV_ACCESSORY_INFO) - service.get_characteristic(CHAR_NAME).set_value(name) - service.get_characteristic(CHAR_MODEL).set_value(model) - service.get_characteristic(CHAR_MANUFACTURER).set_value(manufacturer) - service.get_characteristic(CHAR_SERIAL_NUMBER).set_value(serial_number) - service.get_characteristic(CHAR_FIRMWARE_REVISION) \ - .set_value(firmware_revision) - - class HomeAccessory(Accessory): """Adapter class for Accessory.""" - def __init__(self, hass, name, entity_id, aid, category): + def __init__(self, hass, name, entity_id, aid, category=CATEGORY_OTHER): """Initialize a Accessory object.""" super().__init__(name, aid=aid) domain = split_entity_id(entity_id)[0].replace("_", " ").title() - set_accessory_info(self, name, model=domain, serial_number=entity_id) - self.category = getattr(Category, category, Category.OTHER) + self.set_info_service( + firmware_revision=__version__, manufacturer=MANUFACTURER, + model=domain, serial_number=entity_id) + self.category = category self.entity_id = entity_id self.hass = hass - def _set_services(self): - add_preload_service(self, SERV_ACCESSORY_INFO) - def run(self): """Method called by accessory after driver is started.""" state = self.hass.states.get(self.entity_id) @@ -143,13 +104,11 @@ class HomeBridge(Bridge): def __init__(self, hass, name=BRIDGE_NAME): """Initialize a Bridge object.""" super().__init__(name) - set_accessory_info(self, name, model=BRIDGE_MODEL, - serial_number=BRIDGE_SERIAL_NUMBER) + self.set_info_service( + firmware_revision=__version__, manufacturer=MANUFACTURER, + model=BRIDGE_MODEL, serial_number=BRIDGE_SERIAL_NUMBER) self.hass = hass - def _set_services(self): - add_preload_service(self, SERV_ACCESSORY_INFO) - def setup_message(self): """Prevent print of pyhap setup message to terminal.""" pass diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 9c9f60eef94..ce46e84a2ef 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -23,17 +23,6 @@ BRIDGE_NAME = 'Home Assistant Bridge' BRIDGE_SERIAL_NUMBER = 'homekit.bridge' MANUFACTURER = 'Home Assistant' -# #### Categories #### -CATEGORY_ALARM_SYSTEM = 'ALARM_SYSTEM' -CATEGORY_GARAGE_DOOR_OPENER = 'GARAGE_DOOR_OPENER' -CATEGORY_LIGHT = 'LIGHTBULB' -CATEGORY_LOCK = 'DOOR_LOCK' -CATEGORY_SENSOR = 'SENSOR' -CATEGORY_SWITCH = 'SWITCH' -CATEGORY_THERMOSTAT = 'THERMOSTAT' -CATEGORY_WINDOW_COVERING = 'WINDOW_COVERING' - - # #### Services #### SERV_ACCESSORY_INFO = 'AccessoryInformation' SERV_AIR_QUALITY_SENSOR = 'AirQualitySensor' @@ -56,7 +45,6 @@ SERV_THERMOSTAT = 'Thermostat' SERV_WINDOW_COVERING = 'WindowCovering' # CurrentPosition, TargetPosition, PositionState - # #### Characteristics #### CHAR_AIR_PARTICULATE_DENSITY = 'AirParticulateDensity' CHAR_AIR_QUALITY = 'AirQuality' diff --git a/homeassistant/components/homekit/type_covers.py b/homeassistant/components/homekit/type_covers.py index 8ec715e0e01..b30109f711d 100644 --- a/homeassistant/components/homekit/type_covers.py +++ b/homeassistant/components/homekit/type_covers.py @@ -1,6 +1,8 @@ """Class to hold all cover accessories.""" import logging +from pyhap.const import CATEGORY_WINDOW_COVERING, CATEGORY_GARAGE_DOOR_OPENER + from homeassistant.components.cover import ( ATTR_CURRENT_POSITION, ATTR_POSITION, DOMAIN, SUPPORT_STOP) from homeassistant.const import ( @@ -9,12 +11,11 @@ from homeassistant.const import ( ATTR_SUPPORTED_FEATURES) from . import TYPES -from .accessories import HomeAccessory, add_preload_service, setup_char +from .accessories import HomeAccessory from .const import ( - CATEGORY_WINDOW_COVERING, SERV_WINDOW_COVERING, - CHAR_CURRENT_POSITION, CHAR_TARGET_POSITION, CHAR_POSITION_STATE, - CATEGORY_GARAGE_DOOR_OPENER, SERV_GARAGE_DOOR_OPENER, - CHAR_CURRENT_DOOR_STATE, CHAR_TARGET_DOOR_STATE) + SERV_WINDOW_COVERING, CHAR_CURRENT_POSITION, + CHAR_TARGET_POSITION, CHAR_POSITION_STATE, + SERV_GARAGE_DOOR_OPENER, CHAR_CURRENT_DOOR_STATE, CHAR_TARGET_DOOR_STATE) _LOGGER = logging.getLogger(__name__) @@ -32,12 +33,11 @@ class GarageDoorOpener(HomeAccessory): super().__init__(*args, category=CATEGORY_GARAGE_DOOR_OPENER) self.flag_target_state = False - serv_garage_door = add_preload_service(self, SERV_GARAGE_DOOR_OPENER) - self.char_current_state = setup_char( - CHAR_CURRENT_DOOR_STATE, serv_garage_door, value=0) - self.char_target_state = setup_char( - CHAR_TARGET_DOOR_STATE, serv_garage_door, value=0, - callback=self.set_state) + serv_garage_door = self.add_preload_service(SERV_GARAGE_DOOR_OPENER) + self.char_current_state = serv_garage_door.configure_char( + CHAR_CURRENT_DOOR_STATE, value=0) + self.char_target_state = serv_garage_door.configure_char( + CHAR_TARGET_DOOR_STATE, value=0, setter_callback=self.set_state) def set_state(self, value): """Change garage state if call came from HomeKit.""" @@ -74,12 +74,11 @@ class WindowCovering(HomeAccessory): super().__init__(*args, category=CATEGORY_WINDOW_COVERING) self.homekit_target = None - serv_cover = add_preload_service(self, SERV_WINDOW_COVERING) - self.char_current_position = setup_char( - CHAR_CURRENT_POSITION, serv_cover, value=0) - self.char_target_position = setup_char( - CHAR_TARGET_POSITION, serv_cover, value=0, - callback=self.move_cover) + serv_cover = self.add_preload_service(SERV_WINDOW_COVERING) + self.char_current_position = serv_cover.configure_char( + CHAR_CURRENT_POSITION, value=0) + self.char_target_position = serv_cover.configure_char( + CHAR_TARGET_POSITION, value=0, setter_callback=self.move_cover) def move_cover(self, value): """Move cover to value if call came from HomeKit.""" @@ -115,14 +114,13 @@ class WindowCoveringBasic(HomeAccessory): .attributes.get(ATTR_SUPPORTED_FEATURES) self.supports_stop = features & SUPPORT_STOP - serv_cover = add_preload_service(self, SERV_WINDOW_COVERING) - self.char_current_position = setup_char( - CHAR_CURRENT_POSITION, serv_cover, value=0) - self.char_target_position = setup_char( - CHAR_TARGET_POSITION, serv_cover, value=0, - callback=self.move_cover) - self.char_position_state = setup_char( - CHAR_POSITION_STATE, serv_cover, value=2) + serv_cover = self.add_preload_service(SERV_WINDOW_COVERING) + self.char_current_position = serv_cover.configure_char( + CHAR_CURRENT_POSITION, value=0) + self.char_target_position = serv_cover.configure_char( + CHAR_TARGET_POSITION, value=0, setter_callback=self.move_cover) + self.char_position_state = serv_cover.configure_char( + CHAR_POSITION_STATE, value=2) def move_cover(self, value): """Move cover to value if call came from HomeKit.""" diff --git a/homeassistant/components/homekit/type_lights.py b/homeassistant/components/homekit/type_lights.py index 9a7bce76fba..3efb0e99df6 100644 --- a/homeassistant/components/homekit/type_lights.py +++ b/homeassistant/components/homekit/type_lights.py @@ -1,16 +1,17 @@ """Class to hold all light accessories.""" import logging +from pyhap.const import CATEGORY_LIGHTBULB + from homeassistant.components.light import ( ATTR_HS_COLOR, ATTR_COLOR_TEMP, ATTR_BRIGHTNESS, ATTR_MIN_MIREDS, ATTR_MAX_MIREDS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_BRIGHTNESS) from homeassistant.const import ATTR_SUPPORTED_FEATURES, STATE_ON, STATE_OFF from . import TYPES -from .accessories import ( - HomeAccessory, add_preload_service, debounce, setup_char) +from .accessories import HomeAccessory, debounce from .const import ( - CATEGORY_LIGHT, SERV_LIGHTBULB, CHAR_COLOR_TEMPERATURE, + SERV_LIGHTBULB, CHAR_COLOR_TEMPERATURE, CHAR_BRIGHTNESS, CHAR_HUE, CHAR_ON, CHAR_SATURATION) _LOGGER = logging.getLogger(__name__) @@ -27,7 +28,7 @@ class Light(HomeAccessory): def __init__(self, *args, config): """Initialize a new Light accessory object.""" - super().__init__(*args, category=CATEGORY_LIGHT) + super().__init__(*args, category=CATEGORY_LIGHTBULB) self._flag = {CHAR_ON: False, CHAR_BRIGHTNESS: False, CHAR_HUE: False, CHAR_SATURATION: False, CHAR_COLOR_TEMPERATURE: False, RGB_COLOR: False} @@ -46,30 +47,28 @@ class Light(HomeAccessory): self._hue = None self._saturation = None - serv_light = add_preload_service(self, SERV_LIGHTBULB, self.chars) - self.char_on = setup_char( - CHAR_ON, serv_light, value=self._state, callback=self.set_state) + serv_light = self.add_preload_service(SERV_LIGHTBULB, self.chars) + self.char_on = serv_light.configure_char( + CHAR_ON, value=self._state, setter_callback=self.set_state) if CHAR_BRIGHTNESS in self.chars: - self.char_brightness = setup_char( - CHAR_BRIGHTNESS, serv_light, value=0, - callback=self.set_brightness) + self.char_brightness = serv_light.configure_char( + CHAR_BRIGHTNESS, value=0, setter_callback=self.set_brightness) if CHAR_COLOR_TEMPERATURE in self.chars: min_mireds = self.hass.states.get(self.entity_id) \ .attributes.get(ATTR_MIN_MIREDS, 153) max_mireds = self.hass.states.get(self.entity_id) \ .attributes.get(ATTR_MAX_MIREDS, 500) - self.char_color_temperature = setup_char( - CHAR_COLOR_TEMPERATURE, serv_light, value=min_mireds, + self.char_color_temperature = serv_light.configure_char( + CHAR_COLOR_TEMPERATURE, value=min_mireds, properties={'minValue': min_mireds, 'maxValue': max_mireds}, - callback=self.set_color_temperature) + setter_callback=self.set_color_temperature) if CHAR_HUE in self.chars: - self.char_hue = setup_char( - CHAR_HUE, serv_light, value=0, callback=self.set_hue) + self.char_hue = serv_light.configure_char( + CHAR_HUE, value=0, setter_callback=self.set_hue) if CHAR_SATURATION in self.chars: - self.char_saturation = setup_char( - CHAR_SATURATION, serv_light, value=75, - callback=self.set_saturation) + self.char_saturation = serv_light.configure_char( + CHAR_SATURATION, value=75, setter_callback=self.set_saturation) def set_state(self, value): """Set state if call came from HomeKit.""" diff --git a/homeassistant/components/homekit/type_locks.py b/homeassistant/components/homekit/type_locks.py index f34fc6c6a7f..e7f18d44805 100644 --- a/homeassistant/components/homekit/type_locks.py +++ b/homeassistant/components/homekit/type_locks.py @@ -1,13 +1,15 @@ """Class to hold all lock accessories.""" import logging +from pyhap.const import CATEGORY_DOOR_LOCK + from homeassistant.components.lock import ( ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN) from . import TYPES -from .accessories import HomeAccessory, add_preload_service, setup_char +from .accessories import HomeAccessory from .const import ( - CATEGORY_LOCK, SERV_LOCK, CHAR_LOCK_CURRENT_STATE, CHAR_LOCK_TARGET_STATE) + SERV_LOCK, CHAR_LOCK_CURRENT_STATE, CHAR_LOCK_TARGET_STATE) _LOGGER = logging.getLogger(__name__) @@ -29,16 +31,16 @@ class Lock(HomeAccessory): def __init__(self, *args, config): """Initialize a Lock accessory object.""" - super().__init__(*args, category=CATEGORY_LOCK) + super().__init__(*args, category=CATEGORY_DOOR_LOCK) self.flag_target_state = False - serv_lock_mechanism = add_preload_service(self, SERV_LOCK) - self.char_current_state = setup_char( - CHAR_LOCK_CURRENT_STATE, serv_lock_mechanism, + serv_lock_mechanism = self.add_preload_service(SERV_LOCK) + self.char_current_state = serv_lock_mechanism.configure_char( + CHAR_LOCK_CURRENT_STATE, value=HASS_TO_HOMEKIT[STATE_UNKNOWN]) - self.char_target_state = setup_char( - CHAR_LOCK_TARGET_STATE, serv_lock_mechanism, - value=HASS_TO_HOMEKIT[STATE_LOCKED], callback=self.set_state) + self.char_target_state = serv_lock_mechanism.configure_char( + CHAR_LOCK_TARGET_STATE, value=HASS_TO_HOMEKIT[STATE_LOCKED], + setter_callback=self.set_state) def set_state(self, value): """Set lock state to value if call came from HomeKit.""" diff --git a/homeassistant/components/homekit/type_security_systems.py b/homeassistant/components/homekit/type_security_systems.py index 0762e0f25f9..968e60f2842 100644 --- a/homeassistant/components/homekit/type_security_systems.py +++ b/homeassistant/components/homekit/type_security_systems.py @@ -1,16 +1,18 @@ """Class to hold all alarm control panel accessories.""" import logging +from pyhap.const import CATEGORY_ALARM_SYSTEM + from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED, ATTR_ENTITY_ID, ATTR_CODE) from . import TYPES -from .accessories import HomeAccessory, add_preload_service, setup_char +from .accessories import HomeAccessory from .const import ( - CATEGORY_ALARM_SYSTEM, SERV_SECURITY_SYSTEM, - CHAR_CURRENT_SECURITY_STATE, CHAR_TARGET_SECURITY_STATE) + SERV_SECURITY_SYSTEM, CHAR_CURRENT_SECURITY_STATE, + CHAR_TARGET_SECURITY_STATE) _LOGGER = logging.getLogger(__name__) @@ -33,12 +35,12 @@ class SecuritySystem(HomeAccessory): self._alarm_code = config.get(ATTR_CODE) self.flag_target_state = False - serv_alarm = add_preload_service(self, SERV_SECURITY_SYSTEM) - self.char_current_state = setup_char( - CHAR_CURRENT_SECURITY_STATE, serv_alarm, value=3) - self.char_target_state = setup_char( - CHAR_TARGET_SECURITY_STATE, serv_alarm, value=3, - callback=self.set_security_state) + serv_alarm = self.add_preload_service(SERV_SECURITY_SYSTEM) + self.char_current_state = serv_alarm.configure_char( + CHAR_CURRENT_SECURITY_STATE, value=3) + self.char_target_state = serv_alarm.configure_char( + CHAR_TARGET_SECURITY_STATE, value=3, + setter_callback=self.set_security_state) def set_security_state(self, value): """Move security state to value if call came from HomeKit.""" diff --git a/homeassistant/components/homekit/type_sensors.py b/homeassistant/components/homekit/type_sensors.py index 7d7bbc5edd6..393b6beffd6 100644 --- a/homeassistant/components/homekit/type_sensors.py +++ b/homeassistant/components/homekit/type_sensors.py @@ -1,14 +1,16 @@ """Class to hold all sensor accessories.""" import logging +from pyhap.const import CATEGORY_SENSOR + from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, ATTR_DEVICE_CLASS, STATE_ON, STATE_HOME) from . import TYPES -from .accessories import HomeAccessory, add_preload_service, setup_char +from .accessories import HomeAccessory from .const import ( - CATEGORY_SENSOR, SERV_HUMIDITY_SENSOR, SERV_TEMPERATURE_SENSOR, + SERV_HUMIDITY_SENSOR, SERV_TEMPERATURE_SENSOR, CHAR_CURRENT_HUMIDITY, CHAR_CURRENT_TEMPERATURE, PROP_CELSIUS, SERV_AIR_QUALITY_SENSOR, CHAR_AIR_QUALITY, CHAR_AIR_PARTICULATE_DENSITY, CHAR_CARBON_DIOXIDE_LEVEL, CHAR_CARBON_DIOXIDE_PEAK_LEVEL, @@ -52,10 +54,9 @@ class TemperatureSensor(HomeAccessory): def __init__(self, *args, config): """Initialize a TemperatureSensor accessory object.""" super().__init__(*args, category=CATEGORY_SENSOR) - serv_temp = add_preload_service(self, SERV_TEMPERATURE_SENSOR) - self.char_temp = setup_char( - CHAR_CURRENT_TEMPERATURE, serv_temp, value=0, - properties=PROP_CELSIUS) + serv_temp = self.add_preload_service(SERV_TEMPERATURE_SENSOR) + self.char_temp = serv_temp.configure_char( + CHAR_CURRENT_TEMPERATURE, value=0, properties=PROP_CELSIUS) self.unit = None def update_state(self, new_state): @@ -76,9 +77,9 @@ class HumiditySensor(HomeAccessory): def __init__(self, *args, config): """Initialize a HumiditySensor accessory object.""" super().__init__(*args, category=CATEGORY_SENSOR) - serv_humidity = add_preload_service(self, SERV_HUMIDITY_SENSOR) - self.char_humidity = setup_char( - CHAR_CURRENT_HUMIDITY, serv_humidity, value=0) + serv_humidity = self.add_preload_service(SERV_HUMIDITY_SENSOR) + self.char_humidity = serv_humidity.configure_char( + CHAR_CURRENT_HUMIDITY, value=0) def update_state(self, new_state): """Update accessory after state change.""" @@ -97,12 +98,12 @@ class AirQualitySensor(HomeAccessory): """Initialize a AirQualitySensor accessory object.""" super().__init__(*args, category=CATEGORY_SENSOR) - serv_air_quality = add_preload_service(self, SERV_AIR_QUALITY_SENSOR, - [CHAR_AIR_PARTICULATE_DENSITY]) - self.char_quality = setup_char( - CHAR_AIR_QUALITY, serv_air_quality, value=0) - self.char_density = setup_char( - CHAR_AIR_PARTICULATE_DENSITY, serv_air_quality, value=0) + serv_air_quality = self.add_preload_service( + SERV_AIR_QUALITY_SENSOR, [CHAR_AIR_PARTICULATE_DENSITY]) + self.char_quality = serv_air_quality.configure_char( + CHAR_AIR_QUALITY, value=0) + self.char_density = serv_air_quality.configure_char( + CHAR_AIR_PARTICULATE_DENSITY, value=0) def update_state(self, new_state): """Update accessory after state change.""" @@ -121,14 +122,14 @@ class CarbonDioxideSensor(HomeAccessory): """Initialize a CarbonDioxideSensor accessory object.""" super().__init__(*args, category=CATEGORY_SENSOR) - serv_co2 = add_preload_service(self, SERV_CARBON_DIOXIDE_SENSOR, [ + serv_co2 = self.add_preload_service(SERV_CARBON_DIOXIDE_SENSOR, [ CHAR_CARBON_DIOXIDE_LEVEL, CHAR_CARBON_DIOXIDE_PEAK_LEVEL]) - self.char_co2 = setup_char( - CHAR_CARBON_DIOXIDE_LEVEL, serv_co2, value=0) - self.char_peak = setup_char( - CHAR_CARBON_DIOXIDE_PEAK_LEVEL, serv_co2, value=0) - self.char_detected = setup_char( - CHAR_CARBON_DIOXIDE_DETECTED, serv_co2, value=0) + self.char_co2 = serv_co2.configure_char( + CHAR_CARBON_DIOXIDE_LEVEL, value=0) + self.char_peak = serv_co2.configure_char( + CHAR_CARBON_DIOXIDE_PEAK_LEVEL, value=0) + self.char_detected = serv_co2.configure_char( + CHAR_CARBON_DIOXIDE_DETECTED, value=0) def update_state(self, new_state): """Update accessory after state change.""" @@ -149,9 +150,9 @@ class LightSensor(HomeAccessory): """Initialize a LightSensor accessory object.""" super().__init__(*args, category=CATEGORY_SENSOR) - serv_light = add_preload_service(self, SERV_LIGHT_SENSOR) - self.char_light = setup_char( - CHAR_CURRENT_AMBIENT_LIGHT_LEVEL, serv_light, value=0) + serv_light = self.add_preload_service(SERV_LIGHT_SENSOR) + self.char_light = serv_light.configure_char( + CHAR_CURRENT_AMBIENT_LIGHT_LEVEL, value=0) def update_state(self, new_state): """Update accessory after state change.""" @@ -174,8 +175,8 @@ class BinarySensor(HomeAccessory): if device_class in BINARY_SENSOR_SERVICE_MAP \ else BINARY_SENSOR_SERVICE_MAP[DEVICE_CLASS_OCCUPANCY] - service = add_preload_service(self, service_char[0]) - self.char_detected = setup_char(service_char[1], service, value=0) + service = self.add_preload_service(service_char[0]) + self.char_detected = service.configure_char(service_char[1], value=0) def update_state(self, new_state): """Update accessory after state change.""" diff --git a/homeassistant/components/homekit/type_switches.py b/homeassistant/components/homekit/type_switches.py index aaf13e4ea7e..68a4fcdab0a 100644 --- a/homeassistant/components/homekit/type_switches.py +++ b/homeassistant/components/homekit/type_switches.py @@ -1,13 +1,15 @@ """Class to hold all switch accessories.""" import logging +from pyhap.const import CATEGORY_SWITCH + from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_ON) from homeassistant.core import split_entity_id from . import TYPES -from .accessories import HomeAccessory, add_preload_service, setup_char -from .const import CATEGORY_SWITCH, SERV_SWITCH, CHAR_ON +from .accessories import HomeAccessory +from .const import SERV_SWITCH, CHAR_ON _LOGGER = logging.getLogger(__name__) @@ -22,9 +24,9 @@ class Switch(HomeAccessory): self._domain = split_entity_id(self.entity_id)[0] self.flag_target_state = False - serv_switch = add_preload_service(self, SERV_SWITCH) - self.char_on = setup_char( - CHAR_ON, serv_switch, value=False, callback=self.set_state) + serv_switch = self.add_preload_service(SERV_SWITCH) + self.char_on = serv_switch.configure_char( + CHAR_ON, value=False, setter_callback=self.set_state) def set_state(self, value): """Move switch state to value if call came from HomeKit.""" diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index 4faceefe850..15fd8160a7e 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -1,6 +1,8 @@ """Class to hold all thermostat accessories.""" import logging +from pyhap.const import CATEGORY_THERMOSTAT + from homeassistant.components.climate import ( ATTR_CURRENT_TEMPERATURE, ATTR_TEMPERATURE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, @@ -12,10 +14,9 @@ from homeassistant.const import ( STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT) from . import TYPES -from .accessories import ( - HomeAccessory, add_preload_service, debounce, setup_char) +from .accessories import HomeAccessory, debounce from .const import ( - CATEGORY_THERMOSTAT, SERV_THERMOSTAT, CHAR_CURRENT_HEATING_COOLING, + SERV_THERMOSTAT, CHAR_CURRENT_HEATING_COOLING, CHAR_TARGET_HEATING_COOLING, CHAR_CURRENT_TEMPERATURE, CHAR_TARGET_TEMPERATURE, CHAR_TEMP_DISPLAY_UNITS, CHAR_COOLING_THRESHOLD_TEMPERATURE, CHAR_HEATING_THRESHOLD_TEMPERATURE) @@ -57,38 +58,37 @@ class Thermostat(HomeAccessory): self.chars.extend((CHAR_COOLING_THRESHOLD_TEMPERATURE, CHAR_HEATING_THRESHOLD_TEMPERATURE)) - serv_thermostat = add_preload_service( - self, SERV_THERMOSTAT, self.chars) + serv_thermostat = self.add_preload_service(SERV_THERMOSTAT, self.chars) # Current and target mode characteristics - self.char_current_heat_cool = setup_char( - CHAR_CURRENT_HEATING_COOLING, serv_thermostat, value=0) - self.char_target_heat_cool = setup_char( - CHAR_TARGET_HEATING_COOLING, serv_thermostat, value=0, - callback=self.set_heat_cool) + self.char_current_heat_cool = serv_thermostat.configure_char( + CHAR_CURRENT_HEATING_COOLING, value=0) + self.char_target_heat_cool = serv_thermostat.configure_char( + CHAR_TARGET_HEATING_COOLING, value=0, + setter_callback=self.set_heat_cool) # Current and target temperature characteristics - self.char_current_temp = setup_char( - CHAR_CURRENT_TEMPERATURE, serv_thermostat, value=21.0) - self.char_target_temp = setup_char( - CHAR_TARGET_TEMPERATURE, serv_thermostat, value=21.0, - callback=self.set_target_temperature) + self.char_current_temp = serv_thermostat.configure_char( + CHAR_CURRENT_TEMPERATURE, value=21.0) + self.char_target_temp = serv_thermostat.configure_char( + CHAR_TARGET_TEMPERATURE, value=21.0, + setter_callback=self.set_target_temperature) # Display units characteristic - self.char_display_units = setup_char( - CHAR_TEMP_DISPLAY_UNITS, serv_thermostat, value=0) + self.char_display_units = serv_thermostat.configure_char( + CHAR_TEMP_DISPLAY_UNITS, value=0) # If the device supports it: high and low temperature characteristics self.char_cooling_thresh_temp = None self.char_heating_thresh_temp = None if CHAR_COOLING_THRESHOLD_TEMPERATURE in self.chars: - self.char_cooling_thresh_temp = setup_char( - CHAR_COOLING_THRESHOLD_TEMPERATURE, serv_thermostat, - value=23.0, callback=self.set_cooling_threshold) + self.char_cooling_thresh_temp = serv_thermostat.configure_char( + CHAR_COOLING_THRESHOLD_TEMPERATURE, value=23.0, + setter_callback=self.set_cooling_threshold) if CHAR_HEATING_THRESHOLD_TEMPERATURE in self.chars: - self.char_heating_thresh_temp = setup_char( - CHAR_HEATING_THRESHOLD_TEMPERATURE, serv_thermostat, - value=19.0, callback=self.set_heating_threshold) + self.char_heating_thresh_temp = serv_thermostat.configure_char( + CHAR_HEATING_THRESHOLD_TEMPERATURE, value=19.0, + setter_callback=self.set_heating_threshold) def set_heat_cool(self, value): """Move operation mode to value if call came from HomeKit.""" diff --git a/requirements_all.txt b/requirements_all.txt index 002c5eb93e2..0981ef154c4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -28,7 +28,7 @@ Adafruit-SHT31==1.0.2 DoorBirdPy==0.1.3 # homeassistant.components.homekit -HAP-python==1.1.9 +HAP-python==2.0.0 # homeassistant.components.notify.mastodon Mastodon.py==1.2.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0605b3d2e24..0e0b4f4da9f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -19,7 +19,7 @@ requests_mock==1.4 # homeassistant.components.homekit -HAP-python==1.1.9 +HAP-python==2.0.0 # homeassistant.components.notify.html5 PyJWT==1.6.0 diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index 3df76185a51..faa982f62f3 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -7,12 +7,12 @@ import unittest from unittest.mock import call, patch, Mock from homeassistant.components.homekit.accessories import ( - add_preload_service, set_accessory_info, debounce, HomeAccessory, HomeBridge, HomeDriver) from homeassistant.components.homekit.const import ( - BRIDGE_MODEL, BRIDGE_NAME, SERV_ACCESSORY_INFO, CHAR_FIRMWARE_REVISION, - CHAR_MANUFACTURER, CHAR_MODEL, CHAR_NAME, CHAR_SERIAL_NUMBER, MANUFACTURER) -from homeassistant.const import ATTR_NOW, EVENT_TIME_CHANGED + BRIDGE_MODEL, BRIDGE_NAME, BRIDGE_SERIAL_NUMBER, SERV_ACCESSORY_INFO, + CHAR_FIRMWARE_REVISION, CHAR_MANUFACTURER, CHAR_MODEL, CHAR_NAME, + CHAR_SERIAL_NUMBER, MANUFACTURER) +from homeassistant.const import __version__, ATTR_NOW, EVENT_TIME_CHANGED import homeassistant.util.dt as dt_util from tests.common import get_test_home_assistant @@ -62,73 +62,25 @@ class TestAccessories(unittest.TestCase): hass.stop() - def test_add_preload_service(self): - """Test add_preload_service without additional characteristics.""" - acc = Mock() - serv = add_preload_service(acc, 'AirPurifier') - self.assertEqual(acc.mock_calls, [call.add_service(serv)]) - with self.assertRaises(ValueError): - serv.get_characteristic('Name') - - # Test with typo in service name - with self.assertRaises(KeyError): - add_preload_service(Mock(), 'AirPurifierTypo') - - # Test adding additional characteristic as string - serv = add_preload_service(Mock(), 'AirPurifier', 'Name') - serv.get_characteristic('Name') - - # Test adding additional characteristics as list - serv = add_preload_service(Mock(), 'AirPurifier', - ['Name', 'RotationSpeed']) - serv.get_characteristic('Name') - serv.get_characteristic('RotationSpeed') - - # Test adding additional characteristic with typo - with self.assertRaises(KeyError): - add_preload_service(Mock(), 'AirPurifier', 'NameTypo') - - def test_set_accessory_info(self): - """Test setting the basic accessory information.""" - # Test HomeAccessory - acc = HomeAccessory('HA', 'Home Accessory', 'homekit.accessory', 2, '') - set_accessory_info(acc, 'name', 'model', '0000', MANUFACTURER, '1.2.3') - - serv = acc.get_service(SERV_ACCESSORY_INFO) - self.assertEqual(serv.get_characteristic(CHAR_NAME).value, 'name') - self.assertEqual(serv.get_characteristic(CHAR_MODEL).value, 'model') - self.assertEqual( - serv.get_characteristic(CHAR_SERIAL_NUMBER).value, '0000') - self.assertEqual( - serv.get_characteristic(CHAR_MANUFACTURER).value, MANUFACTURER) - self.assertEqual( - serv.get_characteristic(CHAR_FIRMWARE_REVISION).value, '1.2.3') - - # Test HomeBridge - acc = HomeBridge('hass') - set_accessory_info(acc, 'name', 'model', '0000', MANUFACTURER, '1.2.3') - - serv = acc.get_service(SERV_ACCESSORY_INFO) - self.assertEqual(serv.get_characteristic(CHAR_MODEL).value, 'model') - self.assertEqual( - serv.get_characteristic(CHAR_SERIAL_NUMBER).value, '0000') - self.assertEqual( - serv.get_characteristic(CHAR_MANUFACTURER).value, MANUFACTURER) - self.assertEqual( - serv.get_characteristic(CHAR_FIRMWARE_REVISION).value, '1.2.3') - def test_home_accessory(self): """Test HomeAccessory class.""" hass = get_test_home_assistant() - acc = HomeAccessory(hass, 'Home Accessory', 'homekit.accessory', 2, '') + acc = HomeAccessory(hass, 'Home Accessory', 'homekit.accessory', 2) self.assertEqual(acc.hass, hass) self.assertEqual(acc.display_name, 'Home Accessory') self.assertEqual(acc.category, 1) # Category.OTHER self.assertEqual(len(acc.services), 1) serv = acc.services[0] # SERV_ACCESSORY_INFO + self.assertEqual(serv.display_name, SERV_ACCESSORY_INFO) + self.assertEqual( + serv.get_characteristic(CHAR_NAME).value, 'Home Accessory') + self.assertEqual( + serv.get_characteristic(CHAR_MANUFACTURER).value, MANUFACTURER) self.assertEqual( serv.get_characteristic(CHAR_MODEL).value, 'Homekit') + self.assertEqual(serv.get_characteristic(CHAR_SERIAL_NUMBER).value, + 'homekit.accessory') hass.states.set('homekit.accessory', 'on') hass.block_till_done() @@ -136,7 +88,7 @@ class TestAccessories(unittest.TestCase): hass.states.set('homekit.accessory', 'off') hass.block_till_done() - acc = HomeAccessory('hass', 'test_name', 'test_model.demo', 2, '') + acc = HomeAccessory('hass', 'test_name', 'test_model.demo', 2) self.assertEqual(acc.display_name, 'test_name') self.assertEqual(acc.aid, 2) self.assertEqual(len(acc.services), 1) @@ -155,8 +107,17 @@ class TestAccessories(unittest.TestCase): self.assertEqual(len(bridge.services), 1) serv = bridge.services[0] # SERV_ACCESSORY_INFO self.assertEqual(serv.display_name, SERV_ACCESSORY_INFO) + self.assertEqual( + serv.get_characteristic(CHAR_NAME).value, BRIDGE_NAME) + self.assertEqual( + serv.get_characteristic(CHAR_FIRMWARE_REVISION).value, __version__) + self.assertEqual( + serv.get_characteristic(CHAR_MANUFACTURER).value, MANUFACTURER) self.assertEqual( serv.get_characteristic(CHAR_MODEL).value, BRIDGE_MODEL) + self.assertEqual( + serv.get_characteristic(CHAR_SERIAL_NUMBER).value, + BRIDGE_SERIAL_NUMBER) bridge = HomeBridge('hass', 'test_name') self.assertEqual(bridge.display_name, 'test_name') diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 7ae37becbd5..082953038b5 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -4,7 +4,9 @@ from unittest.mock import call, patch, ANY, Mock from homeassistant import setup from homeassistant.core import State -from homeassistant.components.homekit import HomeKit, generate_aid +from homeassistant.components.homekit import ( + HomeKit, generate_aid, + STATUS_READY, STATUS_RUNNING, STATUS_STOPPED, STATUS_WAIT) from homeassistant.components.homekit.accessories import HomeBridge from homeassistant.components.homekit.const import ( DOMAIN, HOMEKIT_FILE, CONF_AUTO_START, @@ -79,24 +81,28 @@ class TestHomeKit(unittest.TestCase): CONF_IP_ADDRESS: '172.0.0.0'}} self.assertTrue(setup.setup_component( self.hass, DOMAIN, config)) - - self.hass.bus.fire(EVENT_HOMEASSISTANT_START) self.hass.block_till_done() self.assertEqual(mock_homekit.mock_calls, [ call(self.hass, 11111, '172.0.0.0', ANY, {}), call().setup()]) - # Test start call with driver stopped. + # Test auto_start disabled homekit.reset_mock() - homekit.configure_mock(**{'started': False}) + self.hass.bus.fire(EVENT_HOMEASSISTANT_START) + self.hass.block_till_done() + self.assertEqual(homekit.mock_calls, []) + + # Test start call with driver is ready + homekit.reset_mock() + homekit.status = STATUS_READY self.hass.services.call('homekit', 'start') self.assertEqual(homekit.mock_calls, [call.start()]) - # Test start call with driver started. + # Test start call with driver started homekit.reset_mock() - homekit.configure_mock(**{'started': True}) + homekit.status = STATUS_STOPPED self.hass.services.call(DOMAIN, SERVICE_HOMEKIT_START) self.assertEqual(homekit.mock_calls, []) @@ -180,34 +186,38 @@ class TestHomeKit(unittest.TestCase): state = self.hass.states.all()[0] homekit.start() + self.hass.block_till_done() self.assertEqual(mock_add_bridge_acc.mock_calls, [call(state)]) self.assertEqual(mock_show_setup_msg.mock_calls, [ call(self.hass, homekit.bridge)]) self.assertEqual(homekit.driver.mock_calls, [call.start()]) - self.assertTrue(homekit.started) + self.assertEqual(homekit.status, STATUS_RUNNING) # Test start() if already started homekit.driver.reset_mock() homekit.start() + self.hass.block_till_done() self.assertEqual(homekit.driver.mock_calls, []) def test_homekit_stop(self): """Test HomeKit stop method.""" - homekit = HomeKit(None, None, None, None, None) + homekit = HomeKit(self.hass, None, None, None, None) homekit.driver = Mock() - # Test if started = False + self.assertEqual(homekit.status, STATUS_READY) homekit.stop() - self.assertFalse(homekit.driver.stop.called) - - # Test if driver not started - homekit.started = True - homekit.driver.configure_mock(**{'run_sentinel': None}) + self.hass.block_till_done() + homekit.status = STATUS_WAIT homekit.stop() + self.hass.block_till_done() + homekit.status = STATUS_STOPPED + homekit.stop() + self.hass.block_till_done() self.assertFalse(homekit.driver.stop.called) # Test if driver is started - homekit.driver.configure_mock(**{'run_sentinel': 'sentinel'}) + homekit.status = STATUS_RUNNING homekit.stop() + self.hass.block_till_done() self.assertTrue(homekit.driver.stop.called)