- web_ui.py: major refactor with improved runner, mode selection, progress tracking - scripts: add device_runtime, device_calibrate, calibration_trace, record_android_flow - docs: add phone-control-manual, update device-adaptation and windows-migration - invoice_tool: enhance H5 automation flow and visual fallback - calibrate.py: expand calibration capabilities - add skills/ and tests/ directories - remove obsolete .opencode/skills/invoice-wrapup Co-Authored-By: Claude <noreply@anthropic.com>
11 KiB
换手机适配 · 截屏节点与适配清单
目的:换手机后开票流程报错时,按此清单逐节点排查。
配套工具:
scripts/calibrate.py(Web UI 的/calibrate页)+work/device_profiles.json(按 ADB 序列号分设备保存)。相关文档:
screen-adaptation.md是理论全链路清单(按处理顺序列所有识图点);本文是实操版,标注实际运行中哪些节点真正触发截图、哪些是换手机的报错高发点。
新手机最短路径
- 只连接并授权一台手机,打开 12306 的 H5 开票单列表。
- 打开 Web UI 的「设备校准」,点「自动真实校准」,从当前屏待开票乘客中明确选择一人并确认。
- 系统会完成一次真实开票;全过程截图、XML 与页面提示保存到
work/calibration/<ADB序列号>/<时间>/,设备档案写入work/device_profiles.json。 - 成功后该手机可启动正常批量;若屏幕密度、分辨率或方向变更,档案会要求重新校准。
当前自动化口径
主流程固定为逐人开具:只点击乘客行中的精确「开具」按钮,不会点击页面底部的「批量开具」。若页面只剩批量入口,脚本会停止并提示人工回到逐人列表。
一、换手机报错的根因
换手机后开票流程总报错,根因有三类:
| 类别 | 根因 | 影响 | 是否已解决 |
|---|---|---|---|
| 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.pylist-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 类硬编码问题)
- Web UI 打开
http://127.0.0.1:8765/calibrate - 页面自动读
adb shell wm size显示新手机分辨率 - 点「💾 保存设备配置」--即使不逐状态截图,也会按分辨率比例计算滑动参数 + 防熄屏 tap,写入
work/device_profiles.json - 之后
invoice_tool.py的apply_speed_defaults会自动读这个 profile 覆盖硬编码默认值
这一步就能解决大部分"滑动幅度不对导致跳行/重复"问题。
步骤 2 · 逐状态截图校准(验证 C 类视觉识别问题)
- 在新手机上手动导航到校准 step 对应的页面
- 点该 step 的「截图」按钮 ->
work/calibration/<step_id>.png - 点「分析」-> 跑视觉 API,看
page_ok和每个按钮的found - 全绿点「保存设备配置」-> 把校准结果一并存进当前 serial 的设备档案
重点验证这几个高风险按钮(运行时几乎必走视觉、且当前机型已暴露问题):
invoice_confirm页的「确认」--运行时高频视觉 fallback,found:false 直接卡死success_page页的「继续开票」--当前机型已 found:false,新手机大概率也不行,需确认 XML 兜底 bounds 是否对得上invoice_info页的「开具」--after_verify 超时纠偏链路的关键
步骤 3 · 跑一次 dry-run 验证
python3 scripts/invoice_tool.py android-h5-page-run --dry-run --max-actions 1
确认滑动、防熄屏 tap、列表识行正常后再正式跑。
五、当前已暴露的问题(非换手机专项,但换手机会放大)
跟踪当前日志(1260×2800 原机型)发现的既有问题,换手机后会因视觉识别能力下降而放大:
- after_verify 必超时:每个乘客都
wait_page_more label=after_verify reason=network_or_loading extra=12.0s,XML 12s 内拿不到稳定文本,全靠视觉纠偏。新手机上 XML 可能更不稳。 - 「继续开票」视觉恒 found:false:3/3 失败,靠 XML 硬编码 bounds 兜底。新手机 bounds 不同会直接失败。
- 陈杰「核验弹窗存在,但未解析出脱敏姓名」**:非截屏问题,是 XML 解析逻辑问题,连续两次报错走 recover。
六、截图留档建议(当前缺陷)
当前两个截图路径都是单文件覆盖,无法回溯:
work/last_screenshot.png-- vision_list_rows / vision_detect_page 覆盖work/_vision_find_tmp.png-- _vision_find_button 覆盖
换手机排错时建议:在跑流程前手动备份,或改代码让每次视觉调用按 work/screenshots/<timestamp>_<node>.png 留档。否则报错时只能看 last_screenshot.png(已被后续节点覆盖)。
校准工具的 work/calibration/<step_id>.png 是按状态命名不覆盖,可用于静态比对,但不能反映运行时实际页面。