You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

113 lines
4.2 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

from __future__ import annotations
import asyncio
import json
import logging
import websockets
from typing import Dict, Any
from . import config, state
logger = logging.getLogger("PTZTracker")
def _dispatch_alarm(payload: Dict[str, Any]) -> None:
"""
Кладём событие тревоги в очередь event-loop из любого потока.
"""
if state.alarm_queue is not None and state.MAIN_LOOP is not None:
try:
state.MAIN_LOOP.call_soon_threadsafe(state.alarm_queue.put_nowait, payload)
except Exception:
# не роняем приложение из-за уведомлений
pass
async def alarm_sender() -> None:
"""
Фоновая задача: отправляет тревоги по WebSocket.
Требование: отправлять *только* минимальный JSON:
{"type":"freq_alarm","data":true|false}
"""
assert state.alarm_queue is not None
backoff = 1.0
while True:
try:
logger.info("[ALARM] Connecting to %s ...", config.ALARM_WS_URL)
async with websockets.connect(
config.ALARM_WS_URL,
ping_interval=20,
close_timeout=1.0,
compression=None,
) as ws:
logger.info("[ALARM] Connected")
backoff = 1.0
while True:
payload = await state.alarm_queue.get()
try:
# Строго минимальный словарь только с нужными ключами
if isinstance(payload, dict):
minimal = {
"type": payload.get("type"),
"data": bool(payload.get("data")),
}
else:
# на случай если кто-то положил уже готовую строку/другую структуру
minimal = payload
# Без лишних пробелов/форматирования
await ws.send(json.dumps(minimal, separators=(",", ":")))
except Exception as exc:
logger.error("[ALARM] Send failed: %s", exc)
try:
# возвращаем событие в очередь, чтобы не потерять
state.alarm_queue.put_nowait(payload)
except Exception:
pass
break
finally:
state.alarm_queue.task_done()
except Exception as exc:
logger.error("[ALARM] Connection error: %s", exc)
await asyncio.sleep(backoff)
backoff = min(backoff * 2, 10.0)
def queue_alarm(cam_idx: int, state_on: bool = True) -> None:
"""
Поставить тревогу в очередь для WS-отправки.
ВАЖНО: WS-полезная нагрузка должна быть строго минимальной,
без дополнительных полей вроде IdCamera/mac.
"""
_dispatch_alarm({"type": "freq_alarm", "data": bool(state_on)})
def queue_alarm_clear(cam_idx: int) -> None:
"""
Снять тревогу (data=false).
"""
queue_alarm(cam_idx, False)
def _send_record_event(cam_idx: int, flag: bool) -> None:
"""
Вспомогательное событие детекции (если используется где-то ещё в коде).
Оставлено без изменений: это не WS-Alarm, а внутренняя очередь.
"""
if state.detection_queue is not None and state.MAIN_LOOP is not None:
try:
state.MAIN_LOOP.call_soon_threadsafe(
state.detection_queue.put_nowait,
{
"type": "detection",
"data": {"detection": bool(flag), "IdCamera": int(cam_idx)},
},
)
except Exception:
pass