arm_dev
|
dfc945b7e0
|
P6 通止规装配:主流程节点(S00–S11 跑通)+ 入口脚本 + 真机/仿真验证
新增
----
- arm_application/gauge_assembly_node.py:主流程。每个阶段一个 _step_*,
返回值就是状态机的事件;纯逻辑(状态机/配置/限位/夹爪后端/自检)全部
在 gauge_*.py 里,节点只做"发指令 + 读反馈 + 报错"。
* S00 自检报告带"缺了会怎样",必检拦住、选检告警;
* S01 回 home → 切工具系 → 逆时针预绕,**事后用 TF 实测绕工具 Z 转了多少度**
(转轴偏差 >5° 直接中止:以"度"为单位的动作全建立在这条假设上);
* S03 手外粗定位:没有 eye-to-hand 标定文件时明确报"缺哪个文件、怎么生成",
回 S04 兜底(不做假装成功的反投影);
* S05 稳定判据用"到均值的最远距离",不用相邻帧之差;
* S07 悬停处测基线 → 步进下压 → 两路切向任一突变即停,法向超安全上限抬起中止;
* S08 棘轮四步(慢转→松爪→快回转→夹紧),停止判据「切向 >阈值 且 法向突增」,
掉件走 E1 重放;S09 反向拧出(比拧入多一轮);
* E1 重放等 /gauge/reload_ok 人工确认(超时按中止),E2 任何中止都先退安全高度。
- launch/gauge_assembly.launch.py:只做一件事 —— 发布手眼静态 TF(S05 的前提),
其余判据全交给 S00(判断标准只有一处)。
- scripts/run_gauge_assembly.sh:--arm/--sim/--dry-run/--holes/--backend。
- gauge_assembly 入口(setup.py)+ 站点参数 reload_timeout_s。
- test/test_gauge_step_events.py:**读节点源码 AST**,把每个 _step_* 可能返回的
事件逐个问状态机"合法吗" —— 节点与转移表是两份数据,漏登记要在 pytest 里炸,
而不是在真机上跑到那一步才炸。
顺手修掉的两个真问题
--------------------
1. 四份 launch 里的四元数换算**已经互相抄歪了**(退化分支的公式各不相同)。
统一收口到 handeye_lib.read_transform_yaml,并加回环测试(真实标定文件 +
180°/270° 等分支切换角度);实测 4 个 launch 输出与库完全一致。
2. pipeline_node.AssemblyPipeline 支持 node_name 复用(本流程继承它的一阶段
跟踪/二阶段定位链,但在图上必须是另一个节点名)。
验证(都在本机真跑过)
----------------------
pytest(仿真环境) → 349 passed, 14 skipped(缺 onnx 的用例跳过)
干跑全流程 → S00→S01→…→S11→DONE,exit 0
仿真里不干跑(缺 /home 等) → S00 拦住,exit 1,未发任何运动指令
另:仿真环境没有 onnx(只有真机 conda 有),相关用例改为 skip —— 让测试结果
"红就是真红"。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-24 10:12:03 +08:00 |
|
arm_dev
|
04cc9e67e3
|
P6 通止规装配:状态机与配置的纯逻辑层(含 92 项单测)
为什么先做纯逻辑
----------------
状态机、配置、限位计算、夹爪后端选择这四件事都有同一个性质:**算错了不会
抛异常**,只会让流程"走得下去但结果不对"(去错了孔、按毫米当米用、
贴着限位下发、调用了类型不匹配的服务)。所以它们必须在没有 ROS、没有真机
的情况下就能被钉住(规则 R5.1)。
新增
----
- gauge_fsm.py:S00–S11 + E1/E2 的转移表(数据,不是 if/else 丛林)。
含 S05 稳定判据、S07 双通道切向突变、S08「切向且法向」停止判据、
棘轮四步顺序。E1 掉件有重放预算,超限明确 ABORT 并留下现场原因。
- gauge_config.py:site.yaml 的 gauge_assembly: 段(36 个键)解析 + 校验。
多头校验:未知键报错并给最相近候选名;类型不做隐式转换("20" 不当 20);
键名后缀必须体现单位(防"填毫米按米用");跨键矛盾(安全上限低于停止阈值)
直接拒绝;describe() 打印**实际生效**的值与派生结论。
- gauge_kin.py:URDF 限位解析(stdlib,不依赖 urdf_parser_py,XML 坏掉时
逐行兜底)、S01 预绕量的限位夹取、以及"确实绕工具 Z 转了多少度"的事后验证
(只看转了多少度不够:绕错轴转 90° 的读数一样)。
- gauge_gripper.py:按**服务类型**(不是名字)选夹爪后端。现场查证发现
/gripper/open 后面挂着两种类型:真机 dh_pgea 是 std_srvs/Trigger,
契约/仿真 是 arm_msgs/GripperExecute;manifest 声明的 arm_driver_dh_gripper
还不存在。两种都在线时报错,不撞运气。
- gauge_boot.py:S00 自检清单(纯内容层)。触觉/相机是必检(标成选检会让
力判据全部失效但流程照跑);没有的设备不生成检查项(必然失败的项会训练人
忽略自检输出);每项都写"缺了会怎样"。
验证
----
python3 -m pytest src/arm_application/test/test_gauge_*.py -q → 92 passed
python3 -m pyflakes src/arm_application/arm_application/gauge_*.py → 干净
写测试时被自己的检查抓到的两处
------------------------------
1. extreme_speed_deg_s 单位登记成 deg,被"键名必须体现单位"的检查报出来;
2. pick_backend 对"服务不在"和"类型不对"报同一句"节点没起来"——
已拆成两种消息(前者给启动命令,后者给「谁占用了这个服务名」的查法)。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-24 09:57:10 +08:00 |
|
arm_dev
|
6922b18187
|
仿真夹爪适配器:服务接口对齐真机,开口按正运动学换算成真毫米
为什么
------
应用层只认 arm_msgs/srv/GripperExecute(开口毫米),真机与仿真必须同一套接口。
但 GN01 的 URDF 里 <mimic> 是**线性近似**:直接按线性系数下发 finger_width_joint,
"命令开 20 mm"在 Gazebo 里量出来是 21.75 mm —— 抓不住工件,而且看起来像算法问题。
实测(2026-09-24,Gazebo 内量两指尖间距):
线性下发 20 mm → 实际 21.74 mm(偏差 1.74 mm,超 2 mm 抓取容差的前半步)
走 FK 反解 → 实际 20.00 mm(≤0.5 mm)
怎么改
------
- 新增 sim_gripper_kin.py:解析 URDF 正运动学,算出"主关节值 ↔ 真实开口 mm"的
单调曲线,反解用二分。曲线非单调/零行程时**主动报错**,不静默退化成线性。
- sim_gripper.py:开口换算全部走该曲线;容差统一改成毫米。
- sim_gripper_adapter.py:反馈改取**从动关节**(见下);is_gripped 按毫米判定。
- 新增 check_sim_gripper.py:三层验收(服务/开口一致/指尖真实几何行程 + 对称性)。
踩到的坑
--------
- finger_width_joint 挂在无质量的 unused_finger_width_link 上,与手指**没有机械
连接**:命令到 0.100,它的 /joint_states 恒为 1.4e-08,6 个从动关节才准到
0.7854。反馈若取它,/gripper/status 会永远报"开口 0 mm"。
- 残留进程会伪造出"代码写错了"的现场:旧 sim_gripper_adapter 与新实例抢话题,
验收读到"指尖间距 0.03 mm"(真实 127 mm);孤儿 static_transform_publisher
用 latched /tf_static 把旧相机外参按在新仿真上。
→ 新增 scripts/lib/sim_procs.sh;run_sim_gz.sh 用 setsid 独立进程组,
PGID 记进 log_sim/sim.pgid,stop 时整组 kill;启动前预检拒绝带病启动。
- 验收脚本自己也错过两次(把 TF 的米当毫米比 → 127 mm 显示成 0.13 mm;
用会转动指垫的中点漂移判对称 → 必然位移 16 mm)。判据改成"两侧各走多远",
单位只在 _dist_mm 里换算一次。
验证
----
python3 -m pytest src/arm_sim_gazebo/test/ -q → 307 passed
python3 scripts/check_sim_gripper.py → 三层全过
第 2 层:请求/反馈开口最大偏差 < 0.5 mm
第 3 层:期望行程 vs 实际行程、左右行程差,均 < 1.5 mm
bash scripts/stop_sim_gz.sh --list → 整组列出(含 static tf)
文档
----
- 仿真手册:新增 §4.4(开口 mm 怎么换算)、坑 12(宽度关节不动、
反馈必须取从动关节);停止一节改写成进程组语义。
- FRAMEWORK_RULES:新增 R4.11(残留进程会伪造现场)、R4.12(验收判据要挑
与实现无关的物理量);R4.9 表补夹爪;清单补 13/14 两条。
- HANDBOOK:新增 §3.4「在仿真里开合夹爪」,补 §4.4 的两条新教训。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-24 09:46:09 +08:00 |
|
arm_dev
|
f5ed44a987
|
配置:消灭代码里的硬编码臂 SN,并给"换臂漏改"加一把尺子
## 为什么
"设备 ID 全部可配置"这条要求在代码里只做了一半:site.yaml 能配,
但**代码和静态配置里到处躺着 Rizon4-063989 当兜底**。这类兜底的危害
不是"改起来麻烦",而是它把"配置没生效"伪装成"配置生效了":
* `robot_sn` 默认 `Rizon4-063989` → 换臂后节点去订**上一台臂**的关节话题,
节点起来了、不报错、什么都不动;
* launch 里用 `ee_frame == "Rizon4-063989_flange"` 当"用户没指定"的哨兵 →
换个站点这个判断永远不成立,于是 site.yaml 里的 ee_frame 再也不会被用上,
静态 TF 挂错坐标系、点还照样查得到;
* `if SN == "Rizon4-063989"` 决定要不要拉触觉服务 → 换臂后触觉静默消失。
## 改了什么
1. **删掉所有"兜底成某台臂"的默认值**(15 处脚本 + 6 个 launch + 6 个节点)。
没配就为空 / 直接 `:?` 报错,宁可起不来也不要连错机器:
- `scripts/lib/export_site_env.py`:别名解析改为从 `robots[]` 现算
(`arm_short_id` / `resolve_robot_sn` / `describe_arms`),选不出臂时
抛 `ValueError`;`main()` 捕获后打人话 + 退出码 2(供 shell `eval`);
- 手眼文件/路点/工作目录由当前臂 short id 派生,不再写死 `89_*`;
- `ROBOT_HAS_TACTILE` 改为读 site.yaml 的 `robots[].tactile`;
- `arm_application/site_io.py` 新增 `joint_prefix_default()` /
`base_frame_default()` / `ee_frame_default()`:优先读 `SITE_*`,
退而由 `SITE_ROBOT_SN` 现算,**不写死 SN**;
- `arm.urdf.xacro` 的 `robot_sn` 默认值改成明显的占位符 `UNSET_ARM_SN`
(关节名会带出来,报错信息自解释);
- `ros2_control.launch.py` / `move_group.launch.py` **原先根本不传
robot_sn**,靠 xacro 写死默认值"蒙对"本机,现在都从环境取。
2. **新增 `scripts/check_arm_prefixes.py`**:Flexiv 把 SN 编进关节名,
所以有一批一级配置(SRDF/controllers/joint_names/bringup/servo/...)
必须跟着当前臂走。漏改的症状是"MoveIt 规划成功但机械臂不动",很难查。
这个脚本逐文件逐行列出对不上的地方,并直接给出修法。
`--list` 还会说明 SN 来自 site.yaml 还是被 `ROBOT_SN` 覆盖。
3. **文档**:`FRAMEWORK_RULES.md` 新增 R1.1.1(两类含臂名文件怎么分、
换臂四步)+ 检查清单第 12 条 + 反例 2 条;`HANDBOOK.md` 新增 3.6.1
与症状表 2 行。
4. **顺带修掉的真 bug**(都是这次改动带出来、被 pyflakes + 真跑抓住的):
- `convert_flexiv_traj.py` / `rdk_drag_bridge.py` 用了 `os` 但没 import
→ `--help` 直接 NameError;
- `check_arm_prefixes.py` 第一版正则用 `\b` 收尾,而关节名是
`Rizon4-063989_joint1`(`_` 属于 \w,`\b` 不成立)→ SRDF 里 24 处
硬编码**一处都没报**,还打印 OK。已加负向测试钉住;
- 删掉若干死代码/死 import(`pos_line`/`flat`/`names`、`IfCondition` 等)。
## 验收(都真跑过)
- `python3 -m pyflakes <全部改过的 py>` → 干净
- 逐包 pytest:233 + 69 + 78 + 24 + 236 = **640 passed**
- `env -u ROBOT_SN python3 scripts/check_arm_prefixes.py` → OK(8 文件)
- 换成 SN=Rizon4-777001 的临时 site.yaml → 报出**8 个**文件不一致并给修法(退出码 1)
- 故意不给 SITE_* → 各 launch 的 `robot_sn` / `ee_frame` / `handeye_file`
默认值为 `''`,不再悄悄指向上一台臂
- `ros2 launch ... --show-args` 对 stack/sim/ros2_control/mock_control/
move_group/display/visual_servo **全部通过**
- `convert_flexiv_traj.py --help` 正常;拿 `xunhuan.traj` 真转 5 个路点,
输出 `joint_prefix: "Rizon4-063989_"` 来自 site.yaml
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-24 09:18:44 +08:00 |
|
arm_dev
|
fac2d5384d
|
P5b 新手手册:这台机器在干什么 / 改哪里 / 怎么验 / 出问题查哪
此前仓库有规则文档(FRAMEWORK_RULES)、有目录表(WORKSPACE)、有各模块说明,
但**缺一份"从零进来的人读什么"**:业务目标没有一处讲清("通止规装配"散落在
7 个文件里各提一句),测试怎么跑在 docs/ 里一次都没写过,改一个东西该动哪个文件
要靠翻源码猜。
docs/HANDBOOK.md(416 行,新增)
1. 这台机器在干什么 —— 通止规是检验螺纹的量具(通端应旋到底、止端应旋不进);
为什么值得自动化(留下可追溯的力曲线);三段主线的数据流;日志三行判进度。
★ 1.4 节是**承诺书**:逐项标 ✅/⚠️/❌,并给出"哪条命令能证明它已就绪"。
如实写明还没做的四项:自动拉起全部服务、夹爪持规自检、D435 手眼标定、
装配合格判定。指不出验证命令的,就老实标 ❌(R6.1)。
2. 系统长什么样 —— 三层各有硬边界;两块**独占资源**(RDK、D405 USB)
以及各自的冲突表现;从像素到关节的全链路加上"每步的口径写错会怎么表现"。
3. 我要改东西,改哪里 —— 决策表(改 X → 动 Y → 用 Z 验),覆盖换臂/悬高/夹爪/
入孔/触觉/相机/仿真场景/权重/新品牌设备/命名位姿。每行都外链权威文档,
不复制内容(M2)。
4. 怎么验 —— 三层验收(不报错不算验收 / 物理量命中真值 / 任务成功)+ 命令清单 +
**单元测试怎么跑**(此前 docs/ 里没有任何一处写过)。含 608 项分包清单。
4.4 节专讲"验收工具自己也会失效",把 M1/R5.3/R6.6 串成一条线。
5. 出问题按现象查 —— 三处权威文档的分工表 + 15 行"现象 → 先怀疑什么",
覆盖 8 条仿真坑与全部触觉坑(坑 1/2/5/6/7/9/10)。
6. 术语表 + 未做的事(按优先级)+ 往下读的路线。
顺带写进规则的(docs/FRAMEWORK_RULES.md)
R6.7 验收命令必须真跑过 —— 本仓库已经因此踩过两次(CRLF 死脚本被五处文档推荐)
R6.8 install/ 构建产物会互相污染 —— 见下面这个发现
第 7 节清单加两条(第 10 条真跑过命令、第 11 条构建环境对不对)
第 8 节反例表加三行症状
★ 顺带发现一个哑弹(R6.8,**已记录未修**)
colcon build 会把当时激活的那个 python 的绝对路径写进每个入口脚本的 shebang。
实测:install/ 里 31/59 条指向仿真环境 ros_gz_sim,install_sim/ 里 31/55 条
(后者是对的)—— 说明真机 install/ 是在仿真 conda 环境里构建出来的,违反 R5.7。
$ head -1 install/arm_interfaces/lib/arm_interfaces/device_check
#!/home/cc/anaconda3/envs/ros_gz_sim/bin/python3.11
为什么现在没炸:两个 conda 环境的 Python 依赖当前完全相同(pyrealsense2 / serial /
cv2 / numpy / torch / ultralytics / tensorrt 都在),指错了也照样 import 成功。
这是一颗哑弹 —— 哪天某个环境多装或少装一个包,真机栈就会以"模块找不到"的形式炸,
而人是不会想到去查 shebang 的。
修法(写进 R6.8,待执行):在真机 conda 环境里重建 install/,再
`grep -l ros_gz_sim install/*/lib/*/* | wc -l` 应当变成 0。
预防:构建脚本开头应断言当前 conda 环境与目标一致(待补)。
验收
- 文档内所有相对链接与反引号路径引用均存在(脚本检查,0 坏链)
- 文档里给的每条命令都实际执行过:
ros2 run arm_interfaces device_check 通过
device_check --list-brands 列出 5 类设备
scripts/show_site_config.sh 存在
check_sim_cameras.py / check_sim_scene.py / check_text_only.py 存在
for d in src/*/test ... pytest 循环 608 passed
- 单测 608 passed,与改动前一致(本次只改文档)
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-24 09:05:09 +08:00 |
|
arm_dev
|
a91fa93339
|
补齐最后三处英文提示:触觉 mock 后端、mock 驱动拒绝原因、merge 用法行
上次中文化扫出来 8 处英文提示,其中 5 处是 weights.sh 的用法/命令示例(必须保留英文,
它们就是用户要敲的命令),剩下这 3 处是真正给人看的句子,一并改掉:
tactile_node.py "tactile mock backend" -> "触觉使用 mock 后端(假数据,不接真实传感器)"
mock_arm_driver.py "Reject: already executing" -> "拒绝:上一个目标还在执行"
merge_handeye_poses.py "Usage: ..." -> "用法:..."
merge 用法行里的 <taught_poses.txt> <waypoints.yaml> 是真实文件名,必须留着 ——
只把 "Usage:" 换成"用法:",不把文件名换成中文描述。
验收:scripts/check_text_only.py 通过(零逻辑变化);608 passed,与改动前一致。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 17:42:24 +08:00 |
|
arm_dev
|
4884fc34ae
|
R6.6 行尾一律 LF + 验脚本要真跑;反例表补三行症状
补充上一个提交(CRLF 死脚本)的规则化:把"文件在、文档说能用、一跑就死"
和"bash -n 漏报 CRLF"两条症状写进第 8 节反例表,并新增 R6.6。
为什么单独立一条:这次踩的不是"哪个脚本写错了",而是"验收手段本身不可靠"——
bash -n 返回 OK 却执行立刻死。所以 R6.6 明确三件事:
验语法 bash -n(必要但不充分)、验能用 bash <脚本> --help、验行尾看 file/.gitattributes。
顺带在反例表补了"代码里一半英文 docstring 一半中文注释"一行,指向 R6.5。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 17:41:24 +08:00 |
|
arm_dev
|
806efe4df1
|
修掉两个"存在但跑不起来"的脚本:CRLF 行尾 + 新增 .gitattributes 统一为 LF
症状(没有报错,只有"文件在、文档说能用、一跑就死")
$ bash scripts/run_hole_detect.sh --arm 89 --preview
scripts/run_hole_detect.sh: 行 11: $'\r': 未找到命令
scripts/run_hole_detect.sh: 第 12 行: set: pipefail: 无效的选项名
另一处 $'\r': 未找到命令 来自 scripts/run_dual_gripper.sh(同样症状)。
根因
这两个 .sh 是 100% CRLF 行尾。bash 会把行尾的 \r 当成标识符的一部分,
于是 `case "$1" in\r`、`set -eo pipefail\r` 全部变成非法记号。
而文档在五处告诉用户跑它:README.md、docs/WORKSPACE.md、docs/ARCHITECTURE.md、
docs/SITE_CONFIG.md、docs/HOLE_DETECT.md(HOLE_DETECT.md 直接给了命令)。
即"文档齐全、文件存在、命令一敲就死",报错信息里的那个 \r 又完全看不出来。
为什么之前没人发现(这是本次真正的教训)
bash -n 会漏报:对 run_dual_gripper.sh 它返回 OK,可实际执行立刻死在第 6 行。
所以"静态检查通过"不能当成"脚本能用"。验脚本要么真跑 --help,要么用
file/bat 看行尾。这条已写进 .gitattributes 的注释里。
改了什么
1. scripts/run_hole_detect.sh、scripts/run_dual_gripper.sh 由 CRLF 改为 LF。
去行尾符后与改前逐字节相同 —— 只动行尾,一个字符内容都没动。
已验证:两个脚本 bash -n 通过,且 run_hole_detect.sh --help 能正常打印中文帮助。
2. 新增 .gitattributes:
* text=auto eol=lf —— 本仓库自己的文件一律 LF
/src/flexiv_*/** -text —— 厂商上游代码内容与行尾都不动(R4.4)
/thirdparty/** -text
*.onnx/*.engine/*.trt 等二进制显式标 binary,避免被当成文本转换
3. 顺带把本仓库其余 19 个仍是 CRLF 的文件(.py 9 个、.yaml 2 个、.launch.py 2 个、
.msg 5 个、.srv 1 个)统一为 LF。这些文件在 Python/YAML 下本来能正常读,
所以此前没有症状;但同一仓库混着两种行尾,下次再有一个 .sh 中招就是必然。
已核对:去掉行尾符后与 HEAD 逐字节相同 —— 内容零变化。
验收
21 个文件:git diff --ignore-cr-at-eol -w 只列出 .gitattributes(零内容变化)
scripts/*.sh scripts/lib/*.sh:bash -n 失败数 0(改前是 2)
单测:608 passed,与改动前一致
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 17:41:06 +08:00 |
|
arm_dev
|
72353f3773
|
注释与提示信息全面中文化:源码 66 个 .py + 脚本/配置 45 个文件
背景:现场读代码的人只读中文。此前代码里的 docstring 是英文、行内注释是中文,
一半英文一半中文反而更难看懂 —— 新手打开一个 .py 不知道这个模块为什么存在。
范围(本仓库自己的代码,厂商上游不动)
src/arm_application/** 37 个文件:servo_node / perception_node / handeye_* / dh_pgea /
yolo_detector / kalman_filter / goto_ee_plane / dual_gripper …
src/arm_interfaces/** 11 个文件:paths / manifest / loader / config_guard / errors …
src/arm_control/** 5 个文件
src/arm_bringup/** 3 个 launch
scripts/** 6 个 .py + 29 个 .sh(scripts/ 是"唯一操作入口",用户屏幕上
看到的就是这些提示,所以提示信息也一并中文化)
config/ handeye/ devices/ src/arm_*/config/ 17 个 .yaml/.xml/.xacro/.cfg
口径(写进 docs/FRAMEWORK_RULES.md 新增的 R6.5,作为长期约束)
注释、docstring、print/日志/异常里给人看的句子 = 中文;
标识符、API 名、话题名、参数名、字段名、命令名 = 保留英文原文(用 `` `` 包起来);
单位、十六进制、协议报文、CRC 值、命令示例 = 原样保留;
src/flexiv_*/ = 厂商上游代码,不许改也不许翻译(R4.4)。
翻译保原意与原有详细程度:那些"为什么不能用另一种做法""踩过什么坑"的说明
全部完整保留 —— 它们才是这份代码里最贵的部分。
新增 scripts/check_text_only.py:只改文字时的自证工具
人工 review 靠不住(115 个文件、3000 多行 diff)。这个脚本把改动前后的源码解析成
语法树,删掉 docstring、抹平字符串常量、把 f-string 只留"插值了几次 + 插值是什么",
再比对:完全相同 = 改动范围内没有逻辑变化。
留了一个口子:f-string 的插值按内容排序后再比 —— 因为中文语序常常要和英文反着来
(f"on {port} read {idx} failed" → f"在 {port} 上读 {idx} 失败"),只换显示顺序不该判失败;
但多插一个、少插一个、换掉被插入的表达式,立刻会报出来。
本次 66 个 .py 全部通过该闸门。
验收
改动的 66 个 .py:scripts/check_text_only.py 全部通过(零逻辑变化)
45 个非 .py 文件:去注释后与 HEAD 逐字节相同(零配置变化)
docstring 总数:没有一个文件减少(不存在"翻译时顺手删了说明")
单测:arm_application 233 / arm_driver_flexiv 69 / arm_interfaces 46 /
arm_kinematics 24 / arm_sim_gazebo 236 = 608 passed,与改动前一致
全部 .sh:bash -n 通过;并额外实际执行了 --help 验证(见下一个提交:
bash -n 对 CRLF 脚本会漏报,只有真跑才暴露)
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 17:40:55 +08:00 |
|
arm_dev
|
d78e66ce98
|
P5a 框架规则文档:M1-M3 元规则 + R1-R6 分领域规矩 + 反例清单
背景:这个框架踩过的坑几乎都是同一类——"没报错但结果是错的"。
之前这些教训只存在于聊天记录和零散注释里,改一次代码就要重踩一遍。
现在收敛成一份可引用的文档,并压缩成 .cursor/rules/ 常驻规则。
docs/FRAMEWORK_RULES.md(414 行)
第 0 节 三条元规则:M1 静默失效是头号敌人 / M2 单一数据源 / M3 口径必须显式
第 1 节 配置与数据 R1.1-R1.5(分层优先级、site.yaml 新键必须接线或告警、设备走 devices/、权重走 models/)
第 2 节 几何/单位/坐标系 R2.1-R2.7(米与弧度、frame_id 必须是光学系、深度 mm↔m、
rpy=Rz·Ry·Rx、Gazebo camera_info 不可信、像素门槛绑图幅、位姿容差要写理由)
第 3 节 触觉与力 R3.1-R3.6(力是相对基线的增量、fnormal 压为正、轴向映射必须实测、
接触布尔要用超时、force_scale 口径、仿真图像只能占位)
第 4 节 仿真 R4.1-R4.10(唯一 site.yaml、world 里写 plugin 会挤掉默认系统、搜索路径要在
launch 前设好、上游 description 不许改、场景走 scene.yaml、校验要逐碰撞体、
不用"有话题就算通"验收、停仿真用 stop_sim_gz.sh)
第 5 节 代码与测试 R5.1-R5.7(纯逻辑抽纯函数、校验要有测试证明有效、不许把降级写成通过、
解析外部输出先确认真实格式、删除前扫全仓库引用、别在中文路径 colcon build、真机仿真产物隔离)
第 6 节 文档与提交 R6.1-R6.4(文档与实现不一致立刻修文档、每份文档说明记录什么事实、
结论数字要可复现、提交信息讲为什么并附实测数据)
第 7 节 改动前检查清单
第 8 节 历史反例清单——按"你看到的症状"查表,不是按道理排序
第 9 节 给 AI 的具体指令
.cursor/rules/*.mdc 三份压缩版(常驻)
framework-core.mdc alwaysApply,元规则 + 检查清单 + 禁止项
sim-gazebo.mdc globs=devices/sim/**,src/arm_sim_gazebo/**
config-and-devices.mdc globs=config/**,devices/**,models/**,src/arm_interfaces/**
顺手修掉文档与实现不一致(R6.1)
docs/WORKSPACE.md 此前【完全没有 devices/ 这一层】——通用化落点却没进目录总表。
补 devices/ 一节(kind/brand 目录约定、registry.yaml 与 manifest.yaml 分工、
assets[].required 缺失会报错),补 build_sim/install_sim/log_sim 与 .cursor/rules/,
并把 FRAMEWORK_RULES.md 标为 docs/ 首行
README.md 文档表首行加 FRAMEWORK_RULES.md
顺手删死代码(R5.5:先扫引用)
src/arm_interfaces/arm_interfaces/paths.py 删 assets_root()
它返回 ros2_root()/"assets",而仓库里从来没有 assets/ 这个目录——实际权重树是 models/,
且 models/ 的定位已由 arm_application.weights_layout.default_weights_root() 负责
(带 SITE_WEIGHTS_ROOT 覆盖,且能从 install/ 反查仓库根)。
两个实现并存违反 M2,且这个还指向不存在的路径,属于"照着名字用就踩空"。
src/arm_interfaces/test/test_real_devices.py 从清环境变量列表里去掉 SITE_ASSETS_ROOT
验收
src/arm_application 233 passed / arm_driver_flexiv 69 / arm_interfaces 46 /
arm_kinematics 24 / arm_sim_gazebo 236 = 608 passed
(flexiv_rdk 的 test 需装厂商 flexivrdk 包,仿真环境无此依赖,与本改动无关)
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 17:16:47 +08:00 |
|
arm_dev
|
d32e6ae53f
|
P5c-6 场景物料落地 + Gazebo 中文手册:工件/通止规模型、场景校验、两处静默失效修复
一、场景物料(新增)
devices/sim/gazebo/models/workpiece/ 带三个孔的铝板。碰撞体是"带方孔的平板",
视觉额外加两个 φ14/φ7 亮环 —— 因为真机的 HoughCircles 就是靠这两个亮环找孔,
只按碰撞体建视觉会让"孔检测"在仿真里永远失败,而那看起来像算法问题。
devices/sim/gazebo/models/gauge/ 通止规:通端/套环/握柄/止端四段,各段异色
便于肉眼判断方向。接触刚度刻意比台面软 —— 位置控制器会"刚性压进去",
用默认刚度会被弹飞(注释里写了原因,不是随手抄的参数)。
devices/sim/gazebo/models/gauge_stand/ 规座(static)。规直接站在台面上会立刻倒,
加个方孔底座把它立住。
三个模型都是 model.sdf + model.config,原点在**底面中心**,单位米。
二、场景配置与校验(新增)
config/scene.yaml 物体不再写死在 world 里,改为 scene.yaml + 启动时动态 spawn。
理由:world 是"环境"(地面/台面/相机/系统插件),场景是"这一批活做什么"。
动 world 的风险面太大 —— 它只要丢一个系统声明,所有模型都 spawn 不进去(见旧坑 2)。
含 operating(相机到工件的距离等作业条件)与 gates_px(像素门槛)。
sim_scene.py 摆放校验:从 SDF **逐碰撞体**解析 AABB(不抄尺寸,避免漂移),
检查原点在底面 / 不穿台面 / 不出界 / 物体之间不嵌入。
重叠判定必须逐碰撞体三维判,不能拿总包围盒相交 ——
正常装配里本来就有嵌套(规立在规座方孔里),粗判会误报,
而误报的后果是人把检查整个关掉,之后真重叠也抓不到。
另有成像几何:把亮环/孔间距按相机距离换算成像素,与门槛比对。
scripts/check_sim_scene.py 验收工具(退出码可用):摆放 + 门槛漂移 + 成像几何 +
悬停高度 vs 工作空间 + 手外相机是否**完整**看得见工件 + Gazebo 实际位姿。
三、修掉两处静默失效(都是"看起来一切正常")
1) parse_spawned_poses 一直是**死代码**:它按 "x: N / y: N / z: N" 解析,
而 Fortress 实际输出是方括号两行(XYZ / RPY)。对不上 → 永远返回空 →
调用方按设计"降级成跳过位姿核对" → 位姿检查**从来没跑过**,
而日志只说"跳过",看起来像正常降级。
现在两种格式都支持,并用真实抓下来的输出样本做回归测试。
教训:宽容降级会把"没检查"伪装成"检查通过"。
2) stop_sim_gz.sh 的模式表里漏了 robot_state_publisher(而注释里写着它有覆盖):
脚本报"已停止",它却还在发上一份 /robot_description 与 TF,
下一次启动就会"改了描述却不生效"。
顺带修掉同源的两个问题:awk 用 -v 传模式导致它匹配到自己、每次复查都多报
一个空命令行的幽灵 PID;以及"杀的时候"和"复查的时候"两份扫描实现会各自漂移。
现在共用同一个 scan_pids(),模式改用环境变量传递,并在杀完之后**再扫一遍**:
有残留就报出来并非零退出 —— 下次再有漏项,不再静默。
实测:--list 列出全套 9 个进程;停止后 rc=0 且 pgrep 干净。
四、相机:D405 分辨率对齐真机
仿真的 D405 此前渲染 640x480,而真机跑 848x480(fx 337 而非 447)。后果是
58mm 的两孔间距成像只剩 85px,低于 hole_distance_min=90 ——
孔检测在仿真里根本不可能通过,而现象看起来像"算法不行"。
已把 xacro 与 cameras.yaml 一起改成 848x480,并把启动时的比对从"只比视场"
扩成"视场 + 图幅"(sim_cameras.check_camera_consistency),
不一致时说明它对像素门槛的影响。
五、文档
devices/sim/gazebo/README.md 重写为中文手册:使用方法 / 建模要求 / 导入格式 /
配置方式(含"改哪里才真的生效"的接线表)/ 11 条实测坑(每条都有"怎么发现")。
manifest.yaml 的进度、assets、description、坑清单与本轮状态对齐。
scripts/README.md 补两个验收工具与停止脚本;根 README 补仿真入口与手册链接。
cameras.yaml 里一处过期的函数名(check_fov_consistency)修正。
六、测试
新增 test_sim_scene.py(79 条,此前 sim_scene.py 完全没有单测 —— 这正是
位姿解析死掉却无人发现的原因)与 test_check_sim_scene.py(21 条),
含三条回归:world_boxes 的偏移碰撞体不能被对称化放大(假重叠)、
center_bore 不能被 _bore 后缀吃掉、Fortress 位姿输出格式。
全量:src/arm_sim_gazebo + arm_interfaces + arm_application 共 515 通过。
同时附上 config/site.example.yaml 的重新生成(此前 site.yaml 的注释改动没同步,
它的 parity 测试是红的)。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 17:13:00 +08:00 |
|
arm_dev
|
01315773e7
|
P5c-5 触觉适配器落地:Gazebo 关节力 → arm_msgs/TactileStatus
链路:指尖关节 force_torque(持续发布)→ ros_gz_bridge → sim_tactile_adapter
→ /tactile/{left,right}/status。字段口径与真机 tactile_node 完全同构:
20Hz、frame_id=<side>_tactile、fnormal clamp 到非负、所有力都是"相对静止
基线的增量"。所以 compliant_descent 与 stage3_search_node 一行不改就能在
仿真里跑 —— 这是"同一套接口、换后端"契约的一部分。
新增 sim_tactile.py 把三件纯数据变换独立出来(轴向投影、桥接参数、基线
跟踪),因为这三处的错误全都**不会报错**,只会让力的语义静默错位:
轴向映射反了照样"有力、会响应、松开归零",基线把接触吃掉了则表现为
"明明夹住了 fnormal 恒为 0"。抽出来才有单测可钉(新增 25 条)。
已实测验收(关闭夹爪夹静态薄板,60×120×80mm):
空载 fnormal ~1e-8、contact=false(基线相减到位)
夹住 contact=true、fnormal=714.7/102.5,切向同时起变化
松开 contact 立刻回 false(靠 contact_timeout_s,因为 gz 的接触消息
是事件式的,不接触时一条都不发),力回落到 ~1e-8
启动期 check_tactile_collisions 无告警
check_tactile_collisions 顺带被负向单测逼出两处"会被静默骗过"的判据:
sdformat 对缺失的 contact/topic 会补 __default_topic__、对缺失的 collision
会补 __default__,所以只判"空"是抓不到的 —— 必须连占位值一起判。
force_scale 口径写入 config/tactile.yaml:仿真输出 = Gazebo 牛顿 × force_scale。
要共用真机那套 250/600 阈值必须先标定它,否则要么永不触发要么一碰就炸
(实测位置控制器硬压可到几百,量级与真机完全不同)。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 16:32:56 +08:00 |
|
arm_dev
|
768da34cf4
|
P5c-5 触觉三向力落地:改用力指关节 force_torque 传感器(接触消息根本没有力)
此前方案是"从接触传感器拿力",实测证明**行不通**:
gz.msgs.Contacts 虽然 schema 里有 wrench/force 字段,但 Fortress 的
Contact 系统从不填充它们 —— 一条接触消息全文只有 header + 碰撞体名 +
接触点坐标,连 normal/depth 都是空的;该系统的二进制里连一个 "wrench"
字符串都搜不到。所以那不是配置问题,是设计上只有位置。
改为:
- 三向力 ← <sensor type="force_torque"> 挂在**指尖关节**上
(转动关节物理上要承受三向约束力,读到的即整条手指链的力)
- 是否接触 ← 保留接触传感器(几何接触,比力阈值准)
- URDF 无"关节传感器"语法,用 sdformat 扩展 <gazebo reference="关节名">
表达;已验证转换后传感器确实落在 <joint> 内
踩坑(与 contact-system 同一个坑的第二次):
world 里声明了任何 <plugin> 后 gz 不再加载默认系统列表,所以
forcetorque-system 必须显式声明,否则 /tactile/*/wrench 话题**不出现**、
日志一片空白。补上后立即出现。
已实测确认:
- wrench 话题 ~100Hz **持续发布**(无接触也发零)—— 这点关键,接触传感器
只在接触时发消息,若适配器只靠它驱动,下游会误判"话题断流"
- 静止读数 = 远端三个 link 重量 2.94mN,物理自洽
- 轴向:静止重量落在 child x(竖直),child z = 世界 +x,故法向 = child y;
符号与 force_scale 一起作为可配置标定项
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 16:00:52 +08:00 |
|
arm_dev
|
83c7a89731
|
P5c-5 仿真夹爪可开合:Gazebo 不支持 mimic,改为显式驱动全部 7 个关节
三条独立的坑,前两条都在"看起来完全正常"的表象下静默失效:
1) 夹爪在仿真里是死的(关节级)
夹爪只有 finger_width_joint 是真关节(effort=80),其余 6 个手指关节
全是 mimic、effort="0"(上游语义:靠机械耦合跟随,不由电机驱动)。
而 sdformat 转 SDF 时**直接丢弃 <mimic>**(实测生成 SDF 里 "mimic"
出现 0 次),gz-sim 也没有任何 mimic 实现,SDF 规范里就没有这个元素。
于是 7 个关节在 Gazebo 里互相独立、谁都不跟随谁。
2) 补了 effort 才有用(力度)
gz_ros2_control 的 position 接口是"位置误差 → 力"
(实测 position_proportional_gain=0.1),力被 effort 上限夹到 0 →
永远出不了力。症状极隐蔽:接口 [claimed]、控制器 active、话题有订阅、
命令也发得出去,但手指一动不动,只是自由地被重力拽着漂。
对照实验:同一套链路下 sim_joint1 收到 0.2 rad 后精确到位(0.19998),
所以链路本身没问题,差别只在这 6 个关节的 effort=0。
3) 描述与 Gazebo 的事实不一致(语义)
robot_state_publisher 支持 mimic,会用主关节的值覆盖从动关节;而 Gazebo
里它们是独立被驱动的。实测当前 RSP 没有应用 mimic(TF 跟随真实值),
所以碰巧一致 —— 但这是实现细节,它一变 TF 就静默退回闭合位。
改动:
- ros2_control 增加夹爪 7 个关节的接口(含 load_gripper 开关)
- controllers.yaml 增加 gripper_controller(JointGroupPositionController)
- 启动器 spawn 它;并重命名归一化第三条修复
- urdf_normalize 第三条修复:被 mimic 关节补 effort=80、删除 <mimic>
并记录耦合关系;新增 mimic_relations() 供适配器在归一化前读取
(信息只是搬家,不能丢)
- 新增 6 项单测锁定上述行为
验收(实测):命令 width 0→0.1,指尖间距 27.1mm → 127.0mm(约 100mm 行程,
与真机 GRAV 规格吻合),7 个关节实测值均命中命令值,TF 与之一致。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 15:46:08 +08:00 |
|
arm_dev
|
02516fe567
|
P5c-5 触觉链路打通:接触传感器此前是"完全静默失效",已定位并修复
两条独立的静默坑,都不报错、不告警,只会"永远收不到数据":
1) world 里没声明 contact-system 系统插件。
gz-sim 的 <sensor type="contact"> 不由 ignition-sensors 库实现,而是由
ignition-gazebo6-contact-system 插件实现 —— 所以环境里根本没有
libignition-sensors6-contact.so(相机/IMU/lidar 的后端都在,唯独缺它),
那不是包缺失。不声明插件的症状:话题不出现、日志空白。
实测:加上插件后 /tactile/left/raw、/tactile/right/raw 立刻出现。
2) 接触传感器的三个必备写法,错一个就静默失效:
- <contact><collision> 必须写 URDF→SDF 自动生成的 collision 名;
留空 <contact/> 会被补成 SDF 规范的默认值 __default__(不存在的
collision)。实测生成名:
sim_left_finger_mount_fixed_joint_lump__sim_left_finger_tip_collision
- 话题必须写在 <contact><topic> 里,写在外层 <sensor><topic> 无效,
gz 会静默改用默认 scoped 话题名。
- 传感器必须挂在有碰撞体的 link 上(指尖),不是另建的空 link。
两处均已按实测结果修正,并把"为什么这么写"写进文件注释。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 15:29:09 +08:00 |
|
arm_dev
|
47ad9d9a8b
|
P5c-4 相机出图联调:桥接 d405/d435 到 ROS,并修掉两处静默的几何错误
相机链路(devices/sim/gazebo/config/cameras.yaml 是唯一真相)
- sim_cameras.py 纯逻辑:话题映射 / 深度换算 / 内参 / 视场一致性比对
- sim_camera_adapter.py 改光学系 frame_id、32FC1 米 → 16UC1 毫米、覆盖内参
- gazebo_sim.launch.py 桥接 + 适配器 + 世界相机的静态 TF(位姿从 world 解析)
本轮修掉两个**静默**的几何错误(都能出图、都能查到 TF,但点位是错的)
1. gz 发的 camera_info 是错的:恒为 fx=fy=277, cx=160, cy=120
(等于它的默认 60° 视场 + 半分辨率主点),而渲染用的是配置的 87° 视场。
照抄它做三维反投影,点会偏半米量级;单目预览完全正常。
用已知地面平面做最小二乘反解,得 fy=337.2 / cy=239.5,与
640/(2·tan(87°/2))=337.2、480/2=240 吻合(残差中位 0.2mm)。
改为由 sim_cameras.intrinsics() 按 cameras.yaml 的视场重算并覆盖。
代价:cameras.yaml 的 horizontal_fov 必须与 SDF 一致,启动时自动比对告警。
2. assembly.world 里 D435 的 rpy 写成 (0, 50°, 180°),
而 URDF 相机的视线是**本体 X 轴**、rpy 复合顺序又是 Rz·Ry·Rx,
于是"先俯 50° 再偏航 180°"把视线翻到了背面:相机背对台面、只看地面。
它不报错,而且几何自洽(点确实落在真实地面上),只查数值发现不了。
改为 (0, 50°, 0),并加回归测试锁住。
新增验收工具 + 测试
- scripts/check_sim_cameras.py 反投影深度、要求命中地面/台面才算通过。
"有图像"不是验收标准;实测 d435 台面 11.9%/地面 75.3%、d405 台面 37.8%。
- 97 项单测(新增 sim_cameras 22 项、sim_site 16 项)
顺带修掉一处"改了配置静默无效"
- sim.world 此前硬编码在 robot_description._WORLD_REL,而 site.yaml 与
manifest.yaml 都宣称改它就生效。现在真的读了:
优先级 --world > site.yaml:sim.world > 内置默认(相对路径按仓库根解析)。
- physics_enable / joint_prefix 仍未接线,但若其值与实际状态冲突,
启动时会告警(sim_site.unwired_warnings),不再静默忽略。
文档
- devices/sim/gazebo/README.md 重写:原文还写着"仿真不是 Gazebo"、
相机插件用 libgazebo_ros_camera.so,与实现早已不符。现分三部分:
哪个"仿真"是真的 / 现状与缺什么 / 五条静默失效的坑及怎么验。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 15:19:16 +08:00 |
|
arm_dev
|
f67f59f383
|
handeye/calibrate.yaml 精简为纯算法参数,移除站点相关副本
原先这里各留了一份站点相关值的副本,与 poses/ 里的实际数据并不一致:
calibrate.yaml 内嵌示教点 13 点,2026-08-28
poses/89_eye_in_hand.yaml 12 点,2026-09-02
只是恰好被覆盖顺序压住(run_handeye.sh 把 poses 文件作为后一个
--params-file,且用 -p 显式传 joint_prefix / output_dir / result_file),
所以当前行为是对的。但留着就是给"改了脚本顺序 / 直连节点"埋雷:会静默用上
旧数据集,而症状只是"标定结果变差",几乎不可能联想到这份副本。
这也是 SN 的又一处分散暴露面。
已移除 joint_prefix / output_dir / num_poses / waypoint_positions,
保留算法参数(mode、board_*、稳定判据、velocity_scale 等),并在原位写清
这四个值各自由谁提供。节点侧对它们本就有默认值,删掉不会报参数缺失;
但 waypoint_positions 的节点默认值是全 0,所以**直连节点是跑不了的**,
必须经 run_handeye.sh —— 这一点已在注释里说明。
验证:bash scripts/run_handeye.sh show 全部路径正常解析
(calibrate / poses / result / workdir 均正确,num_poses 读自 poses yaml)
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 14:52:32 +08:00 |
|
arm_dev
|
01940f54e7
|
P5c-3 仿真启动器跑通:Gazebo + spawn + 两个控制器 active
端到端验收(headless,本机无显示器):
ign 加载 Physics/UserCommands/SceneBroadcaster/Sensors + gz_ros2_control
机械臂 spawn 进 assembly 世界,7 个 sim_joint 全在 /joint_states
joint_state_broadcaster 与 rizon_arm_controller 双双 active
world → sim_tcp 可查,且末端位姿 3 秒内不变(重力下站住)
网格解析报错 0 条
新增:
scripts/run_sim_gz.sh 此前**不存在**,却被 install_sim_env.sh
与 build_sim.sh 的提示语引用
arm_sim_gazebo/robot_description.py 展开 + 归一化,launch 与 CLI 共用一份
arm_sim_gazebo/gazebo_sim_launcher.py 薄壳:准备描述后转发给 launch 文件
launch/gazebo_sim.launch.py 起 gz → 发归一化 URDF → spawn → 拉控制器
test/test_robot_description.py 含"仓库里这份 world 必须过守卫"的测试
接线过程中实测踩到并修掉的三个坑,共同特征是**每一步看起来都成功**:
1) urdf_normalize 在 Python 路径下静默失效(我自己引入的,最严重)
xacro 的输出格式在不同环境里不一样:真机环境的 CLI 缩进成多行,而仿真环境
走 xacro.process_file().toxml() 时全挤在一行。归一化的正则要求标签之间必须有
\n,于是**仿真路径下它什么都没做**(报告"包裹 0 处"),而 CLI 路径下正常 ——
之前那次"已修好"的验证走的就是 CLI,所以看起来过了。
后果是 Gazebo 会丢掉整条机械臂,正是这个归一化本来要防的事。
已改成对空白格式零假设,并加回归测试锁住。
2) world 里声明了 <plugin> 会把默认系统挤掉
只要 world 声明任何一个 <plugin>,Gazebo 就不再加载 server.config 的
Physics/UserCommands/SceneBroadcaster。缺 UserCommands →
/world/<名>/create 服务不存在 → ros_gz_sim create 调服务超时后回退发 topic、
没人收 → **模型从未进入 Gazebo**,而 create 节点仍打印
"OK creation of entity",控制器则永远卡在 "waiting for service"。
已在这三个系统在 assembly.world 里显式声明,并加纯文本守卫
check_base_plugins(),让"加了插件忘了补基础系统"在启动前就被指出。
3) gz 的插件与资源搜索路径缺 $CONDA_PREFIX/lib
gz_ros2_control 报 "Failed to load system plugin
[libgz_ros2_control-system.so] : couldn't find shared library",而 ldd 全齐、
库就在 $CONDA_PREFIX/lib 下。原因是 gz 的 SystemLoader 先在搜索路径里找文件,
找不到就直接放弃,不去 dlopen 裸文件名(所以 conda 二进制的 RPATH 兜不住);
而 ros_gz_sim 只把 LD_LIBRARY_PATH 拼进搜索路径,conda 激活并不往那里加。
同一原因导致 mesh 的 model:// 也解析不了(视觉报错很吵,但更要紧的是
**碰撞网格也加载不出来**,会让后续装配的接触判据失效)。
由于 gz_sim.launch.py 用 `from os import environ` 在生成 launch 描述时就读
进程环境,这些变量**必须在 ros2 launch 之前**设好,因此放在 sim_env.sh ——
在 launch 文件里用 SetEnvironmentVariable 补是无效的,会被它自己算的那份
字典覆盖。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 14:48:16 +08:00 |
|
arm_dev
|
e9da641160
|
README 与 handeye:把"什么该入库"的规则和仓库实际状态对齐
子代理同步文档时发现 README 的"要提交/不要提交"清单漏了 handeye/,顺着一查发现
规则写的是"不要提交含序列号的文件、克隆后必须重新标定",但仓库里
handeye/poses/ 与 handeye/results/ 一直是**被跟踪**的,而且
results/89_eye_in_hand.yaml 的文件头就写着 Rizon4-063989 —— 正是那个让
config/site.yaml 被排除的序列号,从另一个门漏了出来。规则与实际不符。
澄清后的规则(已写进 README):按"配置 vs 资产"区分,而不是按"是否含序列号"
配置属于站点 → 不入库(config/site.yaml、handeye/tmp/)
标定成果属于资产 → 入库(handeye/poses/、handeye/results/)
理由是手眼标定要上真机示教十几分钟,是费真机时间换来的;入库才有一份可回滚的
备份。若按"含序列号就排除"一刀切,代价是误删后必须重新上真机采点。
同时把结果文件头里的完整序列号去掉(只留臂号 89/91)—— 臂号本来就唯一标识了
是哪台机器,完整 SN 是多余的暴露面。
另:handeye/README.md 补充"tmp 是按臂号分目录"的后果说明,并点出直接启节点
(绕过 run_handeye.sh)会退回共用的 handeye/tmp —— 那种情况下两条臂的采点会
互相覆盖,而症状表现为"标定结果突然变差",很难联想到目录冲突。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 14:27:43 +08:00 |
|
arm_dev
|
6626d18020
|
文档修正:handeye 的 tmp 目录是按臂号分的,不是共用一个
handeye/README.md 里把 tmp/ 画成了扁平的一层,并写"采点数据写在 handeye/tmp/"。
实际生效的取值来自 site.yaml 的 handeye_workdir,是 **按臂号分目录**的
(handeye/tmp/89、handeye/tmp/91)。
这不是措辞问题:两条臂若真的共用一个工作目录,先标定的采点会被后标定的覆盖,
而症状表现为"标定结果突然变得很差",几乎不会有人联想到是目录冲突。
所以顺带把这一点和它的后果一起写清楚。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 14:20:42 +08:00 |
|
arm_dev
|
25635e21f8
|
P5c-2:仿真世界 + 机器人描述 + 控制器配置,并解决两处官方描述与 Gazebo 的不兼容
这一步解决的是"官方 Flexiv 描述能不能喂给 Gazebo"的问题。结论是**不能直接用**,
有两处不兼容,且都只有 Gazebo 会暴露 —— RViz/MoveIt 完全正常,所以此前从未发现。
问题一:7 轴本体的质量/惯量是"孤儿标签"
rizon_arm.xacro 调 get_inertias 时没写 <inertial> 包裹,而那个宏只吐
<origin>/<mass>/<inertia> 三个碎片。RViz/MoveIt 忽略不认识的子元素所以看着
没事;sdformat 判定 "link has no <inertial> block",然后把 **link 和它的父
关节一起丢掉**。症状极具误导性:机械臂凭空消失或只剩夹爪,报错只有一句
"link[sim_link1] is not modeled in sdf"。
问题二:夹爪有零质量 link,导致 sdformat 惯性叠加出 NaN
grav_macro.xacro 给六个纯坐标系 dummy link 写了 mass=0、惯量全 0。
sdformat 对 fixed 关节会把子 link 惯性叠加到父 link,叠加要除以总质量 →
质量 0 就出 NaN。而报错指向的是**父** link:
Pose values must be finite. link[sim_left_finger_mount]/inertial/pose
完全看不出根因在子 link 的质量上。
修法:启动时归一化(不走改上游文件的路,因为上游由 fetch_flexiv.sh 获取、改了会被
覆盖;而且换别的臂时大概率有别的坑,在启动时统一过一道更可持续)
- 孤儿三件套 → 只重新分组已有元素,一个数字都不改
- 零质量 → 补 1e-4 kg(与夹爪其他配件同量级,0.1g 对动力学无影响),
惯量全 0 时补极小对角值避免张量退化
不发明物理数据,也不掩盖真问题:上游真没给质量时这里什么都不会包。
16 项单测,含"已有 <inertial> 不能被二次包裹"(早期用全局正则踩过,
sdformat 报 "Inertial element must have a mass element")与幂等性。
新增/改动
- devices/sim/gazebo/urdf/:arm.sim.urdf.xacro(主)、.ros2_control(gz 硬件)、
.gazebo(系统插件/材质/接触刚度)、.sensors(D405 + 双指触觉)
- devices/sim/gazebo/worlds/assembly.world:1ms 步长 + 150 次接触迭代(力信号
要干净才谈得上"切向力突变"判据)、工作台、world 级渲染插件、D435
- devices/sim/gazebo/config/controllers.yaml:控制器名 rizon_arm_controller
**故意与真机一致** —— 隔离的是关节/TF 层(sim_ 前缀),不是应用层依赖的接口
- src/arm_sim_gazebo/:新包,含 urdf_normalize(库 + CLI)与单测
一处设计纠正:D435 从 URDF 挪进 world。原本挂在 URDF 的 world link 上,结果
整台相机转 SDF 时凭空消失(sdformat 丢弃无质量根 link 时连带丢了整棵子树,
且退出码 0、不报错)。眼在手外的相机物理上就属于场景,放 world 才对;
渲染插件也一并归 world 级,避免"插件在模型里、相机在世界里"的错配。
验证(不是只看退出码)
- 带夹爪全量:sdformat 退出码 0、0 报错、15 link / 15 joint / 26 frame
- 传感器 d405_rgbd + tactile_left/right 均在;控制器插件与参数路径正确
- 世界文件 sdformat 通过
- 包在 ros_gz_sim 环境构建通过,CLI 实跑结果与库调用一致
- 未注册 gazebo_sim_launcher 入口点:模块还没写,注册了构建能过但一运行就
ImportError,不如老实缺着(manifest 里如实标注为未实现)
注:实测确认 gz_ros2_control 的硬件/接口声明**不在 SDF 里**,插件是从
/robot_description 话题读 URDF 的 <ros2_control> —— 所以启动器必须先起
robot_state_publisher 且发布归一化后的同一份 URDF,否则控制器注册的关节名
会和 Gazebo 里的对不上。这条已写进 P5c-3 的实现要求。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 14:19:39 +08:00 |
|
arm_dev
|
d93dab31c9
|
P4d:文档同步 —— 修掉 P3 手眼路径迁移后遗留的过期引用,并指向新的 weights 工具
P3 把 handeye 相关文件从 src/arm_application/config/ 迁到了仓库根的 handeye/,
但文档还写着老路径。这类"代码改了、文档没跟"的偏差最麻烦:新人照着文档敲
命令会直接报文件不存在,然后开始怀疑人生。
同步内容
- docs/HANDEYE.md / WORKSPACE.md / SITE_CONFIG.md / ARCHITECTURE.md:
手眼结果与示教点位改指 handeye/results|poses/<臂号>_<模式>.yaml,
临时目录改指 handeye/tmp/<臂号>/,并补上 --mode / --points / --auto-solve。
- docs/INTERFACES.md / TRACKING.md / docs/HOLE_DETECT.md / README.md:
权重那一行从 export_yolo_trt.sh 改为 weights.sh,并注明前者已是兼容转发壳。
- docs/HOLE_DETECT.md:hole_weights / workpiece_weights 的实际取值改为
models/hole_insert/detect_best_1.onnx(原文档写的是不存在的 best.pt)。
- scripts/merge_handeye_poses.py:只更新文件头注释里的示例路径。
多处改为指向 models/README.md 与 handeye/README.md,避免同一套规则在
七八个文件里各写一遍、然后各自漂移。
验证:rg 扫 docs/ README.md,已无 handeye_poses_/handeye_result_/
/tmp/handeye 之类过期路径残留。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 13:52:28 +08:00 |
|
arm_dev
|
a89250d05d
|
P5c-1:把 Gazebo 仿真环境真正装起来(独立 conda 环境),并固化安装/构建流程
在此之前,仓库里所有 Gazebo 相关的东西都是"声明"而非"事实":
arm.gazebo.xacro 零引用、stack.launch.py 的 isaac 分支只打印日志、
没有 world 文件、devices/sim/gazebo 下的目录全是空占位。
更关键的是:**这台机器上根本没装 Gazebo**,而 ROS 在 conda 里,没有 /opt/ros,
apt 的 ros-humble-gazebo-* 装了也不会被 conda 的 ROS 认到。
环境(与真机环境物理隔离)
- 新建 conda 环境 ros_gz_sim,**不碰能跑真机的 ros_flexiv**。
仿真要往环境里塞 Gazebo 及 protobuf/ignition/渲染一整条链,
和真机环境混装随时可能把它弄坏,代价远大于仿真带来的便利。
- 频道组合照抄 ros_flexiv 的安装记录(robostack-staging + conda-forge),
那是本机已经证明可用的搭配。
两个实测踩到的坑(都写进了脚本注释,避免下次重踩)
1. empy 版本:conda 默认给 empy 4.x,而 Humble 的 rosidl_adapter 用的是 3.x 的
API。症状是构建自定义消息包时报 `_interpreter.shutdown()` 的 AttributeError,
报错点在 rosidl_adapter 内部,完全看不出跟 empy 有关。必须钉 empy=3.3.4。
2. conda 激活是**堆叠**的:同一 shell 里从 ros_gz_sim 切到 ros_flexiv 后,
`which python` 指向 ros_flexiv,但 `python -m pip` 却解析到 ros_gz_sim 的 pip,
导致 colcon --symlink-install 报 "option --editable not recognized"。
这个报错和真实原因毫无关系,极易误判成"环境被搞坏了"。
现在 scripts/lib/conda_switch.sh 会在切换前退到 base,两个 env 脚本共用。
仿真用独立构建产物
- build_sim/ + install_sim/(scripts/build_sim.sh)。工作区只有一份源码,
若仿真也写 install/,就会覆盖真机的构建产物。
- 排除 11 个真机专属包(flexiv_hardware 需要 Flexiv RDK C++ SDK,根本装不上;
另有真机驱动/GPIO/MoveIt 配置/真机 bringup),每个都在脚本里写了理由。
已验证(不是只看退出码)
- Gazebo Fortress 6.16 无头启动正常,物理初始化完成
- /clock 桥接到 ROS 成功收到仿真时间;ros_gz_sim create 成功往世界里建物
- 真机构建 18 包通过、233 项测试通过,build/ install/ 时间戳未变(没被覆盖)
- 仿真侧 7 包构建通过;两环境互切后各自都能正常构建
注:Humble 配的是 Gazebo Fortress(ignition-gazebo6,命令行 `ign gazebo`),
不是现代的 gz-sim —— 后者要 libprotobuf>=6.31,与 RoboStack Humble 钉住的
5.29.3 解不开依赖。Fortress 属于新 Gazebo 家族(非已 EOL 的 Classic),
且是 Humble 的官方配对版本。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 13:52:04 +08:00 |
|
arm_dev
|
d289168dd0
|
P4s:模型审计 —— 移出零引用模型,修掉代码里三处悬空 best.pt
先回答"这些模型到底有没有人用",用全仓库引用扫描判定,不靠猜:
在用 yolo/detect_best_0.onnx + .engine ← paths.yolo_weights
在用 hole_insert/detect_best_1.onnx ← hole_detect.weights / workpiece_weights
零引用 detect_hc90(pt/onnx/engine) ← 只剩注释里的历史说明
零引用 detect_yolo26_gongjian.pt
零引用 detect_best(onnx/engine) ← 仅代码兜底候选,而那个候选是 .pt,不存在
所以 detect_hc90 不需要修复,是不用的东西:连同另两个一起移出仓库。
注意**二进制权重被 gitignore**,删掉就找不回来了,因此是移出到
~/ros2-models-unused-20260923/ 而非 rm。搬回 models/ 即可被 list 重新发现。
顺手挖出并修掉三处悬空引用(和之前 site.yaml 里那个 best.pt 同一类问题):
- hole_detector.weights_from_site:兜底路径写死 models/hole_insert/best.pt(不存在)
- hole_detector._default_model_path:三个候选路径**全都不存在**,函数却
`return candidates[0]`,于是永远返回不存在的 best.pt;报错路径看着很合理,
极易被误判成"文件被误删"。现改为以 site.yaml 为准,再退到规范模型。
- config/hole_insert.yaml 顶部注释同样写的是 best.pt
新增 test_hole_detector.py(8 项)锁住这个回归。测试刻意**不断言文件存在**
——二进制不进 git,新克隆的仓库里 models/ 是空的——只断言解析结果指向规范模型名。
体检从 4 处问题降到 0:bash scripts/weights.sh doctor --check-engines → "没有发现问题"
测试 233 项通过;18 个包构建通过。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 13:08:11 +08:00 |
|
arm_dev
|
2b77ec127d
|
P4:权重/标签模块 —— 旁挂标签文件 + PT→ONNX→TRT 转换工具 + 归档归位
痛点:TensorRT 10.13 的 Python API 没有元数据读写,engine 一转就把类名丢了;
推理侧只能退化成 str(cid)("0"、"1"),界面上看不出是什么类,属于静默失效。
同时旧的 export_yolo_trt.sh 只能手工搬文件、手工改 site.yaml、标签无处安放。
标签子系统(model_labels.py)
- 旁挂标签文件 <模型名>.labels.yaml,放在权重旁边,文本、可手改、进 git
(二进制权重不进 git,标签是配置所以进)
- 读取优先级:旁挂文件 > ONNX 元数据 > 模型元数据 > 占位符 class_N
占位符是故意的:缺标签时要一眼看出来,而不是悄悄退回 cid
- 解析器容忍多种写法:dict / list / 逗号串 / 逐行文本 / Python repr 串
布局与转换(weights_layout.py + weights.py + weights.sh)
- models/<分组>/<模型名>.{pt,onnx,engine} + 同名 .labels.yaml
- convert:PT→ONNX→TensorRT,支持自定义名字、自动归位、固化标签
- staging/:非标准格式先进这里,转换后源文件归档到 staging/done/
- list / labels / doctor / show 四个子命令
- doctor --check-engines 能查出 engine 与 TensorRT 版本不匹配(反序列化返回空)
推理侧接入
- yolo_detector / onnx_workpiece / hole_detect_hough 统一走 read_labels()
- 画框标签不再硬编码 "gongjian",改用实际类名
顺手修掉的真实问题
- setup.py 撞上断掉的符号链接会让整包构建失败,报错还指向 build/ 里的幽灵文件
(源文件被挪走后 build/ 里留下的断链被 os.walk 列出来)。加 _existing() 过滤。
- P3 遗漏:site.yaml / export_site_env.py / run_handeye.sh / handeye_teach.py /
handeye_calibrate.py 里的 handeye 工作目录仍是 /tmp/handeye_calib*,
与已迁到 handeye/ 的其余路径不一致。统一到 handeye/tmp/<臂号>。
- site.yaml 的 hole_weights / hole_detect.weights 原指向不存在的 best.pt,
改为实际存在的 detect_best_1.onnx;active.pt 符号链接已废弃并清理。
- weights.sh labels 原先不接受 "分组/模型名" 写法,而 list 输出正是这个形式,
照着敲会报"找不到模型"。补上,并让报错直接给出可复制的全名。
测试:225 项通过(新增 test_model_labels.py / test_weights_layout.py)。
真实 ONNX→engine 端到端转换已验过。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-23 12:39:18 +08:00 |
|
arm_dev
|
adc72955c7
|
P3c:teach --auto-solve —— 采完自动 merge 并接着标定,收尾手眼手动闭环
新增 teach --auto-solve:采完点按 q 后自动 merge、重启相机、接着 calibrate,
不再需要手敲第二、三条命令。
但只有 --ros(示教器)模式能真正全程跑通,这一点如实处理而非强行自动:
标定必须通过 run_real.sh 的控制器驱动机械臂走点位,而 --rdk 软拖拽要求
RDK 独占,两者无法同时存在。这是硬件约束,脚本绕不过去。
所以 --rdk --auto-solve 会照常自动 merge,然后打印明确的两步指令,
并提示想要一条命令就改用示教器 Free Drive 模式。
硬跑一个必然失败的 calibrate 只会让人以为标定坏了。
顺带修正一处不可达代码:_need_camera / _need_files / _need_robot 内部用的是
exit(终止整个脚本)而非 return,所以 `if ! _need_camera; then echo ERROR; fi`
里的分支永远不会执行。改为直接调用,由它们自己打印原因并退出。
usage 里补上 --auto-solve 及其限制说明;handeye/README.md 第 5 节补
"一条命令跑完"小节,写明为什么 RDK 模式做不到。
usage() 的 sed 行号改为按首个非注释行自动计算,避免以后加注释又要同步。
测试:224 passed
|
2026-09-23 11:55:08 +08:00 |
|
arm_dev
|
e4f513392d
|
P3b:手眼标定支持 --mode 模式选择与 --points 目标点数
--mode eye_in_hand|eye_to_hand
- 切换读写的文件为 <臂号>_<模式>.yaml,两种模式的采点与结果互不覆盖、可共存
- 臂号从 Flexiv 序列号后两位取(Rizon4-063989 → 89),不做硬编码映射
- 非法模式立即报错退出。不静默退回默认值 —— 那会造成"以为标的是
eye-to-hand、其实算的是 eye-in-hand",是这类工具最难排查的错误
- 求解侧本来就支持(calibrate_hand_eye(eye_to_hand=...) 对 gripper2base 求逆),
这次把入口补上,让模式不再只存在于参数文件里
--points N(仅 teach)
- teach 新增 target_points 参数并实现计数:显示 [3/12] ... 还差 9 个点,
采够时提示可以收工
- 刻意不强制关窗:实际采点常出现"某个角度还没覆盖",强制退出要重开
usage 重写:路径改为 handeye/,补上全局选项与组合示例;
usage() 的 sed 行号随之同步(原 2,18p 已不覆盖新头部)。
handeye/README.md 补第 5 节:模式切换与点数的完整用法,
并说明为什么非法模式要报错而不是兜底。
测试:224 passed
|
2026-09-23 11:48:11 +08:00 |
|
arm_dev
|
a3a589b9e3
|
P3a:手眼标定集中到 handeye/,修复三套板尺寸矛盾与从未生效的稳定性参数
新建 handeye/ 专用文件夹(用户要求:专用文件夹 + 使用说明)
- handeye/README.md:从小白视角讲清楚两种模式怎么选、板尺寸怎么量、
完整流程、出问题先看哪里。以前这些知识散在 docs/ 和代码注释里。
- 结构:calibrate.yaml(参数)/ poses/(采点输入)/ results/(结果输出)/ tmp/(临时,不进 git)
- 文件用 git mv 迁移,历史保留
命名约定改为 <臂号>_<模式>.yaml
- 如 89_eye_in_hand.yaml、91_eye_to_hand.yaml
- 臂号 = Flexiv 序列号后两位;模式 = eye_in_hand | eye_to_hand
- 换模式 = 改 site.yaml 里 paths.handeye_file 一行,不动代码
- 迁移只需改 site.yaml(4 个 launch + servo_node 都经 paths 读取,一行没改)
修复:三套互相矛盾的标定板尺寸(真实 bug)
同一个"板子多大"分散在三处,且值各不相同:
handeye_calibrate.py 默认 9x6 / 0.025
handeye/calibrate.yaml 11x16 / 0.011
site.yaml + 结果文件头 8x11 / 0.015 ← 实测的真实板子
谁生效取决于覆盖顺序。症状是"标定流程跑得很顺、不报错,但结果明显不对",
极难排查。已全部统一为 8x11 / 0.015,README 里写明换板必须三处同改。
修复:site.yaml 里五项稳定性配置从未生效
run_handeye.sh 一直在传 settle_qd_max / settle_stable_sec / settle_timeout_sec /
pin_holds / pin_hold_sec,但 handeye_calibrate.py 从未声明这些参数 ——
ROS 2 会静默忽略未声明的覆盖参数,所以"等机械臂停稳再拍照"这件事其实没实现,
实际只有一次固定 settle_sec。现已声明并实现:
- _wait_settled():用关节速度 |qd|_max 判定"真的停了",比固定 sleep 可靠
(负载/姿态不同刹车距离差别很大;等太短会在臂还动时拍照导致角点抖动)
- _pin_hold():静止后再保持几拍,抑制到位后的微小回弹
新增:自动标定进度条
一条可视化进度条([ 4/12] [████░░░░] 33.3%),无人值守的长流程能一眼看出还剩多久。
清理重复文件(内容逐字节比对确认后删除)
- config/handeye_result.yaml 与 _89 逐字节相同 → 删
- config/handeye_poses.yaml 与 _89 逐字节相同 → 删
迁移后 src/arm_application/config/ 下已无 handeye 文件。
测试:224 passed(原 198,新增 26)
新增 src/arm_application/test/test_handeye_lib.py —— 手眼模块此前零测试。
关键用例是合成数据往返:用人为设定的 X_true 反推出每个视图的
(gripper→base, target→cam),再让求解器还原。坐标链方向/求逆/转置
只要有一处写错就必然失败。
- 真值恢复(旋转+平移,atol 1e-6)
- 单独验证平移精确(旋转对而平移错是最易漏的一类 bug)
- 结果必须是 det=+1 的正交阵(排除镜像解)
- 退化输入(全同位姿)不得悄悄给出"看起来能用"的结果
- 稳定性判据:窗口未满不判稳、丢帧后重新计数、抖动超阈不判稳
- 写盘再读回不漂移
|
2026-09-23 11:46:03 +08:00 |
|
arm_dev
|
52c108de07
|
P2:修复 run_all.sh 缺失,消除等待超时陷阱,标注无消费者的配置
修复:run_all.sh 不存在,但被三处依赖
- docs/HOLE_DETECT.md 有 4 处用法示例;
- pipeline.launch.py 文件头写着"相机 / 触觉由 scripts/run_all.sh 负责拉起一次";
- run_pipeline.sh 唯一职责就是 exec 它。
也就是说文档在教用户跑一个不存在的脚本,而 run_pipeline.sh 执行必然失败。
按 pipeline.launch.py 的实际参数(skip_stage1/2/3、target_hole、insert_depth_mm、
screw_pitch_mm、preview、resnap)重建:先确认控制器 ACTIVE,再拉起相机与触觉
(都已就绪则跳过),最后 exec pipeline.launch.py。无硬件时会明确报错退出。
修复:判活循环反复撞超时,脚本看起来像卡死
服务/话题不存在时,`ros2 service call` / `ros2 topic echo` 每次都要耗满 timeout
才返回。原写法 30 次循环最坏等 165s(实测无 ROS 时白等 63s)。改成先
`ros2 service list` / `topic list` 探测,再决定要不要 call/echo。
- run_stage2_locate.sh、run_stage3_search.sh 同步修复(165s → ~0s)
- run_all.sh 一开始就按正确顺序写
- run_handeye.sh 原本就是对的(这次是向它对齐)
标注:site.yaml 的 sim 段没有任何消费者
它声明 platform: gazebo,但 Gazebo 尚未接线,改它不会有任何效果。
加注释说明真实现状,避免用户以为改了就生效。
澄清:camera 与 cameras 不是重复键,两者都在读
- camera(单数)= 当前激活相机的参数,被各 launch 与节点读
- cameras(复数)= 相机目录(型号/序列号),被 export_site_env.py 读
命名容易混淆,但删任一个都会坏的。
保留:fetch_isaac_flexiv.sh
一度按"0 引用"删除,随后发现 thirdparty/ 里确实存在 isaac_sim_ws 与
flexiv_sim_plugin,说明这条路线被真实使用过;且该脚本还负责 clone
flexiv_rdk(核心依赖)。已恢复,并在 scripts/README.md 标为"搁置未用"。
docs/scripts/README.md 补上 run_all.sh、rdk_drag_bridge.py、fetch_isaac_flexiv.sh,
并新增"排查脚本卡住时的常识"一节记录判活顺序。
测试:198 passed
|
2026-09-23 11:25:19 +08:00 |
|
arm_dev
|
883ed6b0b9
|
P1:设备契约层收尾 + Flexiv 驱动适配器 + 触觉四路数据打通
新增包
- arm_kinematics:从 arm_application 提取几何雅可比 + DLS 逆解(纯 numpy)。
它被应用层和驱动层共用,留在应用层会让驱动反向依赖应用层。
arm_application/geom_ik.py 改为 re-export shim,现有 import 一行未改。
- arm_driver_flexiv:非夕驱动适配器。只补 manifest 声明而现有节点缺失的接口
(/home /enable /clear_fault /switch_tool /soft_drag /compliant_descent),
已有能力不重复实现。RDK 访问经 rdk_py310 下的长驻桥,本包不 import flexivrdk。
新接口
- arm_msgs/SwitchTool.srv、arm_msgs/CompliantDescent.action
- 合并 capabilities 里语义重复的 set_tool / switch_tool
修复(都是只在真机上暴露、且难以定位的问题)
- rdk_client 会永久挂死:select 的"可读"不等于"有完整一行",readline 遇到
半行 JSON 会一直阻塞等换行符,超时机制完全失效。改用 os.read + 自己做行缓冲。
- CompliantDescent 行程少一步:descended + step > max 在浮点累加下会提前跳出,
永远到不了标称行程。改用整数步数计数。
- CompliantDescentRunner 的 TF frame 退化成 _base_link:没继承节点已解析的 SN。
- resolve_orisys_config 仍指向旧路径;load_orisys_from_site 读的是旧键 config。
触觉四路数据打通
- 深度图 / 力矢量场原先被 tactile_worker 有意丢弃(注释写"图像不过队列"),
从未发布到 ROS。新增 image_utils(数组→Image,编码自动判定)、独立的图像
队列(maxsize=1 满则丢旧)与 /tactile/<side>/{image,vector}。
默认关闭(tactile.image_hz=0),开启也不影响三力的 30fps 时序。
- mock 后端也会发图像,无硬件即可验证整条发布链路。
配置
- site.yaml 新增 tactile.image_hz;移除与 model 重复的旧键 tactile.config
- 型号配置从 src/arm_application/config/orisys/ 迁到
devices/sensors/orisys/models/dd03/(随 SDK 一起放)
测试:198 个(原 139)
- arm_kinematics 24(雅可比/逆解的解析算例与不变量)
- arm_driver_flexiv 69(下压状态机 34 + RDK 协议层 35)
- arm_application 59(图像打包 36 + 矢量场探测 23)
|
2026-09-23 11:17:12 +08:00 |
|
arm_dev
|
6edb8d39ab
|
chore: 框架科学优化前的完整基线
在开始'设备契约层 + 目录重构 + 通止规装配'改造之前,先钉住当前可运行状态,
使后续任何删除/重构都可回退。
本基线包含:
- 现有 ROS 2 工作区全部源码(arm_application / arm_control / arm_bringup 等)
- 官方 flexiv_ros2 栈(含 flexiv_hardware / flexiv_controllers 等符号链接)
- 设备契约层 src/arm_interfaces(新增,46 项单测通过)
- devices/ 设备选型与品牌清单(新增)
- scripts/gen_site_example.py 与由它生成的 config/site.example.yaml
按 .gitignore 排除:build/ / install/ / log/ / config/site.yaml / 模型权重二进制。
|
2026-09-23 10:53:06 +08:00 |
|