249 lines
7.9 KiB
Python
249 lines
7.9 KiB
Python
"""
|
|
设备WebSocket路由
|
|
接收Android Agent APP的连接和命令
|
|
"""
|
|
|
|
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
|
from typing import Dict, Optional
|
|
import json
|
|
import asyncio
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
router = APIRouter(tags=["WebSocket"])
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class DeviceConnectionManager:
|
|
"""
|
|
设备连接管理器
|
|
管理所有连接的Android设备
|
|
"""
|
|
|
|
def __init__(self):
|
|
# device_id -> WebSocket连接
|
|
self.active_connections: Dict[str, WebSocket] = {}
|
|
# device_id -> 设备信息
|
|
self.device_info: Dict[str, dict] = {}
|
|
# project_id -> [device_id, ...]
|
|
self.project_devices: Dict[str, list] = {}
|
|
|
|
async def connect(self, device_id: str, websocket: WebSocket):
|
|
"""接受新连接"""
|
|
await websocket.accept()
|
|
self.active_connections[device_id] = websocket
|
|
logger.info(f"设备连接: {device_id}")
|
|
|
|
def disconnect(self, device_id: str):
|
|
"""断开连接"""
|
|
if device_id in self.active_connections:
|
|
del self.active_connections[device_id]
|
|
if device_id in self.device_info:
|
|
project_id = self.device_info[device_id].get("project_id")
|
|
if project_id and project_id in self.project_devices:
|
|
if device_id in self.project_devices[project_id]:
|
|
self.project_devices[project_id].remove(device_id)
|
|
del self.device_info[device_id]
|
|
logger.info(f"设备断开: {device_id}")
|
|
|
|
def register_device(self, device_id: str, info: dict):
|
|
"""注册设备信息"""
|
|
self.device_info[device_id] = {
|
|
**info,
|
|
"connected_at": datetime.now().isoformat(),
|
|
"last_heartbeat": datetime.now().isoformat()
|
|
}
|
|
|
|
# 添加到项目
|
|
project_id = info.get("project_id")
|
|
if project_id:
|
|
if project_id not in self.project_devices:
|
|
self.project_devices[project_id] = []
|
|
if device_id not in self.project_devices[project_id]:
|
|
self.project_devices[project_id].append(device_id)
|
|
|
|
logger.info(f"设备注册: {device_id}, 项目: {project_id}")
|
|
|
|
def update_heartbeat(self, device_id: str):
|
|
"""更新心跳时间"""
|
|
if device_id in self.device_info:
|
|
self.device_info[device_id]["last_heartbeat"] = datetime.now().isoformat()
|
|
|
|
async def send_to_device(self, device_id: str, message: dict) -> bool:
|
|
"""发送消息到指定设备"""
|
|
if device_id in self.active_connections:
|
|
try:
|
|
await self.active_connections[device_id].send_json(message)
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"发送失败: {device_id}, {e}")
|
|
return False
|
|
return False
|
|
|
|
async def broadcast_to_project(self, project_id: str, message: dict):
|
|
"""广播消息到项目下所有设备"""
|
|
if project_id in self.project_devices:
|
|
for device_id in self.project_devices[project_id]:
|
|
await self.send_to_device(device_id, message)
|
|
|
|
def get_device(self, device_id: str) -> Optional[dict]:
|
|
"""获取设备信息"""
|
|
return self.device_info.get(device_id)
|
|
|
|
def get_project_devices(self, project_id: str) -> list:
|
|
"""获取项目下所有设备"""
|
|
device_ids = self.project_devices.get(project_id, [])
|
|
return [self.device_info[did] for did in device_ids if did in self.device_info]
|
|
|
|
def get_all_devices(self) -> list:
|
|
"""获取所有设备"""
|
|
return list(self.device_info.values())
|
|
|
|
def is_connected(self, device_id: str) -> bool:
|
|
"""检查设备是否在线"""
|
|
return device_id in self.active_connections
|
|
|
|
|
|
# 全局连接管理器
|
|
manager = DeviceConnectionManager()
|
|
|
|
|
|
@router.websocket("/ws/device/{device_id}")
|
|
async def websocket_endpoint(websocket: WebSocket, device_id: str):
|
|
"""
|
|
设备WebSocket连接端点
|
|
|
|
消息类型:
|
|
- register: 设备注册
|
|
- heartbeat: 心跳
|
|
- result: 命令执行结果
|
|
"""
|
|
await manager.connect(device_id, websocket)
|
|
|
|
try:
|
|
while True:
|
|
data = await websocket.receive_json()
|
|
msg_type = data.get("type")
|
|
|
|
if msg_type == "register":
|
|
# 设备注册
|
|
manager.register_device(device_id, data)
|
|
await websocket.send_json({
|
|
"type": "registered",
|
|
"device_id": device_id,
|
|
"message": "注册成功"
|
|
})
|
|
|
|
elif msg_type == "heartbeat":
|
|
# 心跳
|
|
manager.update_heartbeat(device_id)
|
|
await websocket.send_json({
|
|
"type": "pong",
|
|
"timestamp": datetime.now().timestamp() * 1000
|
|
})
|
|
|
|
elif msg_type == "result":
|
|
# 命令执行结果
|
|
command_id = data.get("command_id")
|
|
logger.info(f"命令结果: {command_id}, 成功: {data.get('success')}")
|
|
# 这里可以存储结果或通知其他系统
|
|
|
|
else:
|
|
logger.warning(f"未知消息类型: {msg_type}")
|
|
|
|
except WebSocketDisconnect:
|
|
manager.disconnect(device_id)
|
|
except Exception as e:
|
|
logger.error(f"WebSocket错误: {device_id}, {e}")
|
|
manager.disconnect(device_id)
|
|
|
|
|
|
# ========== REST API接口 ==========
|
|
|
|
@router.get("/api/v3/devices")
|
|
async def list_devices():
|
|
"""获取所有在线设备"""
|
|
return {
|
|
"success": True,
|
|
"devices": manager.get_all_devices(),
|
|
"total": len(manager.get_all_devices())
|
|
}
|
|
|
|
|
|
@router.get("/api/v3/devices/{device_id}")
|
|
async def get_device(device_id: str):
|
|
"""获取指定设备信息"""
|
|
device = manager.get_device(device_id)
|
|
if device:
|
|
return {
|
|
"success": True,
|
|
"device": device,
|
|
"online": manager.is_connected(device_id)
|
|
}
|
|
return {"success": False, "message": "设备不存在"}
|
|
|
|
|
|
@router.get("/api/v3/projects/{project_id}/devices")
|
|
async def get_project_devices(project_id: str):
|
|
"""获取项目下所有设备"""
|
|
devices = manager.get_project_devices(project_id)
|
|
return {
|
|
"success": True,
|
|
"project_id": project_id,
|
|
"devices": devices,
|
|
"total": len(devices)
|
|
}
|
|
|
|
|
|
@router.post("/api/v3/devices/{device_id}/execute")
|
|
async def execute_command(device_id: str, command: dict):
|
|
"""
|
|
向设备发送执行命令
|
|
|
|
请求体:
|
|
{
|
|
"action": "open_app",
|
|
"params": {"package": "com.tencent.mm"}
|
|
}
|
|
"""
|
|
if not manager.is_connected(device_id):
|
|
return {"success": False, "message": "设备离线"}
|
|
|
|
command_id = f"cmd_{datetime.now().timestamp()}"
|
|
message = {
|
|
"type": "execute",
|
|
"command_id": command_id,
|
|
"action": command.get("action"),
|
|
"params": command.get("params", {})
|
|
}
|
|
|
|
success = await manager.send_to_device(device_id, message)
|
|
|
|
return {
|
|
"success": success,
|
|
"command_id": command_id,
|
|
"message": "命令已发送" if success else "发送失败"
|
|
}
|
|
|
|
|
|
@router.post("/api/v3/projects/{project_id}/broadcast")
|
|
async def broadcast_to_project(project_id: str, command: dict):
|
|
"""向项目下所有设备广播命令"""
|
|
command_id = f"cmd_{datetime.now().timestamp()}"
|
|
message = {
|
|
"type": "execute",
|
|
"command_id": command_id,
|
|
"action": command.get("action"),
|
|
"params": command.get("params", {})
|
|
}
|
|
|
|
await manager.broadcast_to_project(project_id, message)
|
|
|
|
devices = manager.get_project_devices(project_id)
|
|
return {
|
|
"success": True,
|
|
"command_id": command_id,
|
|
"devices_count": len(devices),
|
|
"message": f"已广播到 {len(devices)} 台设备"
|
|
}
|