Add support for homekit garage obstruction sensors (#42243)

This commit is contained in:
J. Nick Koston 2020-10-23 12:18:02 -05:00 committed by GitHub
parent 88231aa541
commit 4bf9ce6fca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 139 additions and 7 deletions

View File

@ -28,6 +28,8 @@ ATTR_MANUFACTURER = "manufacturer"
ATTR_MODEL = "model"
ATTR_SOFTWARE_VERSION = "sw_version"
ATTR_KEY_NAME = "key_name"
# Current attribute used by homekit_controller
ATTR_OBSTRUCTION_DETECTED = "obstruction-detected"
# #### Config ####
CONF_HOMEKIT_MODE = "mode"
@ -45,6 +47,7 @@ CONF_LINKED_BATTERY_CHARGING_SENSOR = "linked_battery_charging_sensor"
CONF_LINKED_DOORBELL_SENSOR = "linked_doorbell_sensor"
CONF_LINKED_MOTION_SENSOR = "linked_motion_sensor"
CONF_LINKED_HUMIDITY_SENSOR = "linked_humidity_sensor"
CONF_LINKED_OBSTRUCTION_SENSOR = "linked_obstruction_sensor"
CONF_LOW_BATTERY_THRESHOLD = "low_battery_threshold"
CONF_MAX_FPS = "max_fps"
CONF_MAX_HEIGHT = "max_height"
@ -192,6 +195,7 @@ CHAR_MODEL = "Model"
CHAR_MOTION_DETECTED = "MotionDetected"
CHAR_MUTE = "Mute"
CHAR_NAME = "Name"
CHAR_OBSTRUCTION_DETECTED = "ObstructionDetected"
CHAR_OCCUPANCY_DETECTED = "OccupancyDetected"
CHAR_ON = "On"
CHAR_OUTLET_IN_USE = "OutletInUse"

View File

@ -26,21 +26,26 @@ from homeassistant.const import (
SERVICE_STOP_COVER,
STATE_CLOSED,
STATE_CLOSING,
STATE_ON,
STATE_OPEN,
STATE_OPENING,
)
from homeassistant.core import callback
from homeassistant.helpers.event import async_track_state_change_event
from .accessories import TYPES, HomeAccessory, debounce
from .const import (
ATTR_OBSTRUCTION_DETECTED,
CHAR_CURRENT_DOOR_STATE,
CHAR_CURRENT_POSITION,
CHAR_CURRENT_TILT_ANGLE,
CHAR_HOLD_POSITION,
CHAR_OBSTRUCTION_DETECTED,
CHAR_POSITION_STATE,
CHAR_TARGET_DOOR_STATE,
CHAR_TARGET_POSITION,
CHAR_TARGET_TILT_ANGLE,
CONF_LINKED_OBSTRUCTION_SENSOR,
DEVICE_PRECISION_LEEWAY,
HK_DOOR_CLOSED,
HK_DOOR_CLOSING,
@ -74,7 +79,6 @@ DOOR_TARGET_HASS_TO_HK = {
STATE_CLOSING: HK_DOOR_CLOSED,
}
_LOGGER = logging.getLogger(__name__)
@ -98,8 +102,55 @@ class GarageDoorOpener(HomeAccessory):
self.char_target_state = serv_garage_door.configure_char(
CHAR_TARGET_DOOR_STATE, value=0, setter_callback=self.set_state
)
self.char_obstruction_detected = serv_garage_door.configure_char(
CHAR_OBSTRUCTION_DETECTED, value=False
)
self.linked_obstruction_sensor = self.config.get(CONF_LINKED_OBSTRUCTION_SENSOR)
if self.linked_obstruction_sensor:
self._async_update_obstruction_state(
self.hass.states.get(self.linked_obstruction_sensor)
)
self.async_update_state(state)
async def run_handler(self):
"""Handle accessory driver started event.
Run inside the Home Assistant event loop.
"""
if self.linked_obstruction_sensor:
async_track_state_change_event(
self.hass,
[self.linked_obstruction_sensor],
self._async_update_obstruction_event,
)
await super().run_handler()
@callback
def _async_update_obstruction_event(self, event):
"""Handle state change event listener callback."""
self._async_update_obstruction_state(event.data.get("new_state"))
@callback
def _async_update_obstruction_state(self, new_state):
"""Handle linked obstruction sensor state change to update HomeKit value."""
if not new_state:
return
detected = new_state.state == STATE_ON
if self.char_obstruction_detected.value == detected:
return
self.char_obstruction_detected.set_value(detected)
_LOGGER.debug(
"%s: Set linked obstruction %s sensor to %d",
self.entity_id,
self.linked_obstruction_sensor,
detected,
)
def set_state(self, value):
"""Change garage state if call came from HomeKit."""
_LOGGER.debug("%s: Set state to %d", self.entity_id, value)
@ -121,6 +172,13 @@ class GarageDoorOpener(HomeAccessory):
target_door_state = DOOR_TARGET_HASS_TO_HK.get(hass_state)
current_door_state = DOOR_CURRENT_HASS_TO_HK.get(hass_state)
if ATTR_OBSTRUCTION_DETECTED in new_state.attributes:
obstruction_detected = (
new_state.attributes[ATTR_OBSTRUCTION_DETECTED] is True
)
if self.char_obstruction_detected.value != obstruction_detected:
self.char_obstruction_detected.set_value(obstruction_detected)
if (
target_door_state is not None
and self.char_target_state.value != target_door_state

View File

@ -37,6 +37,7 @@ from .const import (
CONF_LINKED_DOORBELL_SENSOR,
CONF_LINKED_HUMIDITY_SENSOR,
CONF_LINKED_MOTION_SENSOR,
CONF_LINKED_OBSTRUCTION_SENSOR,
CONF_LOW_BATTERY_THRESHOLD,
CONF_MAX_FPS,
CONF_MAX_HEIGHT,
@ -138,6 +139,15 @@ HUMIDIFIER_SCHEMA = BASIC_INFO_SCHEMA.extend(
{vol.Optional(CONF_LINKED_HUMIDITY_SENSOR): cv.entity_domain(sensor.DOMAIN)}
)
COVER_SCHEMA = BASIC_INFO_SCHEMA.extend(
{
vol.Optional(CONF_LINKED_OBSTRUCTION_SENSOR): cv.entity_domain(
binary_sensor.DOMAIN
)
}
)
CODE_SCHEMA = BASIC_INFO_SCHEMA.extend(
{vol.Optional(ATTR_CODE, default=None): vol.Any(None, cv.string)}
)
@ -247,6 +257,9 @@ def validate_entity_config(values):
elif domain == "humidifier":
config = HUMIDIFIER_SCHEMA(config)
elif domain == "cover":
config = COVER_SCHEMA(config)
else:
config = BASIC_INFO_SCHEMA(config)

View File

@ -116,9 +116,7 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverEntity):
obstruction_detected = self.service.value(
CharacteristicsTypes.OBSTRUCTION_DETECTED
)
if not obstruction_detected:
return {}
return {"obstruction-detected": obstruction_detected}
return {"obstruction-detected": obstruction_detected is True}
class HomeKitWindowCover(HomeKitEntity, CoverEntity):

View File

@ -14,7 +14,9 @@ from homeassistant.components.cover import (
SUPPORT_STOP,
)
from homeassistant.components.homekit.const import (
ATTR_OBSTRUCTION_DETECTED,
ATTR_VALUE,
CONF_LINKED_OBSTRUCTION_SENSOR,
HK_DOOR_CLOSED,
HK_DOOR_CLOSING,
HK_DOOR_OPEN,
@ -27,6 +29,8 @@ from homeassistant.const import (
SERVICE_SET_COVER_TILT_POSITION,
STATE_CLOSED,
STATE_CLOSING,
STATE_OFF,
STATE_ON,
STATE_OPEN,
STATE_OPENING,
STATE_UNAVAILABLE,
@ -76,20 +80,25 @@ async def test_garage_door_open_close(hass, hk_driver, cls, events):
assert acc.char_current_state.value == HK_DOOR_OPEN
assert acc.char_target_state.value == HK_DOOR_OPEN
hass.states.async_set(entity_id, STATE_CLOSED)
hass.states.async_set(entity_id, STATE_CLOSED, {ATTR_OBSTRUCTION_DETECTED: False})
await hass.async_block_till_done()
assert acc.char_current_state.value == HK_DOOR_CLOSED
assert acc.char_target_state.value == HK_DOOR_CLOSED
assert acc.char_obstruction_detected.value is False
hass.states.async_set(entity_id, STATE_OPEN)
hass.states.async_set(entity_id, STATE_OPEN, {ATTR_OBSTRUCTION_DETECTED: True})
await hass.async_block_till_done()
assert acc.char_current_state.value == HK_DOOR_OPEN
assert acc.char_target_state.value == HK_DOOR_OPEN
assert acc.char_obstruction_detected.value is True
hass.states.async_set(entity_id, STATE_UNAVAILABLE)
hass.states.async_set(
entity_id, STATE_UNAVAILABLE, {ATTR_OBSTRUCTION_DETECTED: False}
)
await hass.async_block_till_done()
assert acc.char_current_state.value == HK_DOOR_OPEN
assert acc.char_target_state.value == HK_DOOR_OPEN
assert acc.char_obstruction_detected.value is False
hass.states.async_set(entity_id, STATE_UNKNOWN)
await hass.async_block_till_done()
@ -528,3 +537,53 @@ async def test_windowcovering_restore(hass, hk_driver, cls, events):
assert acc.char_current_position is not None
assert acc.char_target_position is not None
assert acc.char_position_state is not None
async def test_garage_door_with_linked_obstruction_sensor(hass, hk_driver, cls, events):
"""Test if accessory and HA are updated accordingly with a linked obstruction sensor."""
linked_obstruction_sensor_entity_id = "binary_sensor.obstruction"
entity_id = "cover.garage_door"
hass.states.async_set(linked_obstruction_sensor_entity_id, STATE_OFF)
hass.states.async_set(entity_id, None)
await hass.async_block_till_done()
acc = cls.garage(
hass,
hk_driver,
"Garage Door",
entity_id,
2,
{CONF_LINKED_OBSTRUCTION_SENSOR: linked_obstruction_sensor_entity_id},
)
await acc.run_handler()
await hass.async_block_till_done()
assert acc.aid == 2
assert acc.category == 4 # GarageDoorOpener
assert acc.char_current_state.value == HK_DOOR_OPEN
assert acc.char_target_state.value == HK_DOOR_OPEN
hass.states.async_set(entity_id, STATE_CLOSED)
await hass.async_block_till_done()
assert acc.char_current_state.value == HK_DOOR_CLOSED
assert acc.char_target_state.value == HK_DOOR_CLOSED
assert acc.char_obstruction_detected.value is False
hass.states.async_set(entity_id, STATE_OPEN)
hass.states.async_set(linked_obstruction_sensor_entity_id, STATE_ON)
await hass.async_block_till_done()
assert acc.char_current_state.value == HK_DOOR_OPEN
assert acc.char_target_state.value == HK_DOOR_OPEN
assert acc.char_obstruction_detected.value is True
hass.states.async_set(entity_id, STATE_CLOSED)
hass.states.async_set(linked_obstruction_sensor_entity_id, STATE_OFF)
await hass.async_block_till_done()
assert acc.char_current_state.value == HK_DOOR_CLOSED
assert acc.char_target_state.value == HK_DOOR_CLOSED
assert acc.char_obstruction_detected.value is False
hass.states.async_remove(entity_id)
hass.states.async_remove(linked_obstruction_sensor_entity_id)
await hass.async_block_till_done()