Avoid retriggering HomeKit doorbells on forced updates (#74141)

This commit is contained in:
J. Nick Koston 2022-06-28 19:54:27 -05:00 committed by GitHub
parent ee6866b8a3
commit 629c68221e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 71 additions and 8 deletions

View File

@ -14,7 +14,7 @@ from pyhap.const import CATEGORY_CAMERA
from homeassistant.components import camera
from homeassistant.components.ffmpeg import get_ffmpeg_manager
from homeassistant.const import STATE_ON
from homeassistant.core import callback
from homeassistant.core import Event, callback
from homeassistant.helpers.event import (
async_track_state_change_event,
async_track_time_interval,
@ -56,7 +56,7 @@ from .const import (
SERV_SPEAKER,
SERV_STATELESS_PROGRAMMABLE_SWITCH,
)
from .util import pid_is_alive
from .util import pid_is_alive, state_changed_event_is_same_state
_LOGGER = logging.getLogger(__name__)
@ -265,9 +265,10 @@ class Camera(HomeAccessory, PyhapCamera):
await super().run()
@callback
def _async_update_motion_state_event(self, event):
def _async_update_motion_state_event(self, event: Event) -> None:
"""Handle state change event listener callback."""
self._async_update_motion_state(event.data.get("new_state"))
if not state_changed_event_is_same_state(event):
self._async_update_motion_state(event.data.get("new_state"))
@callback
def _async_update_motion_state(self, new_state):
@ -288,9 +289,10 @@ class Camera(HomeAccessory, PyhapCamera):
)
@callback
def _async_update_doorbell_state_event(self, event):
def _async_update_doorbell_state_event(self, event: Event) -> None:
"""Handle state change event listener callback."""
self._async_update_doorbell_state(event.data.get("new_state"))
if not state_changed_event_is_same_state(event):
self._async_update_doorbell_state(event.data.get("new_state"))
@callback
def _async_update_doorbell_state(self, new_state):

View File

@ -37,7 +37,7 @@ from homeassistant.const import (
CONF_TYPE,
TEMP_CELSIUS,
)
from homeassistant.core import HomeAssistant, State, callback, split_entity_id
from homeassistant.core import Event, HomeAssistant, State, callback, split_entity_id
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.storage import STORAGE_DIR
import homeassistant.util.temperature as temp_util
@ -572,3 +572,11 @@ def state_needs_accessory_mode(state: State) -> bool:
and state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
& RemoteEntityFeature.ACTIVITY
)
def state_changed_event_is_same_state(event: Event) -> bool:
"""Check if a state changed event is the same state."""
event_data = event.data
old_state: State | None = event_data.get("old_state")
new_state: State | None = event_data.get("new_state")
return bool(new_state and old_state and new_state.state == old_state.state)

View File

@ -642,11 +642,15 @@ async def test_camera_with_linked_motion_sensor(hass, run_driver, events):
assert char
assert char.value is True
broker = MagicMock()
char.broker = broker
hass.states.async_set(
motion_entity_id, STATE_OFF, {ATTR_DEVICE_CLASS: BinarySensorDeviceClass.MOTION}
)
await hass.async_block_till_done()
assert len(broker.mock_calls) == 2
broker.reset_mock()
assert char.value is False
char.set_value(True)
@ -654,8 +658,28 @@ async def test_camera_with_linked_motion_sensor(hass, run_driver, events):
motion_entity_id, STATE_ON, {ATTR_DEVICE_CLASS: BinarySensorDeviceClass.MOTION}
)
await hass.async_block_till_done()
assert len(broker.mock_calls) == 2
broker.reset_mock()
assert char.value is True
hass.states.async_set(
motion_entity_id,
STATE_ON,
{ATTR_DEVICE_CLASS: BinarySensorDeviceClass.MOTION},
force_update=True,
)
await hass.async_block_till_done()
assert len(broker.mock_calls) == 0
broker.reset_mock()
hass.states.async_set(
motion_entity_id,
STATE_ON,
{ATTR_DEVICE_CLASS: BinarySensorDeviceClass.MOTION, "other": "attr"},
)
await hass.async_block_till_done()
assert len(broker.mock_calls) == 0
broker.reset_mock()
# Ensure we do not throw when the linked
# motion sensor is removed
hass.states.async_remove(motion_entity_id)
@ -747,7 +771,8 @@ async def test_camera_with_linked_doorbell_sensor(hass, run_driver, events):
assert service2
char2 = service.get_characteristic(CHAR_PROGRAMMABLE_SWITCH_EVENT)
assert char2
broker = MagicMock()
char2.broker = broker
assert char2.value is None
hass.states.async_set(
@ -758,9 +783,12 @@ async def test_camera_with_linked_doorbell_sensor(hass, run_driver, events):
await hass.async_block_till_done()
assert char.value is None
assert char2.value is None
assert len(broker.mock_calls) == 0
char.set_value(True)
char2.set_value(True)
broker.reset_mock()
hass.states.async_set(
doorbell_entity_id,
STATE_ON,
@ -769,6 +797,31 @@ async def test_camera_with_linked_doorbell_sensor(hass, run_driver, events):
await hass.async_block_till_done()
assert char.value is None
assert char2.value is None
assert len(broker.mock_calls) == 2
broker.reset_mock()
hass.states.async_set(
doorbell_entity_id,
STATE_ON,
{ATTR_DEVICE_CLASS: BinarySensorDeviceClass.OCCUPANCY},
force_update=True,
)
await hass.async_block_till_done()
assert char.value is None
assert char2.value is None
assert len(broker.mock_calls) == 0
broker.reset_mock()
hass.states.async_set(
doorbell_entity_id,
STATE_ON,
{ATTR_DEVICE_CLASS: BinarySensorDeviceClass.OCCUPANCY, "other": "attr"},
)
await hass.async_block_till_done()
assert char.value is None
assert char2.value is None
assert len(broker.mock_calls) == 0
broker.reset_mock()
# Ensure we do not throw when the linked
# doorbell sensor is removed