# auto_control — Android 多设备自动化任务平台 基于 **uiautomator2 + Flask + 自建设备池** 的 Android 多设备自动化平台(已摘除 OpenSTF 依赖,见 [doc/STF_REMOVAL.md](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 设备 ``` --- ## 目录 - [核心能力](#核心能力) - [快速上手](#快速上手) - [项目结构](#项目结构) - [核心概念](#核心概念) - [Web 管理后台](#web-管理后台) - [任务与步骤](#任务与步骤) - [AI 与 MCP](#ai-与-mcp) - [配置说明](#配置说明) - [日志](#日志) - [常见问题](#常见问题) - [文档索引](#文档索引) --- ## 核心能力 | 能力 | 说明 | |------|------| | **多设备并发任务** | 每设备一个 Worker 线程;同一设备同时只跑一个任务(可配置"抢占"打断其他任务) | | **设备池** | SQLite 清单 + adb 在线状态;支持手工添加、网段自动发现、一键重连、型号采集、启用/停用 | | **电量监控** | 后台每分钟 `dumpsys battery`(只读)采一次电量,**大屏卡片**与**监控页设备列表**都显示(按档位着色 + ⚡ 充电中);低于阈值推 webhook 告警(**充电中不报、"插着却没充电"照报**,掉档/恢复各报一次,不刷屏)。电量只放内存不落库(见 [NOTIFY.md](doc/NOTIFY.md) §3.1) | | **任务调度** | 手动 / cron 定时 / 定时启停;运行窗口;失败重试(含端口耗尽类的长退避) | | **步骤编辑器** | 可视化拖拽编排 24 种步骤(含循环/条件/OCR/通知/录制回放),可打包成"自定义动作"复用 | | **拟人化操作** | 滑动默认走弧线轨迹、位置/幅度/时长每次抖动,且**每台设备有自己的手速与习惯**(同设备风格稳定、设备之间明显不同)——批量跑时不像同步机器人(见 [TASK_DEV.md](doc/TASK_DEV.md) §8.4) | | **录制回放** | 「录制手势」步骤 = **纯录制**:**用手指在真机上划**(读 getevent 真触屏)或在网页画面上拖,把轨迹点列原样存下来,回放时**照原路径与时长重放**——不套滑动那套方向/幅度/拟人参数 | | **动作配置 / 录制** | 「任务 → 动作配置」:① 配**步骤默认值**(新建步骤预填:滑动时长/幅度/抖动/拟人、点击超时、等待区间…)② **录制动作**——在设备画面上点/划/按键,自动翻译成步骤(点元素记选择器、划一下记方向/幅度/时长),可存成可复用动作,也可只取一个手势回填滑动步骤 | | **公共巡检** | 任务级的守护条件(**独立于步骤画布**):每 N 秒检查屏幕亮/熄、元素在不在、前台是不是某 App,命中就点亮/息屏/停本设备/推通知——通知标题正文自己写(见 [TASK_DEV.md](doc/TASK_DEV.md) §4.5) | | **去重账本** | 解决"**同一个号被做两次、有的号还没做**":任务里用「条件判断→去重」+「记为已做」,把"这个身份做过了"记进一张**所有设备共享**的账本——多台手机、反复重跑都不会重复做同一个号。「任务 → 去重记录」页能看"谁做过了、还差谁",也能删记录让它重跑(见 [TASK_DEV.md](doc/TASK_DEV.md) §4.6) | | **账号台账** | 「账号」页维护"**哪台设备上登着哪些号**"(设备号/手机号/账号名/抖音号/注册时间/卡在机内/可发视频/简介/备注):支持**从 Excel 粘贴批量导入**(表头识别、逐行结果、先预览后写入)。三个用处:① 任务的「条件判断」**直接从这里取号**,不用把几十个号手写进比对值;② 手机端 Agent 的**身份大字页**显示本机账号;③ 设备池里一眼看到每台登记了几个号(见 [DATA_MODEL.md](doc/DATA_MODEL.md) §2.10、[TASK_DEV.md](doc/TASK_DEV.md) §4.2) | | **视频发布计划** | 「账号 → 发布计划」页:批量上传视频(文件名 `手机号_日期_编号`)与标题 txt → 自动按 (手机号,日期,编号) 配对到台账账号 → **按日期的时间线**看每天谁要发、发到哪一步。**平台只负责把素材推到手机**(推到 `/sdcard/DCIM/rp/`、触发相册刷新、把标题写进剪贴板),**抖音里怎么发由你自己在任务里写步骤**(步骤顺序:`推送发布视频` → 你的发布步骤 → `标记发布结果`;「发布计划」页顶部有**「发布任务」**块可一键建好骨架 + 看下次运行/启停,`输入文字` 那一步可以选**取值来源=计划标题**自动填文案并回读校验);发完可抓作品的**分享链接**存下来(平台不长期囤视频,链接才是长期资产,也方便后续铺评论:可一键复制、按日期/账号导出 CSV)。状态区分"**可重试**"(推送阶段失败,还没到抖音)与"**结果未知、需人工确认**"(推送之后出的岔子,绝不自动重发)(见 [DATA_MODEL.md](doc/DATA_MODEL.md) §2.11、[TASK_DEV.md](doc/TASK_DEV.md) §4.7) | | **元素抓取** | 拉取设备 UI 元素树 → 点选回填选择器;支持"点一下"与"测选择器"真机验证 | | **实时看屏** | MJPEG 实时流 + 点击/滑动/按键/文字输入;全屏监控大屏(`/wall`) | | **应用管理** | APK 上传/解析/批量安装;设备已装应用与版本查询;剪贴板注入 | | **AI 控制台** | 用自然语言驱动 AI 操作指定设备(MCP 工具 + 截图),流式输出、Markdown 渲染、推理链折叠、token 统计;成功操作自动沉淀「经验库 / 动作库」并在相似任务中召回 | | **MCP 接入** | 20 个 `de_*` 工具,把手机控制开放给外部 AI;写操作有开关、设备忙时拒绝、全量审计 | | **备份导出/导入** | 一键导出 zip(库快照 + manifest + 可选 APK),导入前校验预览、自动预备份、重启生效 | | **通知 / Webhook** | 任务成功失败、设备上下线、安装完成、备份恢复等事件推送到企业微信 / 钉钉 / 飞书 / Bark / 自建服务(Slack 用通用 JSON);多条 webhook 各自订阅;聚合+限流防刷屏(见 [doc/NOTIFY.md](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](doc/DEPLOY.md)) ### 三步启动 ```bash # 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 # 网段扫描发现设备 → 待连接池 │ ├── device_battery.py # 设备电量采集(dumpsys 只读)+ 低电量告警 │ ├── ledger.py # 账号台账(CRUD / 表格粘贴解析 / 给任务取号 / 设备端账号块) │ ├── video_plan.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 # 任务步骤明细(异步写线程 + 保留期清理) │ ├── 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(24 种步骤)+ 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](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()` 上报统一字段,前端统一渲染: ```python 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 数组),由步骤编辑器产出。**没有默认步骤**——空步骤任务执行时会明确报错。 **24 种步骤**(完整参数见 [doc/TASK_DEV.md](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](doc/AI_CONSOLE.md)。 ### MCP(外部 AI 接入) MCP Server 监听 `:8033`,暴露 20 个 `de_*` 工具(截屏、点击、滑动、输入、OCR、元素树、应用管理等)。写操作需 `MCP_ALLOW_WRITE=1`;设备正在跑任务时拒绝(`device_busy`);每次调用写审计日志。 ```bash # 本机手动启动(生产容器由 scripts/start.sh 自动拉起) MCP_ALLOW_WRITE=1 MCP_PLATFORM_USER=admin MCP_PLATFORM_PASS=<密码> \ python -m mcp_server.mcp_server ``` 工具清单与接入示例见 [doc/MCP.md](doc/MCP.md)。 --- ## 配置说明 **密钥类配置统一放项目根 `.env`**(不入 git;模板见 [.env.example](.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//task.py` 顶部的 `DEFAULT_PARAMS`。 完整键表(含 `MCP_*` / `AGENT_*`)见 [doc/DEVELOPMENT.md](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` | 通知发送(成功/失败/平台错误码) | ```python 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/README.md)**(文档地图、阅读路径、维护约定) | 文档 | 内容 | |------|------| | [doc/ARCHITECTURE.md](doc/ARCHITECTURE.md) | 架构详解(分层、装配顺序、线程模型、设备生命周期、调度链路、设计决策) | | [doc/DATA_MODEL.md](doc/DATA_MODEL.md) | 数据模型(表结构、迁移、`app_meta`、数据目录、备份覆盖清单) | | [doc/API.md](doc/API.md) | HTTP 接口全量说明 | | [doc/TASK_DEV.md](doc/TASK_DEV.md) | 任务与步骤开发(24 种步骤、公共巡检、录制回放、去重、选择器、自定义动作) | | [doc/MCP.md](doc/MCP.md) · [doc/MCP_DESIGN.md](doc/MCP_DESIGN.md) | MCP 使用手册 / 设计文档 | | [doc/AI_CONSOLE.md](doc/AI_CONSOLE.md) · [doc/AI_TASK_GEN.md](doc/AI_TASK_GEN.md) | AI 控制台机制 / AI 建任务设计(规划) | | [doc/DEPLOY.md](doc/DEPLOY.md) | 部署与运维(含生产容器、备份导入、故障排查) | | [doc/DEVELOPMENT.md](doc/DEVELOPMENT.md) | 开发手册(git 流程、技术红线、本地开发、文档同步约定) | | [doc/STF_REMOVAL.md](doc/STF_REMOVAL.md) | 摘除 OpenSTF 的历史记录 | > ⚠️ **改动必须同步文档**:功能/配置/接口/表结构的任何增删改,都要在同一个 commit 里更新对应文档(红线,详见 [doc/README.md](doc/README.md) §3)。 > ⚠️ **git 操作需负责人确认**(含 push 到 dev),详见 [doc/DEVELOPMENT.md](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/`) | 设备连接与底层操作 |