Files
workphone-sdk/sdk/app/routers/connection.py

240 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
连接协议与状态路由
用于展示设备端Agent/Hook与服务端的连接方式、消息协议和实时状态。
"""
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any, Dict, List
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field
from services.ws_hub import ws_hub
from services.adb_device import adb_manager
from services.hook_module_service import hook_module_service
from services.connection_priority import connection_priority
router = APIRouter()
class SimRegisterRequest(BaseModel):
device_id: str
project_id: str = "cunkebao"
model: str = "Simulator Device"
platform: str = "android"
capabilities: List[str] = Field(default_factory=lambda: ["event", "device_request"])
class SimHeartbeatRequest(BaseModel):
device_id: str
class SimHookEventRequest(BaseModel):
device_id: str
platform: str = "wechat"
event_type: str = "message_received"
payload: Dict[str, Any] = Field(default_factory=dict)
def _iso_now() -> str:
return datetime.now(timezone.utc).isoformat()
def _heartbeat_age_seconds(last_heartbeat: str) -> int:
if not last_heartbeat:
return -1
try:
dt = datetime.fromisoformat(last_heartbeat.replace("Z", "+00:00"))
return max(0, int((datetime.now(dt.tzinfo or timezone.utc) - dt).total_seconds()))
except Exception:
return -1
@router.get("/connection/protocol")
async def get_connection_protocol() -> Dict[str, Any]:
"""连接方式协议:给前端/对接方直观看结构。"""
return {
"code": 200,
"data": {
"version": "v1",
"generated_at": _iso_now(),
"transport": {
"agent_ws": "ws://<host>:8899/ws/device/{device_id}",
"hook_events_ws": "ws://<host>:8899/api/v3/hook/events/stream",
"rest_base": "http://<host>:8899/api/v3",
},
"handshake": {
"client_register": {
"type": "register",
"data": {
"project_id": "cunkebao",
"agent_version": "3.0.0",
"model": "Redmi 11",
"capabilities": ["u2", "skill_wechat", "event", "device_request"],
},
},
"server_ack": {
"type": "registered",
"success": True,
"device_id": "<device_id>",
"project_id": "<project_id>",
},
},
"heartbeat": {
"client_ping_type": "heartbeat",
"server_pong_type": "pong",
"recommended_interval_seconds": [5, 10, 30],
},
"message_types": {
"device_to_server": [
"register",
"heartbeat",
"response",
"status_report",
"event",
"device_request",
],
"server_to_device": [
"registered",
"pong",
"execute",
"agent_execute",
"config_update",
"device_request_ack",
],
},
"hook_rest": {
"list_modules": "GET /api/v3/modules",
"device_modules": "GET /api/v3/devices/{device_id}/modules",
"deploy_script": "POST /api/v3/scripts/{script_id}/deploy",
"ingest_event": "POST /api/v3/hook/events",
},
},
}
@router.get("/connection/status")
async def get_connection_status() -> Dict[str, Any]:
"""实时连接状态:用于控制台展示。"""
online_devices: List[Dict[str, Any]] = ws_hub.get_online_devices()
adb_devices = adb_manager.scan_devices()
device_rows: List[Dict[str, Any]] = []
for item in online_devices:
device_id = item.get("device_id", "")
last_heartbeat = item.get("last_heartbeat", "")
row = {
"device_id": device_id,
"project_id": item.get("project_id", ""),
"model": item.get("model", ""),
"platform": item.get("platform", ""),
"status": item.get("status", "online"),
"last_heartbeat": last_heartbeat,
"heartbeat_age_seconds": _heartbeat_age_seconds(last_heartbeat),
"capabilities": item.get("capabilities", []),
"wechat_running": item.get("wechat_running"),
"wechat_foreground": item.get("wechat_foreground"),
"network_type": item.get("network_type"),
"screen_on": item.get("screen_on"),
"current_app": item.get("current_app"),
}
device_rows.append(row)
return {
"code": 200,
"data": {
"server_time": _iso_now(),
"ws_path": "/ws/device/{device_id}",
"hook_events_stream": "/api/v3/hook/events/stream",
"online_ws_count": len(ws_hub.connections),
"online_device_ids": list(ws_hub.connections.keys()),
"adb_count": len(adb_devices),
"adb_serials": adb_devices,
"devices": device_rows,
},
}
@router.post("/connection/simulate/register")
async def simulate_register(req: SimRegisterRequest) -> Dict[str, Any]:
"""协议联调:模拟设备 register不建立真实 WS仅写入状态面板"""
await ws_hub.handle_message(
req.device_id,
{
"type": "register",
"data": {
"device_id": req.device_id,
"project_id": req.project_id,
"model": req.model,
"platform": req.platform,
"status": "simulated",
"capabilities": req.capabilities,
"source": "connection_simulator",
},
},
)
return {"code": 200, "data": {"device_id": req.device_id, "simulated": True, "action": "register"}}
@router.post("/connection/simulate/heartbeat")
async def simulate_heartbeat(req: SimHeartbeatRequest) -> Dict[str, Any]:
"""协议联调:模拟心跳。"""
if not ws_hub.get_device_info(req.device_id):
raise HTTPException(status_code=404, detail="设备未注册,先模拟 register")
await ws_hub.handle_message(req.device_id, {"type": "heartbeat", "device_id": req.device_id})
return {"code": 200, "data": {"device_id": req.device_id, "simulated": True, "action": "heartbeat"}}
@router.post("/connection/simulate/hook-event")
async def simulate_hook_event(req: SimHookEventRequest) -> Dict[str, Any]:
"""协议联调:模拟 hook 事件写入并广播到事件流。"""
event = await hook_module_service.add_event(
{
"event_type": req.event_type,
"device_id": req.device_id,
"platform": req.platform,
"payload": req.payload,
"source": "connection_simulator",
}
)
return {"code": 200, "data": event}
# ========== 连接优先级 ==========
@router.get("/connection/modes/{device_id}")
async def get_device_connection_modes(device_id: str) -> Dict[str, Any]:
"""获取设备所有控制模式的可用性与优先级。"""
modes = connection_priority.evaluate(device_id)
best = next((m for m in modes if m.available), None)
return {
"code": 200,
"data": {
"device_id": device_id,
"best_mode": best.to_dict() if best else None,
"modes": [m.to_dict() for m in modes],
},
}
@router.get("/connection/modes")
async def get_all_connection_modes() -> Dict[str, Any]:
"""获取所有已知设备的控制模式概览。"""
online_ids = list(ws_hub.connections.keys())
adb_serials = adb_manager.scan_devices()
all_ids = list(set(online_ids + adb_serials))
result = []
for did in all_ids:
modes = connection_priority.evaluate(did)
best = next((m for m in modes if m.available), None)
result.append({
"device_id": did,
"best_mode": best.to_dict() if best else None,
"available_count": sum(1 for m in modes if m.available),
"modes": [m.to_dict() for m in modes],
})
return {"code": 200, "data": result}