Added video ping to uptime monitor
This commit is contained in:
parent
6481511dcd
commit
de4b7792d1
|
@ -4,7 +4,7 @@ WORKDIR /code
|
||||||
ENTRYPOINT ["python", "-m", "uptime"]
|
ENTRYPOINT ["python", "-m", "uptime"]
|
||||||
|
|
||||||
RUN apt update --fix-missing
|
RUN apt update --fix-missing
|
||||||
RUN apt install -y iputils-ping
|
RUN apt install -y iputils-ping libgl1
|
||||||
|
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
RUN pip3 install -r requirements.txt
|
RUN pip3 install -r requirements.txt
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
aiohttp==3.8.6
|
aiohttp>=3.8.6
|
||||||
PyYAML==6.0.1
|
PyYAML>=6.0.1
|
||||||
|
opencv-python>=4.10.0.84
|
||||||
|
|
|
@ -10,6 +10,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class FrigateConfig:
|
class FrigateConfig:
|
||||||
|
_LOCALHOST_REGEX: ClassVar[Pattern[str]] = re.compile("127\\.0\\.0\\.1|localhost")
|
||||||
_URL_ADDRESS_REGEX: ClassVar[Pattern[str]] = re.compile("(^|(?<=://)|(?<=@))[a-z0-9.\\-]+(:[0-9]+)?($|(?=/))")
|
_URL_ADDRESS_REGEX: ClassVar[Pattern[str]] = re.compile("(^|(?<=://)|(?<=@))[a-z0-9.\\-]+(:[0-9]+)?($|(?=/))")
|
||||||
_WYZE_CAMERAS: ClassVar[dict[str, str]] = {"back_yard_cam": "192.168.0.202:554"}
|
_WYZE_CAMERAS: ClassVar[dict[str, str]] = {"back_yard_cam": "192.168.0.202:554"}
|
||||||
|
|
||||||
|
@ -38,10 +39,18 @@ class FrigateConfig:
|
||||||
|
|
||||||
return [camera for camera in self._config["cameras"] if self._config["cameras"][camera].get("enabled", True)]
|
return [camera for camera in self._config["cameras"] if self._config["cameras"][camera].get("enabled", True)]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def active_camera_urls(self) -> dict[str, str]:
|
||||||
|
return {camera: self._replace_localhost(self._config["cameras"][camera]["ffmpeg"]["inputs"][0]["path"]) for camera in self.active_cameras}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def active_camera_addresses(self) -> dict[str, str]:
|
def active_camera_addresses(self) -> dict[str, str]:
|
||||||
active_cameras = self.active_cameras
|
active_cameras = self.active_cameras
|
||||||
return {camera: self._get_address_from_url(self._config["cameras"][camera]["ffmpeg"]["inputs"][0]["path"]) for camera in active_cameras}
|
return {camera: self._get_address_from_url(url) for camera, url in self.active_camera_urls.items()}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _replace_localhost(cls, url: str) -> str:
|
||||||
|
return cls._LOCALHOST_REGEX.sub("frigate", url, 1)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_address_from_url(cls, url: str) -> str:
|
def _get_address_from_url(cls, url: str) -> str:
|
||||||
|
|
|
@ -1,24 +1,30 @@
|
||||||
|
import time
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import datetime as dt
|
||||||
from contextlib import AsyncExitStack
|
from contextlib import AsyncExitStack
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
from typing import Self
|
from typing import ClassVar, Self
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
from concurrent.futures import ProcessPoolExecutor
|
||||||
|
|
||||||
from uptime.frigate_config import FrigateConfig
|
from uptime.frigate_config import FrigateConfig
|
||||||
from uptime.notify import NtfyNotifier
|
from uptime.notify import NtfyNotifier
|
||||||
from uptime.ping import ip_ping_all
|
from uptime.ping import video_ping
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CameraMonitor:
|
class CameraMonitor:
|
||||||
def __init__(self, wait_time: int = 60, consecutive_down_threshold: int = 3):
|
_FRIGATE_CONFIG_REFRESH_INTERVAL: ClassVar[dt.timedelta] = dt.timedelta(minutes=60)
|
||||||
self._wait_time = wait_time
|
|
||||||
|
def __init__(self, ping_interval: dt.timedelta = dt.timedelta(seconds=60), consecutive_down_threshold: int = 3):
|
||||||
|
self._ping_interval = ping_interval
|
||||||
self._consecutive_down_threshold = consecutive_down_threshold
|
self._consecutive_down_threshold = consecutive_down_threshold
|
||||||
self._camera_downtime = Counter()
|
self._camera_downtime = Counter()
|
||||||
self._frigate_config = FrigateConfig()
|
self._frigate_config = FrigateConfig()
|
||||||
self._ntfy_notifier = NtfyNotifier()
|
self._ntfy_notifier = NtfyNotifier()
|
||||||
|
self._process_pool = ProcessPoolExecutor()
|
||||||
|
|
||||||
async def _on_camera_up(self, camera: str) -> None:
|
async def _on_camera_up(self, camera: str) -> None:
|
||||||
logger.info(f"Camera {camera} is back online")
|
logger.info(f"Camera {camera} is back online")
|
||||||
|
@ -42,14 +48,35 @@ class CameraMonitor:
|
||||||
await self._on_camera_down(camera)
|
await self._on_camera_down(camera)
|
||||||
self._camera_downtime[camera] += 1
|
self._camera_downtime[camera] += 1
|
||||||
|
|
||||||
|
async def _ping_all_cameras(self) -> None:
|
||||||
|
async def video_ping_async(url: str) -> bool:
|
||||||
|
event_loop = asyncio.get_running_loop()
|
||||||
|
return await event_loop.run_in_executor(
|
||||||
|
self._process_pool,
|
||||||
|
video_ping,
|
||||||
|
url,
|
||||||
|
)
|
||||||
|
|
||||||
|
ping_tasks = {}
|
||||||
|
async with asyncio.TaskGroup() as tg:
|
||||||
|
event_loop = asyncio.get_running_loop()
|
||||||
|
for camera, url in self._frigate_config.active_camera_urls.items():
|
||||||
|
ping_tasks[camera] = tg.create_task(video_ping_async(url))
|
||||||
|
|
||||||
|
for camera, task in ping_tasks.items():
|
||||||
|
await self._on_camera_ping(camera, task.result())
|
||||||
|
|
||||||
async def run(self) -> None:
|
async def run(self) -> None:
|
||||||
await self._frigate_config.refresh()
|
await self._frigate_config.refresh()
|
||||||
|
last_refresh = time.time()
|
||||||
while True:
|
while True:
|
||||||
camera_ips = {camera: address.split(":", 1)[0] for camera, address in self._frigate_config.active_camera_addresses.items()}
|
start_time = time.time()
|
||||||
ping_results = await ip_ping_all(*camera_ips.values())
|
if time.time() - last_refresh >= self._FRIGATE_CONFIG_REFRESH_INTERVAL.total_seconds():
|
||||||
for i, camera in enumerate(camera_ips):
|
logger.info("Refreshing Frigate config...")
|
||||||
await self._on_camera_ping(camera, ping_results[i])
|
await self._frigate_config.refresh()
|
||||||
|
last_refresh = time.time()
|
||||||
logger.debug(f"Sleeping for {self._wait_time} seconds...")
|
await self._ping_all_cameras()
|
||||||
await asyncio.sleep(self._wait_time)
|
|
||||||
|
|
||||||
|
sleep_duration = self._ping_interval.total_seconds() - (time.time() - start_time)
|
||||||
|
logger.debug(f"Sleeping for {sleep_duration} seconds...")
|
||||||
|
await asyncio.sleep(sleep_duration)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,9 +13,12 @@ async def ip_ping(host: str) -> bool:
|
||||||
logger.debug(f"Finished pinging {host}")
|
logger.debug(f"Finished pinging {host}")
|
||||||
return return_code == 0
|
return return_code == 0
|
||||||
|
|
||||||
async def ip_ping_all(*hosts: str) -> list[bool]:
|
|
||||||
return await asyncio.gather(*[ip_ping(host) for host in hosts])
|
|
||||||
|
|
||||||
async def tcp_ping(host: str, port: int) -> bool:
|
async def tcp_ping(host: str, port: int) -> bool:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
def video_ping(url: str) -> bool:
|
||||||
|
capture = cv2.VideoCapture(url)
|
||||||
|
success, _ = capture.read()
|
||||||
|
capture.release()
|
||||||
|
return success
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue