# 换手机适配 · 截屏节点与适配清单 > 目的:换手机后开票流程报错时,按此清单逐节点排查。 > > 配套工具:`scripts/calibrate.py`(Web UI 的 `/calibrate` 页)+ `work/device_profiles.json`(按 ADB 序列号分设备保存)。 > > 相关文档:[`screen-adaptation.md`](./screen-adaptation.md) 是理论全链路清单(按处理顺序列所有识图点);**本文是实操版**,标注实际运行中哪些节点真正触发截图、哪些是换手机的报错高发点。 ## 新手机最短路径 1. 只连接并授权一台手机,打开 12306 的 H5 开票单列表。 2. 打开 Web UI 的「设备校准」,点「自动真实校准」,从当前屏待开票乘客中明确选择一人并确认。 3. 系统会完成一次真实开票;全过程截图、XML 与页面提示保存到 `work/calibration//<时间>/`,设备档案写入 `work/device_profiles.json`。 4. 成功后该手机可启动正常批量;若屏幕密度、分辨率或方向变更,档案会要求重新校准。 ## 当前自动化口径 主流程固定为**逐人开具**:只点击乘客行中的精确「开具」按钮,不会点击页面底部的「批量开具」。若页面只剩批量入口,脚本会停止并提示人工回到逐人列表。 ## 一、换手机报错的根因 换手机后开票流程总报错,根因有三类: | 类别 | 根因 | 影响 | 是否已解决 | |---|---|---|---| | **A. 硬编码像素坐标** | 防熄屏 tap `(630,1400)`、滑动 `start_y=2400` 等写死 1260×2800 | tap/swipe 落点错位,小屏 tap 到屏外,大屏滑太少 | 已由按设备 serial 保存的比例配置覆盖 | | **B. 滑动幅度按绝对像素** | 幅度 1800px 写死,小屏占比过大跳行,大屏占比过小翻页慢 | 跳过乘客 / 重复行 / 签名去重失效 | 部分(同上,按比例计算) | | **C. 视觉 fallback 找不到按钮** | 视觉 API 返回 found:false,新手机页面布局/分辨率不同时模型识别失败 | 卡在子流程,走兜底 recover | 否(依赖模型识别能力,校准只能验证不能根治) | **A、B 的解法已经写好**(`calibrate.py` + `device_profiles.json` + `apply_speed_defaults`),但**每台新手机必须先跑一次校准**。配置按 ADB serial 隔离,不会被另一台手机覆盖。 **C 是模型识别能力问题**,校准只能提前发现"这个按钮在这台手机上模型认不认得",不能保证运行时不丢。解法是保留 XML 优先 + 视觉兜底的双通道,并接受一定比例的 recover。 ## 二、实际运行中的截屏节点(按触发频率排序) 跟踪 `work/web_runner.log` 实测,一次完整开票流程(9 乘客/pass)触发的截屏如下。**每个节点都是单文件覆盖式写入,无历史留档**。 ### 节点 1 · 列表页识行 `vision_list_rows()` 【每轮必触发·高频】 - **触发**:每轮(每屏)开头,识别当前可见所有乘客行 - **代码**:`invoice_tool.py:1248` -> `vision_fallback.py` `list-rows` 子命令 - **截图落盘**:`work/last_screenshot.png`(覆盖) - **输入**:adb 截屏 - **输出**:`rows[{masked_name, id_first, id_last, button_bbox, tap_point}]` + `scroll_position` + `page_ok` - **坐标**:0-1000 归一化 -> denormalize 到实际像素(屏幕无关) - **换手机风险**:低(归一化坐标自动适配)。但**滑动幅度不对会导致识到的行不全/重复**,属于 A/B 类问题的连带症状。 - **实测频率**:13 次/批(9 乘客 + 滚动若干屏) ### 节点 2 · after_verify 超时纠偏 - 找「查看」`_vision_find_button("查看")` 【条件触发·报错高发】 - **触发**:`wait_for_page_transition` 等 after_verify 超时返回 `other` 时,进阶段 7 纠偏 - **代码**:`invoice_tool.py:1280` `_vision_find_button` - **截图落盘**:`work/_vision_find_tmp.png`(覆盖) - **输入**:adb 截屏 - **输出**:像素 (x,y) 或 None - **实测**:3 次,全部 `found:false`(页面实为 invoice_info,不是发票管理页) - **换手机风险**:中。这个节点本身是"误判纠偏",新手机上 XML 文本可能更不稳定导致更频繁误判。 ### 节点 3 · 纠偏后探测页面 `vision_detect_page()` 【条件触发】 - **触发**:节点 2 找不到「查看」后,调 `vision_detect_page()` 判断真实页面类型 - **代码**:`invoice_tool.py:1116` - **截图落盘**:`work/last_screenshot.png`(覆盖,覆盖节点 1 的图) - **输出**:`{page, summary}` - **实测**:3 次,全部正确识别为 `invoice_info`,summary="待开票页面显示车票信息及开具按钮" - **换手机风险**:低(只返回页面类型,无坐标)。 ### 节点 4 · 找「开具」`_vision_find_button("开具")` 【条件触发·关键】 - **触发**:节点 3 判定为 invoice_info 后,改找「开具」按钮 - **代码**:`invoice_tool.py:1280` - **截图落盘**:`work/_vision_find_tmp.png`(覆盖) - **输出**:像素 (x,y) - **实测**:3 次,全部 `found:true`,坐标 (1071,1047) / (1071,1044) 等 - **换手机风险**:中。模型识别能力依赖,新手机按钮样式/位置不同可能 found:false。 ### 节点 5 · 找「确认」`_vision_find_button("确认")` 【高频·几乎必走视觉】 - **触发**:发票确认页,XML 找不到「确认」按钮时 fallback - **代码**:`_xml_or_vision_click` -> `_vision_find_button` - **截图落盘**:`work/_vision_find_tmp.png`(覆盖) - **输出**:像素 (x,y) - **实测**:3 次,全部 `found:true`,坐标 (850,2153) - **换手机风险**:**高**。H5 页面常无 text,几乎必走视觉。新手机上 if found:false 会直接卡死子流程。 ### 节点 6 · 找「继续开票」`_vision_find_button("继续开票")` 【条件触发·当前已失效】 - **触发**:成功页找「继续开票」回列表 - **代码**:`_xml_or_vision_click` - **截图落盘**:`work/_vision_find_tmp.png`(覆盖) - **输出**:None - **实测**:3 次,**全部 `found:false`**(XML 也找不到),走兜底 `click=download_done_ok` + `click=continue_invoice` - **换手机风险**:高。当前机型就已经视觉找不到,新手机只会更糟。靠 XML 兜底 bounds 硬编码。 ### 节点 7 · 进列表页 - 找「扫码开票」`_vision_find_button("扫码开票")` 【条件触发】 - **触发**:precheck 不在列表页时,`auto_scan_qr_to_list` 自动扫码进入 - **代码**:`invoice_tool.py:1280` - **截图落盘**:`work/_vision_find_tmp.png`(覆盖) - **输出**:像素 (x,y) - **实测**:2 次,`found:true`,坐标 (1102,204) / (1097,204) - **换手机风险**:中。recover 路径依赖,新手机上 if found:false 会 recover 失败。 ### 节点 8 · 相册找 QR 缩略图 `_vision_find_qr_thumbnail()` 【条件触发】 - **触发**:auto_scan 扫码进相册选码时 - **代码**:`invoice_tool.py:1610` - **截图落盘**:见代码(未细查,应同 `last_screenshot.png`) - **输出**:QR 缩略图坐标 - **换手机风险**:低(归一化坐标)。但相册布局不同可能影响识别。 ## 三、校准工具(`calibrate.py`)对应这 8 个节点中的哪些 `calibrate.py` 的 `CALIBRATION_STEPS` 定义了 8 个校准状态,手动导航到对应页面后截图 + 视觉分析,验证"这个按钮在这台手机上模型认不认得"。对应关系: | 校准 step_id | 校准状态 | 对应运行节点 | 校准验证的按钮 | |---|---|---|---| | `list_page` | H5 列表页 | 节点 1(vision_list_rows) | 「开具」 | | `verify_page` | 身份核验页 | (运行时走 XML,不视觉) | 「核验」 | | `invoice_info` | 发票信息页 | 节点 4(找「开具」)+ 节点 2/3 纠偏 | 「提交」 | | `invoice_confirm` | 发票确认页 | 节点 5(找「确认」) | 「确认」 | | `success_page` | 开票成功页 | 节点 6(找「继续开票」) | 「发票预览/下载」「下载」「继续开票」 | | `no_ticket` | 无票页 | (运行时走 XML 判断) | 无 | | `loading` | 开具中 | (运行时走 XML 判断) | 无 | | `album_qr` | 相册选码 | 节点 8(找 QR 缩略图) | 无(找 QR 缩略图) | **节点 7(找「扫码开票」)没有对应校准 step**--它是 recover 路径,校准时手机不在列表页才能触发,与 `list_page` 校准状态互斥。这是校准工具的一个缺口。 ## 四、换手机适配操作步骤 ### 步骤 1 · 生成 `device_profiles.json` 中当前设备的档案(解决 A/B 类硬编码问题) 1. Web UI 打开 `http://127.0.0.1:8765/calibrate` 2. 页面自动读 `adb shell wm size` 显示新手机分辨率 3. 点「💾 保存设备配置」--即使不逐状态截图,也会按分辨率比例计算滑动参数 + 防熄屏 tap,写入 `work/device_profiles.json` 4. 之后 `invoice_tool.py` 的 `apply_speed_defaults` 会自动读这个 profile 覆盖硬编码默认值 **这一步就能解决大部分"滑动幅度不对导致跳行/重复"问题。** ### 步骤 2 · 逐状态截图校准(验证 C 类视觉识别问题) 1. 在新手机上手动导航到校准 step 对应的页面 2. 点该 step 的「截图」按钮 -> `work/calibration/.png` 3. 点「分析」-> 跑视觉 API,看 `page_ok` 和每个按钮的 `found` 4. 全绿点「保存设备配置」-> 把校准结果一并存进当前 serial 的设备档案 **重点验证这几个高风险按钮**(运行时几乎必走视觉、且当前机型已暴露问题): - `invoice_confirm` 页的「确认」--运行时高频视觉 fallback,found:false 直接卡死 - `success_page` 页的「继续开票」--当前机型已 found:false,新手机大概率也不行,需确认 XML 兜底 bounds 是否对得上 - `invoice_info` 页的「开具」--after_verify 超时纠偏链路的关键 ### 步骤 3 · 跑一次 dry-run 验证 ```bash python3 scripts/invoice_tool.py android-h5-page-run --dry-run --max-actions 1 ``` 确认滑动、防熄屏 tap、列表识行正常后再正式跑。 ## 五、当前已暴露的问题(非换手机专项,但换手机会放大) 跟踪当前日志(1260×2800 原机型)发现的既有问题,换手机后会因视觉识别能力下降而放大: 1. **after_verify 必超时**:每个乘客都 `wait_page_more label=after_verify reason=network_or_loading extra=12.0s`,XML 12s 内拿不到稳定文本,全靠视觉纠偏。新手机上 XML 可能更不稳。 2. **「继续开票」视觉恒 found:false**:3/3 失败,靠 XML 硬编码 bounds 兜底。新手机 bounds 不同会直接失败。 3. **陈**杰「核验弹窗存在,但未解析出脱敏姓名」**:非截屏问题,是 XML 解析逻辑问题,连续两次报错走 recover。 ## 六、截图留档建议(当前缺陷) 当前两个截图路径都是单文件覆盖,无法回溯: - `work/last_screenshot.png` -- vision_list_rows / vision_detect_page 覆盖 - `work/_vision_find_tmp.png` -- _vision_find_button 覆盖 **换手机排错时建议**:在跑流程前手动备份,或改代码让每次视觉调用按 `work/screenshots/_.png` 留档。否则报错时只能看 `last_screenshot.png`(已被后续节点覆盖)。 校准工具的 `work/calibration/.png` 是按状态命名不覆盖,可用于静态比对,但不能反映运行时实际页面。