mirror of
https://github.com/home-assistant/core.git
synced 2025-07-10 14:57:09 +00:00
Add support for homekit garage obstruction sensors (#42243)
This commit is contained in:
parent
88231aa541
commit
4bf9ce6fca
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user