Files
workphone-sdk/sdk/agent/voice_agent.py

415 lines
13 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.

#!/usr/bin/env python3
"""
工作手机Agent - 语音控制版
运行在手机上,支持语音命令和自主执行任务
"""
import asyncio
import json
import logging
import argparse
import base64
import time
from datetime import datetime
from typing import Optional, Dict, Any, List
try:
import websockets
import uiautomator2 as u2
except ImportError:
print("请安装依赖: pip install websockets uiautomator2")
exit(1)
# 尝试导入语音识别
try:
import speech_recognition as sr
VOICE_ENABLED = True
except ImportError:
VOICE_ENABLED = False
print("语音功能未启用,安装: pip install SpeechRecognition")
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
logger = logging.getLogger(__name__)
class VoiceAgent:
"""
语音控制Agent
功能:
1. 手机本地运行,无需电脑
2. 语音命令控制
3. 自主执行任务队列
4. 连接远程服务器接收命令
"""
def __init__(
self,
device_id: str,
server_url: str = None,
voice_enabled: bool = True
):
self.device_id = device_id
self.server_url = server_url
self.voice_enabled = voice_enabled and VOICE_ENABLED
# 初始化uiautomator2
self.d = u2.connect()
self.d.implicitly_wait(10.0)
# 任务队列
self.task_queue: List[Dict] = []
self.running = False
# 语音识别器
if self.voice_enabled:
self.recognizer = sr.Recognizer()
# 预设命令
self.commands = {
"打开微信": self._open_wechat,
"打开豆包": self._open_doubao,
"打开抖音": self._open_douyin,
"打开设置": self._open_settings,
"截图": self._screenshot,
"返回": self._go_back,
"回到桌面": self._go_home,
"向上滑": self._swipe_up,
"向下滑": self._swipe_down,
}
logger.info(f"VoiceAgent初始化完成: {device_id}")
logger.info(f"语音控制: {'启用' if self.voice_enabled else '禁用'}")
# ========== 基础操作 ==========
def _open_wechat(self):
"""打开微信"""
self.d.app_start("com.tencent.mm")
return {"success": True, "message": "微信已打开"}
def _open_doubao(self):
"""打开豆包"""
self.d.app_start("com.larus.nova")
return {"success": True, "message": "豆包已打开"}
def _open_douyin(self):
"""打开抖音"""
self.d.app_start("com.ss.android.ugc.aweme")
return {"success": True, "message": "抖音已打开"}
def _open_settings(self):
"""打开设置"""
self.d.app_start("com.android.settings")
return {"success": True, "message": "设置已打开"}
def _screenshot(self):
"""截图"""
img = self.d.screenshot(format='raw')
# 保存到手机
path = f"/sdcard/screenshot_{int(time.time())}.png"
with open(path, 'wb') as f:
f.write(img)
return {"success": True, "message": f"截图已保存: {path}"}
def _go_back(self):
"""返回"""
self.d.press("back")
return {"success": True, "message": "已返回"}
def _go_home(self):
"""回到桌面"""
self.d.press("home")
return {"success": True, "message": "已回到桌面"}
def _swipe_up(self):
"""向上滑动"""
self.d.swipe_ext("up", scale=0.8)
return {"success": True, "message": "已向上滑动"}
def _swipe_down(self):
"""向下滑动"""
self.d.swipe_ext("down", scale=0.8)
return {"success": True, "message": "已向下滑动"}
# ========== 语音识别 ==========
def listen_voice(self, timeout: int = 5) -> Optional[str]:
"""监听语音命令"""
if not self.voice_enabled:
return None
try:
with sr.Microphone() as source:
logger.info("请说话...")
self.recognizer.adjust_for_ambient_noise(source, duration=0.5)
audio = self.recognizer.listen(source, timeout=timeout)
# 使用Google语音识别中文
text = self.recognizer.recognize_google(audio, language="zh-CN")
logger.info(f"识别到: {text}")
return text
except sr.WaitTimeoutError:
return None
except sr.UnknownValueError:
logger.warning("无法识别语音")
return None
except Exception as e:
logger.error(f"语音识别错误: {e}")
return None
def process_voice_command(self, text: str) -> Dict:
"""处理语音命令"""
text = text.strip()
# 精确匹配
if text in self.commands:
return self.commands[text]()
# 模糊匹配
for cmd, func in self.commands.items():
if cmd in text or text in cmd:
return func()
# 发送消息命令: "给XXX发消息说XXX"
if "发消息" in text or "发送" in text:
return self._parse_send_message(text)
# 打开APP命令: "打开XXX"
if text.startswith("打开"):
app_name = text[2:].strip()
return self._open_app_by_name(app_name)
return {"success": False, "message": f"未识别的命令: {text}"}
def _parse_send_message(self, text: str) -> Dict:
"""解析发消息命令"""
# 示例: "给张三发消息说你好"
import re
match = re.search(r'给(.+?)(发消息|发送)(.+)', text)
if match:
contact = match.group(1).strip()
message = match.group(3).replace("", "").strip()
return self._send_wechat_message(contact, message)
return {"success": False, "message": "无法解析发消息命令"}
def _send_wechat_message(self, contact: str, message: str) -> Dict:
"""发送微信消息"""
try:
# 打开微信
self.d.app_start("com.tencent.mm")
time.sleep(2)
# 搜索联系人
if self.d(text="搜索").exists(timeout=3):
self.d(text="搜索").click()
time.sleep(0.5)
self.d.send_keys(contact)
time.sleep(1)
if self.d(text=contact).exists(timeout=3):
self.d(text=contact).click()
time.sleep(1)
# 发送消息
self.d.send_keys(message)
time.sleep(0.3)
if self.d(text="发送").exists(timeout=3):
self.d(text="发送").click()
return {"success": True, "message": f"已发送给{contact}: {message}"}
except Exception as e:
return {"success": False, "message": str(e)}
def _open_app_by_name(self, name: str) -> Dict:
"""根据名称打开APP"""
app_map = {
"微信": "com.tencent.mm",
"豆包": "com.larus.nova",
"抖音": "com.ss.android.ugc.aweme",
"小红书": "com.xingin.xhs",
"淘宝": "com.taobao.taobao",
"支付宝": "com.eg.android.AlipayGphone",
"设置": "com.android.settings",
"相机": "com.android.camera",
"浏览器": "com.android.chrome",
}
package = app_map.get(name)
if package:
self.d.app_start(package)
return {"success": True, "message": f"{name}已打开"}
return {"success": False, "message": f"未知APP: {name}"}
# ========== 任务队列 ==========
def add_task(self, task: Dict):
"""添加任务到队列"""
task["id"] = f"task_{int(time.time() * 1000)}"
task["status"] = "pending"
task["created_at"] = datetime.now().isoformat()
self.task_queue.append(task)
logger.info(f"添加任务: {task}")
return task["id"]
async def process_task_queue(self):
"""处理任务队列"""
while self.running:
if self.task_queue:
task = self.task_queue[0]
if task["status"] == "pending":
task["status"] = "running"
logger.info(f"执行任务: {task['id']}")
try:
result = await self._execute_task(task)
task["status"] = "completed"
task["result"] = result
except Exception as e:
task["status"] = "failed"
task["error"] = str(e)
self.task_queue.pop(0)
await asyncio.sleep(1)
async def _execute_task(self, task: Dict) -> Dict:
"""执行单个任务"""
task_type = task.get("type")
params = task.get("params", {})
if task_type == "voice_command":
return self.process_voice_command(params.get("text", ""))
elif task_type == "click":
self.d.click(params["x"], params["y"])
return {"success": True}
elif task_type == "send_message":
return self._send_wechat_message(
params.get("contact"),
params.get("message")
)
elif task_type == "open_app":
return self._open_app_by_name(params.get("name"))
return {"success": False, "message": f"未知任务类型: {task_type}"}
# ========== 远程连接 ==========
async def connect_server(self):
"""连接远程服务器"""
if not self.server_url:
return
while self.running:
try:
logger.info(f"连接服务器: {self.server_url}")
async with websockets.connect(self.server_url) as ws:
# 注册
await ws.send(json.dumps({
"type": "register",
"device_id": self.device_id,
"capabilities": ["voice", "ui_control"]
}))
async for message in ws:
data = json.loads(message)
await self._handle_server_message(data, ws)
except Exception as e:
logger.error(f"服务器连接错误: {e}")
await asyncio.sleep(5)
async def _handle_server_message(self, data: Dict, ws):
"""处理服务器消息"""
msg_type = data.get("type")
if msg_type == "execute":
# 添加到任务队列
task_id = self.add_task(data.get("task", {}))
await ws.send(json.dumps({
"type": "task_queued",
"task_id": task_id
}))
elif msg_type == "voice_command":
result = self.process_voice_command(data.get("text", ""))
await ws.send(json.dumps({
"type": "result",
"command_id": data.get("command_id"),
"result": result
}))
# ========== 主循环 ==========
async def voice_loop(self):
"""语音监听循环"""
if not self.voice_enabled:
return
logger.info("开始语音监听...")
while self.running:
text = self.listen_voice(timeout=3)
if text:
# 唤醒词检测
if "小助手" in text or "你好" in text:
logger.info("唤醒成功,等待命令...")
command = self.listen_voice(timeout=5)
if command:
result = self.process_voice_command(command)
logger.info(f"执行结果: {result}")
await asyncio.sleep(0.1)
async def start(self):
"""启动Agent"""
self.running = True
logger.info("VoiceAgent启动")
tasks = [
self.process_task_queue(),
]
if self.server_url:
tasks.append(self.connect_server())
if self.voice_enabled:
tasks.append(self.voice_loop())
await asyncio.gather(*tasks)
def stop(self):
"""停止Agent"""
self.running = False
logger.info("VoiceAgent停止")
def main():
parser = argparse.ArgumentParser(description='语音控制Agent')
parser.add_argument('--device-id', default='voice-agent-001', help='设备ID')
parser.add_argument('--server', help='远程服务器地址 (ws://xxx:8899/ws/device/xxx)')
parser.add_argument('--no-voice', action='store_true', help='禁用语音控制')
args = parser.parse_args()
agent = VoiceAgent(
device_id=args.device_id,
server_url=args.server,
voice_enabled=not args.no_voice
)
try:
asyncio.run(agent.start())
except KeyboardInterrupt:
agent.stop()
if __name__ == "__main__":
main()