Files
vf-cuda-grid/controller/cuda_grid_controller/state.py
T
gx a1090a5f4c controller: Phase 4a — overlay infrastructure (data models + API + Frigate bridge skeleton)
Phase 4a deliverable (no filter rendering yet — это Phase 4b).
End-to-end pipeline: HA/HTTP/MQTT → controller → ZMQ → FFmpeg (logged).

Modules:
- overlays.py — 7 discriminated union types через pydantic:
  rect, text, icon, image, dim, graph, chat. Normalized coords (0.0-1.0),
  optional cell binding, z_order, opacity, visible.
- state.py — overlay storage per instance (CRUD: add/remove/update/get/clear)
- dispatch.py — overlay.add/remove/clear actions:
  - parses JSON payload в Overlay через TypeAdapter
  - serializes to ZMQ string: "<id> <type> <full-json>"
  - sends via FFmpeg process_command (filter will парсить в Phase 4b)
  - updates state + publishes events (overlay_added, overlay_removed, overlays_cleared)
- http_api.py — REST endpoints:
  - POST /overlay/{inst}/add (body = Overlay JSON, returns id)
  - GET /overlay/{inst} — list all
  - DELETE /overlay/{inst}/{id} — single
  - DELETE /overlay/{inst} — clear all
  - PATCH /overlay/{inst}/{id} — update
- mqtt_loop.py — already subscribes cuda_grid/cmd/<inst>/+/+; teper handles
  overlay/add (JSON payload), overlay/remove (id), overlay/clear
- frigate_bridge.py — FrigateBridge skeleton:
  - subscribe frigate/+/motion + frigate/events
  - mapping camera_name → target_instance + cell index
  - Phase 4a: log received events (rendering в Phase 4b)
- config.py — frigate: optional section
- examples/controller.yaml — frigate mappings для 4 наших камер

State management:
- ControllerState.add/remove/update/get/clear_overlay (asyncio.Lock guarded)
- InstanceState.overlays: dict[str, Overlay]
- IDs generated via uuid4()[:8]

Phase 4a limitations:
- Filter side ничего не рендерит (just logs ZMQ commands)
- Frigate bridge принимает events но не auto-generates overlays
- HA Discovery не имеет overlay-specific entities (overlays через REST API)

Phase 4b: filter-side AVFrame side data + CUDA kernels (rect first, NPP-based,
потом text via freetype atlas, потом icon sprite blit).
2026-05-19 22:03:20 +01:00

77 lines
2.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""In-memory state controller'а."""
from __future__ import annotations
import asyncio
from dataclasses import dataclass, field
from .overlays import Overlay
@dataclass
class InstanceState:
name: str
active_layout: str
overlays: dict[str, Overlay] = field(default_factory=dict)
# Future: fps_out, dropped_frames, motion_cameras, last_event_ts
@dataclass
class ControllerState:
instances: dict[str, InstanceState] = field(default_factory=dict)
_lock: asyncio.Lock = field(default_factory=asyncio.Lock)
async def set_layout(self, instance: str, layout: str) -> None:
async with self._lock:
st = self.instances.get(instance)
if st is None:
st = InstanceState(name=instance, active_layout=layout)
self.instances[instance] = st
else:
st.active_layout = layout
async def get_layout(self, instance: str) -> str | None:
async with self._lock:
st = self.instances.get(instance)
return st.active_layout if st else None
# ─── Overlay state ──────────────────────────────────────────────
async def add_overlay(self, instance: str, overlay: Overlay) -> str:
async with self._lock:
st = self.instances.get(instance)
if st is None:
raise KeyError(f"unknown instance '{instance}'")
st.overlays[overlay.id] = overlay
return overlay.id
async def remove_overlay(self, instance: str, overlay_id: str) -> bool:
async with self._lock:
st = self.instances.get(instance)
if st is None or overlay_id not in st.overlays:
return False
del st.overlays[overlay_id]
return True
async def update_overlay(self, instance: str, overlay: Overlay) -> bool:
async with self._lock:
st = self.instances.get(instance)
if st is None or overlay.id not in st.overlays:
return False
st.overlays[overlay.id] = overlay
return True
async def get_overlays(self, instance: str) -> list[Overlay]:
async with self._lock:
st = self.instances.get(instance)
return list(st.overlays.values()) if st else []
async def clear_overlays(self, instance: str) -> int:
async with self._lock:
st = self.instances.get(instance)
if st is None:
return 0
n = len(st.overlays)
st.overlays.clear()
return n