mirror of
https://github.com/ep1cman/unifi-protect-backup.git
synced 2025-12-05 23:53:30 +00:00
2001
poetry.lock
generated
2001
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,7 @@ apprise = "^1.5.0"
|
||||
expiring-dict = "^1.1.0"
|
||||
async-lru = "^2.0.4"
|
||||
aiolimiter = "^1.1.0"
|
||||
uiprotect = "^5.4.0"
|
||||
uiprotect = "^6.3.1"
|
||||
|
||||
[tool.poetry.group.dev]
|
||||
optional = true
|
||||
|
||||
@@ -10,7 +10,7 @@ from unifi_protect_backup import __version__
|
||||
from unifi_protect_backup.unifi_protect_backup_core import UnifiProtectBackup
|
||||
from unifi_protect_backup.utils import human_readable_to_float
|
||||
|
||||
DETECTION_TYPES = ["motion", "person", "vehicle", "ring"]
|
||||
DETECTION_TYPES = ["motion", "person", "vehicle", "ring", "line"]
|
||||
|
||||
|
||||
def _parse_detection_types(ctx, param, value):
|
||||
|
||||
@@ -6,6 +6,7 @@ from time import sleep
|
||||
from typing import List
|
||||
|
||||
from uiprotect.api import ProtectApiClient
|
||||
from uiprotect.websocket import WebsocketState
|
||||
from uiprotect.data.nvr import Event
|
||||
from uiprotect.data.types import EventType
|
||||
from uiprotect.data.websocket import WSAction, WSSubscriptionMessage
|
||||
@@ -34,18 +35,16 @@ class EventListener:
|
||||
self._event_queue: asyncio.Queue = event_queue
|
||||
self._protect: ProtectApiClient = protect
|
||||
self._unsub = None
|
||||
self._unsub_websocketstate = None
|
||||
self.detection_types: List[str] = detection_types
|
||||
self.ignore_cameras: List[str] = ignore_cameras
|
||||
|
||||
async def start(self):
|
||||
"""Main Loop."""
|
||||
logger.debug("Subscribed to websocket")
|
||||
self._unsub_websocket_state = self._protect.subscribe_websocket_state(self._websocket_state_callback)
|
||||
self._unsub = self._protect.subscribe_websocket(self._websocket_callback)
|
||||
|
||||
while True:
|
||||
await asyncio.sleep(60)
|
||||
await self._check_websocket_and_reconnect()
|
||||
|
||||
def _websocket_callback(self, msg: WSSubscriptionMessage) -> None:
|
||||
"""Callback for "EVENT" websocket messages.
|
||||
|
||||
@@ -63,7 +62,12 @@ class EventListener:
|
||||
return
|
||||
if "end" not in msg.changed_data:
|
||||
return
|
||||
if msg.new_obj.type not in [EventType.MOTION, EventType.SMART_DETECT, EventType.RING]:
|
||||
if msg.new_obj.type not in [
|
||||
EventType.MOTION,
|
||||
EventType.SMART_DETECT,
|
||||
EventType.RING,
|
||||
EventType.SMART_DETECT_LINE,
|
||||
]:
|
||||
return
|
||||
if msg.new_obj.type is EventType.MOTION and "motion" not in self.detection_types:
|
||||
logger.extra_debug(f"Skipping unwanted motion detection event: {msg.new_obj.id}") # type: ignore
|
||||
@@ -71,6 +75,9 @@ class EventListener:
|
||||
if msg.new_obj.type is EventType.RING and "ring" not in self.detection_types:
|
||||
logger.extra_debug(f"Skipping unwanted ring event: {msg.new_obj.id}") # type: ignore
|
||||
return
|
||||
if msg.new_obj.type is EventType.SMART_DETECT_LINE and "line" not in self.detection_types:
|
||||
logger.extra_debug(f"Skipping unwanted line event: {msg.new_obj.id}") # type: ignore
|
||||
return
|
||||
elif msg.new_obj.type is EventType.SMART_DETECT:
|
||||
for event_smart_detection_type in msg.new_obj.smart_detect_types:
|
||||
if event_smart_detection_type not in self.detection_types:
|
||||
@@ -94,37 +101,15 @@ class EventListener:
|
||||
|
||||
logger.debug(f"Adding event {msg.new_obj.id} to queue (Current download queue={self._event_queue.qsize()})")
|
||||
|
||||
async def _check_websocket_and_reconnect(self):
|
||||
"""Checks for websocket disconnect and triggers a reconnect."""
|
||||
logger.extra_debug("Checking the status of the websocket...")
|
||||
if self._protect.check_ws():
|
||||
logger.extra_debug("Websocket is connected.")
|
||||
else:
|
||||
self._protect.connect_event.clear()
|
||||
logger.warning("Lost connection to Unifi Protect.")
|
||||
def _websocket_state_callback(self, state: WebsocketState) -> None:
|
||||
"""Callback for websocket state messages.
|
||||
|
||||
# Unsubscribe, close the session.
|
||||
self._unsub()
|
||||
await self._protect.close_session()
|
||||
Flags the websocket for reconnection
|
||||
|
||||
while True:
|
||||
logger.warning("Attempting reconnect...")
|
||||
|
||||
try:
|
||||
# Start the uiprotect connection by calling `update`
|
||||
await self._protect.close_session()
|
||||
self._protect._bootstrap = None
|
||||
await self._protect.update(force=True)
|
||||
if self._protect.check_ws():
|
||||
self._unsub = self._protect.subscribe_websocket(self._websocket_callback)
|
||||
break
|
||||
else:
|
||||
logger.error("Unable to establish connection to Unifi Protect")
|
||||
except Exception as e:
|
||||
logger.error("Unexpected exception occurred while trying to reconnect:", exc_info=e)
|
||||
|
||||
# Back off for a little while
|
||||
await asyncio.sleep(10)
|
||||
|
||||
self._protect.connect_event.set()
|
||||
logger.info("Re-established connection to Unifi Protect and to the websocket.")
|
||||
Args:
|
||||
msg (WebsocketState): new state of the websocket
|
||||
"""
|
||||
if state == WebsocketState.DISCONNECTED:
|
||||
logger.error("Unifi Protect Websocket lost connection. Reconnecting...")
|
||||
elif state == WebsocketState.CONNECTED:
|
||||
logger.info("Unifi Protect Websocket connection restored")
|
||||
|
||||
@@ -65,16 +65,23 @@ class MissingEventChecker:
|
||||
events_chunk = await self._protect.get_events(
|
||||
start=start_time,
|
||||
end=end_time,
|
||||
types=[EventType.MOTION, EventType.SMART_DETECT, EventType.RING],
|
||||
types=[
|
||||
EventType.MOTION,
|
||||
EventType.SMART_DETECT,
|
||||
EventType.RING,
|
||||
EventType.SMART_DETECT_LINE,
|
||||
],
|
||||
limit=chunk_size,
|
||||
)
|
||||
|
||||
if not events_chunk:
|
||||
break # There were no events to backup
|
||||
|
||||
assert events_chunk[-1].end is not None
|
||||
start_time = events_chunk[-1].end
|
||||
unifi_events = {event.id: event for event in events_chunk}
|
||||
# Filter out on-going events
|
||||
unifi_events = {event.id: event for event in events_chunk if event.end is not None}
|
||||
|
||||
# Next chunks start time should be the end of the oldest complete event in the current chunk
|
||||
start_time = max([event.end for event in unifi_events.values()])
|
||||
|
||||
# Get list of events that have been backed up from the database
|
||||
|
||||
@@ -107,6 +114,8 @@ class MissingEventChecker:
|
||||
return False
|
||||
if event.type is EventType.RING and "ring" not in self.detection_types:
|
||||
return False
|
||||
if event.type is EventType.SMART_DETECT_LINE and "line" not in self.detection_types:
|
||||
return False
|
||||
elif event.type is EventType.SMART_DETECT:
|
||||
for event_smart_detection_type in event.smart_detect_types:
|
||||
if event_smart_detection_type not in self.detection_types:
|
||||
|
||||
@@ -197,18 +197,22 @@ class UnifiProtectBackup:
|
||||
# Start the uiprotect connection by calling `update`
|
||||
logger.info("Connecting to Unifi Protect...")
|
||||
|
||||
for attempts in range(10):
|
||||
delay = 5 # Start with a 5 second delay
|
||||
max_delay = 3600 # 1 hour in seconds
|
||||
|
||||
for attempts in range(20):
|
||||
try:
|
||||
await self._protect.update()
|
||||
break
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Failed to connect to UniFi Protect, retrying in {attempts}s...",
|
||||
f"Failed to connect to UniFi Protect, retrying in {delay}s...",
|
||||
exc_info=e,
|
||||
)
|
||||
await asyncio.sleep(attempts)
|
||||
await asyncio.sleep(delay)
|
||||
delay = min(max_delay, delay * 2) # Double the delay but do not exceed max_delay
|
||||
else:
|
||||
raise ConnectionError("Failed to connect to UniFi Protect after 10 attempts")
|
||||
raise ConnectionError("Failed to connect to UniFi Protect after 20 attempts")
|
||||
|
||||
# Add a lock to the protect client that can be used to prevent code accessing the client when it has
|
||||
# lost connection
|
||||
|
||||
Reference in New Issue
Block a user