Files
vf-cuda-grid/controller/cuda_grid_controller/config.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

106 lines
2.7 KiB
Python

"""Конфигурация — pydantic models + YAML loader.
Структура YAML:
broker:
host: localhost
port: 1883
username_env: MQTT_USERNAME
password_env: MQTT_PASSWORD
instances:
- name: livingroom_tv
zmq_endpoint: tcp://127.0.0.1:5555
default_layout: quad
ha_discovery:
enabled: true
prefix: homeassistant
device_name: "CUDA Grid Composer"
http:
host: 0.0.0.0
port: 8080
log:
level: INFO
"""
from __future__ import annotations
import os
from pathlib import Path
from typing import Self
import yaml
from pydantic import BaseModel, Field, field_validator
class BrokerCfg(BaseModel):
host: str = "localhost"
port: int = 1883
client_id: str = "cuda-grid-controller"
username_env: str | None = None
password_env: str | None = None
keepalive_sec: int = 30
@property
def username(self) -> str | None:
return os.environ.get(self.username_env) if self.username_env else None
@property
def password(self) -> str | None:
return os.environ.get(self.password_env) if self.password_env else None
class InstanceCfg(BaseModel):
"""Один FFmpeg pipeline = одна cuda_grid filter instance."""
name: str = Field(description="уникальное имя — становится частью HA entity ID")
zmq_endpoint: str = Field(
description="ZMQ endpoint FFmpeg's zmq filter (tcp://host:port)"
)
default_layout: str = "quad"
filter_target: str = Field(
default="Parsed_cuda_grid_0",
description="Filter target name в FFmpeg filter graph (для process_command)",
)
@field_validator("name")
@classmethod
def name_alnum(cls, v: str) -> str:
if not v.replace("_", "").isalnum():
raise ValueError(f"instance name '{v}' must be alphanumeric + underscore")
return v
class HaDiscoveryCfg(BaseModel):
enabled: bool = True
prefix: str = "homeassistant"
device_name: str = "CUDA Grid Composer"
device_identifier: str = "cuda_grid_controller"
class HttpCfg(BaseModel):
host: str = "0.0.0.0"
port: int = 8080
class LogCfg(BaseModel):
level: str = "INFO"
class Config(BaseModel):
broker: BrokerCfg = BrokerCfg()
instances: list[InstanceCfg] = []
ha_discovery: HaDiscoveryCfg = HaDiscoveryCfg()
http: HttpCfg = HttpCfg()
log: LogCfg = LogCfg()
# Frigate bridge — late import чтобы избежать circular dep
frigate: dict | None = None # parsed в FrigateBridgeCfg при runtime
@classmethod
def from_yaml(cls, path: Path | str) -> Self:
with open(path) as f:
data = yaml.safe_load(f) or {}
return cls.model_validate(data)