351 lines
13 KiB
Python
351 lines
13 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Простой веб-сервер для панели оператора GodEye
|
||
Обслуживает WebRTC интерфейс и Socket.IO соединения
|
||
"""
|
||
|
||
import socketio
|
||
import eventlet
|
||
import eventlet.wsgi
|
||
from flask import Flask, send_file
|
||
import json
|
||
import uuid
|
||
from datetime import datetime
|
||
import os
|
||
|
||
# Создаем Flask приложение
|
||
app = Flask(__name__)
|
||
|
||
# Создаем Socket.IO сервер
|
||
sio = socketio.Server(cors_allowed_origins="*")
|
||
app.wsgi_app = socketio.WSGIApp(sio, app.wsgi_app)
|
||
|
||
# Хранилище подключенных устройств и операторов
|
||
connected_devices = {}
|
||
connected_operators = {}
|
||
active_sessions = {}
|
||
|
||
@app.route('/')
|
||
def serve_interface():
|
||
"""Обслуживает панель оператора"""
|
||
return send_file('operator-interface.html')
|
||
|
||
@sio.event
|
||
def connect(sid, environ):
|
||
"""Обработка подключения клиента"""
|
||
print(f"[{datetime.now().strftime('%H:%M:%S')}] 🔗 Клиент подключился: {sid}")
|
||
|
||
@sio.event
|
||
def disconnect(sid):
|
||
"""Обработка отключения клиента"""
|
||
print(f"[{datetime.now().strftime('%H:%M:%S')}] ❌ Клиент отключился: {sid}")
|
||
|
||
# Удаляем из устройств
|
||
if sid in connected_devices:
|
||
device_data = connected_devices[sid]
|
||
del connected_devices[sid]
|
||
sio.emit('device_disconnected', {
|
||
'deviceId': device_data['deviceId'],
|
||
'deviceName': device_data['deviceName']
|
||
})
|
||
print(f"[{datetime.now().strftime('%H:%M:%S')}] 📱 Устройство отключилось: {device_data['deviceName']}")
|
||
|
||
# Удаляем из операторов
|
||
if sid in connected_operators:
|
||
del connected_operators[sid]
|
||
print(f"[{datetime.now().strftime('%H:%M:%S')}] 👨💼 Оператор отключился")
|
||
|
||
@sio.event
|
||
def device_register(sid, data):
|
||
"""Регистрация Android устройства"""
|
||
device_id = data.get('deviceId')
|
||
device_name = data.get('deviceName', 'Unknown Device')
|
||
|
||
connected_devices[sid] = {
|
||
'deviceId': device_id,
|
||
'deviceName': device_name,
|
||
'connectedAt': datetime.now().isoformat()
|
||
}
|
||
|
||
print(f"[{datetime.now().strftime('%H:%M:%S')}] 📱 Устройство зарегистрировано: {device_name} ({device_id})")
|
||
|
||
# Уведомляем всех операторов о новом устройстве
|
||
sio.emit('device_connected', {
|
||
'deviceId': device_id,
|
||
'deviceName': device_name
|
||
})
|
||
|
||
# Отправляем подтверждение устройству
|
||
sio.emit('registration_confirmed', {
|
||
'status': 'success',
|
||
'message': 'Устройство успешно зарегистрировано'
|
||
}, room=sid)
|
||
|
||
@sio.event
|
||
def operator_register(sid, data):
|
||
"""Регистрация оператора"""
|
||
operator_id = data.get('operatorId', f'operator_{uuid.uuid4().hex[:8]}')
|
||
|
||
connected_operators[sid] = {
|
||
'operatorId': operator_id,
|
||
'connectedAt': datetime.now().isoformat()
|
||
}
|
||
|
||
print(f"[{datetime.now().strftime('%H:%M:%S')}] 👨💼 Оператор зарегистрирован: {operator_id}")
|
||
|
||
# Отправляем список подключенных устройств
|
||
devices_list = []
|
||
for device_sid, device_data in connected_devices.items():
|
||
devices_list.append({
|
||
'deviceId': device_data['deviceId'],
|
||
'deviceName': device_data['deviceName']
|
||
})
|
||
|
||
sio.emit('devices_list', {'devices': devices_list}, room=sid)
|
||
|
||
@sio.event
|
||
def request_camera(sid, data):
|
||
"""Запрос камеры от оператора"""
|
||
device_id = data.get('deviceId')
|
||
operator_id = data.get('operatorId')
|
||
camera_type = data.get('cameraType', 'back') # Убеждаемся что есть значение по умолчанию
|
||
|
||
print(f"[{datetime.now().strftime('%H:%M:%S')}] 📹 Запрос камеры от {operator_id} для устройства {device_id}, тип камеры: {camera_type}")
|
||
|
||
# Находим устройство по ID
|
||
target_device_sid = None
|
||
for device_sid, device_data in connected_devices.items():
|
||
if device_data['deviceId'] == device_id:
|
||
target_device_sid = device_sid
|
||
break
|
||
|
||
if target_device_sid:
|
||
session_id = str(uuid.uuid4())
|
||
|
||
# Сохраняем сессию с правильными данными
|
||
active_sessions[session_id] = {
|
||
'deviceSid': target_device_sid,
|
||
'operatorSid': sid,
|
||
'deviceId': device_id,
|
||
'operatorId': operator_id,
|
||
'cameraType': camera_type, # Явно сохраняем тип камеры
|
||
'status': 'pending', # Добавляем статус
|
||
'startedAt': datetime.now().isoformat()
|
||
}
|
||
|
||
# Отправляем запрос устройству с полными данными
|
||
sio.emit('camera_request', {
|
||
'sessionId': session_id,
|
||
'operatorId': operator_id,
|
||
'cameraType': camera_type,
|
||
'deviceId': device_id, # Добавляем deviceId
|
||
'message': f'Оператор {operator_id} запрашивает доступ к камере'
|
||
}, room=target_device_sid)
|
||
|
||
# Уведомляем оператора о создании сессии
|
||
sio.emit('session_created', {
|
||
'sessionId': session_id,
|
||
'deviceId': device_id,
|
||
'cameraType': camera_type,
|
||
'status': 'pending'
|
||
}, room=sid)
|
||
|
||
print(f"[{datetime.now().strftime('%H:%M:%S')}] 📤 Запрос отправлен устройству, сессия: {session_id}, камера: {camera_type}")
|
||
else:
|
||
sio.emit('error', {
|
||
'message': f'Устройство {device_id} не найдено'
|
||
}, room=sid)
|
||
|
||
@sio.event
|
||
def camera_approved(sid, data):
|
||
"""Подтверждение доступа к камере от устройства"""
|
||
session_id = data.get('sessionId')
|
||
|
||
if session_id in active_sessions:
|
||
session = active_sessions[session_id]
|
||
operator_sid = session['operatorSid']
|
||
|
||
# Обновляем статус сессии
|
||
active_sessions[session_id]['status'] = 'approved'
|
||
|
||
print(f"[{datetime.now().strftime('%H:%M:%S')}] ✅ Доступ к камере подтвержден для сессии: {session_id}")
|
||
|
||
# Уведомляем оператора с полными данными
|
||
sio.emit('camera_approved', {
|
||
'sessionId': session_id,
|
||
'deviceId': session['deviceId'],
|
||
'cameraType': session['cameraType'],
|
||
'status': 'approved'
|
||
}, room=operator_sid)
|
||
|
||
@sio.event
|
||
def camera_rejected(sid, data):
|
||
"""Отклонение доступа к камере от устройства"""
|
||
session_id = data.get('sessionId')
|
||
reason = data.get('reason', 'Отклонено пользователем')
|
||
|
||
if session_id in active_sessions:
|
||
session = active_sessions[session_id]
|
||
operator_sid = session['operatorSid']
|
||
|
||
print(f"[{datetime.now().strftime('%H:%M:%S')}] ❌ Доступ к камере отклонен для сессии: {session_id}, причина: {reason}")
|
||
|
||
# Уведомляем оператора об отклонении
|
||
sio.emit('camera_rejected', {
|
||
'sessionId': session_id,
|
||
'deviceId': session['deviceId'],
|
||
'reason': reason
|
||
}, room=operator_sid)
|
||
|
||
# Удаляем сессию, так как она отклонена
|
||
del active_sessions[session_id]
|
||
|
||
@sio.event
|
||
def camera_started(sid, data):
|
||
"""Уведомление о запуске камеры"""
|
||
session_id = data.get('sessionId')
|
||
|
||
if session_id in active_sessions:
|
||
session = active_sessions[session_id]
|
||
operator_sid = session['operatorSid']
|
||
|
||
# Обновляем статус сессии на активный
|
||
active_sessions[session_id]['status'] = 'active'
|
||
active_sessions[session_id]['actualStartedAt'] = datetime.now().isoformat()
|
||
|
||
print(f"[{datetime.now().strftime('%H:%M:%S')}] 📹 Камера запущена для сессии: {session_id}")
|
||
|
||
# Уведомляем оператора с обновленным статусом
|
||
sio.emit('camera_started', {
|
||
'sessionId': session_id,
|
||
'deviceId': session['deviceId'],
|
||
'cameraType': session['cameraType'],
|
||
'status': 'active'
|
||
}, room=operator_sid)
|
||
|
||
# Также отправляем обновление статуса сессии
|
||
sio.emit('session_status_update', {
|
||
'sessionId': session_id,
|
||
'status': 'active',
|
||
'cameraType': session['cameraType']
|
||
}, room=operator_sid)
|
||
|
||
@sio.event
|
||
def webrtc_offer(sid, data):
|
||
"""Пересылка WebRTC offer от устройства к оператору"""
|
||
session_id = data.get('sessionId')
|
||
offer = data.get('offer')
|
||
|
||
if session_id in active_sessions:
|
||
session = active_sessions[session_id]
|
||
operator_sid = session['operatorSid']
|
||
|
||
print(f"[{datetime.now().strftime('%H:%M:%S')}] 📡 Пересылка WebRTC offer для сессии: {session_id}")
|
||
|
||
# Пересылаем offer оператору
|
||
sio.emit('webrtc_offer', {
|
||
'sessionId': session_id,
|
||
'offer': offer
|
||
}, room=operator_sid)
|
||
|
||
@sio.event
|
||
def webrtc_answer(sid, data):
|
||
"""Пересылка WebRTC answer от оператора к устройству"""
|
||
session_id = data.get('sessionId')
|
||
answer = data.get('answer')
|
||
|
||
if session_id in active_sessions:
|
||
session = active_sessions[session_id]
|
||
device_sid = session['deviceSid']
|
||
|
||
print(f"[{datetime.now().strftime('%H:%M:%S')}] 📡 Пересылка WebRTC answer для сессии: {session_id}")
|
||
|
||
# Пересылаем answer устройству
|
||
sio.emit('webrtc_answer', {
|
||
'sessionId': session_id,
|
||
'answer': answer
|
||
}, room=device_sid)
|
||
|
||
@sio.event
|
||
def webrtc_ice_candidate(sid, data):
|
||
"""Пересылка ICE candidates между устройством и оператором"""
|
||
session_id = data.get('sessionId')
|
||
candidate = data.get('candidate')
|
||
|
||
if session_id in active_sessions:
|
||
session = active_sessions[session_id]
|
||
|
||
# Определяем куда пересылать (от устройства к оператору или наоборот)
|
||
if sid == session['deviceSid']:
|
||
# От устройства к оператору
|
||
target_sid = session['operatorSid']
|
||
else:
|
||
# От оператора к устройству
|
||
target_sid = session['deviceSid']
|
||
|
||
print(f"[{datetime.now().strftime('%H:%M:%S')}] 🧊 Пересылка ICE candidate для сессии: {session_id}")
|
||
|
||
sio.emit('webrtc_ice_candidate', {
|
||
'sessionId': session_id,
|
||
'candidate': candidate
|
||
}, room=target_sid)
|
||
|
||
@sio.event
|
||
def switch_camera(sid, data):
|
||
"""Переключение камеры"""
|
||
session_id = data.get('sessionId')
|
||
|
||
if session_id in active_sessions:
|
||
session = active_sessions[session_id]
|
||
device_sid = session['deviceSid']
|
||
|
||
print(f"[{datetime.now().strftime('%H:%M:%S')}] 🔄 Переключение камеры для сессии: {session_id}")
|
||
|
||
# Отправляем команду устройству
|
||
sio.emit('switch_camera', {
|
||
'sessionId': session_id
|
||
}, room=device_sid)
|
||
|
||
@sio.event
|
||
def stop_camera(sid, data):
|
||
"""Остановка камеры"""
|
||
session_id = data.get('sessionId')
|
||
|
||
if session_id in active_sessions:
|
||
session = active_sessions[session_id]
|
||
device_sid = session['deviceSid']
|
||
operator_sid = session['operatorSid']
|
||
|
||
print(f"[{datetime.now().strftime('%H:%M:%S')}] 🛑 Остановка камеры для сессии: {session_id}")
|
||
|
||
# Отправляем команду устройству
|
||
sio.emit('stop_camera', {
|
||
'sessionId': session_id
|
||
}, room=device_sid)
|
||
|
||
# Уведомляем оператора
|
||
sio.emit('camera_stopped', {
|
||
'sessionId': session_id
|
||
}, room=operator_sid)
|
||
|
||
# Удаляем сессию
|
||
del active_sessions[session_id]
|
||
|
||
if __name__ == '__main__':
|
||
# ИСПРАВЛЕНО: Используем localhost для локального тестирования
|
||
host = '0.0.0.0' # Слушаем на всех интерфейсах
|
||
port = 3001
|
||
|
||
print("🚀 Запуск GodEye сервера...")
|
||
print(f"🌐 Веб-интерфейс: http://localhost:{port}")
|
||
print(f"🔌 Socket.IO endpoint: http://localhost:{port}/socket.io/")
|
||
print("📱 Настройте Android приложение на адрес вашего компьютера")
|
||
print("-" * 50)
|
||
|
||
try:
|
||
eventlet.wsgi.server(eventlet.listen((host, port)), app)
|
||
except KeyboardInterrupt:
|
||
print("\n🛑 Сервер остановлен")
|
||
except Exception as e:
|
||
print(f"❌ Ошибка сервера: {e}")
|