/
nacha
/
pseado
Обзор
Документация
Войти
/
nacha
/
pseado
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
1
CI/CD
Аналитика
Безопасность
master
holo_sync_server.py
421 строка
16 KB
alex
Добавлен HoloSyncServer — сервер синхронизации для голографического куба
02 авг 2026, 23:03
Верифицирован
02 авг 2026, 23:03
e3af971
Код
Авторство
О чём код?
#!/usr/bin/env python3 """ HoloSyncServer — Сервер синхронизации для голографического куба. WebSocket сервер для управления 4-мя проекторами/мониторами. Передаёт 3D-трансформации, UI-состояние, синхронизацию кадров. Использование: python holo_sync_server.py [--port 8080] [--max-clients 4] python holo_sync_server.py --config holo_config.json """ import sys import os import json import time import asyncio import logging from dataclasses import dataclass, field, asdict from typing import Dict, List, Optional, Set from enum import Enum from pathlib import Path import websockets import numpy as np # Настройка логирования logging.basicConfig( level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s', datefmt='%H:%M:%S' ) logger = logging.getLogger("HoloSyncServer") class SyncState(Enum): """Состояние синхронизации.""" IDLE = "idle" RENDERING = "rendering" SYNCING = "syncing" ERROR = "error" PAUSED = "paused" class MessageType(Enum): """Типы сообщений протокола.""" # Управление HEARTBEAT = "heartbeat" REGISTER = "register" UNREGISTER = "unregister" CONFIG_UPDATE = "config_update" # Данные сцены SCENE_LOAD = "scene_load" SCENE_UPDATE = "scene_update" TRANSFORM_UPDATE = "transform_update" # Синхронизация кадров FRAME_SYNC = "frame_sync" FRAME_ACK = "frame_ack" # Камера и проекторы CAMERA_CONTROL = "camera_control" PROJECTOR_STATUS = "projector_status" # UI и жесты GESTURE_INPUT = "gesture_input" UI_UPDATE = "ui_update" # Статус STATUS_REPORT = "status_report" ERROR_REPORT = "error_report" @dataclass class ProjectorConfig: """Конфигурация проектора/монитора.""" id: str name: str position: str # front, right, back, left resolution: tuple = (1920, 1080) refresh_rate: float = 120.0 latency_ms: float = 16.0 brightness: float = 1.0 contrast: float = 1.0 color_matrix: List[List[float]] = field(default_factory=lambda: [[1.0]*9]) @property def is_ready(self) -> bool: return self.id != "" and self.name != "" @dataclass class SceneTransform: """Трансформация сцены.""" rotation_x: float = 0.0 rotation_y: float = 0.0 rotation_z: float = 0.0 scale: float = 1.0 center_x: float = 0.0 center_y: float = 0.0 center_z: float = 0.0 timestamp: float = 0.0 def to_dict(self) -> dict: return asdict(self) @dataclass class HoloClient: """Клиент подключения (проектор/камера/панель).""" id: str websocket: Optional[object] = None role: str = "projector" # projector, camera, panel position: Optional[str] = None config: Optional[ProjectorConfig] = None last_heartbeat: float = 0.0 is_connected: bool = False frame_buffer: List[dict] = field(default_factory=list) def update_heartbeat(self): self.last_heartbeat = time.time() def is_alive(self, timeout: float = 5.0) -> bool: return time.time() - self.last_heartbeat < timeout class HoloSyncServer: """Сервер синхронизации голографического куба.""" def __init__(self, port: int = 8080, max_clients: int = 4): self.port = port self.max_clients = max_clients self.clients: Dict[str, HoloClient] = {} self.scene_transform = SceneTransform() self.sync_state = SyncState.IDLE self.fps_target = 120 self.frame_interval = 1.0 / self.fps_target self.last_frame_time = 0.0 self.config_path = Path("holo_config.json") self.server_instance = None self._run_task = None async def handle_client(self, websocket, path): """Обработка подключения клиента.""" client_id = None try: # Регистрация клиента async for message in websocket: try: data = json.loads(message) msg_type = data.get("type", "") if msg_type == MessageType.REGISTER.value: client_id = data.get("id", f"client_{len(self.clients)}") if len(self.clients) >= self.max_clients: await websocket.send(json.dumps({ "type": "error", "message": f"Максимум клиентов: {self.max_clients}" })) break self.clients[client_id] = HoloClient( id=client_id, websocket=websocket, role=data.get("role", "projector"), position=data.get("position"), last_heartbeat=time.time(), is_connected=True ) logger.info(f"Клиент подключён: {client_id} ({data.get('role', 'unknown')})") # Отправить текущую конфигурацию await self._send_config(websocket) elif msg_type == MessageType.HEARTBEAT.value: if client_id and client_id in self.clients: self.clients[client_id].update_heartbeat() elif msg_type == MessageType.TRANSFORM_UPDATE.value: # Обновление трансформации сцены self.scene_transform = SceneTransform( rotation_x=data.get("rotation_x", 0), rotation_y=data.get("rotation_y", 0), rotation_z=data.get("rotation_z", 0), scale=data.get("scale", 1.0), center_x=data.get("center_x", 0), center_y=data.get("center_y", 0), center_z=data.get("center_z", 0), timestamp=time.time() ) elif msg_type == MessageType.GESTURE_INPUT.value: # Обработка жестов с панели logger.info(f"Жест получен: {data.get('gesture', 'unknown')}") await self._broadcast_gesture(client_id, data) elif msg_type == MessageType.KEEPALIVE.value: pass # Простой пинг elif msg_type == MessageType.FRAME_SYNC.value: # Подтверждение получения кадра pass except json.JSONDecodeError: logger.warning("Неверный JSON от клиента") except websockets.exceptions.ConnectionClosed: logger.warning(f"Клиент {client_id} отключился") finally: if client_id and client_id in self.clients: del self.clients[client_id] logger.info(f"Клиент {client_id} удалён. Осталось: {len(self.clients)}") async def _send_config(self, websocket): """Отправить конфигурацию сервера клиенту.""" config = { "type": "config", "fps": self.fps_target, "max_clients": self.max_clients, "port": self.port, "timestamp": time.time() } await websocket.send(json.dumps(config)) async def _broadcast_gesture(self, source_id: str, gesture_data: dict): """Рассылка жеста всем проекторам.""" message = { "type": "gesture_broadcast", "source": source_id, "gesture": gesture_data, "timestamp": time.time() } json_msg = json.dumps(message) for client in self.clients.values(): if client.is_connected and client.role == "projector": try: await client.websocket.send(json_msg) except Exception: logger.warning(f"Ошибка отправки жеста клиенту {client.id}") async def _send_to_all(self, message: dict): """Отправить сообщение всем подключённым клиентам.""" json_msg = json.dumps(message) for client in self.clients.values(): if client.is_connected and client.websocket: try: await client.websocket.send(json_msg) except Exception as e: logger.error(f"Ошибка отправки: {e}") async def run(self): """Запуск сервера.""" logger.info(f"Запуск HoloSyncServer на порту {self.port}") try: self.server_instance = await websockets.serve( self.handle_client, "0.0.0.0", self.port, ping_interval=2, ping_timeout=5 ) logger.info("Сервер готов к приёму соединений") # Запуск периодических задач asyncio.create_task(self._heartbeat_monitor()) asyncio.create_task(self._fps_monitor()) await self.server_instance.wait_closed() except Exception as e: logger.error(f"Ошибка сервера: {e}") raise async def _heartbeat_monitor(self): """Мониторинг heartbeat клиентов.""" while True: await asyncio.sleep(2) for client_id, client in list(self.clients.items()): if not client.is_alive(timeout=5.0): logger.warning(f"Клиент {client_id} не отвечает. Отключение.") try: await client.websocket.close() except: pass del self.clients[client_id] async def _fps_monitor(self): """Мониторинг FPS и стабильности.""" while True: await asyncio.sleep(5) now = time.time() if now - self.last_frame_time < self.frame_interval * 1.5: fps = 1.0 / (now - self.last_frame_time) if self.last_frame_time else 0 if fps < self.fps_target * 0.9: logger.warning(f"Низкий FPS: {fps:.1f} (цель: {self.fps_target})") def get_status(self) -> dict: """Текущий статус сервера.""" return { "state": self.sync_state.value, "clients_count": len(self.clients), "clients": { cid: { "role": c.role, "position": c.position, "alive": c.is_alive() } for cid, c in self.clients.items() }, "scene_transform": self.scene_transform.to_dict(), "fps_target": self.fps_target } def create_example_config() -> dict: """Создаёт пример конфигурации для голографического куба.""" return { "cube": { "name": "HoloCube-01", "type": "nappe", # nappe, mirror, hybrid "material": "glass", "size_mm": [200, 200, 300], "nappe_thickness_nm": 80, "angle_deg": 45.0 }, "projectors": [ { "id": "proj_front", "name": "Фронтальный проектор", "position": "front", "resolution": [1920, 1080], "refresh_rate": 120, "latency_ms": 16 }, { "id": "proj_right", "name": "Правый проектор", "position": "right", "resolution": [1920, 1080], "refresh_rate": 120, "latency_ms": 16 }, { "id": "proj_back", "name": "Задний проектор", "position": "back", "resolution": [1920, 1080], "refresh_rate": 120, "latency_ms": 16 }, { "id": "proj_left", "name": "Левый проектор", "position": "left", "resolution": [1920, 1080], "refresh_rate": 120, "latency_ms": 16 } ], "sync": { "port": 8080, "protocol": "websocket", "max_clients": 4, "fps_target": 120, "latency_ms": 16 }, "calibration": { "auto_calibrate": True, "checkerboard_size": [8, 6], "square_size_mm": 25.0, "camera": "rgb_d" }, "performance": { "max_latency_ms": 20, "fps_min": 50, "gpu_memory_mb": 4096 } } def main(): """Точка входа.""" import argparse parser = argparse.ArgumentParser(description="HoloSyncServer — Сервер синхронизации голографического куба") parser.add_argument("--port", "-p", type=int, default=8080, help="Порт сервера") parser.add_argument("--max-clients", "-m", type=int, default=4, help="Максимум клиентов") parser.add_argument("--config", "-c", type=str, help="Файл конфигурации") parser.add_argument("--create-config", action="store_true", help="Создать пример конфигурации") args = parser.parse_args() if args.create_config: config = create_example_config() with open("holo_config.json", "w", encoding="utf-8") as f: json.dump(config, f, indent=2, ensure_ascii=False) print("✅ Создан holo_config.json") return if args.config and os.path.exists(args.config): with open(args.config, "r", encoding="utf-8") as f: config = json.load(f) logger.info(f"Загружена конфигурация из {args.config}") server = HoloSyncServer(port=args.port, max_clients=args.max_clients) print("🔷 HoloSyncServer запущен") print(f" Порт: {args.port}") print(f" Макс. клиентов: {args.max_clients}") print(" Подключайте проекторы/панели через WebSocket") print(" Нажмите Ctrl+C для остановки") try: asyncio.run(server.run()) except KeyboardInterrupt: print("\n🔷 Сервер остановлен") if __name__ == "__main__": main()