#!/usr/bin/env python3 """ GodEye Signaling Server Сервер для обмена SDP и ICE кандидатами между устройством и оператором """ import asyncio import websockets import json import logging from datetime import datetime import uuid # Настройка логирования logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class SignalingServer: def __init__(self): self.clients = {} # client_id -> websocket self.sessions = {} # session_id -> {device: ws, operator: ws} async def register_client(self, websocket, path): """Регистрация нового клиента""" client_id = str(uuid.uuid4()) self.clients[client_id] = websocket logger.info(f"🔗 Клиент подключен: {client_id}") try: # Отправляем ID клиенту await websocket.send(json.dumps({ "type": "client_registered", "client_id": client_id, "timestamp": datetime.now().isoformat() })) async for message in websocket: await self.handle_message(client_id, message) except websockets.exceptions.ConnectionClosed: logger.info(f"🔌 Клиент отключен: {client_id}") finally: # Очистка при отключении await self.cleanup_client(client_id) async def handle_message(self, client_id, message): """Обработка сообщений от клиентов""" try: data = json.loads(message) message_type = data.get("type") logger.info(f"📨 Сообщение от {client_id}: {message_type}") if message_type == "create_session": await self.create_session(client_id, data) elif message_type == "join_session": await self.join_session(client_id, data) elif message_type == "offer": await self.relay_offer(client_id, data) elif message_type == "answer": await self.relay_answer(client_id, data) elif message_type == "ice_candidate": await self.relay_ice_candidate(client_id, data) elif message_type == "hangup": await self.handle_hangup(client_id, data) else: logger.warning(f"⚠️ Неизвестный тип сообщения: {message_type}") except json.JSONDecodeError: logger.error(f"❌ Ошибка парсинга JSON от {client_id}") except Exception as e: logger.error(f"❌ Ошибка обработки сообщения: {e}") async def create_session(self, client_id, data): """Создание новой сессии (устройство)""" session_id = str(uuid.uuid4()) device_info = data.get("device_info", {}) self.sessions[session_id] = { "device": self.clients[client_id], "operator": None, "device_info": device_info, "created_at": datetime.now().isoformat() } # Отправляем подтверждение устройству await self.clients[client_id].send(json.dumps({ "type": "session_created", "session_id": session_id, "timestamp": datetime.now().isoformat() })) logger.info(f"📱 Сессия создана: {session_id} для устройства {device_info.get('model', 'Unknown')}") async def join_session(self, client_id, data): """Подключение к сессии (оператор)""" session_id = data.get("session_id") if session_id not in self.sessions: await self.clients[client_id].send(json.dumps({ "type": "error", "message": "Session not found" })) return session = self.sessions[session_id] if session["operator"] is not None: await self.clients[client_id].send(json.dumps({ "type": "error", "message": "Session already has an operator" })) return # Подключаем оператора session["operator"] = self.clients[client_id] # Уведомляем оператора await self.clients[client_id].send(json.dumps({ "type": "session_joined", "session_id": session_id, "device_info": session["device_info"], "timestamp": datetime.now().isoformat() })) # Уведомляем устройство await session["device"].send(json.dumps({ "type": "operator_joined", "session_id": session_id, "timestamp": datetime.now().isoformat() })) logger.info(f"👤 Оператор подключен к сессии: {session_id}") async def relay_offer(self, client_id, data): """Передача SDP offer""" session_id = data.get("session_id") session = self.sessions.get(session_id) if not session or session["operator"] is None: return # Передаем offer оператору await session["operator"].send(json.dumps({ "type": "offer", "session_id": session_id, "sdp": data.get("sdp"), "timestamp": datetime.now().isoformat() })) logger.info(f"📞 SDP Offer передан в сессии: {session_id}") async def relay_answer(self, client_id, data): """Передача SDP answer""" session_id = data.get("session_id") session = self.sessions.get(session_id) if not session: return # Передаем answer устройству await session["device"].send(json.dumps({ "type": "answer", "session_id": session_id, "sdp": data.get("sdp"), "timestamp": datetime.now().isoformat() })) logger.info(f"📞 SDP Answer передан в сессии: {session_id}") async def relay_ice_candidate(self, client_id, data): """Передача ICE кандидатов""" session_id = data.get("session_id") session = self.sessions.get(session_id) if not session: return # Определяем кому передавать (устройству или оператору) if session["device"] == self.clients[client_id]: # От устройства к оператору if session["operator"]: await session["operator"].send(json.dumps({ "type": "ice_candidate", "session_id": session_id, "candidate": data.get("candidate"), "timestamp": datetime.now().isoformat() })) else: # От оператора к устройству await session["device"].send(json.dumps({ "type": "ice_candidate", "session_id": session_id, "candidate": data.get("candidate"), "timestamp": datetime.now().isoformat() })) logger.info(f"🧊 ICE candidate передан в сессии: {session_id}") async def handle_hangup(self, client_id, data): """Завершение сессии""" session_id = data.get("session_id") session = self.sessions.get(session_id) if not session: return # Уведомляем обе стороны if session["device"]: await session["device"].send(json.dumps({ "type": "hangup", "session_id": session_id, "timestamp": datetime.now().isoformat() })) if session["operator"]: await session["operator"].send(json.dumps({ "type": "hangup", "session_id": session_id, "timestamp": datetime.now().isoformat() })) # Удаляем сессию del self.sessions[session_id] logger.info(f"📴 Сессия завершена: {session_id}") async def cleanup_client(self, client_id): """Очистка при отключении клиента""" if client_id in self.clients: del self.clients[client_id] # Удаляем сессии с этим клиентом sessions_to_remove = [] for session_id, session in self.sessions.items(): if (session["device"] == self.clients.get(client_id) or session["operator"] == self.clients.get(client_id)): sessions_to_remove.append(session_id) for session_id in sessions_to_remove: del self.sessions[session_id] logger.info(f"🧹 Сессия {session_id} удалена из-за отключения клиента") def main(): """Запуск сигналинг сервера""" server = SignalingServer() print("🚀 GodEye Signaling Server") print("==========================") print("📡 Сервер запущен на ws://localhost:8765") print("💡 Для остановки нажмите Ctrl+C") print() # Исправляем проблему с event loop async def run_server(): start_server = websockets.serve( server.register_client, "0.0.0.0", # Слушаем на всех интерфейсах 8765 ) await start_server await asyncio.Future() # Запускаем вечно try: asyncio.run(run_server()) except KeyboardInterrupt: print("\n🛑 Сервер остановлен") if __name__ == "__main__": main()