mirror of https://github.com/home-assistant/core
Coalesce nest media source preview clips by session and bump google-nest-sdm (#61081)
This commit is contained in:
parent
1bcff0907b
commit
bbe4a67a98
|
@ -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)
|
||||
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue