auto_control — Android 多设备自动化任务平台

基于 uiautomator2 + Flask + 自建设备池 的 Android 多设备自动化平台(已摘除 OpenSTF 依赖,见 doc/STF_REMOVAL.md)。

一台机器上统管一批 Android 设备:定时/手动下发任务、并发执行、实时看屏与远程触控、元素抓取与步骤编排、APK 批量安装、数据备份导出/导入;并通过 MCP 把手机控制能力开放给外部 AI(数字员工)。

                       ┌──────────── Web 管理后台(单页应用,:18050)────────────┐
   浏览器 ────────────▶ │ 监控 │ 任务 │ 日志 │ 用户 │ 工具 │ AI 控制台 │ 系统     │
                       └───────────────────────┬─────────────────────────────────┘
                                               │ Flask 蓝图(web/)
                       ┌───────────────────────▼──────┐   ┌──────────────────────┐
                       │ 调度 TaskManager             │   │ AI Agent(mcp_agent)│
                       │ 设备池 device_pool           │   └──────────┬───────────┘
                       │ Worker(每设备一线程)        │              │ MCP :8033
                       └───────┬──────────────┬───────┘   ┌──────────▼───────────┐
                               │              │           │ MCP Server(de_* 工具)│
                          adb 直连         uiautodev        └──────────┬───────────┘
                        IP:5555 / USB   :20242(抓元素)                │
                               ▼                                      ▼
                         Android 设备 ◀──────────────────────── Android 设备

目录


核心能力

能力 说明
多设备并发任务 每设备一个 Worker 线程;同一设备同时只跑一个任务(可配置"抢占"打断其他任务)
设备池 SQLite 清单 + adb 在线状态;支持手工添加、网段自动发现、一键重连、型号采集、启用/停用
任务调度 手动 / cron 定时 / 定时启停;运行窗口;失败重试(含端口耗尽类的长退避)
步骤编辑器 可视化拖拽编排 18 种步骤(含循环/条件/OCR),可打包成"自定义动作"复用
拟人化操作 滑动默认走弧线轨迹、位置/幅度/时长每次抖动,且每台设备有自己的手速与习惯(同设备风格稳定、设备之间明显不同)——批量跑时不像同步机器人(见 TASK_DEV.md §8.4)
元素抓取 拉取设备 UI 元素树 → 点选回填选择器;支持"点一下"与"测选择器"真机验证
实时看屏 MJPEG 实时流 + 点击/滑动/按键/文字输入;全屏监控大屏(/wall)
应用管理 APK 上传/解析/批量安装;设备已装应用与版本查询;剪贴板注入
AI 控制台 用自然语言驱动 AI 操作指定设备(MCP 工具 + 截图),流式输出、Markdown 渲染、推理链折叠、token 统计;成功操作自动沉淀「经验库 / 动作库」并在相似任务中召回
MCP 接入 20 个 de_* 工具,把手机控制开放给外部 AI;写操作有开关、设备忙时拒绝、全量审计
备份导出/导入 一键导出 zip(库快照 + manifest + 可选 APK),导入前校验预览、自动预备份、重启生效
通知 / Webhook 任务成功失败、设备上下线、安装完成、备份恢复等事件推送到企业微信 / 钉钉 / 飞书 / Bark / 自建服务(Slack 用通用 JSON);多条 webhook 各自订阅;聚合+限流防刷屏(见 doc/NOTIFY.md)

快速上手

环境要求

  • Python 3.10+(推荐 3.12)
  • Windows / Linux / macOS 均可(bin/adb/ 需放对应平台的 adb 二进制)
  • 设备开启 USB 调试;网络调试设备建议 adb tcpip 5555 并接入同一网络(本项目生产环境走 Tailscale 100.100.10.x,见 doc/DEPLOY.md)

三步启动

# 1. 安装依赖
pip install -r requirements.txt

# 2. 配置(可选):把 .env.example 复制成 .env,至少填 WEB_SECRET_KEY
#    密钥类配置一律放 .env(不入 git)

# 3. 启动
python web_server.py

启动后访问 http://localhost:18050/,默认账号 admin / admin123(首次登录请立即改密)。

验证启动

[INFO] [core.worker] 心跳看门狗已启动
[INFO] [core.tm] 从数据库加载 X 个分组, X 个任务
[INFO] [web] uiautodev 服务已启动 (PID=...)
[INFO] [web] 管理后台: http://localhost:18050/ (admin/admin123)
[INFO] [web] 设备池预连接完成: X 台在线
[INFO] [web] 启动服务: http://localhost:18050/

首次使用

  1. 登录 → admin/admin123,改密
  2. 加设备 → 「工具 → 设备池管理」添加 IP:5555(自动连接 + 采集型号);也可用「设备自动发现」扫描网段后确认入池
  3. 看设备 → 「监控」页设备表(在线/型号/任务/进度/前台 App)
  4. 建任务 → 「任务 → 任务计划 → 新建任务」(只有 generic_steps 一种类型)→ 拖步骤 → 选目标设备 → 保存
  5. 跑任务 → 任务行「执行」,或在「任务」/「监控」页操作
  6. 看日志 → 「日志」页按模块实时查看

项目结构

auto_control/
├── config.py                  # 程序级配置常量(部署配置从 .env 读,模板 .env.example)
├── web_server.py              # 入口:app 装配 + 蓝图注册 + 恢复消费 + 拉起 uiautodev + 启动
├── requirements.txt           # 依赖清单
│
├── web/                       # Web 层(蓝图包,路由都在这里,全部无 url_prefix)
│   ├── auth.py                #   登录/登出/CSRF/权限装饰器/页面路由(/、/wall)
│   ├── monitor.py             #   设备状态/运行控制/设备操作/远程看屏(MJPEG、点击、按键)
│   ├── tasks_api.py           #   任务计划/分组/自定义动作/元素抓取/步骤测试
│   ├── admin_api.py           #   用户管理/日志
│   ├── tools_api.py           #   adb 终端/剪贴板注入/应用版本查询
│   ├── devices_api.py         #   设备池管理 + 自动发现
│   ├── apks_api.py            #   APK 上传/安装/删除
│   ├── tailscale_api.py       #   Tailscale 管理(改名/授权/密钥/IP/auth key)
│   ├── agent_api.py           #   AI 控制台(会话/SSE/经验库/动作库/巡检)
│   ├── system_api.py          #   系统数据备份导出/导入
│   ├── notify_api.py          #   通知 / Webhook 配置(系统 Tab)
│   ├── common.py              #   跨模块共享工具(合并设备列表、屏幕状态)
│   └── context.py             #   共享对象注入(mgr / apk_mgr / device_pool)
│
├── core/                      # 基础设施层
│   ├── models.py              #   SQLAlchemy 模型 + 建表 + 版本化迁移 + 旧 JSON 迁移
│   ├── device_pool.py         #   设备池(清单/在线状态/型号)
│   ├── device_worker.py       #   BaseWorker + 设备生命周期 + 心跳看门狗 + 全局状态表
│   ├── task_manager.py        #   调度器:分组/任务/APScheduler/重试/运行控制
│   ├── adb_helper.py          #   adb 命令封装(全局锁,红线:绝不 kill-server)
│   ├── device_discovery.py    #   网段扫描发现设备 → 待连接池
│   ├── u2_helper.py           #   uiautomator2 通用辅助(等首页/安全点击等)
│   ├── uiauto_helper.py       #   uiautodev 客户端(元素树 + XPath 建议)
│   ├── ocr.py                 #   屏幕 OCR(RapidOCR,条件判断用)
│   ├── clipboard_helper.py    #   剪贴板注入(ClipInject 通道)
│   ├── apk_manager.py         #   APK 上传/解析/批量安装
│   ├── system_backup.py       #   数据备份导出/导入(重启生效)
│   ├── humanize.py            #   拟人化:弧线滑动/抖动 + 每台设备的"手感"(按 serial 播种)
│   ├── notifier.py            #   通知分发(队列/聚合/限流/适配器)+ notify_events.py 事件目录
│   ├── step_log.py            #   任务步骤明细(异步写线程 + 保留期清理)
│   ├── tailscale_client.py    #   Tailscale API v2 客户端
│   ├── ssh_client.py          #   SSH 封装(当前无人调用,预留)
│   ├── logger.py              #   分文件日志(core/task/web/action)
│   └── actions/               #   全局 Action 框架(BaseAction + 注册器)
│
├── tasks/                     # 任务定义层
│   ├── base.py                #   BaseTask + _TASK_TYPES + register_task
│   └── generic/               #   通用步骤任务(task_type=generic_steps,当前唯一类型)
│       └── task.py            #     STEP_TYPES(18 种步骤)+ Worker + 执行器
│
├── mcp_server/                # MCP Server(20 个 de_* 工具,:8033)
│   ├── mcp_server.py          #   工具定义 + 平台登录 + 门控
│   ├── platform_client.py     #   平台 HTTP 客户端(复用 Web 账号)
│   ├── direct_ops.py          #   直连设备的 adb/u2 操作
│   ├── audit.py               #   调用审计(JSON Lines)
│   └── config.py              #   MCP_* 环境变量
│
├── mcp_agent/                 # AI Agent 编排层(OpenAI 兼容模型 → MCP 工具)
│   ├── agent.py               #   Agent:流式循环 + 工具调用 + 截图 + token 统计
│   ├── config.py              #   AGENT_* 环境变量
│   └── cli.py                 #   命令行入口
│
├── templates/admin/           # 页面
│   ├── monitor.html           #   主单页应用(7 个顶级 Tab + 模态框 + 内联样式)
│   ├── login.html             #   登录页
│   └── wall.html              #   监控大屏(独立页面,自包含)
├── static/admin/              # 前端 JS(13 个文件,按顺序同步加载,见下)
├── static/fonts/              # 自托管字体(Bricolage Grotesque + IBM Plex Mono)
│
├── data/                      # 运行时数据(不入 git)
│   ├── users.db               #   SQLite 主库
│   ├── apks/                  #   上传的 APK
│   ├── backups/               #   导出 zip / 预恢复快照
│   ├── restore_staging/       #   导入暂存(TTL 30 分钟)
│   └── restore_pending/       #   待重启生效的恢复任务
├── logs/                      # 日志(10MB 滚动,保留 5 份)
├── bin/adb/                   # adb 二进制
├── scripts/                   # pack.py / start.sh / supervise.sh / regression_test.py
└── doc/                       # 完整文档(入口 → doc/README.md)

前端 JS 加载顺序(templates/admin/monitor.html 底部,全部为全局脚本,无模块隔离):

base.js → markdown.js → list.js → monitor.js → editor.js → tasks.js
        → tools.js → apps.js → admin.js → agent.js → taskgen.js → system.js → notify.js → steplog.js

核心概念

设备与 Worker

一台设备对应一个 Worker 线程(core/device_worker.py 的 BaseWorker)。基类封装了设备获取、adb/u2 连接(带超时保护)、状态上报、异常分类、停止信号、心跳看门狗;子类只实现 run_task(d)。

执行链路:设备池选定 → adb connect 直连(IP:5555)或经 220 远程 adb server(USB)→ u2.connect(自动推送 atx-agent)→ run_task → 释放(不断开连接)。

同一设备同时只允许一个 Worker:由 TaskManager._running 内存锁保证;任务可开 preempt 抢占(先停掉正在跑的任务再接管,结束后归还)。

任务类型(TaskType)

task_type 名称 说明
generic_steps 通用步骤 步骤编辑器编排的流程(当前唯一类型)

新增专属任务类型的方法见 doc/TASK_DEV.md。

任务计划(TaskJob)

描述"什么任务、跑哪些设备、什么参数、何时跑、失败怎么重试":

字段 说明
task_type 任务类型(不传默认 generic_steps)
target {"mode":"all"} / {"mode":"group","group_name":"A组"} / {"mode":"serial","serial":"100.100.10.20:5555"}
params 任务参数,与任务类默认值合并;含两个隐藏开关 skip_offline(默认 true)、preempt(默认 false)
schedule {"mode":"once"} / {"mode":"cron","cron":"0 9 * * *"} / {"mode":"cron_stop","cron":...,"stop_cron":...},可选 window 运行窗口
retry {"max_attempts":1,"delay":60}
enabled 是否参与调度

调度模式:once 仅手动;cron 到点启动;cron_stop 到点启动 + 到点停止。cron 为标准 5 段 分 时 日 月 周,编辑器提供"每天/每小时/每隔 N 小时/每周"的可视化选择自动生成,也可手填。

运行窗口(schedule.window,如 21:00-09:00,支持跨午夜):窗口外定时触发与手动执行都不启动。

目标解析(TaskJob.resolve_serials):all = 设备池 ∩ 在线(开了抢占则取全部在线池内设备);group = 分组 ∩ 设备池;serial = 指定设备。后两者默认跳过离线设备。

进度上报

Worker 通过 set_progress() 上报统一字段,前端统一渲染:

self.set_progress(done=5, total=80, unit="视频", action_counts={"like": 3}, elapsed=120)

前端展示为进度条 + 5/80 视频 + 计数徽章 + 运行时长。


Web 管理后台

单页应用(templates/admin/monitor.html),7 个顶级 Tab;「任务 / 工具 / 系统」内部还有页内子分栏(会记住上次选中位置)。

Tab 内容 可见性
监控 统计卡片 + 设备表(在线/型号/任务状态/进度/前台 App/最近错误,支持排序、搜索、分页、多选批量操作)+ 异常汇总 + 任务运行概况(每张任务卡:执行任务 / 停用任务 + 覆盖设备彩色 chip);导航栏「📺 大屏」打开 /wall 所有登录用户(设备操作按钮需"设备控制")
任务 子分栏:任务计划(CRUD / 启停 / 执行 / 下次运行时间 / 离线跳过 / 步骤编辑器)、自定义动作(步骤打包复用) 可看;写操作需"任务管理"
日志 子分栏:文件日志(文件切换含滚动历史、关键字/级别/时间过滤、命中高亮、下载筛选结果、自动刷新)、步骤明细(按设备/任务/结果/时间过滤、按运行归组的概览、导出 CSV) 需"日志查看"
用户 用户 CRUD、改密、分配权限 仅管理员
工具 8 个子分栏:剪贴板注入 / adb 远程终端 / Tailscale 管理 / 应用管理 / 应用版本管理 / 设备已装应用 / 设备池管理(含自动发现)/ 设备分组 仅管理员
AI 控制台 会话列表 + 对话区(Markdown 渲染、推理链折叠、token 统计)+ 实时画面(MJPEG)+ 目标设备选择;右上角:经验库 / 动作库 / 模型配置 仅管理员
系统 子分栏:数据备份(导出 zip)/ 导入恢复(上传→校验预览→应用,重启生效) 仅管理员

权限模型

默认账号 admin/admin123(管理员,权限不受限)。权限位:

权限位 键 覆盖
任务管理 tasks 任务/分组/自定义动作的写操作
设备控制 devices 停止设备、亮息屏、定位、清异常、前台扫描、元素抓取、步骤测试
应用管理 apks APK 上传/安装/删除
日志查看 logs 日志 Tab
  • 查看类接口(设备/任务/分组列表、状态、截图)所有登录用户可用
  • 后端 @perm_required / @admin_required 拦截并返回 403;前端用 data-perm 属性与 _can(perm) 隐藏入口(仅隐藏,安全依赖后端)
  • 不能删除/降级最后一个管理员

任务与步骤

generic_steps 的执行内容全在 params.steps(JSON 数组),由步骤编辑器产出。没有默认步骤——空步骤任务执行时会明确报错。

18 种步骤(完整参数见 doc/TASK_DEV.md):

类别 步骤
屏幕 screen_on 亮屏 · screen_off 息屏 · keep_screen 保持亮屏
应用与输入 open_app 打开 App · stop_app 结束 App · input_text 输入文字 · clipboard 剪贴板注入 · key_event 按键
交互 click 点击元素 · click_xy 点击坐标 · long_click 长按 · swipe 滑动 · swipe_until 滑动直到元素出现 · wait_el 等待元素 · wait 等待时长
流程 loop 循环块 · group 动作组 · if_el 条件判断(元素或 OCR 识别)

要点:

  • 每一步都可有 probability(0-100,默认 100)决定本次是否执行
  • 容器类步骤(loop/group/if_el)可嵌套,深度上限 5 层
  • 选择器支持 xpath / description / text / resourceId / descriptionContains / className(ocr 仅条件判断)
  • 优先用文字/id 定位,坐标 (click_xy) 是最脆的方式
  • 未知步骤类型、缺必填参数只告警跳过,不会中断任务链(排查时留意"看起来成功但没做事")

AI 与 MCP

AI 控制台

在「AI 控制台」选一台设备,用自然语言下指令,AI 通过 MCP 工具看屏幕、点按、输入,边做边把过程流式显示出来。配置(模型 / API Key / 默认设备 / 最大步数)存在数据库 app_meta,不落 .env。

自带两个"自进化记忆":

  • 经验库:任务成功后把操作套路蒸馏成配方,下次相似任务自动召回注入;每日 03:47 由 AI 巡检建议清理(删除永远需人工确认)
  • 动作库:把成功步骤沉淀为带元素定位的命名动作(禁坐标),可复用、可编辑

详细机制见 doc/AI_CONSOLE.md。

MCP(外部 AI 接入)

MCP Server 监听 :8033,暴露 20 个 de_* 工具(截屏、点击、滑动、输入、OCR、元素树、应用管理等)。写操作需 MCP_ALLOW_WRITE=1;设备正在跑任务时拒绝(device_busy);每次调用写审计日志。

# 本机手动启动(生产容器由 scripts/start.sh 自动拉起)
MCP_ALLOW_WRITE=1 MCP_PLATFORM_USER=admin MCP_PLATFORM_PASS=<密码> \
  python -m mcp_server.mcp_server

工具清单与接入示例见 doc/MCP.md。


配置说明

密钥类配置统一放项目根 .env(不入 git;模板见 .env.example)。加载规则:逐行解析后用 os.environ.setdefault 注入,真实环境变量优先。

配置 默认值 说明
WEB_SECRET_KEY 未配置则随机 会话密钥;不配则每次重启登录态失效(生产必须固定)
WEB_HOST / WEB_PORT 0.0.0.0 / 18050 Web 监听(常量,改 config.py)
ADB_PATH 自动识别 bin/adb/ adb 二进制(常量,按平台自动选)
DATA_DIR / APK_DIR data/ / data/apks/ 持久化目录(常量)
USB_ADB_HOST / USB_ADB_PORT 100.100.10.1 / 5037 USB 设备所在的部署机(远程 adb server)
DISCOVERY_PORT / DISCOVERY_INTERVAL 5555 / 60 设备自动发现(网段也在工具页配置,存 app_meta)
TAILSCALE_API_KEY / TAILSCALE_TAILNET 空 Tailscale 管理功能
WEB_SECRET_KEY 之外的一切密钥 — 一律进 .env,不要写进 config.py

任务参数不放 config.py——放各自 tasks/<name>/task.py 顶部的 DEFAULT_PARAMS。

完整键表(含 MCP_* / AGENT_*)见 doc/DEVELOPMENT.md §配置速查。


日志

按模块分文件,单文件 10MB 滚动、保留 5 份:

文件 前缀 内容
logs/core.log core.* adb / device_worker / task_manager / 设备池
logs/task.log task.* 任务执行(worker 业务逻辑)
logs/web.log web.* Web 请求与管理操作
logs/action.log action.* 操作执行
logs/notify.log notify 通知发送(成功/失败/平台错误码)
from core.logger import get_logger
log = get_logger("task.generic")   # → logs/task.log

「日志」Tab 可在线查看:文件下拉包含 .1/.2… 滚动历史(可回溯更早的日志), 支持关键字(命中高亮)/最低级别/时间范围过滤,点「下载」导出当前筛选结果 (无筛选就是整个文件)。读取逻辑在 core/logger.py 的 list_log_files()/query_log()/ read_log_text(),file 参数走白名单,不接受任意路径。

「日志 → 步骤明细」是结构化的另一半:每一次步骤执行落一行 task_step_log (设备/任务/步骤路径/结果/耗时/选择器),因此能按设备、任务、结果、时间过滤, 也能按 run_id 归组看"这一次运行为什么失败"、导出 CSV。写入是异步的 (core/step_log.py,任务线程只入队,不阻塞执行),保留 14 天、单次运行最多 2000 条 —— 这两个上限决定这张表(以及备份包)能长多大。


常见问题

现象 处理
端口被占用(WinError 10013/10048) 启动会自动回退候选端口(18050 → 127.0.0.1:18050 → 127.0.0.1:18051..18055);仍失败则改 WEB_PORT(注意 Windows 动态端口排除段)
设备显示离线 「工具 → 设备池管理」一键重连;确认设备在线且同一网络(生产走 Tailscale 同 tailnet)
新加的设备不被调度 必须加入设备池(仅 adb 连上不算);确认该设备为"启用"状态
u2.connect 卡死 基类有 30s 超时保护;频繁超时说明 atx-agent 异常,重启设备或重新推送
任务运行中被看门狗标记卡死 看门狗 120s 无心跳即判定;长循环内需周期性 self.heartbeat()
任务"成功"但什么都没做 步骤类型未知/缺必填参数会被告警跳过;查 logs/task.log 的 WARNING
中文输入失败 u2 默认 IME 不支持中文,需 set_fastinput_ime(True)(input_text 步骤已处理)
改了 core/ 不生效 服务以 debug=False 运行、不热重载;必须重启。前端改动还需强刷浏览器(Ctrl+Shift+R)
元素抓取按钮不可用 依赖本机 uiautodev(:20242),web_server.py 启动时自动拉起;未安装时 pip install uiautodev

文档索引

完整文档入口 → doc/README.md(文档地图、阅读路径、维护约定)

文档 内容
doc/ARCHITECTURE.md 架构详解(分层、装配顺序、线程模型、设备生命周期、调度链路、设计决策)
doc/DATA_MODEL.md 数据模型(表结构、迁移、app_meta、数据目录、备份覆盖清单)
doc/API.md HTTP 接口全量说明
doc/TASK_DEV.md 任务与步骤开发(18 种步骤、选择器、自定义动作、新增任务类型)
doc/MCP.md · doc/MCP_DESIGN.md MCP 使用手册 / 设计文档
doc/AI_CONSOLE.md · doc/AI_TASK_GEN.md AI 控制台机制 / AI 建任务设计(规划)
doc/DEPLOY.md 部署与运维(含生产容器、备份导入、故障排查)
doc/DEVELOPMENT.md 开发手册(git 流程、技术红线、本地开发、文档同步约定)
doc/STF_REMOVAL.md 摘除 OpenSTF 的历史记录

⚠️ 改动必须同步文档:功能/配置/接口/表结构的任何增删改,都要在同一个 commit 里更新对应文档(红线,详见 doc/README.md §3)。 ⚠️ git 操作需负责人确认(含 push 到 dev),详见 doc/DEVELOPMENT.md。


技术栈

组件 用途
Flask + Flask-Login + Flask-SQLAlchemy Web 后台 / 认证 / SQLite ORM
APScheduler 定时任务调度(任务 + 经验巡检两个独立调度器)
uiautomator2 / uiautodev Android UI 自动化 / 元素抓取
rapidocr_onnxruntime (+ opencv-headless) 屏幕 OCR
pyaxmlparser APK 元信息解析
fastmcp MCP Server(Streamable HTTP)+ AI 控制台 Agent
adb(项目自带 bin/adb/) 设备连接与底层操作
S
Description
No description provided
Readme
4.8 MiB
Languages
Python 64.5%
JavaScript 26.1%
HTML 8.7%
Shell 0.4%
CSS 0.3%