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_MODEL = "model"
|
||||||
ATTR_SOFTWARE_VERSION = "sw_version"
|
ATTR_SOFTWARE_VERSION = "sw_version"
|
||||||
ATTR_KEY_NAME = "key_name"
|
ATTR_KEY_NAME = "key_name"
|
||||||
|
# Current attribute used by homekit_controller
|
||||||
|
ATTR_OBSTRUCTION_DETECTED = "obstruction-detected"
|
||||||
|
|
||||||
# #### Config ####
|
# #### Config ####
|
||||||
CONF_HOMEKIT_MODE = "mode"
|
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_DOORBELL_SENSOR = "linked_doorbell_sensor"
|
||||||
CONF_LINKED_MOTION_SENSOR = "linked_motion_sensor"
|
CONF_LINKED_MOTION_SENSOR = "linked_motion_sensor"
|
||||||
CONF_LINKED_HUMIDITY_SENSOR = "linked_humidity_sensor"
|
CONF_LINKED_HUMIDITY_SENSOR = "linked_humidity_sensor"
|
||||||
|
CONF_LINKED_OBSTRUCTION_SENSOR = "linked_obstruction_sensor"
|
||||||
CONF_LOW_BATTERY_THRESHOLD = "low_battery_threshold"
|
CONF_LOW_BATTERY_THRESHOLD = "low_battery_threshold"
|
||||||
CONF_MAX_FPS = "max_fps"
|
CONF_MAX_FPS = "max_fps"
|
||||||
CONF_MAX_HEIGHT = "max_height"
|
CONF_MAX_HEIGHT = "max_height"
|
||||||
@ -192,6 +195,7 @@ CHAR_MODEL = "Model"
|
|||||||
CHAR_MOTION_DETECTED = "MotionDetected"
|
CHAR_MOTION_DETECTED = "MotionDetected"
|
||||||
CHAR_MUTE = "Mute"
|
CHAR_MUTE = "Mute"
|
||||||
CHAR_NAME = "Name"
|
CHAR_NAME = "Name"
|
||||||
|
CHAR_OBSTRUCTION_DETECTED = "ObstructionDetected"
|
||||||
CHAR_OCCUPANCY_DETECTED = "OccupancyDetected"
|
CHAR_OCCUPANCY_DETECTED = "OccupancyDetected"
|
||||||
CHAR_ON = "On"
|
CHAR_ON = "On"
|
||||||
CHAR_OUTLET_IN_USE = "OutletInUse"
|
CHAR_OUTLET_IN_USE = "OutletInUse"
|
||||||
|
@ -26,21 +26,26 @@ from homeassistant.const import (
|
|||||||
SERVICE_STOP_COVER,
|
SERVICE_STOP_COVER,
|
||||||
STATE_CLOSED,
|
STATE_CLOSED,
|
||||||
STATE_CLOSING,
|
STATE_CLOSING,
|
||||||
|
STATE_ON,
|
||||||
STATE_OPEN,
|
STATE_OPEN,
|
||||||
STATE_OPENING,
|
STATE_OPENING,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers.event import async_track_state_change_event
|
||||||
|
|
||||||
from .accessories import TYPES, HomeAccessory, debounce
|
from .accessories import TYPES, HomeAccessory, debounce
|
||||||
from .const import (
|
from .const import (
|
||||||
|
ATTR_OBSTRUCTION_DETECTED,
|
||||||
CHAR_CURRENT_DOOR_STATE,
|
CHAR_CURRENT_DOOR_STATE,
|
||||||
CHAR_CURRENT_POSITION,
|
CHAR_CURRENT_POSITION,
|
||||||
CHAR_CURRENT_TILT_ANGLE,
|
CHAR_CURRENT_TILT_ANGLE,
|
||||||
CHAR_HOLD_POSITION,
|
CHAR_HOLD_POSITION,
|
||||||
|
CHAR_OBSTRUCTION_DETECTED,
|
||||||
CHAR_POSITION_STATE,
|
CHAR_POSITION_STATE,
|
||||||
CHAR_TARGET_DOOR_STATE,
|
CHAR_TARGET_DOOR_STATE,
|
||||||
CHAR_TARGET_POSITION,
|
CHAR_TARGET_POSITION,
|
||||||
CHAR_TARGET_TILT_ANGLE,
|
CHAR_TARGET_TILT_ANGLE,
|
||||||
|
CONF_LINKED_OBSTRUCTION_SENSOR,
|
||||||
DEVICE_PRECISION_LEEWAY,
|
DEVICE_PRECISION_LEEWAY,
|
||||||
HK_DOOR_CLOSED,
|
HK_DOOR_CLOSED,
|
||||||
HK_DOOR_CLOSING,
|
HK_DOOR_CLOSING,
|
||||||
@ -74,7 +79,6 @@ DOOR_TARGET_HASS_TO_HK = {
|
|||||||
STATE_CLOSING: HK_DOOR_CLOSED,
|
STATE_CLOSING: HK_DOOR_CLOSED,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -98,8 +102,55 @@ class GarageDoorOpener(HomeAccessory):
|
|||||||
self.char_target_state = serv_garage_door.configure_char(
|
self.char_target_state = serv_garage_door.configure_char(
|
||||||
CHAR_TARGET_DOOR_STATE, value=0, setter_callback=self.set_state
|
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)
|
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):
|
def set_state(self, value):
|
||||||
"""Change garage state if call came from HomeKit."""
|
"""Change garage state if call came from HomeKit."""
|
||||||
_LOGGER.debug("%s: Set state to %d", self.entity_id, value)
|
_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)
|
target_door_state = DOOR_TARGET_HASS_TO_HK.get(hass_state)
|
||||||
current_door_state = DOOR_CURRENT_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 (
|
if (
|
||||||
target_door_state is not None
|
target_door_state is not None
|
||||||
and self.char_target_state.value != target_door_state
|
and self.char_target_state.value != target_door_state
|
||||||
|
@ -37,6 +37,7 @@ from .const import (
|
|||||||
CONF_LINKED_DOORBELL_SENSOR,
|
CONF_LINKED_DOORBELL_SENSOR,
|
||||||
CONF_LINKED_HUMIDITY_SENSOR,
|
CONF_LINKED_HUMIDITY_SENSOR,
|
||||||
CONF_LINKED_MOTION_SENSOR,
|
CONF_LINKED_MOTION_SENSOR,
|
||||||
|
CONF_LINKED_OBSTRUCTION_SENSOR,
|
||||||
CONF_LOW_BATTERY_THRESHOLD,
|
CONF_LOW_BATTERY_THRESHOLD,
|
||||||
CONF_MAX_FPS,
|
CONF_MAX_FPS,
|
||||||
CONF_MAX_HEIGHT,
|
CONF_MAX_HEIGHT,
|
||||||
@ -138,6 +139,15 @@ HUMIDIFIER_SCHEMA = BASIC_INFO_SCHEMA.extend(
|
|||||||
{vol.Optional(CONF_LINKED_HUMIDITY_SENSOR): cv.entity_domain(sensor.DOMAIN)}
|
{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(
|
CODE_SCHEMA = BASIC_INFO_SCHEMA.extend(
|
||||||
{vol.Optional(ATTR_CODE, default=None): vol.Any(None, cv.string)}
|
{vol.Optional(ATTR_CODE, default=None): vol.Any(None, cv.string)}
|
||||||
)
|
)
|
||||||
@ -247,6 +257,9 @@ def validate_entity_config(values):
|
|||||||
elif domain == "humidifier":
|
elif domain == "humidifier":
|
||||||
config = HUMIDIFIER_SCHEMA(config)
|
config = HUMIDIFIER_SCHEMA(config)
|
||||||
|
|
||||||
|
elif domain == "cover":
|
||||||
|
config = COVER_SCHEMA(config)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
config = BASIC_INFO_SCHEMA(config)
|
config = BASIC_INFO_SCHEMA(config)
|
||||||
|
|
||||||
|
@ -116,9 +116,7 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverEntity):
|
|||||||
obstruction_detected = self.service.value(
|
obstruction_detected = self.service.value(
|
||||||
CharacteristicsTypes.OBSTRUCTION_DETECTED
|
CharacteristicsTypes.OBSTRUCTION_DETECTED
|
||||||
)
|
)
|
||||||
if not obstruction_detected:
|
return {"obstruction-detected": obstruction_detected is True}
|
||||||
return {}
|
|
||||||
return {"obstruction-detected": obstruction_detected}
|
|
||||||
|
|
||||||
|
|
||||||
class HomeKitWindowCover(HomeKitEntity, CoverEntity):
|
class HomeKitWindowCover(HomeKitEntity, CoverEntity):
|
||||||
|
@ -14,7 +14,9 @@ from homeassistant.components.cover import (
|
|||||||
SUPPORT_STOP,
|
SUPPORT_STOP,
|
||||||
)
|
)
|
||||||
from homeassistant.components.homekit.const import (
|
from homeassistant.components.homekit.const import (
|
||||||
|
ATTR_OBSTRUCTION_DETECTED,
|
||||||
ATTR_VALUE,
|
ATTR_VALUE,
|
||||||
|
CONF_LINKED_OBSTRUCTION_SENSOR,
|
||||||
HK_DOOR_CLOSED,
|
HK_DOOR_CLOSED,
|
||||||
HK_DOOR_CLOSING,
|
HK_DOOR_CLOSING,
|
||||||
HK_DOOR_OPEN,
|
HK_DOOR_OPEN,
|
||||||
@ -27,6 +29,8 @@ from homeassistant.const import (
|
|||||||
SERVICE_SET_COVER_TILT_POSITION,
|
SERVICE_SET_COVER_TILT_POSITION,
|
||||||
STATE_CLOSED,
|
STATE_CLOSED,
|
||||||
STATE_CLOSING,
|
STATE_CLOSING,
|
||||||
|
STATE_OFF,
|
||||||
|
STATE_ON,
|
||||||
STATE_OPEN,
|
STATE_OPEN,
|
||||||
STATE_OPENING,
|
STATE_OPENING,
|
||||||
STATE_UNAVAILABLE,
|
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_current_state.value == HK_DOOR_OPEN
|
||||||
assert acc.char_target_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()
|
await hass.async_block_till_done()
|
||||||
assert acc.char_current_state.value == HK_DOOR_CLOSED
|
assert acc.char_current_state.value == HK_DOOR_CLOSED
|
||||||
assert acc.char_target_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()
|
await hass.async_block_till_done()
|
||||||
assert acc.char_current_state.value == HK_DOOR_OPEN
|
assert acc.char_current_state.value == HK_DOOR_OPEN
|
||||||
assert acc.char_target_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()
|
await hass.async_block_till_done()
|
||||||
assert acc.char_current_state.value == HK_DOOR_OPEN
|
assert acc.char_current_state.value == HK_DOOR_OPEN
|
||||||
assert acc.char_target_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)
|
hass.states.async_set(entity_id, STATE_UNKNOWN)
|
||||||
await hass.async_block_till_done()
|
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_current_position is not None
|
||||||
assert acc.char_target_position is not None
|
assert acc.char_target_position is not None
|
||||||
assert acc.char_position_state 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