142 lines
3.3 KiB
Python
142 lines
3.3 KiB
Python
from enum import Enum
|
|
from typing import Any
|
|
from uuid import uuid4
|
|
|
|
from pydantic import BaseModel, Field, HttpUrl
|
|
|
|
from led_platform.core.clock import epoch_ms
|
|
|
|
|
|
class CommandType(str, Enum):
|
|
HELLO = "hello"
|
|
STATE = "state"
|
|
PREPARE_SCENE = "prepare_scene"
|
|
COMMIT_SCENE = "commit_scene"
|
|
COMPONENT_ACTION = "component_action"
|
|
CLOCK_PING = "clock_ping"
|
|
CLOCK_PONG = "clock_pong"
|
|
TELEMETRY = "telemetry"
|
|
HEARTBEAT = "heartbeat"
|
|
ACK = "ack"
|
|
ERROR = "error"
|
|
|
|
|
|
class AckStatus(str, Enum):
|
|
PREPARED = "prepared"
|
|
COMMITTED = "committed"
|
|
ACTION_COMMITTED = "action_committed"
|
|
LATE = "late"
|
|
ERROR = "error"
|
|
|
|
|
|
class NodeRole(str, Enum):
|
|
ADMIN = "admin"
|
|
OUTPUT = "output"
|
|
RENDERER = "renderer"
|
|
|
|
|
|
class NodeStatus(str, Enum):
|
|
STARTING = "starting"
|
|
READY = "ready"
|
|
DEGRADED = "degraded"
|
|
ERROR = "error"
|
|
|
|
|
|
class PhysicalOutput(BaseModel):
|
|
id: str
|
|
index: int
|
|
x: int
|
|
y: int
|
|
width: int = 3840
|
|
height: int = 2160
|
|
connector: str | None = None
|
|
|
|
|
|
class Tile(BaseModel):
|
|
id: str
|
|
name: str
|
|
x: int
|
|
y: int
|
|
width: int
|
|
height: int
|
|
wall_width: int
|
|
wall_height: int
|
|
desktop_width: int
|
|
desktop_height: int
|
|
physical_outputs: list[PhysicalOutput] = Field(default_factory=list)
|
|
|
|
@property
|
|
def right(self) -> int:
|
|
return self.x + self.width
|
|
|
|
|
|
class Scene(BaseModel):
|
|
id: str
|
|
name: str
|
|
url: str | HttpUrl = "/wall-app"
|
|
view: str
|
|
description: str | None = None
|
|
preload: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class SceneState(BaseModel):
|
|
active_scene_id: str
|
|
active_view: str
|
|
active_url: str
|
|
version: int
|
|
scene_started_at_ms: int
|
|
apply_at_ms: int | None = None
|
|
action: str | None = None
|
|
|
|
|
|
class CommandEnvelope(BaseModel):
|
|
type: CommandType
|
|
command_id: str = Field(default_factory=lambda: uuid4().hex)
|
|
payload: dict[str, Any] = Field(default_factory=dict)
|
|
sent_at_ms: int = Field(default_factory=epoch_ms)
|
|
|
|
|
|
class OutputNode(BaseModel):
|
|
node_id: str
|
|
tile_id: str
|
|
status: NodeStatus = NodeStatus.READY
|
|
connected_at_ms: int = Field(default_factory=epoch_ms)
|
|
last_seen_ms: int = Field(default_factory=epoch_ms)
|
|
app_version: str | None = None
|
|
user_agent: str | None = None
|
|
clock_offset_ms: float | None = None
|
|
rtt_ms: float | None = None
|
|
fps: float | None = None
|
|
frame_time_ms: float | None = None
|
|
dropped_frames: int | None = None
|
|
gpu_hint: str | None = None
|
|
|
|
|
|
class PerformanceSample(BaseModel):
|
|
node_id: str
|
|
tile_id: str
|
|
fps: float
|
|
frame_time_ms: float
|
|
dropped_frames: int = 0
|
|
at_ms: int = Field(default_factory=epoch_ms)
|
|
|
|
|
|
class CommandRecord(BaseModel):
|
|
command_id: str
|
|
command_type: CommandType
|
|
target_tiles: list[str]
|
|
apply_at_ms: int
|
|
created_at_ms: int = Field(default_factory=epoch_ms)
|
|
expires_at_ms: int
|
|
payload: dict[str, Any] = Field(default_factory=dict)
|
|
acks: list[dict[str, Any]] = Field(default_factory=list)
|
|
|
|
@property
|
|
def complete(self) -> bool:
|
|
completed = {
|
|
ack.get("tile_id")
|
|
for ack in self.acks
|
|
if ack.get("status") in {AckStatus.COMMITTED, AckStatus.ACTION_COMMITTED}
|
|
}
|
|
return set(self.target_tiles).issubset({str(tile) for tile in completed if tile})
|