用户场景(他原话):一台手机登录 5 个抖音号、一共 5 台手机,每个任务只让其中一个
目标号评论;每天跑一次但不知道什么时候跑完,于是"一直重复跑" → 结果
"一个手机还没评论到,一个手机都评论两次了"。
**根因不是"单设备重复",是跨设备没有共享的判断 + 进度不可见。** 所以做两件事:
① 幂等;② 把"谁做过了、还差谁"摆到台面上(不然只能靠重跑确认,而重跑又在制造重复)。
- `core/models.py`:新表 `done_mark`(迁移账本补 v7)。**判据只有 `scope_key` 的
唯一索引**——多台设备会同时判断"没做过","先查后插"有竞态(两台都插),
唯一索引 + `INSERT ... ON DUPLICATE KEY`/`INSERT OR IGNORE` 的**受影响行数**才原子。
- `core/dedup.py`(新):`build_key`(`任务|身份|时间桶`)/ `check` / `mark` /
`list_marks`(带"今天做了几台/几个号"统计)/ `delete_mark` / `clear_job` / `purge_old`。
自建 app context(照 device_pool 的 `_ctx()`),任务线程/Web/清理都不用关心。
- 任务侧两个部件(**检查在前、记账在后**):
· `if_el` 新增条件类型 `selector_type="dedup"`:命中=这个身份做过了 → 走 then 分支。
身份元素在 `ident_type`/`ident_value`(留空 = 用设备 serial,一号一机场景)。
· 新步骤 `mark_done`「记为已做」(22 种步骤):放动作**成功之后**。
拆两步的用意:动作失败就不记账,下次重跑还会重试该设备 —— 失败不丢。
- 有效期(`dedup_reset` = day/all/hours)放**任务级**:检查与记账两处各填一份的话,
填不一致就算出两个 key、去重会**静默失效**,所以强制只配一处(编辑器顶部下拉)。
- 三条防误伤规则(都有测试兜着):
· 身份读不到 / 身份值过长 → **不去重、当没做过照常执行**。绝不能把"读不到"
当成空身份——那会让所有设备共用一个 key、第一台记账后其余全被误判成"做过"。
· `kind='all'`(只做一次)的记录**永不清理**(清了等于语义失效);清理只删 day/hours。
· 去重的两个易错点在保存时直接告警:身份元素两边不一致、有检查没记账/有记账没检查。
- 「任务 → 去重记录」新子分栏(`static/admin/dedup.js`):统计行 + 明细表 +
删单条(那个号重跑)/ 清空任务(整批重跑)。接口 3 个(GET/delete/clear,PERM_TASKS)。
- 每日 04:23 清理(挂现有 APScheduler),`TABLE_LABELS` 补中文名(备份覆盖自动派生)。
- AI 建任务草稿校验同步:`dedup` 走自己的规则(要 ident_value、xpath 前缀校验),
没填身份元素只警告不拦(用设备当身份是合法用法);普通条件空选择器仍然拦。
- 文档:TASK_DEV §4.6(去重专章 + App 内检测的兜底配方与它的三个局限)、
DATA_MODEL §2.9、API 三个接口、ARCHITECTURE(分层/装配/子分栏/JS 分工/清理)、
DEPLOY §5.2(15 张表)、步骤数 21→22 全库同步。
自测:单元 + 集成 33 项(**含 8 线程抢同一个身份、恰好一个成功**的原子性断言,
以及"all 记录不被清理""身份读不到不去重""清了能重跑")、
**真机端到端**(cs1 上"检查→动作→记账"跑两遍:第二遍被拦、换 serial 的"另一台设备"
同样被拦、删记录后能重跑)、草稿校验 5 项、GET 冒烟 56 路由 0 个 500。
(注:本分支基于 feat/if-el-multi-value,因为它俩都要改 task.py 的 STEP_TYPES 与
editor.js 的 STEP_LIB 同一区域,分开从 dev 拉必然冲突——这份是超集,合一次两份都进。)
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 在线状态;支持手工添加、网段自动发现、一键重连、型号采集、启用/停用 |
| 电量监控 | 后台每分钟 dumpsys battery(只读)采一次电量,大屏卡片与监控页设备列表都显示(按档位着色 + ⚡ 充电中);低于阈值推 webhook 告警(充电中不报、"插着却没充电"照报,掉档/恢复各报一次,不刷屏)。电量只放内存不落库(见 NOTIFY.md §3.1) |
| 任务调度 | 手动 / cron 定时 / 定时启停;运行窗口;失败重试(含端口耗尽类的长退避) |
| 步骤编辑器 | 可视化拖拽编排 22 种步骤(含循环/条件/OCR/通知/录制回放),可打包成"自定义动作"复用 |
| 拟人化操作 | 滑动默认走弧线轨迹、位置/幅度/时长每次抖动,且每台设备有自己的手速与习惯(同设备风格稳定、设备之间明显不同)——批量跑时不像同步机器人(见 TASK_DEV.md §8.4) |
| 录制回放 | 「录制手势」步骤 = 纯录制:用手指在真机上划(读 getevent 真触屏)或在网页画面上拖,把轨迹点列原样存下来,回放时照原路径与时长重放——不套滑动那套方向/幅度/拟人参数 |
| 动作配置 / 录制 | 「任务 → 动作配置」:① 配步骤默认值(新建步骤预填:滑动时长/幅度/抖动/拟人、点击超时、等待区间…)② 录制动作——在设备画面上点/划/按键,自动翻译成步骤(点元素记选择器、划一下记方向/幅度/时长),可存成可复用动作,也可只取一个手势回填滑动步骤 |
| 公共巡检 | 任务级的守护条件(独立于步骤画布):每 N 秒检查屏幕亮/熄、元素在不在、前台是不是某 App,命中就点亮/息屏/停本设备/推通知——通知标题正文自己写(见 TASK_DEV.md §4.5) |
| 去重账本 | 解决"同一个号被做两次、有的号还没做":任务里用「条件判断→去重」+「记为已做」,把"这个身份做过了"记进一张所有设备共享的账本——多台手机、反复重跑都不会重复做同一个号。「任务 → 去重记录」页能看"谁做过了、还差谁",也能删记录让它重跑(见 TASK_DEV.md §4.6) |
| 元素抓取 | 拉取设备 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并接入同一网络(本项目生产环境走 Tailscale100.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/
首次使用
- 登录 →
admin/admin123,改密 - 加设备 → 「工具 → 设备池管理」添加
IP:5555(自动连接 + 采集型号);也可用「设备自动发现」扫描网段后确认入池 - 看设备 → 「监控」页设备表(在线/型号/任务/进度/前台 App)
- 建任务 → 「任务 → 任务计划 → 新建任务」(只有
generic_steps一种类型)→ 拖步骤 → 选目标设备 → 保存 - 跑任务 → 任务行「执行」,或在「任务」/「监控」页操作
- 看日志 → 「日志」页按模块实时查看
项目结构
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 # 网段扫描发现设备 → 待连接池
│ ├── device_battery.py # 设备电量采集(dumpsys 只读)+ 低电量告警
│ ├── 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 # 任务步骤明细(异步写线程 + 保留期清理)
│ ├── patrol.py # 任务级公共巡检(检查项/动作注册表,穿插执行)
│ ├── step_defaults.py # 步骤默认值(app_meta 单键,出厂值 + 用户覆盖)
│ ├── 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(22 种步骤)+ 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 → recorder.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 数组),由步骤编辑器产出。没有默认步骤——空步骤任务执行时会明确报错。
22 种步骤(完整参数见 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 识别 / 屏幕状态 / 前台App / 去重)· mark_done 记为已做(配套去重) |
要点:
- 每一步都可有
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 | 任务与步骤开发(22 种步骤、公共巡检、录制回放、去重、选择器、自定义动作) |
| 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/) |
设备连接与底层操作 |