romanbo 1.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. romanbo-1.1.1/CHANGELOG.md +282 -0
  2. romanbo-1.1.1/CONTRIBUTING.md +98 -0
  3. romanbo-1.1.1/LICENSE +21 -0
  4. romanbo-1.1.1/MANIFEST.in +16 -0
  5. romanbo-1.1.1/PKG-INFO +210 -0
  6. romanbo-1.1.1/README.en.md +239 -0
  7. romanbo-1.1.1/README.md +167 -0
  8. romanbo-1.1.1/docs/CLI.md +112 -0
  9. romanbo-1.1.1/docs/FINDINGS.md +277 -0
  10. romanbo-1.1.1/docs/INSTALL.md +117 -0
  11. romanbo-1.1.1/docs/LOAD_LIMITING.md +67 -0
  12. romanbo-1.1.1/docs/PROTOCOL.md +71 -0
  13. romanbo-1.1.1/docs/README.md +52 -0
  14. romanbo-1.1.1/docs/RELEASING.md +162 -0
  15. romanbo-1.1.1/docs/RSC.md +68 -0
  16. romanbo-1.1.1/docs/SAFETY.md +48 -0
  17. romanbo-1.1.1/docs/SERVO_SPEC.md +962 -0
  18. romanbo-1.1.1/docs/SERVO_TEST_PLAN.md +930 -0
  19. romanbo-1.1.1/docs/TESTING.md +79 -0
  20. romanbo-1.1.1/docs/TEST_REPORTS.md +77 -0
  21. romanbo-1.1.1/docs/WEBUI.md +107 -0
  22. romanbo-1.1.1/docs/api.md +58 -0
  23. romanbo-1.1.1/docs/evidence/README.md +43 -0
  24. romanbo-1.1.1/mkdocs.yml +102 -0
  25. romanbo-1.1.1/pyproject.toml +80 -0
  26. romanbo-1.1.1/romanbo/__init__.py +54 -0
  27. romanbo-1.1.1/romanbo/__main__.py +6 -0
  28. romanbo-1.1.1/romanbo/cli.py +1368 -0
  29. romanbo-1.1.1/romanbo/golden.py +172 -0
  30. romanbo-1.1.1/romanbo/joints.py +272 -0
  31. romanbo-1.1.1/romanbo/protocol.py +849 -0
  32. romanbo-1.1.1/romanbo/py.typed +1 -0
  33. romanbo-1.1.1/romanbo/robot.py +661 -0
  34. romanbo-1.1.1/romanbo/rsc.py +456 -0
  35. romanbo-1.1.1/romanbo/servo.py +810 -0
  36. romanbo-1.1.1/romanbo/transport.py +552 -0
  37. romanbo-1.1.1/romanbo/webui.py +689 -0
  38. romanbo-1.1.1/romanbo/webui_page.py +635 -0
  39. romanbo-1.1.1/romanbo.egg-info/PKG-INFO +210 -0
  40. romanbo-1.1.1/romanbo.egg-info/SOURCES.txt +54 -0
  41. romanbo-1.1.1/romanbo.egg-info/dependency_links.txt +1 -0
  42. romanbo-1.1.1/romanbo.egg-info/entry_points.txt +2 -0
  43. romanbo-1.1.1/romanbo.egg-info/requires.txt +11 -0
  44. romanbo-1.1.1/romanbo.egg-info/top_level.txt +1 -0
  45. romanbo-1.1.1/setup.cfg +4 -0
  46. romanbo-1.1.1/tests/test_cli.py +814 -0
  47. romanbo-1.1.1/tests/test_doc_led.py +151 -0
  48. romanbo-1.1.1/tests/test_load.py +200 -0
  49. romanbo-1.1.1/tests/test_mock.py +273 -0
  50. romanbo-1.1.1/tests/test_protocol.py +296 -0
  51. romanbo-1.1.1/tests/test_rsc.py +148 -0
  52. romanbo-1.1.1/tests/test_servo.py +51 -0
  53. romanbo-1.1.1/tests/test_speed.py +258 -0
  54. romanbo-1.1.1/tests/test_standalone.py +309 -0
  55. romanbo-1.1.1/tests/test_torque_level.py +99 -0
  56. romanbo-1.1.1/tests/test_webui.py +772 -0
@@ -0,0 +1,282 @@
1
+ # 更新日志
2
+
3
+ 本文件记录本项目的所有重要变更。
4
+ 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
5
+ 版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
6
+
7
+ ## [1.1.1] - 2026-09-27
8
+
9
+ ### 新增
10
+ - **`ports --fix`:串口权限一键放权**(Linux):整条权限流程里唯一需要 root 的一步是"装
11
+ udev 规则",其余(判定成因、生成规则、复验)都能自动。现在按设备**真实的 VID:PID**
12
+ 生成规则(不依赖你去仓库里找文件)、把要写入的内容与要执行的操作先打出来、问一次、
13
+ 走 `sudo`、最后复验。**已经能用时什么都不做**(不写系统文件、不触发 udev);
14
+ 「已被占用」不算权限问题、非 USB 口(没有 VID:PID)不硬塞规则;非交互环境配 `--yes`
15
+ - 规则模板与仓库文件**同源**(`transport.UDEV_RULE_TEMPLATE` / `udev_rule_for` /
16
+ `parse_usb_ids`),并有测试守住一致性,避免两边漂移
17
+
18
+ ### 修复
19
+ - **设备消失的判定补上「设备节点是否还在」**(`SerialTransport._ensure_alive`):真机实测
20
+ 拔掉适配器后读写**只会静默超时**(`in_waiting` 返回 0、`read` 返回空、`write` 甚至不报错),
21
+ 按 errno 根本抓不住——上层只看到"舵机未应答",`is_open` 一直为真、控制台以为还连着
22
+ (2026-09-27 真机日志:`TimeoutError: 等待回包超时` + `is_open=True` +`lost_reason=None`)。
23
+ 现在 POSIX 下每次读写顺带 `stat` 一次设备节点(微秒级,相对 2 ms 轮询可忽略),
24
+ 节点消失即判定断开并把 `OSError` 抛给上层;Windows 的端口名不是文件系统路径,不做该检查
25
+ - **规则文件名改为 `60-` 前缀**(原 `99-`)——**uaccess 真正生效的关键**:udev 按文件名顺序
26
+ 执行规则,而执行 `uaccess` 内建的是系统规则 `73-seat-late.rules`,`99-` 排在其后,于是
27
+ tag 加上了(`udevadm info` 里 `CURRENT_TAGS=:uaccess:` 清清楚楚)**ACL 却永远不生成**,
28
+ 症状是"规则装了、tag 有了,还是 `Permission denied`"(2026-09-27 实测)。仓库文件随之
29
+ 改名 `deploy/60-romanbo-usb-serial.rules`,并有测试守住"名字必须排在 73 之前"
30
+ - `ports --fix` 三处配套:写入新路径、清理早期遗留的 `99-*` 规则(避免两份并存)、
31
+ trigger 后加 `udevadm settle` + 复验重试(trigger 只是排队事件,立刻探测会读到旧权限
32
+ ——第一次就是这么误报"仍然打不开"的)
33
+ - **`deploy/99-usb-serial.rules` 加 `TAG+="uaccess"`**:原规则只给 `0660 + dialout`,而
34
+ `dialout` 组要**注销重新登录**才进会话——"装了规则却还是 Permission denied"多半是这个
35
+ (2026-09-27 就卡在这)。`uaccess` 由 systemd-logind 给当前登录的桌面用户补一条 ACL,
36
+ reload/trigger 后**立刻生效**。`docs/INSTALL.md` 同时写明两个顺序坑:`chmod` 之后再跑
37
+ `udevadm trigger` 会被按规则覆盖回 `0660`;只 `usermod -aG dialout` 而没重新登录同样
38
+ 不生效(`id -nG | grep dialout` 一眼可查)
39
+ - **控制台端口下拉改为过滤占位口**(前端):`/api/ports` 仍返回全部(数据不丢),但下拉框
40
+ 只列已识别的设备,状态行报「已隐藏 N 个没有接硬件的占位口」——原先 33 项里得在 32 个
41
+ `ttyS*` 中找 FT230X
42
+ - **控制台的端口下拉沿用同一口径**(`/api/ports` 已识别设备排前;页面不再一律写"被占用"):
43
+ 原先 32 个占位口会把 FT230X 挤到列表最后,且**权限不足被写成"被占用"**(与 `ports` 当初
44
+ 那个误导同源)。现在按 `reason` 显示"权限不足 / 已被占用 / 打不开",没有 `description`
45
+ 的标为"没有接硬件的占位口"。GUI 里**不隐藏**(下拉框得能选到任何端口),只排序
46
+ - **`ports` 默认不再列出没有接硬件的占位口**(Linux 上 `ttyS0..ttyS31` 这类主板遗留串口,
47
+ 真机上 32 个):它们会以 100 多行噪音把真正的适配器埋掉(FT230X 排在第 35 位),而且对
48
+ "没有硬件"的口给 `dialout` / `chmod` 建议属于误导。现在默认只列**已识别**的设备,末尾
49
+ 报「已隐藏 N 个…加 `--all` 显示」(**不静默丢弃**),`--all` 看全;排查建议也从"每个端口
50
+ 各打一份"改为**按原因汇总一次**(`--json` 与文本同一口径)
51
+ - **管道被下游提前关闭时安静退出**(`... | head` / `| grep -q`):原先写入撞上 `EPIPE`
52
+ 会打印整段 `BrokenPipeError` 栈回溯——正常用法下的噪音
53
+ - **设备消失(拔插/重枚举)后不再继续自报「已连接」**(`SerialTransport._mark_lost`,
54
+ `webui.WebConsole.state` 新增 `lost_reason`):设备被拔掉后**句柄仍是"打开"状态**
55
+ (`is_open` 为真),控制台于是继续显示 `connected: true` 并保留陈旧的在线列表,直到
56
+ 发命令才报 `Input/output error`——这期间用户会以为机器人还能控。现在读写遇到"设备消失"
57
+ 类 errno(`EIO` / `ENXIO` / `ENOENT` / `ENODEV` / `ESTALE` / `EBADF`)会**主动关闭**
58
+ 并记下原因,`connected` 立刻变假,HTTP 错误里补一句「串口已断开…请重新连接」。
59
+ **普通读超时不算断线**(舵机偶尔漏答是常态)
60
+ - **控制台连不上串口时给出可照做的处置**(`webui.WebConsole.connect`,新增
61
+ `transport.port_error_hint`):原先页面上只有 `SerialException: [Errno 13] Permission
62
+ denied`,看不出是"该去加 `dialout` 组"还是"该去关占用程序"。现在按 `errno` 分类后把处置
63
+ 拼进**消息本身**(启动时的自动连接与页面上的「连接」都只打印 `str(exc)`),并且这类失败
64
+ 由 500 改判为 **409**——它是用户可修的问题,不是服务端故障
65
+ - **`ports` 不再把「权限不足」误报成「已被占用」**(`cli.cmd_ports` +
66
+ `transport.list_serial_ports` 新增 `reason` 字段):原先两者共用一句提示("多为权限
67
+ 问题(dialout 组)或已被占用"),遇到**适配器重枚举后新节点没放权**的情况
68
+ (`ttyUSB0` → `ttyUSB1`,权限退回默认的 `0660 root:dialout`)很容易被当成有进程在
69
+ 占用它——实际没有任何进程持有。现在按 `errno` 分流:`EACCES`/`EPERM` → 给出
70
+ `usermod -aG dialout` 与临时 `chmod` 的处置;`EBUSY` → 给出 `lsof` / `fuser` 查持有者。
71
+ **不能按异常类型判断**:pyserial 把权限不足也包成 `SerialException`(`[Errno 13]`),
72
+ 与「已被占用」的异常类型完全相同(真机实测)
73
+
74
+ ### CI
75
+ - **Release 工作流加标签/版本一致性守卫**:标签必须等于 `v` + `romanbo.__version__`,
76
+ 否则构建阶段直接失败——避免把 1.1.0 的包打上 `v1.2.0` 的标签发出去
77
+
78
+ ### 文档
79
+ - **随仓库提供 Linux 串口权限的 udev 规则**(`deploy/99-usb-serial.rules`):`docs/INSTALL.md`
80
+ 的「临时放开权限」一段补上**永久解决**的三条命令(加入 `dialout` 组 + 装规则 + reload),
81
+ 并写明两个让规则静默失效的易错点(`idVendor` 留字面量 `xxxx`、把 `ACTION` 拼成
82
+ `KERNELACTION`——2026-09-27 排查的那台机器上就是这两种写法);同时给出
83
+ `/dev/serial/by-id/` 稳定路径的用法,避免重插后 `ttyUSB0`/`ttyUSB1` 变号
84
+ - **新增[发版与发布](docs/RELEASING.md)**:PyPI Trusted Publishing 首次配置的五处字段、
85
+ GitHub environment 与仓库变量的两个坑(必须建在 Variables、值必须是**小写** `true`)、
86
+ 每次发版的固定流程、发布后验证清单、出错处置(`invalid-publisher`、版本号不可重用、
87
+ yank 的语义)、版本号约定
88
+ - `CONTRIBUTING.md` 的「发布」收敛为速记版 + 指向上面的新页,发布细节不再存两份
89
+ - **「测试记录」独立成页**(`docs/TEST_REPORTS.md`):测试方案只保留用例定义、判定门限与
90
+ 记录模板(该记什么),每轮执行的环境、基线快照、逐条结果、准出结论与当轮新增缺陷
91
+ 移到记录页(实际记了什么)。文档站导航同步按「使用说明 / 规格与验证记录」两组对齐
92
+ - `docs/TESTING.md` 的「真机验证」改为一张指路表(实测数据 / 用例 / 记录 / 证据各去哪看),
93
+ 环境描述统一由 FINDINGS 承载,不再重复
94
+ - **按「使用说明 / 规格与验证记录」重划文档边界**:使用说明(README、INSTALL、CLI、
95
+ WEBUI、RSC、LOAD_LIMITING、SAFETY、PROTOCOL)不再记录开发与测试过程及结果——
96
+ 删掉「验证环境:两个 MOS 舵机串联,ID 8/10,COM3,2026-09-25」这类句子、
97
+ README 的「关键实测结论」表(数据保留在 FINDINGS)、WEBUI 的整节「真机验证记录」、
98
+ LOAD_LIMITING 的实测阈值表与复测叙述、各处的内联实测数值与日期
99
+ - 被移出的内容全部归入[真机实测结论](docs/FINDINGS.md)(新增 §9 控制台验证:
100
+ 扫描/读数/运动/LED 的逐帧报文与到位误差;补扫描耗时与真实 `.rsc` 播放记录)
101
+ - 每篇文档开头标明**本文讲什么 / 不讲什么**,`docs/README.md` 索引改为两张表并写明
102
+ 两类文档的边界;英文 README 同步
103
+ - 顺带修正 `README` / `CLI.md` / 测试计划里会误触发起步涌流的限力示例
104
+ (`--max-load 60` → `100` + `--load-every 3`)
105
+
106
+ ## [1.1.0] - 2026-09-27
107
+
108
+ ### 新增
109
+ - **可视化控制台**:`romanbo/webui.py`(后端)+ `romanbo/webui_page.py`(前端)+
110
+ `romanbo webui` 子命令 —— 只监听本机的 Web 界面:扫描、实时读数、单/多关节运动、
111
+ 参数读写,以及**原始收发报文日志**。只用标准库(`http.server`),页面是单个内联
112
+ HTML(无 CDN、无框架),运行期依赖仍只有 pyserial;带访问令牌、Host 校验,默认只绑
113
+ 回环;报文日志抓在传输层,与串口助手看到的一致。见 `docs/WEBUI.md`
114
+ - `romanbo.transport.list_serial_ports()`:串口枚举与占用探测提成共用函数,`ports`
115
+ 子命令改为调用它(行为不变)
116
+ - `tests/test_webui.py`:30 项后端回归,含访问控制(令牌 / Host)、**前后端接口契约**
117
+ (页面调用的每个接口都必须真实存在)与页面静态一致性(JS 引用的 id 必须存在)
118
+ - **文档站**:`mkdocs.yml`(Material 主题)+ `docs/`,其中 **API 参考由 docstring 自动生成**
119
+ (mkdocstrings),`mkdocs build --strict` 已在 CI 中作为门禁
120
+ - `docs/evidence/`:真机探针日志归档为可独立复核的原始证据,并附证据说明
121
+ - `.github/workflows/docs.yml`:PR 校验 + 主分支自动部署到 GitHub Pages
122
+ - `.github/workflows/release.yml`:推送 `v*` 标签即构建、校验、从 sdist 冒烟后
123
+ 经 Trusted Publishing 发布到 PyPI 并创建 GitHub Release(配置见 `CONTRIBUTING.md`)
124
+ - **命令行限力间隔 `--load-every N`**(`move` / `jog` / `angle` / `sweep` / `play`):
125
+ 起步涌流只是**几毫秒的瞬态**,而 `move_at_speed` 的采样点就在它峰上,取 `3` 可跳过
126
+ 它(阈值 `100` 左右即可,沿用默认 `1` 时阈值需 >150)。同时给
127
+ `Servo.set_angle / rotate / sweep` 补上 `load_check_every` 参数——此前这三处根本没有
128
+ 该参数,传进去会被直接忽略(只有 `move_at_speed` 生效)
129
+ - `README.en.md`:英文版项目说明,与中文版互相链接
130
+
131
+ ### 变更
132
+ - **docstring 规范化**:RST 指令(`.. note::` / `.. warning::` / `.. math::`)改为 Markdown 等价写法,
133
+ Sphinx 角色(`:class:` / `:meth:` 等 61 处)改为行内代码,使自动生成的 API 参考可读
134
+ - 文档站锚点改用 Unicode slugify,与 GitHub 的锚点规则保持一致(中文标题可正常跳转)
135
+ - `docs/SERVO_SPEC.md` §7 明确与自动生成 API 参考的主从关系(以 docstring 为准)
136
+ - `Servo.__all__` 补上 `LoadLimitExceeded`
137
+ - **主页 README 精简为入口页**(特性 / 安装 / 30 秒上手 / API 速览 / 实测摘要 / 安全提示 / 文档导航),
138
+ 细节全部下沉到 `docs/`:新增 [安装与环境](docs/INSTALL.md)、[命令行参考](docs/CLI.md)、
139
+ [协议速览](docs/PROTOCOL.md)、[真机实测结论](docs/FINDINGS.md)、[工程文件](docs/RSC.md)、
140
+ [软件限力](docs/LOAD_LIMITING.md)、[测试与自检](docs/TESTING.md)、[安全与已知限制](docs/SAFETY.md)
141
+ 八个页面;`docs/README.md` 与文档站导航按「入门 / 协议与硬件 / 接口与数据 / 测试 / 安全」重组
142
+ - **限力机制补上实测机制解释**:`docs/FINDINGS.md` §3 新增单步实验(步长 5/10/20/30 ADC
143
+ 的涌流峰值、以及它只持续十几毫秒的样本序列),`docs/LOAD_LIMITING.md` 据此说明
144
+ 「涌流由**步长**决定」与「两条路径采样点不同(`move`/`play` 在节拍 sleep 之后采样,
145
+ 量的是持续负荷;`jog`/`angle`/`sweep` 发出该步后立刻采样,含涌流)」
146
+ - `docs/CLI.md` 的限力示例改为 `--max-load 100 --load-every 3`(原 `--max-load 60`
147
+ 按实测定会在起步涌流上误触发)
148
+ - **CI action 升到 Node 24 系列**(`checkout` v7、`setup-python` v7、`upload-artifact` v7、
149
+ `download-artifact` v8、`configure-pages` v6、`upload-pages-artifact` v5、`deploy-pages` v5、
150
+ `action-gh-release` v3),消除 "target Node.js 20 but are being forced to run on Node.js 24"
151
+ 弃用告警;已逐个核对跨主版本发布说明(`download-artifact` v5 的破坏性变更只影响"按 ID 下载
152
+ 单个产物",本仓库按 `name` 下载;`upload-pages-artifact` v4+ 默认排除点文件,而本站产物
153
+ `site/` 无点文件)
154
+ - `.github/workflows/docs.yml`:`configure-pages` 仅在非 PR 事件执行(PR 令牌无 Pages 写权限),
155
+ 并注明前置条件——仓库需先启用 Pages,且 `enablement` 不接受 `GITHUB_TOKEN`
156
+
157
+ ### 修复
158
+ - **`TimeoutError` 不再被报成「串口打不开」**:它是 `OSError` 的子类,原先被
159
+ `except OSError` 一并捕获,于是**设备没应答**(ID 不在线上、总线偶发丢帧)时会打印
160
+ 「串口打不开 → 常见原因:权限不足(需要 dialout 组)/ 端口名不对」——把排查方向
161
+ 带偏(本轮自己就被这条提示误导过一次)。现在先拦 `TimeoutError`,给「设备未应答」
162
+ 与正确的排查步骤;设备返回错误帧(如过载)也从栈回溯改为一行「协议错误:…」。
163
+ 退出码仍是 `5`(= 串口/通信错误)
164
+ - **`--json` 的 stdout 现在只有结果**:进度/日志行(「读取起始位置…」、`sweep` 的每步
165
+ 报告、`play` 的逐帧行、`--frames` 的原始帧)一律改走 **stderr**,
166
+ `python -m romanbo --json … | jq` 可以直接用;顺带给 `play --json` 补上结果
167
+ (原先只打印进度行,`--json` 下 stdout 是空的)
168
+ - **确认类命令补上 `--json` 结果**:`torque` / `led` / `pid` / `limit` / `param` /
169
+ `wheel` / `sync` / `calib` / `set-id` / `reset` / `export` 原先在 `--json` 下仍只打
170
+ 一行中文,机器无法消费。现在统一经 `_result()` 输出,且**多 ID 命令汇总成一份**
171
+ (`pid` / `limit` 的键是 ID 字符串,值为回读确认后的实测值),
172
+ 因此「stdout 只有一份 JSON 文档」的契约对所有命令成立
173
+ - **节拍与超时改用 `time.perf_counter()`**:`Servo.move_at_speed` 的节拍预算、
174
+ `sweep` 的 `elapsed_s`、`Robot._wait_for` 与两个 `read_available` 的超时原先都用
175
+ `time.monotonic()`,而 Windows + CPython <= 3.12 下它粒度约 15.6 ms——100 ms 的节拍
176
+ 会有 ±15% 误差,直接吃掉文档承诺的 ±4% 角速度(与 `SerialTransport.write` 同一类问题)
177
+ - **紧急停止不再排队等设备锁**(`webui.WebConsole.stop_all`):它原先与 `goto` /
178
+ `multi_move` 共用一把设备锁,而运动会把锁持有整个动作过程(慢速长距离可达数十秒),
179
+ 于是"紧急停止"要等运动自己走完才生效——安全按钮失效。现在直接下发 `SET_TORQUE`,
180
+ 每帧只短暂占用收发锁,能**插进运动节拍之间**生效:实测运动进行中调用返回 **0.1 ms**,
181
+ 反事实(等锁)是 **1654 ms**,那段时间舵机一直在动
182
+ - **运动期间实时读数不再冻结**(`webui.WebConsole.servo_live`):读操作也不该取设备锁
183
+ (单次请求的原子性已由 `RomanboRobot` 内部收发锁保证),否则界面读数会在最需要看
184
+ 读数的运动过程中整体停住
185
+ - **位置越界不再静默变成相对运动**(`protocol.build_set_position`):`position` 只占
186
+ 10 位,`d[5]` 的 bit2 是 `relative`、bit3/bit4 是出力档位,而原守卫放到 2047
187
+ (注释还写着"3 位给位置高位")。于是 1024..2047 会置上 relative 位:`1500` 被固件
188
+ 当成相对运动、`2047` 变成"相对 +1023",都是静默的非预期运动。现按真实位宽收紧到
189
+ `0..1023`;`tests/test_torque_level.py` 里固化该错误边界的断言一并改正
190
+ - `Servo._decode_position()` 在回包被截断(数值段不足 2 字节)时不再返回 `body[0]`
191
+ ——那会给出"看着合理"的错值(3 而不是 1012),调用方据此算出的位移、角度、
192
+ `rotate()` 基准全错却没有提示;现在抛 `ProtocolError`
193
+ - `FrameParser` 的 `LEN` 候选顺序改为**整帧长度优先**(舵机请求与回包都是这个语义):
194
+ 原先先试 `declared + 6`,缓冲恰好够长时会把一条完整帧错切成长窗口,凑出校验和
195
+ (约 1/256)就吞掉后续字节,表现为偶发丢帧/串位
196
+ - `Servo.sweep()` 的 `period_ms` 分支改用注入的 `sleep`(原先写死 `time.sleep`,
197
+ 传了 `sleep=` 也照样真阻塞,与步进分支不一致,测试无法压缩时间)
198
+ - HTTP 请求体加上限(1 MiB):声明超大 `Content-Length` 时立即报错,不再让线程挂在
199
+ `read()` 上(此前是唯一没有上限的入口)
200
+ - **CLI 输出编码**:Windows 上输出被重定向时 stdout 用区域编码(en-US 为 cp1252),
201
+ 编码不了中文的 `print` 抛 `UnicodeEncodeError` 直接中断命令——`python -m romanbo
202
+ selftest` 因此在 windows-latest 上稳定失败(基准向量名含"[协议推算]")。
203
+ 现在 `cli.main()` 只在当前编码真的表示不了中文时才把 stdout/stderr 切到 UTF-8
204
+ - `SerialTransport.write()` 的帧间隔守卫改用 `time.perf_counter()`:Windows 上
205
+ `time.monotonic()` 在 CPython <= 3.12 走 `GetTickCount64()`(粒度约 15.6 ms),
206
+ 跨刻度时会误判"已过 15.6 ms"而**跳过节流**,真机上仍可能丢帧——即
207
+ `MIN_FRAME_GAP` 本要避免的"多关节只有第一个生效"(本地反事实复现:真实间隔 0.00 ms)
208
+ - `tests/test_standalone.py::TestFrameGap` 改为**注入时钟**判定"节流决策",
209
+ 不再读真实秒表:原实现用 `time.monotonic()` 打点,在 windows-latest /
210
+ Python 3.11 上恒读到 0.0 ms,是 CI 长期红灯的原因之一
211
+ - `tests/test_cli.py` 的串口提示用例断言了写死的 `"USB"`,而 macOS 分支文案是
212
+ `/dev/tty.usbserial-XXXX` → 只在 macOS 上失败;现改为逐平台校验各自分支
213
+ - `pyproject.toml`:`authors` 里不允许出现 `url` 字段(PEP 621),此前会导致
214
+ **`python -m build` 直接失败**——即发布流程不可用;现改为只保留 `name`,
215
+ 仓库地址放到 `[project.urls]`。同时移除已弃用的 License 分类器(改由 `license` 声明)
216
+
217
+ ### 变更(行为)
218
+ - **`build_set_position` 接受的位置范围由 `0..2047` 收紧为 `0..1023`**(原因见「修复」):
219
+ 越界值以前会静默置上 `relative` 位,现在抛 `ValueError`。仓库内没有依赖旧范围的调用方
220
+ (样例 `.rsc` 的 ADC 实测落在 205..600)
221
+ - `--targets` 改为 argparse 的 `type=` 解析:位置越界(>1023)或 ID 越界现在是一行干净的
222
+ 参数错误(退出码 2),而不是走到协议层才抛栈回溯
223
+ - 控制台的 LED 控制由「原始数值输入」改为 **8 色选择**(灭/红/绿/蓝/黄/紫/青/白):
224
+ 协议文档只定义了这 8 种组合,原来的 0..255 输入极易误解——`set_led(8)` 实际是
225
+ **全灭**(内部左移 5 位后低 3 位被挤出)。`/api/servo/{id}/led` 同时保留 `{value}`
226
+ 写法,响应里返回**实际下发的 `d[5]`** 便于对着报文日志核对
227
+ - **软件限力的阈值指导被真机复测修正**:2026-09-27 测得**起步第 1 步**的负荷冲击达
228
+ **128~147**(加速涌流,不是卡死),运动中只有约 85——原文档建议的「阈值取 60 左右」
229
+ 会把正常起步判成故障。现改为推荐「阈值 100 + `load_check_every=3`」,
230
+ 详见 `docs/LOAD_LIMITING.md` 与 `docs/FINDINGS.md` §3。控制台里限力默认**关闭**,
231
+ 并新增「负荷检查间隔」输入(默认 3);命令行暂未暴露 `--load-every`
232
+ - `RomanboRobot.capture()` 默认 **`retries=0`**(探测式):对不存在的 ID 只等一次超时。
233
+ 此前默认重试 2 次,17 通道只接 2 个舵机时整批回读会多花约 13 s。
234
+ 需要更强容错时显式传 `retries=2`。`Servo.get_position()` 相应新增 `retries=` 参数。
235
+ - `romanbo move` 新增 `--settle 秒` 与 `--readback`:`--speed` 是步进逼近,函数返回时
236
+ 最后一拍刚下发完、舵机仍在运动,此前直接回读会读到中间值;`--readback` 现会等待
237
+ 到位(默认 0.3 s)后回读实际位置并输出误差。`read` 改为单次超时(`retries=0`)。
238
+
239
+ ### 文档
240
+ - **按 `docs/SERVO_TEST_PLAN.md` 完成一轮真机验收**(2026-09-27,覆盖 L0~L8-01):
241
+ L0 全过(黄金向量 60/60)、L1~L4 通过率 100%、L5 已备份且参数全部成功回滚、
242
+ L7 全过(连发丢帧回归等)、`L8-01` 连续 50 次 `get_position` → `ok=50 fail=0`,
243
+ 满足 §5 的 Release Gate;完整记录见 `docs/SERVO_TEST_PLAN.md` §10
244
+ - **修正测试计划自身的两处缺陷**:L3-03 / L3-06 的限力阈值(60 / 100,默认每步采样)
245
+ 会撞上起步涌流而中止、容易被误判为缺陷 → 改为 `100 + --load-every 3`;
246
+ L3-04 的「角速度测量方法」描述不足,用进程墙钟会得到 −35%/−99% 的假失败
247
+ → 补上 `on_step` 逐步时间戳法(复测 −2.4%/−2.4%/+7.0%)
248
+ - `docs/SERVO_TEST_PLAN.md` / `docs/INSTALL.md` 的版本号引用同步到 v1.1.0
249
+
250
+ ## [1.0.0] - 2026-09-26
251
+
252
+ 首个公开发布版本。
253
+
254
+ ### 新增
255
+ - **协议层**(`romanbo.protocol`):帧编解码、命令码、校验、增量帧解析
256
+ - **传输层**(`romanbo.transport`):`SerialTransport`(pyserial)与 `MockTransport`(离线模拟)
257
+ - **单舵机 API**(`romanbo.servo`):位置/角度/角速度、轮子、扭矩、周期、PID、限位、
258
+ 实测负荷、LED、零点校准、参数回读
259
+ - **整机 API**(`romanbo.robot`):连接自检、ID 扫描、多关节同步动作、示教回读、`.rsc` 播放
260
+ - **工程文件**(`romanbo.rsc`):`.rsc` 解析与生成(兼容「重复键」文本格式)
261
+ - **关节换算**(`romanbo.joints`):机型通道表、ADC↔角度、步进逼近规划
262
+ - **命令行**(`romanbo.cli`):`selftest` / `ports` / `scan` / `read` / `teach` / `export` /
263
+ `move` / `jog` / `angle` / `sweep` / `load` / `pid` / `limit` / `play` 等 20 余个子命令
264
+ - **基准报文自检**(`romanbo.golden`):60 条逐字节基准向量
265
+ - 软件限力(`--max-load`):通过轮询实测负荷实现,超限即停止并返回退出码 4
266
+
267
+ ### 修复
268
+ - **帧间隔**:两帧之间无间隔时,后发的那一帧会被舵机静默丢弃,导致多关节动作
269
+ 只有第一个关节生效。现由 `SerialTransport.write()` 统一强制 `MIN_FRAME_GAP = 2 ms`
270
+ (`protocol.MIN_FRAME_GAP`),`move` / `play` 的多关节路径一并修复
271
+
272
+ ### 文档
273
+ - `README.md`:安装、上手、命令行参考、Python API、真机实测结论、安全与已知限制
274
+ - `docs/SERVO_SPEC.md`:字节级逐命令协议规格(含证据强度标记与未验证项清单)
275
+ - `docs/SERVO_TEST_PLAN.md`:分级测试方案(L0~L7 用例、判定门限、缺陷回归)
276
+ - `docs/evidence/`:真机联调探针日志(部分实测结论的原始证据)
277
+ - `docs/README.md`:文档索引
278
+
279
+ [Unreleased]: https://github.com/LQX-Code-SH/Romanbo-Python-SDK/compare/v1.1.1...HEAD
280
+ [1.1.1]: https://github.com/LQX-Code-SH/Romanbo-Python-SDK/compare/v1.1.0...v1.1.1
281
+ [1.1.0]: https://github.com/LQX-Code-SH/Romanbo-Python-SDK/releases/tag/v1.1.0
282
+ [1.0.0]: https://github.com/LQX-Code-SH/Romanbo-Python-SDK/releases/tag/v1.0.0
@@ -0,0 +1,98 @@
1
+ # 贡献指南
2
+
3
+ 感谢你参与改进本项目。以下是最小必要约定。
4
+
5
+ ## 环境准备
6
+
7
+ ```bash
8
+ git clone https://github.com/LQX-Code-SH/Romanbo-Python-SDK.git
9
+ cd romanbo
10
+ python -m pip install -e ".[dev]" # 或:pip install -e . && pip install pyserial
11
+ ```
12
+
13
+ ## 开发约定
14
+
15
+ - **Python 3.8+**:包内模块统一使用 `from __future__ import annotations`,
16
+ 新增代码请保持该约定,避免在运行期求值 `list[int]` 这类写法。
17
+ - **依赖最小化**:运行期只允许 `pyserial`,且只在打开真实串口时惰性导入。
18
+ 离线功能(`.rsc` 解析、报文编码、模拟器)必须**纯标准库**。
19
+ - **类型标注**:公开函数与方法的参数、返回值都要标注(本包带 `py.typed`)。
20
+ - 行宽 100,编码 UTF-8,换行 LF(见 `.editorconfig`)。
21
+
22
+ ## 测试
23
+
24
+ ```bash
25
+ python -m unittest discover -s tests -t . # 全部单元测试
26
+ python -m romanbo selftest # 60 条基准报文逐字节比对
27
+ python -m romanbo --mock scan # 离线冒烟
28
+ ```
29
+
30
+ - 新增协议行为 → 在 `tests/test_protocol.py` 补基准向量;
31
+ - 新增整机逻辑 → 用 `MockTransport` 覆盖,**不要在单元测试里访问真实串口**;
32
+ - 涉及真机的结论 → 写清「协议文档定义 / 真机实测 / 未验证」,并附实测环境与日期。
33
+
34
+ ## 文档
35
+
36
+ 文档站用 **MkDocs + Material + mkdocstrings** 构建,源文件在 `docs/`:
37
+
38
+ ```bash
39
+ python -m pip install -e ".[docs]" ruff
40
+ python -m mkdocs serve # 本地预览 http://127.0.0.1:8000
41
+ python -m mkdocs build --strict # CI 用;任何告警都会导致失败
42
+ ```
43
+
44
+ 约定:
45
+
46
+ - **`docs/api.md` 的 API 参考由 docstring 自动生成**,不要在文档里重复维护函数签名;
47
+ - 改公开接口时,把「语义、限制、实测结论」写进 docstring;
48
+ - docstring 用 **Sphinx 风格**(`:param x:`、`:returns:`),正文用 Markdown;
49
+ 需要强调风险时用 `!!! warning` 这类 Markdown admonition,**不要写 `.. warning::`**(RST 写法不会渲染);
50
+ - 交叉引用写成行内代码(`` `romanbo.servo.Servo` ``),不要用 `:class:` 之类的 RST 角色;
51
+ - 站内锚点由 pymdownx 的 Unicode slugify 生成,与 GitHub 保持一致,中文标题可正常跳转。
52
+
53
+ ## 危险的改动
54
+
55
+ 以下内容会改动真机状态或协议语义,请单独开 issue 讨论后再提 PR:
56
+
57
+ - `0x0F`(`GetPositionLimit` / `GetMotionPeriod` 同码)相关行为——无数据帧会改写位置限值
58
+ - 位置限值、PID、零点校准、改 ID、复位等**掉电保存**的写入命令
59
+ - 帧间隔、扫描静默期等时序常量(会直接影响多关节动作正确性)
60
+
61
+ ## 提交与 PR
62
+
63
+ 1. 从 `main` 切分支:`git checkout -b fix/xxx` 或 `feat/xxx`;
64
+ 2. 提交信息用「类型: 摘要」形式,例如 `fix: 多关节连发丢帧`、`docs: 补充 .rsc 迁移说明`;
65
+ 3. PR 中说明**动机、改动点、验证方式**;涉及真机验证的附上命令与输出;
66
+ 4. 确保 `unittest` 与 `selftest` 全绿。
67
+
68
+ ## 发布
69
+
70
+ 推送 `v*` 标签触发 `.github/workflows/release.yml`:构建 sdist + wheel → `twine check`
71
+ 与产物内容校验 → **从 sdist 安装并跑自检** → 发布到 PyPI → 创建 GitHub Release。
72
+
73
+ **逐步操作清单见 [`docs/RELEASING.md`](docs/RELEASING.md)**(首次配置 PyPI Trusted
74
+ Publishing 的五处字段、每次发版、发布后验证、发错了怎么补救)。速记版:
75
+
76
+ 1. 更新 `CHANGELOG.md`:`[Unreleased]` → 新版本号 + 日期;
77
+ 2. 同步 `romanbo/__init__.py` 的 `__version__`——**打包版本取自它**,且必须与标签
78
+ 一致(`v` + 版本号;工作流里有守卫会拦下不一致的情况);
79
+ 3. 提交并等 CI 绿,再打标签推送:
80
+
81
+ ```bash
82
+ git tag -a v1.0.1 -m "romanbo 1.0.1"
83
+ git push origin v1.0.1
84
+ ```
85
+
86
+ 4. 首次发布前要先完成 `docs/RELEASING.md` §1 的三步配置,否则 PyPI 作业会被跳过
87
+ (GitHub Release 仍会正常创建)。
88
+
89
+ > 本地可先自查:`python -m build && python -m twine check dist/*`。
90
+ > 其中 `project.license` 的 TOML 表形式会打印一条弃用告警,属预期(原因见 `pyproject.toml` 注释)。
91
+
92
+ ## 安全
93
+
94
+ 本项目会驱动真实硬件。提交涉及运动的示例或测试时,务必:
95
+
96
+ - 默认使用小幅度、低速度并带 `--max-load`;
97
+ - 明确标注「会驱动真机」;
98
+ - 不要提交会让关节失控(如关闭限位后满速运行)的默认参数。
romanbo-1.1.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LQX-Code-SH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,16 @@
1
+ # sdist 必须自带这些文件,否则 `.github/workflows/release.yml` 的
2
+ # 「校验元数据与产物内容」会失败(它会断言 sdist 含 README.md / LICENSE /
3
+ # CHANGELOG.md / mkdocs.yml)。2026-09-27 首次发版才发现缺 MANIFEST.in——
4
+ # 此前该工作流从未跑过。
5
+ include LICENSE
6
+ include README.md
7
+ include README.en.md
8
+ include CHANGELOG.md
9
+ include CONTRIBUTING.md
10
+ include mkdocs.yml
11
+
12
+ # 源码包应能直接构建文档站(mkdocs.yml 引用的就是这些页面)
13
+ recursive-include docs *.md
14
+
15
+ # Linux 串口权限的 udev 规则(装法见 docs/INSTALL.md)
16
+ include deploy/99-usb-serial.rules
romanbo-1.1.1/PKG-INFO ADDED
@@ -0,0 +1,210 @@
1
+ Metadata-Version: 2.4
2
+ Name: romanbo
3
+ Version: 1.1.1
4
+ Summary: ROMANBO 舵机 / 整机 RS485 控制库(Python SDK)
5
+ Author: LQX-Code-SH
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/LQX-Code-SH/Romanbo-Python-SDK
8
+ Project-URL: Repository, https://github.com/LQX-Code-SH/Romanbo-Python-SDK
9
+ Project-URL: Issues, https://github.com/LQX-Code-SH/Romanbo-Python-SDK/issues
10
+ Project-URL: Changelog, https://github.com/LQX-Code-SH/Romanbo-Python-SDK/blob/main/CHANGELOG.md
11
+ Keywords: romanbo,servo,rs485,robotics,sdk
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Education
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Operating System :: MacOS
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.8
22
+ Classifier: Programming Language :: Python :: 3.9
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Classifier: Programming Language :: Python :: 3.13
27
+ Classifier: Typing :: Typed
28
+ Classifier: Topic :: Software Development :: Embedded Systems
29
+ Classifier: Topic :: System :: Hardware :: Hardware Drivers
30
+ Requires-Python: >=3.8
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ Requires-Dist: pyserial<4.0,>=3.5
34
+ Provides-Extra: dev
35
+ Requires-Dist: build>=1.0; extra == "dev"
36
+ Requires-Dist: ruff>=0.4; extra == "dev"
37
+ Requires-Dist: mypy>=1.8; extra == "dev"
38
+ Provides-Extra: docs
39
+ Requires-Dist: mkdocs>=1.6; extra == "docs"
40
+ Requires-Dist: mkdocs-material>=9.5; extra == "docs"
41
+ Requires-Dist: mkdocstrings[python]>=0.25; extra == "docs"
42
+ Dynamic: license-file
43
+
44
+ # ROMANBO 舵机 / 整机控制库(Python SDK)
45
+
46
+ [简体中文] | [English](README.en.md)
47
+
48
+ [![CI](https://github.com/LQX-Code-SH/Romanbo-Python-SDK/actions/workflows/ci.yml/badge.svg)](https://github.com/LQX-Code-SH/Romanbo-Python-SDK/actions/workflows/ci.yml)
49
+ [![Python](https://img.shields.io/badge/python-3.8%2B-blue)](https://www.python.org/downloads/)
50
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
51
+ [![Typing: py.typed](https://img.shields.io/badge/typing-py.typed-blue)](https://peps.python.org/pep-0561/)
52
+ [![Lint: ruff](https://img.shields.io/badge/lint-ruff-000000.svg)](https://github.com/astral-sh/ruff)
53
+ [![Docs](https://img.shields.io/badge/docs-online-2ea44f)](https://lqx-code-sh.github.io/Romanbo-Python-SDK/)
54
+
55
+ 面向 **ROMANBO** 舵机型号的 RS485 总线控制 SDK,完整实现舵机与整机控制协议。
56
+
57
+ **结论都标注来源**:协议文档定义 / 实测确认 / 未验证——未验证项不会写成已验证。
58
+ 本文档只讲**怎么用**;具体数值、验证过程与未验证边界集中记录在
59
+ [真机实测结论](docs/FINDINGS.md)与[字节级协议规格](docs/SERVO_SPEC.md)。
60
+
61
+ 📖 文档站 <https://lqx-code-sh.github.io/Romanbo-Python-SDK/> ·
62
+ [协议规格](docs/SERVO_SPEC.md) · [API 参考](docs/api.md) · [文档索引](docs/README.md)
63
+
64
+ ---
65
+
66
+ ## 特性
67
+
68
+ - **协议全覆盖**:扫描 / 位置 / 角度 / 角速度 / 轮子 / 扭矩档位 / PID / 限值 / 实测负荷 / LED / 零点校准 / 参数回读
69
+ - **角速度是真的角速度**:固件忽略 `SET_PERIOD`,本库用**步进逼近**实现(精度见[实测结论](docs/FINDINGS.md))
70
+ - **软件限力**:轮询实测负荷,超阈值立即停止(硬件没有可用的力矩环)
71
+ - **时序按实测确定**:帧间隔、总线静默期、重试策略都不是猜的(见[实测结论](docs/FINDINGS.md))
72
+ - **离线可用**:`.rsc` 解析、报文编码、`--mock` 模拟器、60 条基准报文自检全部零依赖
73
+ - **可视化控制台**:`python -m romanbo webui` 起一个只监听本机的 Web 界面——扫描、运动、
74
+ 读写参数,并把**原始收发报文**实时打在页面上(见[可视化控制台](docs/WEBUI.md))
75
+ - **工程化**:201 项单元测试(无需硬件)+ CI(Python 3.8~3.13)+ 类型标注(`py.typed`)
76
+
77
+ ## 安装
78
+
79
+ ```bash
80
+ pip install pyserial # 仅真实串口需要;离线功能零依赖
81
+ pip install -e . # 或把 romanbo/ 目录直接拷进你的项目
82
+ ```
83
+
84
+ **Python 3.8+**。Linux 下需要串口权限:
85
+
86
+ ```bash
87
+ sudo usermod -aG dialout $USER # 执行后需重新登录
88
+ python3 -m romanbo ports # 确认端口名,如 /dev/ttyUSB0
89
+ ```
90
+
91
+ 平台差异、并发边界等细节见 [安装与环境](docs/INSTALL.md)。
92
+
93
+ ## 30 秒上手
94
+
95
+ ```bash
96
+ # 离线:校验 60 条基准报文
97
+ python -m romanbo selftest
98
+
99
+ # 真机:扫描 → 读参数 → 看载荷
100
+ python -m romanbo --port /dev/ttyUSB0 scan
101
+ python -m romanbo --port /dev/ttyUSB0 --json config --ids 8,10
102
+ python -m romanbo --port /dev/ttyUSB0 load --ids 8 --watch 10 --interval 0.1
103
+
104
+ # 运动:相对 +15° @ 60°/s,带软件限力(阈值 100 + 每 3 拍抽检,跳过起步涌流)
105
+ python -m romanbo --port /dev/ttyUSB0 jog --id 8 --degrees 15 --speed 60 --max-load 100 --load-every 3
106
+
107
+ # 多关节(--readback 会等到位再回读实际位置与误差)
108
+ python -m romanbo --port /dev/ttyUSB0 move --targets 8:600,10:480 --speed 30 --readback
109
+
110
+ # 播放工程文件(examples/data/demo.rsc 是随仓库样例)
111
+ python -m romanbo --port /dev/ttyUSB0 play examples/data/demo.rsc --speed 60
112
+
113
+ # 可视化控制台(浏览器操作;--mock 无需硬件,换 --port … 即用真机)
114
+ python -m romanbo webui --mock
115
+ ```
116
+
117
+ 完整命令与退出码见 [命令行参考](docs/CLI.md)。
118
+
119
+ ## Python API
120
+
121
+ ```python
122
+ from romanbo import RomanboRobot, LoadLimitExceeded
123
+
124
+ with RomanboRobot("/dev/ttyUSB0") as robot: # 或 connect("COM3", mock=True)
125
+ print(robot.scan(1, 32)) # 在线 ID
126
+ s = robot.servo(8)
127
+ print(s.get_position(), s.angle()) # ADC / 角度
128
+ print(s.get_load()) # 实测负荷(0x18)
129
+
130
+ s.torque(True) # 力矩使能
131
+ s.rotate(15, speed_dps=60) # 以 60 °/s 相对转 15°
132
+ try:
133
+ # 60 °/s + 软件限力:阈值 100、每 3 拍抽检一次(跳过起步涌流)
134
+ s.move_at_speed(600, 60, max_load=100, load_check_every=3)
135
+ except LoadLimitExceeded as exc:
136
+ print(exc.as_dict())
137
+
138
+ robot.move({8: 600, 10: 480}, speed_dps=60, start=robot.capture([8, 10]))
139
+ robot.play_rsc("examples/data/demo.rsc", speed_dps=60, max_load=60)
140
+ ```
141
+
142
+ | 类 / 模块 | 职责 |
143
+ |---|---|
144
+ | `RomanboRobot` | 连接、扫描、多关节同步动作、示教回读、`.rsc` 播放 |
145
+ | `Servo` | 单个舵机的全部命令(位置/角度/角速度/轮子/扭矩/周期/PID/限值/负荷/LED/校准/回读) |
146
+ | `ServoConfig` | 一次读回的全部参数(字段 `load` 为实测负荷) |
147
+ | `LoadLimitExceeded` | 软件限力触发(`.as_dict()` 便于上报) |
148
+ | `RscProject` / `MotionFrame` | `.rsc` 工程解析与生成 |
149
+ | `joints` / `protocol` / `transport` | 换算与步进规划 / 帧编解码与命令码 / 串口与离线模拟 |
150
+
151
+ 完整签名与字段说明见 **[API 参考](docs/api.md)**(由 docstring 自动生成)。
152
+
153
+ ## 安全提示
154
+
155
+ - 运动类命令(`jog/angle/sweep/move/play`)**会立即驱动舵机**;下肢/悬臂请先做好支撑。
156
+ - 参数类命令(`pid/limit/param/calib/set-id/reset`)**写入舵机并掉电保存**,
157
+ 建议先 `config` 记录原值。**SET 类命令没有 ACK**,写入可能被静默丢弃。
158
+ - `torque off` 后关节失去保持力(下肢关节会倒下)。
159
+ - **任何情况下都不要发送 `0x0F`**(同码于 `SetPositionLimit`,会误写限值)。
160
+
161
+ 完整限制清单见 **[安全与已知限制](docs/SAFETY.md)**。
162
+
163
+ ## 文档
164
+
165
+ **使用说明**——面向使用者,只讲怎么用;**不含测试过程与实测数据**
166
+
167
+ | 文档 | 内容 |
168
+ |---|---|
169
+ | [安装与环境](docs/INSTALL.md) | 依赖、三种用法、Linux/macOS 权限、平台差异、并发边界 |
170
+ | [命令行参考](docs/CLI.md) | 全部子命令、全局选项、退出码、`--json` 输出契约 |
171
+ | [可视化控制台](docs/WEBUI.md) | 只监听本机的 Web 界面:连接/扫描、实时读数、运动与参数读写、原始报文日志 |
172
+ | [工程文件 `.rsc`](docs/RSC.md) | 三层结构、示教导出、播放语义 |
173
+ | [软件限力](docs/LOAD_LIMITING.md) | 原理、阈值与检查间隔怎么选、API 与退出码 |
174
+ | [安全与已知限制](docs/SAFETY.md) | 危险命令、不支持/未验证清单、软件边界 |
175
+ | [协议速览](docs/PROTOCOL.md) | 帧格式、地址分配、命令码表、应答约定 |
176
+
177
+ **规格与验证记录**——面向开发者/维护者;数据、过程与未验证边界都在这里
178
+
179
+ | 文档 | 内容 |
180
+ |---|---|
181
+ | [字节级协议规格](docs/SERVO_SPEC.md) | 逐命令请求/回包布局、时序、数据语义 |
182
+ | [真机实测结论](docs/FINDINGS.md) | 全部实测数据与结论(角速度、负荷、时序、帧间隔…) |
183
+ | [测试与自检](docs/TESTING.md) | 单元测试、基准报文自检、MkDocs 构建 |
184
+ | [测试方案](docs/SERVO_TEST_PLAN.md) | L0~L9 分级用例、判定门限、记录模板、缺陷回归 |
185
+ | [真机测试记录](docs/TEST_REPORTS.md) | 每轮环境、基线快照、逐条结果、准出结论、当轮缺陷 |
186
+ | [证据日志](docs/evidence/README.md) | 真机探针原始收发字节 |
187
+ | [API 参考](docs/api.md) | 由 docstring 自动生成,不会与代码脱节 |
188
+ | [发版与发布](docs/RELEASING.md) | PyPI Trusted Publishing 配置、发版流程、发布后验证、出错处置 |
189
+
190
+ 在线文档站:<https://lqx-code-sh.github.io/Romanbo-Python-SDK/>(由 `docs/` 构建)
191
+
192
+ ## 项目结构
193
+
194
+ ```
195
+ romanbo/ 核心包(protocol / transport / servo / robot / rsc / joints / cli / golden)
196
+ docs/ 文档(协议规格、实测结论、API 参考、证据日志)
197
+ examples/ 示例脚本 + data/demo.rsc 样例工程
198
+ tests/ 201 项单元测试(不需要硬件)
199
+ tools/servo_probe.py 单舵机原始十六进制联调工具
200
+ ```
201
+
202
+ ```bash
203
+ python -m unittest discover -s tests -t . # 单元测试
204
+ python -m romanbo selftest # 60 条基准报文
205
+ ```
206
+
207
+ ## 许可
208
+
209
+ MIT,见 [`LICENSE`](LICENSE)。版本变更见 [`CHANGELOG.md`](CHANGELOG.md),
210
+ 贡献指南见 [`CONTRIBUTING.md`](CONTRIBUTING.md)。