Skip to content

feat(cli): 完善机器人配网引导与 Wi-Fi 状态闭环 - #67

Merged
Mr-KID-github merged 9 commits into
mainfrom
codex/setup-interaction-recovery
Aug 18, 2026
Merged

feat(cli): 完善机器人配网引导与 Wi-Fi 状态闭环#67
Mr-KID-github merged 9 commits into
mainfrom
codex/setup-interaction-recovery

Conversation

@Mr-KID-github

@Mr-KID-github Mr-KID-github commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

背景

机器人首次配置原先在写入 Wi-Fi 凭据后就结束,用户需要自行观察机器人屏幕,SDK 无法判断密码错误、找不到网络或连接超时。扫描、设备选择、蓝牙异常和 Application ID 初始化也缺少面向普通用户的完整引导。

改动

首次配网引导

  • watcherobot robot setup 扫描前提示打开电脑蓝牙并进入机器人 Settings > Wi-Fi
  • 扫描过程持续显示进度;多设备时按 Device ID 使用上下键选择
  • 针对蓝牙关闭/无适配器、权限拒绝、未发现机器人、连接超时、协议异常、取消等情况给出恢复步骤
  • 旧固件没有 Device ID 时明确提示固件可能需要升级

Wi-Fi 双向状态闭环

  • SDK 收到 cfg.wifi.set ACK 后保持 BLE 会话,继续等待机器人真实联网终态
  • 支持 connectingconnectedauth_failednetwork_not_foundtimeout
  • 使用 command_id 和 SSID 关联本次配置,SDK 侧另设 25 秒保护期限
  • 密码错误、找不到网络和超时分别展示不同的恢复建议
  • 删除依赖用户观察机器人屏幕并手动按 Enter 确认的旧流程

交互与项目初始化

  • 进度为蓝色、成功为绿色、确认提示为黄色、错误为红色、Device ID 为青色
  • 颜色仅在交互终端启用,兼容 Windows,并支持 NO_COLOR / FORCE_COLOR
  • 最新 watcherobot app init <目录> 自动生成稳定的 local.<项目名> Application ID
  • 文档说明默认 ID 不拼用户名、时间戳或随机值,以保持升级身份稳定

配套固件

文档

已同步更新中英文 CLI 参考、蓝牙配网说明、源码安装与正式安装相关引导。

验证

  • Python 全量 pytest 通过
  • mypy 通过
  • pip check 通过
  • COM20 实机端到端验证:对 orulink 故意输入错误密码,ESP32 将 reason=15 回传为 auth_failed,SDK 正确显示密码错误及恢复步骤

robot setup 在开始扫描前明确提示打开电脑蓝牙,并针对蓝牙关闭或缺少适配器、系统权限拒绝、未发现机器人、连接超时、固件拒绝或响应异常、配对阶段失败和用户取消分别输出可执行的恢复步骤,不再向普通用户返回单行 JSON 异常。

旧固件缺少 Device ID 时明确说明可能需要升级固件,同时保留 Bluetooth ID 兼容能力。补充各异常分支与敏感信息不泄露测试,并在中英文 CLI 参考、蓝牙配网文档和排障表中增加完整交互状态矩阵。

文档同时明确 app init 已按项目目录自动生成稳定的 local.<项目名> ID;若仍出现 Application ID 逐项提问,应检查是否误用了旧版 CLI。用户名、时间戳和随机值不用于默认 ID,以保持安装升级身份稳定。
使用 Bleak 原生 POWERED_OFF、NO_BLUETOOTH 和 NO_BLE_CENTRAL_ROLE 原因验证扫描后端统一映射为 BluetoothUnavailableError,并确保平台底层错误细节不会直接暴露给用户。
@Mr-KID-github

Copy link
Copy Markdown
Contributor Author

补充提交覆盖 Bleak 原生 POWERED_OFF、NO_BLUETOOTH 和 NO_BLE_CENTRAL_ROLE 三类状态,确认电脑蓝牙关闭、无适配器或不支持 BLE Central 时都会进入明确的 Bluetooth unavailable 恢复引导。

@github-actions

Copy link
Copy Markdown

🤖 Luxiao PR 审查报告

🤖 PR 审查报告

PR: fix(cli): 补齐配网异常恢复与自动 ID 说明
变更: 9 个文件,+312 / -25


维度一:代码质量

🏗️ 架构视角 — ✅

整体改动遵循现有结构:

  • 底层 provisioning 模块继续负责产生具体异常类型。
  • CLI 层将异常转换为面向用户的恢复指引,职责位置合理。
  • _print_robot_setup_failure()_print_robot_setup_cli_failure() 将展示逻辑从主流程中抽离,没有侵入底层蓝牙实现。
  • 文档、CLI 行为和测试同步修改,改动边界基本清晰。

没有新增不合理依赖、循环依赖或过度抽象。

需要注意的是,当前 CLI 恢复策略依赖较多具体异常类型。后续如果异常类型继续增加,建议将“异常分类—用户提示—退出码”集中为结构化映射,避免 isinstance 分支持续膨胀。本次规模下暂不构成合并障碍。

🎨 产品视角 — ⚠️

方向正确:将单行 JSON 转换为普通用户可以执行的恢复步骤,显著改善首次配网体验。扫描前提醒电脑蓝牙、区分权限问题、提示旧固件升级,以及解释稳定 Application ID,均符合实际用户场景。

但实现与 PR 描述中的“分别提供恢复步骤”尚未完全一致:

  1. ProvisioningProtocolErrorProvisioningRejectedErrorProvisioningResponseTimeoutError 被合并为同一提示:

    The robot did not accept the Wi-Fi setup.
    Check the Wi-Fi name and password.

    三者含义并不相同:

    • ProvisioningRejectedError:检查 SSID/密码通常合理。
    • ProvisioningResponseTimeoutError:更可能是距离、连接中断或固件未响应。
    • ProvisioningProtocolError:更可能是固件协议不兼容或响应格式异常。

    把协议异常和响应超时解释成“机器人未接受 Wi-Fi 配置”,可能误导用户反复修改正确的密码。

  2. 配对阶段的 CliError 仍然只是输出通用标题,再原样打印异常文本:

    Robot setup could not be completed.
    <原始 CliError 文本>
    

    这依赖 pair_robot() 的错误文本始终足够友好,没有真正形成独立、稳定的“配对失败恢复状态”。

  3. 文档声称是“完整交互状态矩阵”,但矩阵没有明确列出配对失败场景,与 PR 描述不完全一致。

  4. 旧版 CLI 排查只提供了 Windows 命令:

    where.exe watcherobot
    

    CLI 和蓝牙配网显然也可能运行在 macOS/Linux。文档至少应补充:

    command -v watcherobot

📏 规范视角 — ⚠️

优点:

  • 新增函数命名清晰,类型标注完整。
  • 异常提示使用 stderr,并维持非零退出码。
  • 中英文文档基本同步。
  • 测试覆盖了蓝牙不可用、权限拒绝、未发现设备、连接超时和配对错误。
  • 测试明确检查密码没有出现在输出中,这是必要的安全回归保护。

需要改进:

  1. ValueError 路径仍可能回到旧的 JSON 错误输出。

    当前捕获顺序为:

    except BluetoothProvisioningError as exc:
        return _print_robot_setup_failure(exc)
    except CliError as exc:
        return _print_robot_setup_cli_failure(exc)
    except ValueError as exc:
        raise CliError(str(exc)) from exc

    except ValueError 中重新抛出的 CliError 不会被前面的同级 except CliError 再次捕获,而会继续进入外层全局错误处理。如果全局处理仍输出 JSON,那么 robot setup 的部分错误依然会显示单行 JSON,直接违背本 PR 的核心承诺。

    应直接转换为引导式输出,例如:

    except ValueError as exc:
        return _print_robot_setup_cli_failure(CliError(str(exc)))

    或将可预期输入错误统一在 _run_robot_setup() 边界转换。

  2. 文档测试主要是关键词断言:

    assert "Bluetooth" in content
    assert "timeout" in content or "超时" in content

    这只能证明文件出现了相关单词,不能证明状态矩阵完整或中英文内容一致。尤其当前测试没有发现“配对失败行缺失”的问题。

  3. 缺少以下分支的行为测试:

    • ProvisioningProtocolError
    • ProvisioningRejectedError
    • ProvisioningResponseTimeoutError
    • 未知 BluetoothProvisioningError
    • ValueError 不再输出 JSON

⚠️ 损伤视角 — ⚠️

主要风险有三项:

  1. 错误输出兼容性变化

    原测试明确使用:

    json.loads(capsys.readouterr().err)

    说明至少测试层曾将 JSON 视为既有输出格式。本 PR 将其改为普通文本,属于可观察的 CLI 行为变化。

    对引导式命令而言,这一变化有合理产品目的,但仍应确认项目是否承诺机器可解析的错误格式。如果有脚本消费 robot setup 的 JSON,需要提供明确的 --json 模式、迁移说明或版本变更记录。

  2. 错误恢复建议可能误导

    协议异常和响应超时被统一建议检查 Wi-Fi 密码,可能使用户在真正的固件兼容性或蓝牙通信问题上采取错误操作。

  3. 核心目标仍存在遗漏路径

    ValueError -> CliError -> 外层 JSON 的路径意味着“setup 不再输出单行 JSON”尚未被完整实现和验证。

未发现密码打印、Token 泄露、硬编码凭据或明显性能问题。


维度二:意图分析

🎯 意图提炼

该 PR 的核心意图是把 robot setup 从面向开发者的底层错误输出,升级为面向普通用户的完整配网恢复流程;同时补齐旧固件 Device ID 和新版 app init 稳定自动 ID 的说明,降低版本混用造成的困惑。

🔀 偏离检测

整体与项目方向一致,没有明显功能蔓延:

  • CLI 行为改进与文档更新属于同一用户旅程。
  • Application ID 说明虽然不是蓝牙配网功能,但同属 CLI 新手引导和版本识别问题,放在同一修复 PR 中可以接受。
  • 稳定使用 local.<项目名>,不拼接用户名、时间戳和随机值,符合应用升级身份需要,也避免泄露本机信息。

偏差主要出现在实现完整度,而不是方向:

  • 宣称“分别处理”的协议异常、拒绝和响应超时实际上被合并处理。
  • 宣称“完整状态矩阵”,但缺少配对失败。
  • 宣称不再输出 JSON,但 ValueError 路径可能仍会进入全局 JSON 处理。

Merge 建议

⚠️ 有条件合并

建议合并前至少完成以下事项:

  1. 修复 ValueError 路径,确保 robot setup 的所有预期错误都进入引导式文本输出,不再落入外层 JSON 错误处理。
  2. 增加对应回归测试,明确断言 ValueError 场景中不存在 "error" JSON。
  3. 拆分或修正以下异常的恢复提示:
    • ProvisioningProtocolError
    • ProvisioningRejectedError
    • ProvisioningResponseTimeoutError
  4. 在中英文状态矩阵中补充配对失败场景,或调整 PR 描述,避免宣称尚未覆盖的“完整矩阵”。
  5. 为旧版 CLI 来源检查补充 macOS/Linux 命令 command -v watcherobot
  6. 确认普通文本替代 JSON 是否属于允许的 CLI 输出兼容性变更;若 JSON 是公开契约,需要保留显式机器可读模式。

PR 描述声称 pytest 和 mypy 已通过,但本次审查仅基于提供的 Diff,未独立执行验证。


总结: 产品方向和整体实现结构正确,但核心的“统一友好错误输出”仍有 ValueError 遗漏路径,且部分异常恢复建议分类不准确;修复这些问题后可以合并。

@github-actions

Copy link
Copy Markdown

🤖 Luxiao PR 审查报告

🤖 PR 审查报告

PR: fix(cli): 补齐配网异常恢复与自动 ID 说明
变更: 10 个文件,+349 / -25


维度一:代码质量

🏗️ 架构视角 — ⚠️

整体分层合理:

  • 底层 provisioning 异常继续使用类型化异常表达。
  • robot setup 在 CLI 层将底层异常转换为面向用户的恢复指引,职责位置正确。
  • 没有把交互文案下沉到蓝牙后端,也没有引入新的跨模块耦合。
  • DeviceNotFoundError 替代通用 CliError,提高了异常语义的准确性。

但有一个需要合并前确认的架构问题:

main()robot setup 分支内无条件捕获 BluetoothProvisioningErrorCliError,直接输出人类可读文本。这可能绕过 CLI 原有的统一错误输出机制,特别是显式 JSON/机器可读模式。

PR 的目标是“普通用户不再看到单行 JSON”,但当前实现看起来是“所有 robot setup 调用都不再输出 JSON”。如果项目支持 --json、非交互模式或脚本调用,这会破坏稳定的自动化接口。

建议按输出模式区分:

  • 普通交互模式:输出恢复步骤。
  • 显式 JSON/机器模式:继续返回结构化错误,并可增加 recovery_stepserror_type 等字段。

🎨 产品视角 — ✅

这是本次改动最明显的提升:

  • 扫描前补充“打开电脑蓝牙”,符合真实操作顺序。
  • 蓝牙不可用、权限拒绝、未发现设备、连接超时、协议或响应异常都有明确下一步。
  • 已联网机器人引导改用 robot pair,避免用户反复进入错误流程。
  • 取消操作返回 130,并明确不打印密码或配对码,行为清晰。
  • 旧固件缺少 Device ID 时解释可能需要升级,不再让用户误以为 Bluetooth ID 就是稳定设备身份。
  • app init 自动生成 local.<项目名> 的说明清楚解释了稳定 ID 对升级和覆盖安装的价值。
  • 中英文 README、CLI 参考和排障文档基本同步。

不过,部分错误分类的用户指引还不够精确:

ProvisioningProtocolError
ProvisioningRejectedError
ProvisioningResponseTimeoutError

三者统一显示:

The robot did not accept the Wi-Fi setup.
Check the Wi-Fi name and password.

这对 ProvisioningRejectedError 合理,但对协议格式错误或固件响应超时未必成立,容易让用户反复检查正确的密码。

建议至少区分:

  • ProvisioningRejectedError:检查 Wi-Fi 名称、密码及网络兼容性。
  • ProvisioningProtocolError:提示固件响应异常,建议升级固件或收集诊断信息。
  • ProvisioningResponseTimeoutError:提示保持页面、靠近设备、关闭竞争连接后重试。

📏 规范视角 — ⚠️

优点:

  • 新增辅助函数边界明确,返回码统一。
  • 使用具体异常类型而非字符串匹配,可靠性较好。
  • 测试覆盖了蓝牙不可用、权限拒绝、连接超时、配对失败、缺少 Device ID 和敏感信息不泄露。
  • 文档测试保证中英文参考中存在关键恢复信息。
  • 没有暴露底层平台异常详情,platform details 的测试有助于避免技术噪声或环境信息泄漏。

需要改进的地方:

  1. _print_robot_setup_cli_failure() 仍直接输出任意 CliError 内容:

    print(str(exc), file=sys.stderr)

    这虽然消除了 JSON,但没有保证错误内容一定面向普通用户,也没有形成稳定的错误分类。建议只针对配对相关的已知错误进行转换;未知 CliError 应交回统一错误处理,或增加安全的通用提示并保留调试日志入口。

  2. 文档测试过于宽松:

    assert "Bluetooth" in content
    assert "timeout" in content or "超时" in content

    这些断言只能证明关键词存在,无法证明状态矩阵完整、恢复步骤正确或中英文内容一致。文案稍有无关内容也可能让测试继续通过。

  3. PR 描述声称分别覆盖“固件响应异常、配对失败”等状态,但新增测试主要覆盖:

    • 蓝牙不可用;
    • 权限拒绝;
    • 连接超时;
    • 通用配对 CliError

    缺少对以下分支的直接测试:

    • ProvisioningProtocolError
    • ProvisioningRejectedError
    • ProvisioningResponseTimeoutError
    • 未知 BluetoothProvisioningError
    • 显式 JSON/机器可读模式
  4. 文档只推荐 Windows 命令:

    where.exe watcherobot
    

    如果 CLI 支持 macOS/Linux,应同时说明:

    command -v watcherobot

⚠️ 损伤视角 — ⚠️

主要回归风险是错误输出协议变化。

原行为显然允许调用方解析:

{"error": "..."}

现在 robot setup 内部异常会直接变成多行文本。即使这改善了默认交互体验,也可能损伤:

  • 使用 JSON 错误结果的安装器或桌面端;
  • CI 和自动化脚本;
  • 依赖统一 CLI 错误格式的上层调用;
  • 显式请求机器可读输出的用户。

现有测试甚至从解析 JSON 改为断言:

assert '"error"' not in error

但没有证明这种变化只发生在“普通用户模式”。这与 PR 描述限定的范围存在差距。

其他风险:

  • 协议错误被误导为 Wi-Fi 凭据错误,可能增加排障成本。
  • CliError 的捕获范围较宽,未来 setup 流程新增的程序性错误也会被当成可恢复用户错误吞掉。
  • 文档宣称有完整状态矩阵,但代码和测试尚未完全证明每种状态都被独立处理。
  • 当前只根据提供的 Diff 和 PR 自述审查,无法独立确认完整 pytest 与 mypy 的实际执行结果。

未发现明显的密码、配对码泄漏;新增连接超时测试明确验证了 Wi-Fi 密码不会出现在错误输出中。


维度二:意图分析

🎯 意图提炼

本 PR 的核心意图是把 watcherobot robot setup 从“暴露底层结构化错误”改造成真正面向普通用户的恢复式引导,并补齐旧固件 Device ID 与新版 app init 稳定自动 ID 的使用说明。

🔀 偏离检测

整体与项目方向一致,没有明显功能蔓延:

  • CLI 行为、测试和中英文文档围绕同一个用户问题展开。
  • 自动 ID 部分虽然不直接属于蓝牙配网,但同属 CLI 新旧版本识别和首次使用体验,放在同一修复 PR 中尚可接受。
  • 没有引入不必要的新抽象或扩大公共 API。

实现与意图的主要偏差是:

需求说“不再向普通用户输出 JSON”,实现可能变成“任何调用方都不再获得 JSON”。

此外,“分别提供恢复步骤”的实现粒度不完全匹配描述,协议错误、拒绝和响应超时仍被合并为同一提示。


Merge 建议

⚠️ 有条件合并

建议合并前完成以下事项:

  1. 确认并保护机器可读输出契约
    如果存在 --json 或其他结构化输出模式,必须保留其 JSON 错误结果;恢复步骤只应用于默认人类可读模式。

  2. 拆分固件协议错误、配置拒绝和响应超时提示
    不应把所有固件通信异常都归因于 Wi-Fi 名称或密码。

  3. 补充关键分支测试
    至少覆盖:

    • ProvisioningProtocolError
    • ProvisioningRejectedError
    • ProvisioningResponseTimeoutError
    • 未知 provisioning 异常
    • 显式机器可读模式
  4. 收窄或明确 CliError 的捕获边界
    避免未来无关的程序错误被统一包装成“Robot setup could not be completed”,导致真实缺陷被隐藏。

非阻塞建议:

  • 为 macOS/Linux 增加 command -v watcherobot 的旧版 CLI 定位方式。
  • 将文档关键词测试强化为具体状态或关键恢复语句断言。

总结: PR 的用户体验方向正确,文档与测试投入充分,但错误输出格式可能造成自动化接口回归,且部分异常指引分类不准确;完成机器可读模式保护和异常分支补测后可合并。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8f3f5541fa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/watcherobot/cli.py
Comment on lines +502 to +507
else:
lines = (
"Robot setup could not be completed.",
" 1. Keep the robot on Settings > Wi-Fi and nearby.",
" 2. Run watcherobot robot setup again.",
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve recovery guidance for oversized provisioning payloads

When the SSID and password are each within their individual limits but their combined JSON request exceeds the 180-byte protocol limit, build_request() raises PayloadTooLargeError. Because that exception falls into this generic branch, setup now hides the actionable size error and only tells the user to retry, which will fail indefinitely with the same credentials. Handle this exception separately by explaining that the SSID or password must be shortened, or otherwise preserve its sanitized limit details.

Useful? React with 👍 / 👎.

Comment thread src/watcherobot/cli.py
Comment on lines +472 to +474
elif isinstance(exc, DeviceNotFoundError):
lines = (
"No WatcheRobot was found.",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Distinguish a missing requested ID from an empty scan

When scanning finds robots but none matches an explicit --device value, _select_setup_device() raises DeviceNotFoundError with the requested identifier. This handler discards that distinction and reports No WatcheRobot was found, misleading the user into troubleshooting advertising or proximity instead of correcting the ID or selecting one of the discovered devices. Preserve a separate message for the requested-ID mismatch rather than treating every DeviceNotFoundError as an empty scan.

Useful? React with 👍 / 👎.

Comment thread docs/cli-reference.md Outdated
Comment on lines +171 to +173
If an explicit directory is followed by `Application ID:` or other metadata
prompts, the terminal is running an older CLI. Activate the intended virtual
environment and check the command source with `where.exe watcherobot`. An

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include the POSIX command-source check

In the supported macOS environment, where.exe is unavailable, so users following this newly added recovery instruction receive another command-not-found error and still cannot determine which CLI installation is being invoked. The installation guide already uses which watcherobot for macOS/Linux; include that alternative here and in the Chinese reference rather than providing only the Windows command.

Useful? React with 👍 / 👎.

将 Wi-Fi 配置拒绝、固件响应超时、协议不兼容和 Runtime 配对失败拆分为独立用户提示,避免把通信或版本问题误导为密码错误。引入 RobotSetupError 与 RobotPairingError 收窄预期交互异常边界,并修复 ValueError 仍回落为 JSON 的遗漏路径。

明确 robot setup 是面向人的引导命令;需要结构化输出的自动化继续使用 watcherobot bluetooth 命令,其 JSON 合同不变。补充所有异常分支、未知配网异常、ValueError 与配对恢复测试,并增强中英文状态矩阵及 Windows、macOS/Linux 命令来源排查说明。
@Mr-KID-github

Copy link
Copy Markdown
Contributor Author

已在 d28611f 处理审查条件:\n\n- 修复 ValueError 回落到 JSON 的遗漏路径\n- 分开 Wi-Fi 配置拒绝、响应超时、协议不兼容和 Runtime 配对失败提示\n- 使用 RobotSetupError / RobotPairingError 收窄预期交互异常边界\n- 补齐上述分支、未知 provisioning 异常与 ValueError 回归测试\n- 明确
obot setup 是人工引导命令;机器自动化继续使用保留 JSON 合同的 watcherobot bluetooth ...\n- 文档状态矩阵补充配对失败,并加入 macOS/Linux command -v watcherobot\n\n完整 pytest 与 mypy 已重新通过。

@github-actions

Copy link
Copy Markdown

🤖 Luxiao PR 审查报告

🤖 PR 审查报告

PR: fix(cli): 补齐配网异常恢复与自动 ID 说明
变更: 10 个文件,+522 / -33


维度一:代码质量

🏗️ 架构视角 — ⚠️

整体方向合理:

  • 将底层 BluetoothProvisioningError 子类型转换为面向用户的恢复指引,符合 CLI 表现层职责。
  • RobotSetupErrorRobotPairingError 区分了配网输入错误和 Runtime 配对错误,避免继续依赖统一的底层 JSON 错误。
  • 没有修改 provisioning 层的结构化异常模型,低层 API 与高层引导命令的职责边界基本清楚。

但有两个需要注意的问题:

  1. main()ValueError 的捕获范围过大
except ValueError as exc:
    return _print_robot_setup_input_failure(
        RobotSetupError(str(exc))
    )

这会把 _run_robot_setup() 内部任何意外的 ValueError 都归类为用户输入问题。未来如果扫描、设备解析或配网逻辑出现程序缺陷,也会被包装成普通的:

Robot setup could not be completed.
<原始异常文本>

后果是:

  • 掩盖内部编程错误;
  • 让真正需要修复的问题看起来像用户操作错误;
  • 可能把不适合直接展示的底层异常文本输出给用户。

建议只在明确的输入解析边界捕获 ValueError,例如 SSID、Device ID、配对码解析处;其他意外异常应继续进入统一错误处理或调试日志。

  1. 引导命令仍具备明显的非交互接口,但输出合同被无条件改写

robot setup 支持:

  • --device
  • --ssid
  • --pairing-code
  • 非交互终端检测

这说明它并不完全是只能由人操作的命令。但本次修改无论是否 TTY,都将错误从 JSON 改成普通文本。仅通过文档重新定义其为“human-guided command”,不足以消除既有自动化兼容风险。

更稳妥的架构方案是:

  • TTY 默认输出引导文本;
  • 非 TTY 保留结构化错误;或
  • 提供明确的 --json / --format json 兼容入口。

🎨 产品视角 — ✅

用户体验提升明显:

  • 扫描前明确提醒打开电脑蓝牙,补上了此前缺失的关键前置条件。
  • 蓝牙关闭、权限拒绝、无设备、连接超时、响应超时、协议不兼容、Wi-Fi 被拒绝、配对失败和取消被分别处理。
  • 错误信息不仅说明“发生了什么”,还提供了可执行的恢复步骤。
  • “已联网机器人使用 robot pair”可以避免用户重复进入 BLE 配网。
  • 旧固件缺少 Device ID 时明确解释可能与固件版本有关,同时保留 Bluetooth ID 兼容信息,没有误导用户把平台蓝牙地址当成稳定设备身份。
  • app init 文档准确解释了 local.<项目名> 的稳定升级价值,也给出了识别旧 CLI 的具体命令。
  • Wi-Fi 密码和配对码不被打印的安全约束在测试与文档中均有体现。

小问题:

NO_BLE_CENTRAL_ROLE 与“蓝牙关闭”并不是同一种恢复场景。当前统一提示用户打开蓝牙或检查适配器,可能让使用不支持 BLE Central 的硬件或系统的用户反复重试。建议针对该 reason 补充“当前适配器或系统不支持 BLE Central,需要更换兼容适配器/环境”的说明。


📏 规范视角 — ⚠️

做得较好的部分:

  • 新异常类命名清晰,类型标注完整。
  • 不同错误类型对应的文案集中在 _print_robot_setup_failure(),便于查阅。
  • 中英文 CLI Reference、README、蓝牙配网文档和排障文档同步更新。
  • 测试覆盖了主要异常分支,并验证敏感密码和 JSON 错误不会泄漏到新输出。
  • 文档测试覆盖了中英文关键概念的一致性。

需要改进的部分:

  1. 新增文档测试偏向关键词存在性检查

例如:

assert "permission" in content or "权限" in content
assert "reject" in content or "拒绝" in content

这只能证明文件中出现了关键词,不能证明:

  • 关键词位于正确命令章节;
  • 状态和恢复步骤一一对应;
  • 中英文状态矩阵保持一致;
  • 文档与实际 CLI 输出一致。

这类测试容易在文档重排或其他章节出现相同单词时产生假阳性。建议至少检查完整状态标题或关键恢复句,最好将错误文案和文档状态标识建立更稳定的对应关系。

  1. 缺少部分新行为的直接测试

当前 Diff 中未看到以下场景的专门验证:

  • Ctrl+C / ProvisioningCancelledError 仍返回 130;
  • EOF 取消现在返回 2,与普通取消语义是否一致;
  • 非交互缺失 SSID 等字段时的新文本输出;
  • DeviceAmbiguityError 是否仍得到合理输出;
  • lower-level watcherobot bluetooth ... 的 JSON 合同确实未改变;
  • pair_robot() 返回非零状态而不是抛出 CliError 时,是否也会显示配对恢复步骤。
  1. 文案实现采用较长的 isinstance 分支

当前规模尚可接受,但错误类型继续增加后会变得难以维护。可考虑用异常类型到文案生成器的映射表降低分支复杂度。此项不是本 PR 的合并阻塞项。


⚠️ 损伤视角 — ⚠️

主要回归风险是输出兼容性。

此前 robot setup 的失败输出为结构化 JSON,本次改成普通文本,并且测试明确要求:

assert '"error"' not in output

这不是单纯的文案优化,而是 CLI 输出合同变化。可能受影响的调用方包括:

  • Shell 脚本通过 jq 读取错误;
  • 桌面端或安装器调用完整 robot setup 并解析 stderr;
  • CI 或设备批量初始化流程根据 JSON error code 分类;
  • 非交互环境中传入全部参数执行 setup 的现有用户。

文档建议自动化改用 watcherobot bluetooth ...,但没有证明底层命令能够替代完整 setup 中的 Wi-Fi 配置加 Runtime 配对流程,也没有提供迁移示例。

其他风险:

  • 宽泛捕获 ValueError 可能掩盖真实缺陷并直接暴露底层异常字符串。
  • 未知 BluetoothProvisioningError 被统一降级为泛化恢复文案,虽然对普通用户更友好,但会损失诊断信息。建议在 verbose/debug 模式或日志中保留异常类型和原始原因。
  • 取消语义存在不一致:Ctrl+C 返回 130,而 EOF 被包装成 RobotSetupError 返回 2。若两者都代表用户取消,应统一退出码和展示路径。

未发现密码输出、硬编码凭据、明显性能问题或 provisioning 协议层破坏。


维度二:意图分析

🎯 意图提炼

该 PR 的核心意图是把 robot setup 从“底层异常的 JSON 转发器”完善为真正面向普通用户的配网向导,为常见失败提供明确恢复步骤;同时通过文档解释旧固件缺少 Device ID 以及旧版 CLI 仍询问 Application ID 的原因。

🔀 偏离检测

意图符合项目方向,尤其适合面向消费级 AI 硬件和桌面机器人的首次连接体验。代码、测试和中英文文档整体围绕同一目标,没有明显功能蔓延。

但实现存在一处边界偏移:

  • “改善交互式用户体验”被实现成了“无条件废弃 robot setup 的结构化错误输出”。

这扩大了变更影响范围。考虑到命令本身支持完整参数和非交互执行,不能仅依靠新增文档声明来认定没有兼容性责任。

app init 部分本次只有说明和排障文档,没有修改初始化逻辑,符合“补齐说明”的 PR 标题;但 PR 描述中的“确认 (base) 为旧版”属于环境结论,不是可复现验证,不能作为代码正确性的主要依据。


Merge 建议

⚠️ 有条件合并

建议完成以下条件后合并:

  1. 明确并保护 robot setup 的输出兼容策略
    推荐 TTY 输出恢复指引,非 TTY 或显式 --json 保留结构化错误。若项目决定正式废弃 JSON,也应按 breaking change 处理,提供迁移说明、版本策略和完整替代命令。

  2. 缩小 ValueError 捕获范围
    只在明确的用户输入解析位置转换为 RobotSetupError,不要吞掉整个 setup 流程中的意外 ValueError

  3. 统一取消语义
    明确 EOF、Ctrl+C 和 ProvisioningCancelledError 是否都属于取消;若是,应统一走 _print_robot_setup_cancelled() 并返回 130。

  4. 补充关键回归测试
    至少覆盖非交互输出合同、取消退出码,以及 lower-level Bluetooth 命令的 JSON 输出未被破坏。

非阻塞建议:

  • NO_BLE_CENTRAL_ROLE 提供更准确的硬件/系统兼容提示。
  • 在 verbose 或日志模式中保留原始异常信息,兼顾普通用户体验和工程诊断能力。
  • 将文档测试从宽泛关键词检查收紧到具体状态和恢复步骤。

总结: 配网错误恢复体验和文档质量提升明显,方向值得合并;但当前无条件改变 robot setup 输出合同,并宽泛吞掉 ValueError,存在真实兼容性和可诊断性风险,建议修正后再合并。

根据配网向导审查继续完善交互状态:仅在 TTY 终端展示面向用户的恢复步骤,非交互调用继续沿用结构化 JSON 错误合同;将 EOF 与 Ctrl+C 统一为取消并返回 130,避免自动化调用和人工流程出现不一致。

新增不支持 BLE Central 的独立异常和恢复建议,补齐设备身份冲突、配对返回非零、缺失输入、未知程序性 ValueError 不被吞掉等边界。同步中英文 CLI 参考和排障文档,并用 hello_robot_test 固化 local.hello_robot_test 的自动稳定 ID 行为。

验证:pytest -q tests/test_robot_onboarding.py tests/provisioning tests/test_documentation.py tests/test_app_cli_usability.py;python -m mypy src;git diff --check。
@Mr-KID-github

Copy link
Copy Markdown
Contributor Author

已在 d5f096e 继续处理最新审查意见:
obot setup 仅在 TTY 中输出人工恢复引导,非交互调用保留 JSON;移除全流程 ValueError 吞错;EOF、Ctrl+C 与 provisioning cancellation 统一返回 130;单独区分缺少 BLE Central;补齐 Device ID 冲突、缺失输入、配对非零返回及 hello_robot_test -> local.hello_robot_test 回归测试。相关定向测试 121 项、mypy 与 diff check 已通过。完整测试中的 3 个本地 REST 用例因系统代理 127.0.0.1:7897 代理了 localhost 而返回 502,与本次改动无关;GitHub Development checks 将在干净环境重新执行。

@github-actions

Copy link
Copy Markdown

🤖 Luxiao PR 审查报告

🤖 PR 审查报告

PR: fix(cli): 补齐配网异常恢复与自动 ID 说明
变更: 14 个文件,+771 / -41


维度一:代码质量

🏗️ 架构视角 — ✅

整体分层合理,改动基本保持在既有职责边界内:

  • provisioning/errors.py 定义底层蓝牙错误类型。
  • bleak_backend.py 将平台异常转换为领域异常,避免 Bleak 的平台细节泄漏到 CLI。
  • cli.py 负责把领域异常转换为面向用户的恢复步骤。
  • 文档同步描述公开行为,测试覆盖异常映射和 CLI 输出。

新增 BluetoothUnsupportedError,将“不支持 BLE Central”与“蓝牙关闭或没有适配器”分开,是有价值的领域建模。用户面对这两种情况需要采取完全不同的恢复措施,不应继续混用 BluetoothUnavailableError

RobotSetupErrorRobotPairingError 也避免继续用宽泛的 ValueError 表示预期交互失败。删除原先对所有 ValueError 的包装尤其正确:内部编程错误现在不会再被伪装成普通用户输入错误。

交互终端输出恢复指导、非交互终端保留 JSON 合同的处理方式兼顾了人机交互与自动化兼容性,没有直接破坏底层命令的结构化输出。

需要关注的长期问题是:恢复文案目前同时存在于 CLI、英文文档、中文文档和排障文档中。当前规模尚可,但后续新增错误类型时容易发生行为与文档漂移。现阶段不构成合并阻塞。


🎨 产品视角 — ✅

这是本次改动最明显的提升。

原先单行 JSON 对普通用户几乎没有恢复价值;现在针对以下状态提供了明确、可执行的下一步:

  • 蓝牙关闭、无适配器或不可用
  • 缺少 BLE Central 能力
  • 操作系统权限拒绝
  • 未发现机器人
  • 多设备标识冲突
  • 蓝牙连接超时
  • 固件响应超时
  • Wi-Fi 配置被机器人拒绝
  • 固件与 SDK 协议不兼容
  • Runtime 配对失败
  • 用户取消

错误提示没有直接展示 Bleak 的平台内部信息,也没有输出 Wi-Fi 密码。错误恢复步骤围绕用户能够执行的动作展开,而不是暴露底层异常堆栈,符合面向消费级硬件的 CLI 定位。

Device ID 的说明也比较准确:

  • 新固件优先使用稳定 Device ID。
  • 旧固件仅保留 Bluetooth ID 作为兼容标识。
  • 不把 Bluetooth ID 误称为 Device ID。
  • 明确提示固件升级可能是解决方式。

app init 文档对 local.<项目名> 的解释解决了两个真实困惑:

  1. 为什么最新版不再逐项询问 Application ID。
  2. 为什么默认 ID 不拼接用户名、时间戳或随机值。

稳定 ID 对应用升级和覆盖安装是必要的;避免用户名也减少了本机信息泄露。

一个轻微的产品风险是 _print_robot_pairing_failure() 会直接打印 str(exc)。当前已知错误文案看起来不包含敏感数据,但既然文档明确承诺“不打印 Wi-Fi 密码或配对码”,最好确保 pair_robot() 产生的所有 CliError 都不会把配对码、请求体或底层响应原文放进异常消息。


📏 规范视角 — ⚠️

优点:

  • 新异常命名清晰,继承关系简单。
  • 错误类型通过 provisioning/__init__.py 正确导出。
  • 使用专门异常代替宽泛的 ValueError,异常边界更规范。
  • 类型标注与现有代码风格一致。
  • 中英文 CLI 参考、README、配网文档和排障文档同步更新。
  • 测试覆盖了主要异常分支、交互/非交互输出以及敏感密码不出现在错误输出中。

主要问题在测试完整性上:

  1. 缺少非交互配对失败的 JSON 合同测试

    当前只验证了蓝牙扫描失败和缺少 Wi-Fi 名称时仍输出 JSON,但没有覆盖 RobotPairingError 在非交互模式下重新抛出后是否仍由顶层统一转换为 JSON。

  2. 敏感信息测试覆盖不完整

    连接和配网失败测试验证了 "secret" 不出现在输出中,但 Runtime 配对失败测试没有使用包含配对码的异常消息验证输出边界。由于实现会原样打印 str(exc),这项承诺目前依赖下游异常消息保持安全。

  3. 取消流程只覆盖了第一个输入点

    EOF 测试验证了准备阶段的取消,但没有覆盖 Wi-Fi 名称输入、密码输入或配对码输入阶段。虽然代码中部分路径已经转换为 ProvisioningCancelledError,仍建议至少补一个后续输入阶段的取消测试。

  4. 文档测试偏字符串存在性检查

    test_documentation.py 只能证明关键词出现,不能证明状态矩阵与真实 CLI 分支一一对应。它可以防止大段文档被误删,但不足以避免错误名称或恢复建议逐渐漂移。

这些问题目前更偏向测试加固,并未从给出的 Diff 中形成确定的功能性阻塞。


⚠️ 损伤视角 — ⚠️

兼容性处理总体谨慎:

  • 非交互 robot setup 仍保留 JSON 错误。
  • 底层 watcherobot bluetooth ... 的结构化输出合同未修改。
  • 旧固件的 Bluetooth ID 仍能通过 --device 使用。
  • 退出码保持清晰:普通失败为 2,取消为 130
  • 未继续吞掉意外 ValueError,有助于暴露真正的内部缺陷。

需要注意以下回归风险:

1. 配对阶段的非零退出码被统一改写为 2

if result != 0:
    raise RobotPairingError(...)

这会把 pair_robot() 返回的所有非零状态统一转换为 setup 失败码 2。如果 pair_robot() 存在具有独立语义的退出码,例如取消 130 或其他可供自动化识别的状态,这里会丢失原始语义。

建议确认 pair_robot() 的返回合同。如果它只返回 0/1/2 且 setup 本身只承诺 0/2/130,当前实现可以接受;如果可能返回 130,应单独保留取消语义。

2. 配对失败可能出现重复或混合输出

需要确认 pair_robot() 在返回非零值之前是否已经自行向 stderr 输出错误。如果它既打印错误又返回非零,外层随后再打印 Robot pairing could not be completed,用户可能看到两套错误信息,甚至出现 JSON 与普通文本混合。

现有测试使用的是简单 lambda,没有覆盖“下层已经输出错误后返回非零”的情况。

3. Bleak 版本兼容性

代码和测试直接依赖:

BleakBluetoothNotAvailableReason.NO_BLE_CENTRAL_ROLE

PR 声称完整测试和 mypy 已通过,说明当前开发依赖支持该枚举。仍应确认项目安装元数据对 Bleak 的最低版本约束已经覆盖引入该枚举的版本,否则旧环境升级 SDK 后可能在相关代码路径或测试导入阶段失败。


维度二:意图分析

🎯 意图提炼

该 PR 的核心意图是把 watcherobot robot setup 从“暴露底层机器错误的命令”升级为真正面向普通用户的配网向导,同时明确新旧固件的 Device ID 差异。

另一部分意图是通过文档解释最新版 app init 的自动稳定 ID 策略,并帮助用户识别当前终端是否误用了旧版 CLI。

🔀 偏离检测

意图与项目方向一致,没有明显功能蔓延。

虽然改动达到 14 个文件、771 行,但主要增量来自:

  • 中英文文档状态矩阵
  • 对应的 CLI 行为测试
  • Bleak 异常映射测试

实际生产代码改动集中在异常分类和错误展示,没有顺带重构无关模块,也没有改变应用初始化逻辑。app init 部分基本是在补充已有行为的文档说明,而不是混入新的产品功能。

测试中将 hello_robot 改为 hello_robot_test 只是调整测试数据,没有实质改变测试目标,但与本 PR 的核心修复关系较弱;不构成问题。


Merge 建议

✅ 建议合并

理由:

  1. 解决了普通用户看到原始 JSON、无法自行恢复的明确产品问题。
  2. 异常分类位于正确层级,没有让 Bleak 平台细节侵入 CLI。
  3. 交互模式改善体验的同时,保留了非交互 JSON 兼容合同。
  4. 没有破坏旧固件 Bluetooth ID 的兼容路径。
  5. 中英文文档和排障说明同步完整。
  6. 主要异常分支已有针对性测试,且 PR 声明完整 pytest 与 mypy 均通过。
  7. 未发现必须阻止合并的确定性缺陷。

建议合并前确认两项,不必扩大为大规模重构:

  • 确认 pair_robot() 不会返回需要保留的特殊退出码,尤其是 130
  • 确认项目的 Bleak 最低版本包含 NO_BLE_CENTRAL_ROLE

建议后续补充:

  • 非交互 Runtime 配对失败的 JSON 合同测试。
  • pair_robot() 已输出错误后返回非零的测试,防止混合或重复输出。
  • 配对错误不泄露六位码的测试。
  • Wi-Fi、密码或配对码输入阶段的 EOF/取消测试。

总结: 改动方向正确、架构边界清晰,并显著改善首次配网体验;未发现阻塞性问题,确认配对退出码与 Bleak 最低版本后可以合并。

保留 pair_robot 返回 130 时的取消语义,避免被统一折叠为普通 setup 失败;在配对异常进入交互文本或非交互 JSON 前统一遮蔽六位配对码。

新增非交互配对失败 JSON、配对码脱敏与配对取消退出码测试。验证配网和文档相关 123 项测试、mypy 与 diff check 通过。
@Mr-KID-github

Copy link
Copy Markdown
Contributor Author

补充确认并在 d0f0f96 加固两项非阻塞风险:pair_robot 当前实现只会成功返回 0、失败抛 CliError,但 setup 仍显式保留未来返回 130 的取消语义;配对异常进入人工文本或非交互 JSON 前会遮蔽六位配对码。新增非交互配对失败、脱敏与 130 回归测试。当前最低依赖为 bleak>=3,<4,本机 bleak 3.0.2 已包含 NO_BLE_CENTRAL_ROLE,映射测试通过。

@github-actions

Copy link
Copy Markdown

🤖 Luxiao PR 审查报告

🤖 PR 审查报告

PR: fix(cli): 补齐配网异常恢复与自动 ID 说明
变更: 14 个文件,+852 / -41


维度一:代码质量

🏗️ 架构视角 — ✅

整体分层合理,改动基本遵循现有架构:

  • 底层 provisioning 负责识别并归一化平台蓝牙错误:
    • 新增 BluetoothUnsupportedError
    • 将 Bleak 的 NO_BLE_CENTRAL_ROLE 映射为明确的领域异常
  • CLI 层负责根据异常类型生成面向用户的恢复指引,没有把展示文案下沉到蓝牙后端。
  • RobotSetupErrorRobotPairingError 将引导流程中的预期失败与普通 CliError 区分开,职责清晰。
  • 交互终端输出恢复说明,非交互场景保留 JSON 合同,兼顾人工操作和自动化调用。
  • 未发现循环依赖、不合理跨层调用或明显过度抽象。

异常到文案的映射目前集中在 _print_robot_setup_failure()isinstance 分支中。现阶段异常数量有限,这种实现比额外引入注册表或策略类更直接;如果后续恢复场景继续增加,再考虑改成数据驱动映射即可,本 PR 无需提前抽象。

🎨 产品视角 — ✅

这是一次有明确用户价值的修复:

  • 普通用户不再面对缺少上下文的单行 JSON,而是获得可执行的恢复步骤。
  • 蓝牙关闭、无适配器、BLE Central 不支持、权限拒绝、未发现设备、连接超时、固件协议不兼容等场景得到区分,避免所有失败都被归为“蓝牙不可用”。
  • 首次提示增加“打开电脑蓝牙”,减少扫描开始后才发现环境未准备好的情况。
  • 旧固件缺少 Device ID 时明确说明“可能需要升级固件”,同时保留 Bluetooth ID 兼容路径,没有直接破坏旧设备支持。
  • 多设备、旧固件、取消、Runtime 配对失败等状态都有明确反馈。
  • Application ID 文档解释了稳定 ID 的必要性,并给出了识别旧版 CLI 的实际命令,对 (base)、虚拟环境混用问题有帮助。
  • 中英文文档基本同步,CLI 参考、蓝牙配网文档和排障文档覆盖一致。

交互与自动化边界也处理得较好:交互终端使用可读文本,非交互调用继续得到 JSON,避免为了改善人工体验而破坏脚本。

📏 规范视角 — ⚠️

整体规范良好:

  • 新异常命名明确,继承关系合理。
  • 类型标注完整。
  • 恢复文案集中管理,没有散落在底层后端。
  • 对六位配对码做了脱敏,且测试明确验证原始配对码不会进入错误输出。
  • 新增测试覆盖异常映射、交互与非交互输出、取消退出码、EOF、设备歧义和敏感信息。
  • 文档测试确保中英文参考不会遗漏关键状态。

有两点建议:

  1. _print_robot_setup_failure() 已经形成一组稳定的“异常类型 → 标题与恢复步骤”映射,但测试主要断言部分关键词。后续修改文案时,代码、CLI Reference 和 Troubleshooting 表格仍可能发生语义漂移。建议未来把恢复状态提取成共享的数据结构,或至少增加一项测试确保所有公开的 BluetoothProvisioningError 子类都有明确映射。

  2. tests/test_app_cli_usability.py 将测试目录从 hello_robot 改成 hello_robot_test,看起来只是为了更新自动生成 ID 的断言,与本 PR 的核心修复关联较弱。该改动没有明显风险,但 PR 描述最好说明这是为了验证项目名到稳定 ID 的完整派生,而不是规避已有测试环境冲突。

以上均不是阻塞问题。

⚠️ 损伤视角 — ⚠️

现有兼容性保护比较充分:

  • 非交互 robot setup 的 JSON 错误合同有专门回归测试。
  • 底层 watcherobot bluetooth ... 的结构化输出未修改。
  • 旧固件仍可使用 Bluetooth ID。
  • 取消继续返回 130
  • 未预期的 ValueError 不再被错误包装为普通用户输入问题,有利于暴露真实程序缺陷。
  • Wi-Fi 密码和配对码没有进入新增错误输出。
  • 测试与 mypy 均通过。

需要关注一个边界:

_run_robot_setup() 调用 pair_robot() 后,将非零返回值转换成 RobotPairingError。这里依赖 pair_robot() 在失败时不会先自行向 stderr 输出 JSON 或其他错误,再返回非零状态。若 pair_robot() 存在“先输出、后返回”的实现路径,交互式 setup 可能同时出现旧 JSON 和新的恢复说明。

当前新增测试只覆盖了 mock 的两种情况:

  • pair_robot() 直接抛出 CliError
  • pair_robot() 直接返回非零值

它没有使用真实 pair_robot() 的失败路径验证 stderr 只产生一份错误。建议补一个接近真实调用链的回归测试,确认:

  • 交互式 robot setup 配对失败时 stderr 不包含 JSON
  • 错误只输出一次
  • 配对码始终被脱敏

如果现有 pair_robot() 保证失败只抛异常、不自行输出,则该风险不成立,但最好用测试固定这一隐含合同。

另一个兼容性注意点是新增代码直接引用 BleakBluetoothNotAvailableReason.NO_BLE_CENTRAL_ROLE。当前测试环境显然支持该枚举;仍应确认项目声明的最低 Bleak 版本也包含该成员。若依赖已锁定到支持版本,则无需处理;若允许更旧版本,异常处理路径可能因枚举成员不存在而触发 AttributeError


维度二:意图分析

🎯 意图提炼

该 PR 的核心意图是把 watcherobot robot setup 从“底层错误的薄包装”升级为真正面向普通用户的配网引导:针对不同失败原因提供可执行的恢复步骤,同时保持非交互自动化和旧固件兼容性。

第二个意图是澄清新版 app init 自动生成稳定 Application ID 的行为,并帮助用户识别虚拟环境中误调用旧版 CLI 的情况。

🔀 偏离检测

意图与项目方向一致,没有明显功能蔓延。

蓝牙异常恢复、Device ID 解释和相关文档属于同一条首次连接体验链路;Application ID 说明虽然不是蓝牙功能,但同样是在解决 CLI 新旧版本混用导致的用户困惑,并未引入额外产品能力。

实现与描述基本一致:

  • 代码确实区分了主要蓝牙和配对失败类型。
  • 交互场景不再默认输出 JSON。
  • 非交互 JSON 合同得到保留。
  • Device ID unavailable 增加固件升级说明。
  • 中英文文档补充状态矩阵和旧版 CLI 识别方式。
  • 没有借修复之名重构无关模块。

变更规模较大主要来自测试和双语文档,不属于无控制的代码扩张。


Merge 建议

⚠️ 有条件合并

整体实现方向正确,用户价值明确,分层和兼容性处理成熟,没有发现确定性的功能阻塞问题。

合并前建议确认以下事项:

  1. 确认真实 pair_robot() 失败路径不会自行输出错误后再返回非零值;最好补一项真实调用链回归测试,防止交互式 setup 同时输出 JSON 和恢复说明。
  2. 确认项目支持的最低 Bleak 版本包含 BleakBluetoothNotAvailableReason.NO_BLE_CENTRAL_ROLE;如果不包含,应提升最低版本或采用兼容读取方式。

如果上述两项已经由依赖约束和现有实现保证,可以直接合并。


总结: 这是一次值得合并的 CLI 可用性修复,异常分层、恢复指引、隐私保护和自动化兼容均处理得较完整;合并前只需确认真实配对失败输出链路和 Bleak 最低版本兼容性。

robot setup 在最长约十秒的蓝牙扫描期间持续输出进度点,并在扫描结束后显示发现的机器人数量,避免用户误判命令卡死。

Wi-Fi 写入阶段不再把凭据保存描述成联网成功,明确提示当前 BLE ACK 只证明 SSID 和密码已存储、尚未验证;交互式流程要求机器人设置页显示 Connected 后才继续配对,Offline 或 Wi-Fi failed 时给出断开、忘记网络和重新配网步骤。配对失败提示同步前移检查 Wi-Fi 状态。

同步中英文 CLI 参考与排障文档,新增慢扫描进度、Wi-Fi 确认取消和错误密码恢复文案测试。验证相关 125 项测试、mypy 与 diff check 通过。
@Mr-KID-github

Copy link
Copy Markdown
Contributor Author

新增 5b7d20f:蓝牙扫描期间按秒输出进度点并在结束后报告发现数量;Wi-Fi 阶段不再把 credentials_saved 误报成联网成功,明确说明密码尚未验证,并要求机器人设置页显示 Connected 后才进入配对。Offline / Wi-Fi failed 会提示网络名或密码可能错误以及断开/忘记网络后的重试步骤。协议核对确认当前固件必须释放 BLE 后才能开始 Wi-Fi,因此真正自动回传 auth_failed 仍需要后续 ESP32 协议配套,本提交先保证 SDK 不误导且用户有明确判断入口。相关 125 项测试、mypy 与 diff check 通过。

@github-actions

Copy link
Copy Markdown

🤖 Luxiao PR 审查报告

🤖 PR 审查报告

PR: fix(cli): 补齐配网异常恢复与自动 ID 说明
变更: 14 个文件,+1043 / -50


维度一:代码质量

🏗️ 架构视角 — ⚠️

整体分层合理:

  • 平台异常仍由 provisioning/bleak_backend.py 负责映射。
  • BluetoothUnsupportedError 放在 provisioning 错误体系中,没有把 Bleak 平台细节泄漏到 CLI。
  • CLI 负责把领域异常转换为面向用户的恢复步骤,职责边界清楚。
  • 交互模式输出可读文本、非交互模式保留 JSON,兼顾了人工使用与现有自动化兼容性。
  • RobotSetupErrorRobotPairingError 将输入错误、配网错误和 Runtime 配对错误分开,方向正确。

但有两个需要修正的设计问题:

  1. _scan_setup_devices() 显示“up to 10 seconds”,代码本身却没有传入或强制执行 10 秒超时,只是依赖 BluetoothProvisioner.scan_devices() 的隐式默认值。
    这使 CLI 文案、文档和底层默认参数形成隐藏耦合。底层默认值一旦变化,CLI 会继续向用户承诺错误的时长。

    建议定义统一常量,例如:

    _SETUP_SCAN_TIMEOUT_SECONDS = 10.0

    并显式传给扫描方法,同时由该常量生成提示文案。

  2. _scan_setup_devices() 创建了独立 scan_task,但在等待循环被取消时,finally 只打印换行,没有显式取消并回收该任务。
    asyncio.wait() 期间发生取消,扫描任务可能继续占用蓝牙扫描资源,直到事件循环关闭或底层扫描自行结束。

    建议在异常或取消路径执行:

    if not scan_task.done():
        scan_task.cancel()
        with contextlib.suppress(asyncio.CancelledError):
            await scan_task

🎨 产品视角 — ✅

这部分是本 PR 最有价值的改进。

优点:

  • 不再让普通终端用户面对单行 JSON,而是明确告诉用户发生了什么、下一步做什么。
  • 对以下场景给出了差异化恢复路径:
    • 蓝牙关闭或无适配器
    • 不支持 BLE Central
    • 系统权限拒绝
    • 未发现机器人
    • 设备标识冲突
    • 连接超时
    • 固件响应超时
    • Wi-Fi 配置被拒绝
    • 协议不兼容
    • Runtime 配对失败
    • 用户取消
  • “Wi-Fi credentials stored”与“连接已验证”明确区分,避免用户把 BLE 写入成功误解为 Wi-Fi 密码正确。
  • 配对失败提示同时覆盖 Wi-Fi 状态、同网条件、Python SDK 应用和最新配对码,恢复链路完整。
  • 旧固件缺少 Device ID 时明确提示可能需要升级,同时保留 Bluetooth ID 作为兼容标识,没有把平台蓝牙 ID 错称为稳定设备身份。
  • app init 文档解释了 local.<项目名> 的稳定性,以及为何不引入用户名、时间戳或随机值,符合应用升级身份的产品语义。
  • 中英文文档基本同步。
  • 配对码在错误信息中经过脱敏,避免六位码通过 stderr 或 JSON 泄漏。

需要注意但不阻塞:

  • _confirm_robot_wifi_connected() 只能等待用户按 Enter,不能验证机器人实际上已经连接。当前文案应始终保持“用户确认”,不要在后续版本中把它描述成 CLI 自动验证。
  • 非交互模式不会等待 Wi-Fi 建连,写入凭据后会立即进入 Runtime 配对。机器人释放 BLE、加入 Wi-Fi 较慢时仍可能出现时序竞争。此次没有明显扩大原有问题,但建议后续为非交互流程提供明确的等待或重试机制。

📏 规范视角 — ⚠️

做得较好的部分:

  • 新异常命名清楚,继承关系合理。
  • 所有新增函数都有完整类型标注。
  • 用户可恢复错误与意外 ValueError 被区分,不再把内部编程错误伪装成普通 CLI 输入错误。
  • 测试覆盖了交互文本、非交互 JSON、退出码、错误脱敏、平台异常映射和取消行为。
  • BluetoothUnsupportedError 已从 provisioning 包公开导出。
  • 中英文 CLI 参考、配网文档和 troubleshooting 文档同步更新。

需要改进:

  1. 扫描时间“10 seconds”散落在 CLI 输出和中英文文档中,但没有对应的公共代码常量或契约测试。当前测试只检查字符串存在,不能证明实际扫描上限确实是 10 秒。

  2. tests/test_app_cli_usability.py 将测试目录从 hello_robot 改成 hello_robot_test,与本 PR 的生产代码改动没有直接关系。
    如果这是为避免本地残留目录或测试冲突,说明测试隔离可能有问题;应修复隔离原因,而不是通过更名绕开。若只是更新示例,则建议在 PR 描述中说明。

  3. 文档测试主要是关键词断言,例如只验证 "JSON""Offline""timestamp" 是否存在。这类测试能防止段落被整体删除,但无法验证状态、原因和恢复步骤之间是否仍然匹配。不是阻塞问题,但不应把它视为完整的行为验证。

  4. _redact_pairing_codes() 是通用文本替换,当前会隐藏错误文本中所有独立六位数字,而不只是真实配对码。安全上偏保守可以接受,但函数注释应说明这一行为,避免以后误认为它是精确的字段级脱敏。


⚠️ 损伤视角 — ⚠️

兼容性处理总体谨慎:

  • 非交互 robot setup 仍保留 JSON 错误合同。
  • 底层 watcherobot bluetooth ... 的结构化输出未修改。
  • 取消继续返回退出码 130
  • 普通失败继续返回退出码 2
  • 原始 Bleak 平台错误详情没有直接暴露。
  • Wi-Fi 密码和配对码均有对应的不泄漏测试。
  • 旧固件 Bluetooth ID 兼容路径保留。
  • 意外 ValueError 会向上暴露,有利于发现真实代码缺陷。

主要风险如下:

1. 扫描任务取消后可能残留

_scan_setup_devices() 在等待期间被取消时,没有保证 scan_task 被取消和 await。蓝牙扫描是独占性、平台相关性较强的资源,残留扫描可能影响紧接着的重试或其他蓝牙程序。

这是建议合并前修复的问题。

2. 新增交互暂停改变了完整参数调用的行为

即使用户已经提供:

--device
--ssid
--pairing-code

只要当前终端被识别为交互式 TTY,命令仍会新增一次:

Press Enter after the robot shows Wi-Fi Connected:

这符合“人工引导命令”的新定位,但属于可观察的 CLI 行为变化。依赖伪终端执行、Expect 脚本或封装该命令的现有工具可能因此挂起。

建议至少:

  • 在变更说明中明确这是交互流程的兼容性变化;
  • 增加明确的跳过/非交互选项,或定义只要全部必要参数齐全就不增加确认提示;
  • 如果决定必须保留暂停,应增加测试覆盖“全参数 + TTY”这一兼容性决策,并将其写入 CLI reference。

3. “最长 10 秒”目前不是代码保证

如果底层默认超时发生变化,或者平台扫描 API 超出预期时间,CLI 会违反对用户的明确承诺。这不仅是文案问题,也会影响用户对“命令是否卡死”的判断。

4. 未提供实际 CI 输出

PR 描述声明:

  • python -m pytest -q 通过
  • python -m mypy src/watcherobot 通过

从 diff 看测试覆盖较充分,但当前材料没有包含命令输出或 CI 状态,因此只能认为验证声明合理,不能独立确认真实执行结果。


维度二:意图分析

🎯 意图提炼

本 PR 的真实目标是把 watcherobot robot setup 从“底层错误的简单包装”升级为面向普通用户的完整恢复式引导流程,同时澄清旧固件 Device ID 缺失和旧版 app init 元数据提问的识别方式。

另一个重要意图是建立稳定身份语义:机器人优先使用固件广播的 Device ID,应用初始化默认使用稳定的 local.<项目名>,避免平台 ID、用户名、时间戳或随机值破坏后续升级关系。

🔀 偏离检测

整体与项目方向一致,没有明显功能蔓延。

合理范围:

  • provisioning 异常分类属于配网体验修复。
  • CLI 恢复步骤属于同一用户旅程。
  • Device ID 文档属于配网身份说明。
  • app init 的旧 CLI 识别和稳定 ID 说明虽然不是蓝牙问题,但与 PR 标题中的“自动 ID 说明”一致。

需要警惕的是,这个 PR 同时包含:

  • 蓝牙异常分类
  • CLI 输出合同调整
  • Wi-Fi 状态确认流程
  • Runtime 配对错误包装
  • Device ID 兼容说明
  • Application ID 文档
  • 大量文档矩阵和测试

范围偏大,但各部分仍围绕“首次使用时的可恢复性与身份解释”,尚未构成功能蔓延。后续不建议继续在该 PR 中加入新的 onboarding 功能。


Merge 建议

⚠️ 有条件合并

建议在满足以下条件后合并:

  1. 修复 _scan_setup_devices() 的任务取消清理,确保取消或异常时不会留下后台蓝牙扫描任务。
  2. 将扫描超时定义为显式常量,并真正传入底层扫描调用,保证“最长 10 秒”是代码契约而不是隐式默认值。
  3. 明确新增 Wi-Fi 确认提示对“全参数 + 交互式 TTY”调用的兼容性策略:
    • 要么确认这是有意的 breaking behavior,并补充变更说明;
    • 要么提供可预测的免交互路径。
  4. 确认 CI 中完整 pytest 与 mypy 实际通过,而不只依赖 PR 描述。
  5. 说明或撤销 hello_robothello_robot_test 这一无直接关联的测试改名,排除测试隔离问题被掩盖的可能。

总结: PR 的产品方向正确,异常恢复、身份说明、敏感信息保护和兼容性测试都明显提升了首次配网体验;但扫描任务生命周期和“10 秒”超时契约仍有实际工程缺口,建议修复后合并。

将 setup 蓝牙扫描的十秒上限定义为单一代码常量并显式传递给 provisioning 层,确保进度文案与真实行为使用同一契约。扫描等待被取消或异常中断时,主动取消并回收后台任务,避免残留蓝牙扫描占用后续重试。

明确 Wi-Fi 确认兼容策略:纯引导流程等待用户确认 Connected;显式提供 pairing-code 时仍展示联网未验证说明,但不增加 Enter 阻塞。补充扫描超时传参、任务回收和全参数 TTY 无新增提示测试。

验证相关 126 项测试、mypy 与 diff check 通过。
@Mr-KID-github

Copy link
Copy Markdown
Contributor Author

已在 1a05533 处理扫描审查条件:十秒上限改为单一常量并显式传入 scan_devices;取消/异常时主动 cancel + await 回收扫描任务;纯引导模式等待 Connected 确认,显式提供 --pairing-code 时保留警告但不新增 Enter 阻塞。新增实际超时传参、扫描任务回收和全参数 TTY 兼容测试。相关 126 项测试、mypy 与 diff check 通过。hello_robot_test 是用户本次真实复现名,用于锁定该输入生成 local.hello_robot_test,并非规避测试隔离。

@github-actions

Copy link
Copy Markdown

🤖 Luxiao PR 审查报告

🤖 PR 审查报告

PR: fix(cli): 补齐配网异常恢复与自动 ID 说明
变更: 14 个文件,+1143 / -52


维度一:代码质量

🏗️ 架构视角 — ✅

整体分层合理:

  • 蓝牙平台异常继续由 provisioning 层识别并转换为领域异常,例如新增的 BluetoothUnsupportedError
  • cli.py 只负责把领域异常转换为面向用户的恢复步骤,没有把平台错误判断进一步散落到 CLI。
  • RobotSetupErrorRobotPairingError 将输入错误、配网错误、Runtime 配对错误分开,避免继续使用宽泛的 ValueErrorCliError 掩盖内部缺陷。
  • ValueError 不再被无差别转换为普通 CLI 错误,有助于暴露真实编程错误。
  • _scan_setup_devices 对异步任务的取消和回收处理完整,没有遗留后台扫描任务。

需要留意两个兼容性点:

  1. BleakBluetoothNotAvailableReason.NO_BLE_CENTRAL_ROLE 依赖 Bleak 版本。应确认项目最低支持版本已经包含该枚举,或者依赖已被明确约束;否则旧环境可能在异常映射阶段再次抛出属性错误。
  2. robot setup 内部直接复用 pair_robot(),随后根据返回码重新包装错误。当前 diff 中的失败测试主要通过 monkeypatch 模拟 pair_robot,尚未完整证明真实失败路径不会先输出旧错误,再被 setup 层重复包装。

以上属于需要确认的兼容边界,不构成当前 diff 中已经确定的架构缺陷。


🎨 产品视角 — ✅

这是本 PR 最有价值的部分。

原来的单行 JSON 对普通用户几乎没有恢复价值;现在针对以下状态提供了明确、可执行的下一步:

  • 蓝牙关闭或无适配器
  • 不支持 BLE Central
  • 系统权限拒绝
  • 未发现机器人
  • 多设备标识冲突
  • 蓝牙连接超时
  • 固件响应超时
  • Wi-Fi 配置被拒绝
  • 配网协议不兼容
  • Runtime 配对失败
  • 用户取消

交互改进也基本合理:

  • 扫描前明确提醒打开电脑蓝牙。
  • 10 秒扫描期间输出进度点,避免用户误以为命令卡死。
  • 明确区分 Device ID 和兼容用 Bluetooth ID。
  • 旧固件没有 Device ID 时说明可能需要升级,而不是让用户自行猜测。
  • 将“凭据已保存”和“Wi-Fi 已连接”明确区分,修正了原提示容易造成的错误预期。
  • 对 Runtime 配对错误中的六位配对码做脱敏处理。
  • app init 文档明确解释 local.<项目名> 是稳定升级身份,并提供旧 CLI 的识别方法。

有一个非阻塞的产品风险:

当用户显式传入 --pairing-code 时,CLI 会跳过 Wi-Fi Connected 的确认并立即执行 pair_robot()。而文档同时说明机器人需要先释放 BLE 再连接 Wi-Fi。如果 pair_robot() 的等待机制不能覆盖机器人切网耗时,这类显式参数调用仍可能出现竞态失败。建议确认真实 pair_robot() 会持续等待设备上线,而不是只进行一次即时请求。


📏 规范视角 — ✅

规范性整体较好:

  • 新异常名称准确,继承关系清晰。
  • 用户可恢复错误和非预期内部错误被明确区分。
  • 新增函数职责单一:
    • _print_robot_setup_failure
    • _print_robot_pairing_failure
    • _scan_setup_devices
    • _confirm_robot_wifi_connected
    • _redact_pairing_codes
  • 类型标注完整,改动声明已通过 mypy。
  • 中英文文档基本同步。
  • 测试覆盖了退出码、TTY/非 TTY 输出、异常分类、任务取消、敏感信息脱敏和文档内容。

测试方面有两点可以继续加强:

  1. 配对失败测试通过 monkeypatch 直接抛出 CliError,没有覆盖真实 pair_robot() 失败时是否已经向 stderr 输出内容。建议增加一条更接近真实调用链的测试,防止出现“旧 JSON + 新恢复说明”双重输出。
  2. 文档测试主要是关键词存在性断言,只能防止内容被完全删除,不能验证中英文状态矩阵是否真正一致。这不是本 PR 的阻塞项,但后续维护时容易出现两份文档语义漂移。

另外,test_app_init_derives_metadata_without_prompts 将测试目录从 hello_robot 改为 hello_robot_test,功能上没有问题,但与本次配网错误恢复的关系较弱。如果只是为了覆盖下划线 ID 派生规则,建议通过测试名称或注释体现目的。


⚠️ 损伤视角 — ⚠️

没有发现明确的安全问题或敏感信息泄露,反而新增了六位配对码脱敏,方向正确。

主要回归风险如下:

1. Bleak 版本兼容风险

新增枚举成员必须与项目声明的最低 Bleak 版本一致。完整测试在当前环境通过,只能证明当前环境兼容,不能证明所有受支持安装环境兼容。

建议合并前确认依赖约束;如果最低版本不保证该成员存在,应升级最低版本或增加兼容判断。

2. 交互流程增加了一个确认步骤

未显式提供 --pairing-code 的交互调用现在会新增:

Press Enter after the robot shows Wi-Fi Connected:

这是合理的产品改动,但属于真实的交互行为变化。任何通过伪终端自动驱动 robot setup 的外部脚本都可能因此阻塞。非交互模式和显式 --pairing-code 模式没有增加该提示,已经降低了影响范围。

3. 固定 10 秒扫描窗口

robot setup 现在显式传入 10 秒超时。它提高了可预期性,但也改变了原先由 provisioner 默认值控制的行为。真实硬件环境中的 Windows、macOS 蓝牙初始化速度可能不同,单元测试无法证明 10 秒在全部平台都足够。

建议至少在发布说明中把它视为可观察行为;如果后续收到慢设备反馈,应考虑配置项或更宽松的上限。

4. 描述与实际输出边界应保持精确

PR 描述中的“robot setup 不再向普通用户输出单行 JSON 错误”对交互终端成立,但非交互模式仍明确保留 JSON,这是合理的兼容策略。文档和发布说明最好明确写成:

交互式 robot setup 输出恢复指引;非交互调用继续保留结构化错误。

避免用户将其理解为所有调用方式都取消 JSON。


维度二:意图分析

🎯 意图提炼

本 PR 的核心意图是把 watcherobot robot setup 从“暴露底层错误的技术命令”提升为真正面向普通用户的配网向导,同时补齐旧固件 Device ID 缺失和旧版 app init CLI 的识别说明。

它还在不破坏非交互结构化错误合同的前提下,改善了交互终端的错误恢复、状态反馈和敏感信息保护。

🔀 偏离检测

整体与项目方向一致,没有明显功能蔓延。

配网异常恢复、Device ID 说明、中英文文档和对应测试属于同一个用户上手问题域。app init 自动 ID 文档与蓝牙配网不是同一执行链,但都在解决“用户误用了旧版 CLI 后产生困惑”的背景问题,仍可接受。

需要指出的是,本 PR 中 app init 部分主要是文档澄清和测试数据调整,并没有新增自动 ID 的生产代码。因此标题和发布说明不应让用户误以为自动 ID 功能是在本 PR 才实现的;更准确的表述应是“补充自动 ID 行为及旧 CLI 识别说明”。


Merge 建议

✅ 建议合并

理由:

  • 解决了真实且高频的首次配网可用性问题。
  • 异常分类和 CLI 展示层边界清楚。
  • 未牺牲非交互调用的结构化错误兼容性。
  • 对密码和配对码的保护方向正确。
  • 中英文文档、排障文档和测试同步充分。
  • 已声明完整 pytest 和 mypy 通过。
  • 当前 diff 中没有发现确定的功能阻塞或安全问题。

建议合并前完成以下确认,其中第一项优先级最高:

  1. 确认最低支持的 Bleak 版本包含 NO_BLE_CENTRAL_ROLE;不满足时调整依赖约束或兼容实现。
  2. 确认真实 pair_robot() 失败路径不会先打印旧 JSON,再由 setup 层输出恢复说明。
  3. 确认 pair_robot() 的等待时间能够覆盖显式 --pairing-code 场景下机器人从 BLE 切换到 Wi-Fi 的耗时。
  4. 将发布说明中的输出变化限定为“交互式终端”,避免与保留的非交互 JSON 合同产生歧义。

总结: 这是一次方向正确、实现扎实的 CLI 可用性修复,建议合并;合并前应重点确认 Bleak 最低版本兼容性和真实配对失败调用链。

- 使用蓝、绿、黄、红、青分别表达进行中、成功、待确认、失败和设备标识

- 仅在交互式终端启用颜色,支持 NO_COLOR 与 FORCE_COLOR,并保持重定向输出无 ANSI 控制码

- 将 colorama 声明为直接依赖,自动适配 Windows PowerShell 控制台

- 补充成功、失败、禁色场景测试以及中英文 CLI 文档

验证:139 项相关测试通过;mypy、pip check 与 git diff --check 通过
将配网成功语义从凭据已保存调整为机器人已确认联网。SDK 在收到 cfg.wifi.set 应答后继续监听同一蓝牙会话,通过 command_id 和 SSID 关联状态,直到 connected 或明确失败。

CLI 以语义颜色展示发送、连接中和连接成功状态;针对密码错误、找不到网络、固件超时及 SDK 侧保护超时提供不同恢复建议,并移除依赖用户手动确认机器人屏幕的步骤。同步更新中英文 CLI 与蓝牙配网文档。

测试覆盖连接中到成功、三类固件终态、SDK 保护超时、消息相关性、敏感信息不泄漏、清理流程和 setup 交互;全量 pytest、mypy 与 pip check 均通过。
@Mr-KID-github

Copy link
Copy Markdown
Contributor Author

已补齐 ESP32 ↔ SDK 的 Wi-Fi 联网终态闭环,并完成 COM20 实机端到端验收。

配套固件 PR:orulink-ai/WatcheRobot_esp32#165

本 PR 新增:

  • SDK 在 cfg.wifi.set ACK 后继续等待 connected / auth_failed / network_not_found / timeout
  • 通过 command_id 与 SSID 关联本次配网状态
  • CLI 彩色展示连接进度,并按密码错误、找不到网络、超时给出不同恢复建议
  • 移除“请用户观察机器人屏幕并手动按 Enter 确认”的旧流程
  • 更新中英文 CLI 和蓝牙配网文档

实机验证:COM20 机器人连接 orulink 时故意输入错误密码,ESP32 报 reason=15 并回传 auth_failed,SDK 随即显示“检查 Wi-Fi 密码”的恢复指引。全量 pytest、mypy、pip check 均通过。

@Mr-KID-github Mr-KID-github changed the title fix(cli): 补齐配网异常恢复与自动 ID 说明 feat(cli): 完善机器人配网引导与 Wi-Fi 状态闭环 Aug 18, 2026
@github-actions

Copy link
Copy Markdown

🤖 Luxiao PR 审查报告

🤖 PR 审查报告

PR: fix(cli): 补齐配网异常恢复与自动 ID 说明
变更: 21 个文件,+1639 / -92


维度一:代码质量

🏗️ 架构视角 — 🔴

CLI 引导层的拆分整体合理:

  • RobotSetupErrorRobotPairingError 将引导流程中的预期错误与内部异常区分开。
  • 蓝牙平台异常被映射为稳定的领域错误,避免直接暴露 Bleak 的平台细节。
  • robot setup 与底层 bluetooth ... 命令继续承担“人机引导”和“自动化接口”两种不同职责。
  • 扫描任务在取消时会主动 cancel 并回收,异步资源处理是完整的。

但该 PR 实际上不只是 CLI 修复,而是修改了核心 provisioning API 和固件协议语义:

ProvisioningState = Literal["connected"]

替换了原来的:

ProvisioningState = Literal["credentials_saved"]

同时 ProvisioningResult 新增必填的 wifi 字段,并且 provision_wifi() 从“收到配置 ACK 即成功”改成“必须等待 Wi-Fi 终态才成功”。

这属于 SDK 公共接口和设备协议行为的破坏性变更,不应隐藏在 fix(cli) 类型的 PR 中。它至少涉及:

  1. SDK 公共返回模型变更;
  2. CLI JSON 输出合同变更;
  3. 固件最低兼容能力变更;
  4. 配网成功定义变更;
  5. 默认执行耗时从 ACK 后立即返回变为最多再等待 25 秒。

建议将核心协议升级单独拆分,明确兼容策略、版本边界和迁移说明。当前范围过大,无法作为单纯的 CLI 异常恢复修复合并。

阻断问题:状态关联并不严格

wait_for_wifi_terminal() 中:

if message.command_id not in {None, command_id}:
    continue
if message.ssid is not None and message.ssid != ssid:
    continue

这意味着以下事件都可能被接受:

  • 没有 command_id 的状态;
  • 没有 ssid 的状态;
  • 同时没有 command_idssid 的状态。

但文档明确声称:

status events carry the originating Wi-Fi command_id for attempt correlation

以及:

followed by a correlated evt.wifi.status

实现与协议承诺不一致。缺失 command_id 的历史状态、固件启动状态或其他非当前尝试状态,可能被误认为本次配置结果。

既然当前代码已经要求 ACK 必须具有 command_id

if ack.command_id is None:
    raise ProvisioningProtocolError(...)

那么后续终态也应严格要求:

if message.command_id != command_id:
    continue

如果确实需要兼容旧固件的无 command_id 事件,应明确设计兼容分支,而不是在主关联逻辑中静默放宽。


🎨 产品视角 — ⚠️

用户体验方向正确,主要改善明显:

  • 不再向普通用户显示单行 JSON;
  • 对蓝牙关闭、无适配器、权限拒绝、BLE Central 不支持等情况提供独立说明;
  • 扫描过程显示进度,避免用户误认为命令卡死;
  • Device ID 缺失时明确提示固件升级可能性;
  • Wi-Fi 认证失败、网络不存在和超时具有不同恢复步骤;
  • Runtime 配对失败不会泄露六位配对码;
  • 取消流程统一返回 130;
  • 颜色只是辅助信号,文字和退出码仍然保留;
  • NO_COLORFORCE_COLOR 行为清楚;
  • app init 的稳定 ID 解释合理,不拼用户名、时间戳或随机值是正确的产品决策。

但存在两个重要产品风险。

1. 旧固件可能从“可以配网”退化为“必然超时”

此前固件只要返回 cfg.wifi.set ACK,SDK 就返回 credentials_saved。现在必须继续收到:

evt.wifi.status + 当前 command_id + terminal state

否则 25 秒后抛出:

WifiConnectionFailedError("timeout")

PR 一方面强调兼容旧固件,并继续允许 Bluetooth ID fallback;另一方面又可能要求固件实现新的 Wi-Fi 终态协议。若缺少 Device ID 的旧固件也缺少带 command_id 的终态事件,那么用户能够扫描、选择设备、写入凭据,却最终收到“连接超时”。

当前测试只覆盖了支持新协议的 FakeConnection,缺少“旧固件只返回 ACK、不返回终态事件”的兼容测试。

合并前必须明确:

  • 支持的最低固件版本;
  • 缺少终态事件时是兼容旧语义,还是明确拒绝旧固件;
  • 如果拒绝,应提示“固件协议过旧”,不能误报为 Wi-Fi 超时;
  • Device ID fallback 与 Wi-Fi 终态协议是否属于同一固件版本边界。

2. 文档声称结构化输出合同不变,但实际已经变化

中英文文档写道:

lower-level watcherobot bluetooth ... commands, whose structured output contract is unchanged

但代码和测试明确将输出从:

{
  "state": "credentials_saved"
}

改为类似:

{
  "state": "connected",
  "wifi": {
    "state": "connected"
  }
}

这是直接的 JSON 合同变更,可能破坏依赖以下判断的脚本:

result["state"] == "credentials_saved"

该文档表述必须修正,或者通过兼容字段/版本化输出真正维持原合同。


📏 规范视角 — ⚠️

做得较好的部分:

  • 新异常类型命名清晰;
  • 错误消息没有暴露 Bleak 平台原始信息;
  • Wi-Fi 密码仍未进入命令行参数或输出;
  • 配对码脱敏有独立函数和测试;
  • 新增类型标注基本完整;
  • 中英文文档同步程度较高;
  • 测试覆盖了颜色、取消、扫描回收、非交互 JSON、不同恢复路径和 Wi-Fi 终态;
  • mypy 和 pytest 验证范围从描述看较完整。

需要修正的问题:

1. PR 类型和实际范围不匹配

标题是:

fix(cli)

但变更包含:

  • 公共 SDK 数据模型;
  • Provisioning 成功语义;
  • 固件协议;
  • JSON 输出合同;
  • 新运行时依赖;
  • 默认超时行为。

更接近 provisioning 协议升级或 breaking change。应拆分,或者至少修改标题、描述和版本影响说明。

2. on_status 回调缺少异常边界说明

wait_for_wifi_terminal() 直接同步调用:

on_status(status)

如果 SDK 调用方的回调抛出异常,整个配网会被中断并进入清理。若这是预期行为,应在 API 文档中明确;若只是进度通知,则更稳妥的设计是不让展示层回调破坏协议流程,或者限定/包装回调异常。

此外这是同步回调,耗时回调会阻塞 BLE 事件循环,也应在接口文档中说明回调必须快速返回。

3. 缺少公共 API 兼容性测试

现有测试全部被直接更新为 connected,但没有测试旧调用方式会发生什么,也没有版本化迁移验证。对于公开 SDK,单纯修改所有测试期望不能证明兼容性。

4. 排障文档仍混用旧语义

docs/troubleshooting.md 仍保留:

Wi-Fi credentials saved but the robot shows Offline...

而其他文档已经宣称当前命令只有收到 connected 才成功。需要说明该提示来自旧版 CLI/旧固件,否则用户会看到相互冲突的语义。


⚠️ 损伤视角 — 🔴

存在明确的兼容性和误判风险。

阻断问题 1:公开 JSON 输出合同被破坏

代码、模型和测试都证明输出合同已经变化,但文档声称“不变”。这会直接损伤已有自动化调用方。

至少应选择一种方案:

  • 保留 credentials_saved,另加 Wi-Fi 最终状态字段;
  • 引入新的结果版本;
  • 提供明确的 major/minor 版本升级和迁移说明;
  • 将等待连接的行为放入新的 API,而不是改变现有 provision_wifi() 语义。

阻断问题 2:旧固件兼容性未得到证明

新增的 25 秒终态等待,使只支持 ACK 的固件从成功路径变成超时路径。PR 正在面向旧固件用户增加说明,却可能同时破坏这些用户的实际配网能力。

必须增加至少以下测试:

  1. 旧固件只返回 ACK,不返回 evt.wifi.status
  2. 无 Device ID 的固件是否支持新的终态事件;
  3. 收到不带 command_id 的旧状态时如何处理;
  4. 收到其他 command_idconnected 后,再收到当前命令失败状态;
  5. 收到当前命令 ACK 前后的陈旧 Wi-Fi 状态;
  6. 终态事件缺少 SSID、command ID 或同时缺少两者;
  7. 新 SDK 配合旧固件时应显示的实际错误。

阻断问题 3:相关性判断可能接受非当前尝试的终态

允许 command_id=None 与“严格关联”目标相冲突,可能造成:

  • 错误报告本次连接成功;
  • 错误进入 Runtime 配对;
  • 将旧网络的 connected 状态归因于刚输入的新凭据;
  • 将无关的 auth_failed 归因于本次配置。

这是配网正确性问题,不只是文档问题。

一般风险:额外运行时依赖

为 ANSI 颜色新增:

colorama>=0.4.6,<1

用途主要是 Windows 控制台兼容。依赖本身成熟、风险不高,但这是核心包全局依赖,应确认 lockfile、构建产物及支持平台安装验证均已同步。提供的 Diff 中未看到锁文件或安装矩阵结果。


维度二:意图分析

🎯 意图提炼

该 PR 的核心意图是把首次配网从“底层异常和 JSON 直接暴露给用户”升级为具有进度、状态区分和可执行恢复步骤的完整引导流程,并补充旧固件 Device ID 与 app init 自动稳定 ID 的文档说明。

同时,它进一步把“Wi-Fi 凭据已保存”升级为“机器人已经确认联网”才算 provisioning 成功。

🔀 偏离检测

用户体验改善与项目方向一致,特别适合面向普通用户的硬件 CLI。

但实现出现明显范围扩张:

  • “错误展示优化”扩展成“Provisioning 成功语义重定义”;
  • “旧固件 Device ID 说明”扩展成“新固件 Wi-Fi 终态协议依赖”;
  • “普通用户不看 JSON”伴随底层自动化 JSON 合同变化;
  • fix(cli) 同时修改 SDK 公共类型和协议兼容边界。

其中协议升级本身可能是正确方向,但不应作为附带改动隐藏在 CLI UX 修复中。当前 PR 缺少对 breaking change、旧固件兼容性和自动化迁移的明确决策。

建议拆成:

  1. CLI 错误恢复、颜色、扫描进度及 Device ID/app init 文档;
  2. Wi-Fi 终态协议与 ProvisioningResult API 升级;
  3. 旧固件兼容或最低固件版本策略。

Merge 建议

🔴 不建议合并

合并前至少需要完成以下事项:

  1. 修复 Wi-Fi 终态关联逻辑

    • 不应默认接受 command_id=None
    • 如果需要兼容旧固件,必须设计明确、可测试的兼容策略;
    • 实现必须与“correlated status”的文档承诺一致。
  2. 解决公开 API 和 JSON 输出兼容问题

    • credentials_saved → connected 以及新增 wifi 字段属于合同变化;
    • 不能继续声称底层命令结构化输出合同未变化;
    • 明确采用兼容字段、版本化接口或正式的 breaking-change 迁移方案。
  3. 验证旧固件行为

    • 增加 ACK-only 固件测试;
    • 明确无 Device ID 固件是否支持 Wi-Fi 终态协议;
    • 旧固件不支持时应给出固件版本不兼容提示,而不是误报普通网络超时。
  4. 控制 PR 范围

    • 优先拆分 CLI UX 修复与核心 provisioning 协议升级;
    • 如果不拆分,应调整 PR 标题、描述和发布影响等级,明确这是 SDK 行为变更。
  5. 补齐文档一致性

    • 修正“structured output contract is unchanged”;
    • 区分旧版 Wi-Fi credentials saved 与新版 connected 语义;
    • 写明最低固件版本或能力检测方式。

总结: CLI 的恢复引导和文档体验明显改善,但当前 PR 同时引入了未妥善处理的公共 API 破坏、旧固件兼容风险和不严格的 Wi-Fi 状态关联,现阶段不应合并。

@Mr-KID-github
Mr-KID-github merged commit 9b152df into main Aug 18, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant