Coalesce nest media source preview clips by session and bump google-nest-sdm (#61081)

This commit is contained in:
Allen Porter 2021-12-05 23:59:24 -08:00 committed by GitHub
parent 1bcff0907b
commit bbe4a67a98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 56 additions and 42 deletions

View File

@ -198,7 +198,7 @@ class SignalUpdateCallback:
"device_id": device_entry.id,
"type": event_type,
"timestamp": event_message.timestamp,
"nest_event_id": image_event.event_id,
"nest_event_id": image_event.event_session_id,
}
self._hass.bus.async_fire(NEST_EVENT, message)

View File

@ -4,7 +4,7 @@
"config_flow": true,
"dependencies": ["ffmpeg", "http", "media_source"],
"documentation": "https://www.home-assistant.io/integrations/nest",
"requirements": ["python-nest==4.1.0", "google-nest-sdm==0.4.3"],
"requirements": ["python-nest==4.1.0", "google-nest-sdm==0.4.4"],
"codeowners": ["@allenporter"],
"quality_scale": "platinum",
"dhcp": [

View File

@ -181,7 +181,7 @@ class NestMediaSource(MediaSource):
browse_device.children = []
events = await _get_events(device)
for child_event in events.values():
event_id = MediaId(media_id.device_id, child_event.event_id)
event_id = MediaId(media_id.device_id, child_event.event_session_id)
browse_device.children.append(
_browse_event(event_id, device, child_event)
)
@ -203,7 +203,7 @@ class NestMediaSource(MediaSource):
async def _get_events(device: Device) -> Mapping[str, ImageEventBase]:
"""Return relevant events for the specified device."""
events = await device.event_media_manager.async_events()
return {e.event_id: e for e in events}
return {e.event_session_id: e for e in events}
def _browse_root() -> BrowseMediaSource:

View File

@ -741,7 +741,7 @@ google-cloud-pubsub==2.1.0
google-cloud-texttospeech==0.4.0
# homeassistant.components.nest
google-nest-sdm==0.4.3
google-nest-sdm==0.4.4
# homeassistant.components.google_travel_time
googlemaps==2.5.1

View File

@ -464,7 +464,7 @@ google-api-python-client==1.6.4
google-cloud-pubsub==2.1.0
# homeassistant.components.nest
google-nest-sdm==0.4.3
google-nest-sdm==0.4.4
# homeassistant.components.google_travel_time
googlemaps==2.5.1

View File

@ -117,7 +117,7 @@ async def test_doorbell_chime_event(hass):
"device_id": entry.device_id,
"type": "doorbell_chime",
"timestamp": event_time,
"nest_event_id": EVENT_ID,
"nest_event_id": EVENT_SESSION_ID,
}
@ -145,7 +145,7 @@ async def test_camera_motion_event(hass):
"device_id": entry.device_id,
"type": "camera_motion",
"timestamp": event_time,
"nest_event_id": EVENT_ID,
"nest_event_id": EVENT_SESSION_ID,
}
@ -173,7 +173,7 @@ async def test_camera_sound_event(hass):
"device_id": entry.device_id,
"type": "camera_sound",
"timestamp": event_time,
"nest_event_id": EVENT_ID,
"nest_event_id": EVENT_SESSION_ID,
}
@ -201,7 +201,7 @@ async def test_camera_person_event(hass):
"device_id": entry.device_id,
"type": "camera_person",
"timestamp": event_time,
"nest_event_id": EVENT_ID,
"nest_event_id": EVENT_SESSION_ID,
}
@ -238,13 +238,13 @@ async def test_camera_multiple_event(hass):
"device_id": entry.device_id,
"type": "camera_motion",
"timestamp": event_time,
"nest_event_id": EVENT_ID,
"nest_event_id": EVENT_SESSION_ID,
}
assert events[1].data == {
"device_id": entry.device_id,
"type": "camera_person",
"timestamp": event_time,
"nest_event_id": EVENT_ID,
"nest_event_id": EVENT_SESSION_ID,
}

View File

@ -27,6 +27,7 @@ DEVICE_ID = "example/api/device/id"
DEVICE_NAME = "Front"
PLATFORM = "camera"
NEST_EVENT = "nest_event"
EVENT_ID = "1aXEvi9ajKVTdDsXdJda8fzfCa..."
EVENT_SESSION_ID = "CjY5Y3VKaTZwR3o4Y19YbTVfMF..."
CAMERA_DEVICE_TYPE = "sdm.devices.types.CAMERA"
CAMERA_TRAITS = {
@ -81,26 +82,28 @@ async def async_setup_devices(hass, auth, device_type, traits={}, events=[]):
return subscriber
def create_event(event_id, event_type, timestamp=None, device_id=None):
def create_event(
event_session_id, event_id, event_type, timestamp=None, device_id=None
):
"""Create an EventMessage for a single event type."""
if not timestamp:
timestamp = dt_util.now()
event_data = {
event_type: {
"eventSessionId": EVENT_SESSION_ID,
"eventSessionId": event_session_id,
"eventId": event_id,
},
}
return create_event_message(event_id, event_data, timestamp, device_id=device_id)
return create_event_message(event_data, timestamp, device_id=device_id)
def create_event_message(event_id, event_data, timestamp, device_id=None):
def create_event_message(event_data, timestamp, device_id=None):
"""Create an EventMessage for a single event type."""
if device_id is None:
device_id = DEVICE_ID
return EventMessage(
{
"eventId": f"{event_id}-{timestamp}",
"eventId": f"{EVENT_ID}-{timestamp}",
"timestamp": timestamp.isoformat(timespec="seconds"),
"resourceUpdate": {
"name": device_id,
@ -163,7 +166,6 @@ async def test_supported_device(hass, auth):
async def test_camera_event(hass, auth, hass_client):
"""Test a media source and image created for an event."""
event_id = "FWWVQVUdGNUlTU2V4MGV2aTNXV..."
event_timestamp = dt_util.now()
await async_setup_devices(
hass,
@ -172,7 +174,8 @@ async def test_camera_event(hass, auth, hass_client):
CAMERA_TRAITS,
events=[
create_event(
event_id,
EVENT_SESSION_ID,
EVENT_ID,
PERSON_EVENT,
timestamp=event_timestamp,
),
@ -213,7 +216,7 @@ async def test_camera_event(hass, auth, hass_client):
# The device expands recent events
assert len(browse.children) == 1
assert browse.children[0].domain == DOMAIN
assert browse.children[0].identifier == f"{device.id}/{event_id}"
assert browse.children[0].identifier == f"{device.id}/{EVENT_SESSION_ID}"
event_timestamp_string = event_timestamp.strftime(DATE_STR_FORMAT)
assert browse.children[0].title == f"Person @ {event_timestamp_string}"
assert not browse.children[0].can_expand
@ -221,19 +224,19 @@ async def test_camera_event(hass, auth, hass_client):
# Browse to the event
browse = await media_source.async_browse_media(
hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{event_id}"
hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{EVENT_SESSION_ID}"
)
assert browse.domain == DOMAIN
assert browse.identifier == f"{device.id}/{event_id}"
assert browse.identifier == f"{device.id}/{EVENT_SESSION_ID}"
assert "Person" in browse.title
assert not browse.can_expand
assert not browse.children
# Resolving the event links to the media
media = await media_source.async_resolve_media(
hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{event_id}"
hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{EVENT_SESSION_ID}"
)
assert media.url == f"/api/nest/event_media/{device.id}/{event_id}"
assert media.url == f"/api/nest/event_media/{device.id}/{EVENT_SESSION_ID}"
assert media.mime_type == "image/jpeg"
auth.responses = [
@ -250,9 +253,9 @@ async def test_camera_event(hass, auth, hass_client):
async def test_event_order(hass, auth):
"""Test multiple events are in descending timestamp order."""
event_id1 = "FWWVQVUdGNUlTU2V4MGV2aTNXV..."
event_session_id1 = "FWWVQVUdGNUlTU2V4MGV2aTNXV..."
event_timestamp1 = dt_util.now()
event_id2 = "GXXWRWVeHNUlUU3V3MGV3bUOYW..."
event_session_id2 = "GXXWRWVeHNUlUU3V3MGV3bUOYW..."
event_timestamp2 = event_timestamp1 + datetime.timedelta(seconds=5)
await async_setup_devices(
hass,
@ -261,12 +264,14 @@ async def test_event_order(hass, auth):
CAMERA_TRAITS,
events=[
create_event(
event_id1,
event_session_id1,
EVENT_ID + "1",
PERSON_EVENT,
timestamp=event_timestamp1,
),
create_event(
event_id2,
event_session_id2,
EVENT_ID + "2",
MOTION_EVENT,
timestamp=event_timestamp2,
),
@ -293,7 +298,7 @@ async def test_event_order(hass, auth):
# Motion event is most recent
assert len(browse.children) == 2
assert browse.children[0].domain == DOMAIN
assert browse.children[0].identifier == f"{device.id}/{event_id2}"
assert browse.children[0].identifier == f"{device.id}/{event_session_id2}"
event_timestamp_string = event_timestamp2.strftime(DATE_STR_FORMAT)
assert browse.children[0].title == f"Motion @ {event_timestamp_string}"
assert not browse.children[0].can_expand
@ -301,7 +306,7 @@ async def test_event_order(hass, auth):
# Person event is next
assert browse.children[1].domain == DOMAIN
assert browse.children[1].identifier == f"{device.id}/{event_id1}"
assert browse.children[1].identifier == f"{device.id}/{event_session_id1}"
event_timestamp_string = event_timestamp1.strftime(DATE_STR_FORMAT)
assert browse.children[1].title == f"Person @ {event_timestamp_string}"
assert not browse.children[1].can_expand
@ -395,9 +400,12 @@ async def test_resolve_invalid_event_id(hass, auth):
async def test_camera_event_clip_preview(hass, auth, hass_client):
"""Test an event for a battery camera video clip."""
event_id = "FWWVQVUdGNUlTU2V4MGV2aTNXV..."
event_timestamp = dt_util.now()
event_data = {
"sdm.devices.events.CameraMotion.Motion": {
"eventSessionId": EVENT_SESSION_ID,
"eventId": "n:2",
},
"sdm.devices.events.CameraClipPreview.ClipPreview": {
"eventSessionId": EVENT_SESSION_ID,
"previewUrl": "https://127.0.0.1/example",
@ -410,7 +418,6 @@ async def test_camera_event_clip_preview(hass, auth, hass_client):
BATTERY_CAMERA_TRAITS,
events=[
create_event_message(
event_id,
event_data,
timestamp=event_timestamp,
),
@ -439,7 +446,7 @@ async def test_camera_event_clip_preview(hass, auth, hass_client):
assert browse.children[0].domain == DOMAIN
actual_event_id = browse.children[0].identifier
event_timestamp_string = event_timestamp.strftime(DATE_STR_FORMAT)
assert browse.children[0].title == f"Event @ {event_timestamp_string}"
assert browse.children[0].title == f"Motion @ {event_timestamp_string}"
assert not browse.children[0].can_expand
assert len(browse.children[0].children) == 0
@ -490,7 +497,6 @@ async def test_event_media_render_invalid_event_id(hass, auth, hass_client):
async def test_event_media_failure(hass, auth, hass_client):
"""Test event media fetch sees a failure from the server."""
event_id = "FWWVQVUdGNUlTU2V4MGV2aTNXV..."
event_timestamp = dt_util.now()
await async_setup_devices(
hass,
@ -499,7 +505,8 @@ async def test_event_media_failure(hass, auth, hass_client):
CAMERA_TRAITS,
events=[
create_event(
event_id,
EVENT_SESSION_ID,
EVENT_ID,
PERSON_EVENT,
timestamp=event_timestamp,
),
@ -517,9 +524,9 @@ async def test_event_media_failure(hass, auth, hass_client):
# Resolving the event links to the media
media = await media_source.async_resolve_media(
hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{event_id}"
hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{EVENT_SESSION_ID}"
)
assert media.url == f"/api/nest/event_media/{device.id}/{event_id}"
assert media.url == f"/api/nest/event_media/{device.id}/{EVENT_SESSION_ID}"
assert media.mime_type == "image/jpeg"
auth.responses = [
@ -535,7 +542,6 @@ async def test_event_media_failure(hass, auth, hass_client):
async def test_media_permission_unauthorized(hass, auth, hass_client, hass_admin_user):
"""Test case where user does not have permissions to view media."""
event_id = "FWWVQVUdGNUlTU2V4MGV2aTNXV..."
event_timestamp = dt_util.now()
await async_setup_devices(
hass,
@ -544,7 +550,8 @@ async def test_media_permission_unauthorized(hass, auth, hass_client, hass_admin
CAMERA_TRAITS,
events=[
create_event(
event_id,
EVENT_SESSION_ID,
EVENT_ID,
PERSON_EVENT,
timestamp=event_timestamp,
),
@ -560,7 +567,7 @@ async def test_media_permission_unauthorized(hass, auth, hass_client, hass_admin
assert device
assert device.name == DEVICE_NAME
media_url = f"/api/nest/event_media/{device.id}/{event_id}"
media_url = f"/api/nest/event_media/{device.id}/{EVENT_SESSION_ID}"
# Empty policy with no access to the entity
hass_admin_user.mock_policy({})
@ -616,7 +623,12 @@ async def test_multiple_devices(hass, auth, hass_client):
# Send events for device #1
for i in range(0, 5):
await subscriber.async_receive_event(
create_event(f"event-id-{i}", PERSON_EVENT, device_id=device_id1)
create_event(
f"event-session-id-{i}",
f"event-id-{i}",
PERSON_EVENT,
device_id=device_id1,
)
)
browse = await media_source.async_browse_media(
@ -631,7 +643,9 @@ async def test_multiple_devices(hass, auth, hass_client):
# Send events for device #2
for i in range(0, 3):
await subscriber.async_receive_event(
create_event(f"other-id-{i}", PERSON_EVENT, device_id=device_id2)
create_event(
f"other-id-{i}", f"event-id{i}", PERSON_EVENT, device_id=device_id2
)
)
browse = await media_source.async_browse_media(