Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion ring_doorbell/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import Any
from typing import Any, NamedTuple


@dataclass
Expand All @@ -18,6 +18,7 @@ class RingEvent:
expires_in: float
kind: str
state: str
is_update: bool = False

def __getitem__(self, key: str) -> Any:
"""Get a value by string."""
Expand All @@ -26,3 +27,19 @@ def __getitem__(self, key: str) -> Any:
def get(self, key: str) -> Any | None:
"""Get a value by string and return None if not present."""
return getattr(self, key) if hasattr(self, key) else None

def get_key(self) -> RingEventKey:
"""Return the identificationkey for the event."""
return RingEventKey(self.id, self.doorbot_id, self.kind, self.now)


class RingEventKey(NamedTuple):
"""Class to identify an event.

Used for determining if messages are updates to events.
"""

id: int
doorbot_id: int
kind: str
now: float
25 changes: 23 additions & 2 deletions ring_doorbell/listen/eventlistener.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
PUSH_NOTIFICATION_KINDS,
SUBSCRIPTION_ENDPOINT,
)
from ring_doorbell.event import RingEvent
from ring_doorbell.event import RingEvent, RingEventKey
from ring_doorbell.exceptions import RingError
from ring_doorbell.util import parse_datetime

Expand Down Expand Up @@ -81,6 +81,8 @@ def __init__(
self.session_refresh_task: asyncio.Task | None = None
self.fcm_token: str | None = None

self._seen_events: set[RingEventKey] = set()

def _credentials_updated_cb(self, creds: dict[str, Any]) -> None:
self._credentials = creds
if self._credentials_updated_callback:
Expand Down Expand Up @@ -263,6 +265,24 @@ def _get_intercom_unlock_event(self, gcm_data: dict[str, Any]) -> RingEvent | No
state="unlock",
)

def _check_is_update(self, ring_event: RingEvent) -> None:
"""Battery doorbells send two events.

First without an image and the second with an image.
"""
now = time.time()
seen_events = {
key
for key in self._seen_events
if (now - key.now) < DEFAULT_LISTEN_EVENT_EXPIRES_IN
}
event_key = ring_event.get_key()
if event_key in seen_events:
ring_event.is_update = True
else:
seen_events.add(event_key)
self._seen_events = seen_events

def _on_notification(
self,
notification: dict[str, dict[str, str]],
Expand All @@ -277,6 +297,7 @@ def _on_notification(
ring_event = self._get_ring_event(msg_data)

if ring_event:
self._check_is_update(ring_event)
_logger.debug("Event received %s", ring_event)
for callback in self._callbacks.values():
callback(ring_event)
Expand All @@ -299,7 +320,7 @@ def _get_ring_event(self, msg_data: dict) -> RingEvent | None:
event_kind = PUSH_NOTIFICATION_KINDS.get(event_category, "Unknown")
device = data["device"]
event = data["event"]
event_id = event["ding"]["id"]
event_id = int(event["ding"]["id"])
created_at = event["ding"]["created_at"]
create_seconds = parse_datetime(created_at).timestamp()
return RingEvent(
Expand Down
2 changes: 1 addition & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ async def test_listen_event_handler(mocker, auth):
"2023-10-24 09:42:18.789709: RingEvent(id=12345678901234, "
"doorbot_id=12345678, device_name='Front Floodcam'"
", device_kind='floodlight_v2', now=1698140538.789709,"
" expires_in=180, kind='motion', state='human') : "
" expires_in=180, kind='motion', state='human', is_update=False) : "
"Currently active count = 1"
)
echomock.assert_called_with(exp)
Expand Down