From 11c74cd0d7170d6c36a5f35b997c5d4c26eb6477 Mon Sep 17 00:00:00 2001 From: David Radcliffe Date: Mon, 22 Jul 2019 03:40:55 -0400 Subject: [PATCH] Add support for contact binary sensors in homekit_controller (#25355) --- .../homekit_controller/binary_sensor.py | 79 +++++++++++++------ .../components/homekit_controller/const.py | 1 + .../homekit_controller/test_binary_sensor.py | 33 +++++++- 3 files changed, 84 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/homekit_controller/binary_sensor.py b/homeassistant/components/homekit_controller/binary_sensor.py index b9922ea43bb..74b5bcab724 100644 --- a/homeassistant/components/homekit_controller/binary_sensor.py +++ b/homeassistant/components/homekit_controller/binary_sensor.py @@ -1,6 +1,8 @@ """Support for Homekit motion sensors.""" import logging +from homekit.model.characteristics import CharacteristicsTypes + from homeassistant.components.binary_sensor import BinarySensorDevice from . import KNOWN_DEVICES, HomeKitEntity @@ -8,29 +10,8 @@ from . import KNOWN_DEVICES, HomeKitEntity _LOGGER = logging.getLogger(__name__) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): - """Legacy set up platform.""" - pass - - -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up Homekit lighting.""" - hkid = config_entry.data['AccessoryPairingID'] - conn = hass.data[KNOWN_DEVICES][hkid] - - def async_add_service(aid, service): - if service['stype'] != 'motion': - return False - info = {'aid': aid, 'iid': service['iid']} - async_add_entities([HomeKitMotionSensor(conn, info)], True) - return True - - conn.add_listener(async_add_service) - - class HomeKitMotionSensor(HomeKitEntity, BinarySensorDevice): - """Representation of a Homekit sensor.""" + """Representation of a Homekit motion sensor.""" def __init__(self, *args): """Initialise the entity.""" @@ -39,9 +20,6 @@ class HomeKitMotionSensor(HomeKitEntity, BinarySensorDevice): def get_characteristic_types(self): """Define the homekit characteristics the entity is tracking.""" - # pylint: disable=import-error - from homekit.model.characteristics import CharacteristicsTypes - return [ CharacteristicsTypes.MOTION_DETECTED, ] @@ -58,3 +36,54 @@ class HomeKitMotionSensor(HomeKitEntity, BinarySensorDevice): def is_on(self): """Has motion been detected.""" return self._on + + +class HomeKitContactSensor(HomeKitEntity, BinarySensorDevice): + """Representation of a Homekit contact sensor.""" + + def __init__(self, *args): + """Initialise the entity.""" + super().__init__(*args) + self._state = None + + def get_characteristic_types(self): + """Define the homekit characteristics the entity is tracking.""" + return [ + CharacteristicsTypes.CONTACT_STATE, + ] + + def _update_contact_state(self, value): + self._state = value + + @property + def is_on(self): + """Return true if the binary sensor is on/open.""" + return self._state == 1 + + +ENTITY_TYPES = { + 'motion': HomeKitMotionSensor, + 'contact': HomeKitContactSensor, +} + + +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): + """Legacy set up platform.""" + pass + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Homekit lighting.""" + hkid = config_entry.data['AccessoryPairingID'] + conn = hass.data[KNOWN_DEVICES][hkid] + + def async_add_service(aid, service): + entity_class = ENTITY_TYPES.get(service['stype']) + if not entity_class: + return False + info = {'aid': aid, 'iid': service['iid']} + async_add_entities([entity_class(conn, info)], True) + return True + + conn.add_listener(async_add_service) diff --git a/homeassistant/components/homekit_controller/const.py b/homeassistant/components/homekit_controller/const.py index f112737ca24..de37e6bace8 100644 --- a/homeassistant/components/homekit_controller/const.py +++ b/homeassistant/components/homekit_controller/const.py @@ -19,6 +19,7 @@ HOMEKIT_ACCESSORY_DISPATCH = { 'window': 'cover', 'window-covering': 'cover', 'lock-mechanism': 'lock', + 'contact': 'binary_sensor', 'motion': 'binary_sensor', 'humidity': 'sensor', 'light': 'sensor', diff --git a/tests/components/homekit_controller/test_binary_sensor.py b/tests/components/homekit_controller/test_binary_sensor.py index bfcd51b55fb..d7f795a4bc0 100644 --- a/tests/components/homekit_controller/test_binary_sensor.py +++ b/tests/components/homekit_controller/test_binary_sensor.py @@ -1,11 +1,12 @@ -"""Basic checks for HomeKitLock.""" +"""Basic checks for HomeKit motion sensors and contact sensors.""" from tests.components.homekit_controller.common import ( FakeService, setup_test_component) MOTION_DETECTED = ('motion', 'motion-detected') +CONTACT_STATE = ('contact', 'contact-state') -def create_sensor_motion_service(): +def create_motion_sensor_service(): """Define motion characteristics as per page 225 of HAP spec.""" service = FakeService('public.hap.service.sensor.motion') @@ -15,9 +16,9 @@ def create_sensor_motion_service(): return service -async def test_sensor_read_state(hass, utcnow): +async def test_motion_sensor_read_state(hass, utcnow): """Test that we can read the state of a HomeKit motion sensor accessory.""" - sensor = create_sensor_motion_service() + sensor = create_motion_sensor_service() helper = await setup_test_component(hass, [sensor]) helper.characteristics[MOTION_DETECTED].value = False @@ -27,3 +28,27 @@ async def test_sensor_read_state(hass, utcnow): helper.characteristics[MOTION_DETECTED].value = True state = await helper.poll_and_get_state() assert state.state == 'on' + + +def create_contact_sensor_service(): + """Define contact characteristics.""" + service = FakeService('public.hap.service.sensor.contact') + + cur_state = service.add_characteristic('contact-state') + cur_state.value = 0 + + return service + + +async def test_contact_sensor_read_state(hass, utcnow): + """Test that we can read the state of a HomeKit contact accessory.""" + sensor = create_contact_sensor_service() + helper = await setup_test_component(hass, [sensor]) + + helper.characteristics[CONTACT_STATE].value = 0 + state = await helper.poll_and_get_state() + assert state.state == 'off' + + helper.characteristics[CONTACT_STATE].value = 1 + state = await helper.poll_and_get_state() + assert state.state == 'on'