This commit is contained in:
TJY
2026-07-16 08:31:52 +08:00
commit 8590fafac1
34 changed files with 2611 additions and 0 deletions
+141
View File
@@ -0,0 +1,141 @@
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})