arm_dev
|
f882c4309c
|
克隆下来必须带上正在用的视觉权重和本机 site.yaml,否则感知只能指到一台机器上的路径,别人起不来。
顶视迁臂改走厂家 MoveL,D405 框心对准后再对孔;夹爪停在 caught 时 open 不再把相同目标当成已经张开,init 会真的回零。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-12 17:08:43 +08:00 |
|
arm_dev
|
b903161113
|
左臂换成 tool10120938 后,手眼仍按旧 tool1 和 8×11 板求解,顶视迁臂又按短跳 rest-to-rest,真机上要么整体偏一截,要么中途弹射启停。
眼在手上改用该 TCP 做 AX=XB,再折回相机→法兰;A5 内角点改为 11×8,09-04 / 10-09 旧结果留在 backup。一阶段 D435 迁臂改成一条 FollowJointTrajectory(余弦加减速,近处降速,XY 进门槛后再单独收垂直),跟踪锁高与悬停齐平,避免倾斜相机下先降一截把孔位重投影偏掉。--no-descent 只降到离面 15 mm;跳过三阶段时触觉 USB 打不开不再空等几分钟。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-12 15:04:23 +08:00 |
|
arm_dev
|
7d22f777c8
|
把当前控制栈收成一份可克隆的源码:腕部看不见时用顶视一次定到基座,二阶段对孔和手外标定跟着进同一份树。
现场 site.yaml、权重和编译产物仍不入库;模板只留占位符。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-09 14:51:03 +08:00 |
|
arm_dev
|
0a2a28a355
|
真机落地实测修的三处:参数类型不一致导致节点启动即崩、权重目录判据、相机日志
1) site.yaml 的整数把节点写死(ros_node.TolerantNode)
现场 site.yaml 写 `ae_limit_us: 8000`(YAML 整数),脚本按 `-p ae_limit_us:=8000`
传参,而节点 declare 的是 double —— rclpy 严格比对类型,节点**启动即崩**:
InvalidParameterTypeException: Trying to set parameter 'ae_limit_us'
to '8000' of type 'INTEGER', expecting type 'DOUBLE'
现场看到的只是「顶置 D435 起不来」,报错里一个字都没提 site.yaml,而
`8000` 与 `8000.0` 在运维眼里是同一个数。同一族的雷还有 width 848.0→int、
emitter_enabled 1→bool、带不带引号的序列号。
修法是新增应用层统一基类 TolerantNode:在**声明阶段**把覆盖值的类型按
声明类型收敛(异常就发生在声明阶段,所以 `float(...)` 这种事后 cast 救不了)。
全部应用节点改继承它;补 test_ros_node.py。
2) 权重目录判据(weights_layout)
模型文件「在不在、名字对不对」以前散在几处各判一半,缺文件时的现象是
推理静默退回随机权重。收成 weights_layout.py 一条判据 + scripts/weights.py 调用。
3) 相机日志:USB 降速要说清怎么办
realsense_color_pub 协商到 USB 2.1 / 480Mbps 时只打一行速率,没人知道该做什么;
现在直接给可执行的处置(换线/换口/SuperSpeed 不复用)。realsense_view 同步。
4) stage2_locate:接上 hover_z_guard 与 tool_tip_below_flange_m
悬停高度必须让工具尖端留在工件面上方(09-24 的教训:悬停时尖端已钻进工件
25mm,S05 横向对准把工件拖走 92mm)。step2 侧也按同一判据兜住,且工具长
允许由启动参数覆盖(仿真上料后实测得到)。
5) config/site.example.yaml 与 site.yaml 重新同步
上一批给夹爪加了 auto_init: false,模板没跟着重跑,TestExampleParity 因此失败。
重跑 scripts/gen_site_example.py 同步(模板是生成物,不手改)。
测试:全仓库 1085 passed(此前 1 failed)。
|
2026-09-28 10:17:16 +08:00 |
|
arm_dev
|
1ac5472788
|
工具几何进配置:S07 悬停/下探不再靠拍,预算由几何推导
真机与仿真**同参数同结果**的一个几何错误:流程里的"悬停高度"是按**相机**
定的(要看清孔得离工件面 ~0.17m),不是按**工具**定的 —— 而工具装在法兰
下面、比相机长得多。仿真里实测:
悬停法兰 0.632 − 工具长 0.243 = 尖端 0.389
工件上表面 0.412
→ **悬停这一刻,通止规已经插进工件 23mm**
后果极具误导性:S07 一路压到顶都等不到"台肩落面"那一下,最后报
"下探 N mm 都没触发接触判据" —— 看起来像**力阈值标错了**,实际是几何错。
上一轮就是照这个假象去调阈值的,白费。
新增三个纯函数 + 两个配置项:
- gauge_fsm.tool_clearance_m(法兰 z, 工具长, 面 z) → 尖端离面多高(负=已插入)
- gauge_fsm.descend_needed_m(...) → 台肩落面还需下压多少
判"到底了"的是**台肩落面**,不是尖端碰面。台肩在规尖上方 30mm,
所以按尖端算会**少算 30mm** —— 那正是"预算 25mm 永远到不了落面"的 bug。
- gauge_fsm.descend_budget_m(needed) → 预算 = 1.5×需求,含绝对下限
- site.yaml: tool_tip_below_flange_m(0.243 的来历写清了:
TCP 0.200 + 规尖到指腹接触面 0.043,而 0.043 不是"夹持段中心到尖端"
的 0.0555 —— 指腹接触面比 TCP 原点高 12.5mm)
- site.yaml: workpiece_surface_z_m(0 = 用 stage2 实测孔位兜底)
S07 启动时现在会打印(本轮实测输出):
S07 工具几何:悬停法兰 z=0.6317、工具尖端在法兰下方 243.0mm
→ 尖端 z=0.3887,工件上表面 z=0.4117
悬停时尖端离面 -23.0mm(**已经插进工件了**);台肩落面还需下压 +7.0mm
配的预算不够走完落面那一段时,就地按几何改大并告警(不是静默用错的数)。
回归测试 5 项,其中 test_shipped_budget_covers_shipped_gauge_and_hover
专防"预算又调回不够"这个 bug 复发。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-24 14:52:55 +08:00 |
|
arm_dev
|
ddf30e57f8
|
仿真场景:台面开 90mm 通槽,让工件的贯通孔真能穿过
现象(非常隐蔽,容易被误判成阈值问题):
通止规的通端长 30mm,工件是 12mm 厚板、孔是**贯通孔** —— 真机上通端要
穿过板再伸出去 18mm,靠**台肩落到工件上表面**那一下判"到底了"。
但台面原本是一整块、工件平放在上面 → 孔下方被堵死,规只能进 12mm 就顶
在台面上。于是 S07 一路压到上限都等不到台肩落面的大接触力,报
"下探 N mm 都没触发接触判据"然后 ABORT。
力也不是完全没有(顶台面时切向读 30~45N),所以看起来像"阈值调太高"。
修法:台面沿 Y 开一道 90mm 宽通槽(|y| ≤ 45mm),南北两块各留 5mm 承压面
(工件 100mm 宽,跨在槽上;板是刚体只受重力,够用)。桌腿视觉同步分两块,
别把通槽在画面上堵回去。槽**只开在工件孔位所在的 Y 带**,台面高度一点没动
→ 相机距离、孔检测像素门槛、site 的悬停高度全都不用改。
顺带:contact_max_depth_mm 25 → 35。判"到底了"的是台肩,而台肩在规尖上方
30mm(通端长 30),25mm 的上限**根本到不了**,只会白报超时。
35 = 30(台肩落面)+ 5(对位残差/板厚公差余量)。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-24 14:43:02 +08:00 |
|
arm_dev
|
bda2fa7129
|
通止规装配:配置段进 site.yaml + 中文手册 + 成败判定「不看 $?」
配置(site.yaml → gauge_assembly:,38 键全部带中文注释)
------------------------------------------------------
- 键名/单位/类型/交叉矛盾由 gauge_config.py 校验:拼错键名报错并给最相近的候选,
单位后缀(_deg/_mm/_m/_s/_pct/_delta)逐条对表,`contact_safe_delta ≤
contact_tangential_delta` 这类矛盾启动就拦(R1.2:没接线的键不许静默存在)。
- 借机清掉 4 个**没接线**的键(require_preview / track_*_s / hover_m:前两个是
stage1 那层的、后两个是 servo 那层的,放在这里等于第二份真相),
并把 contact_speed_m_s / coarse_timeout_s / 夹爪力速 真的接上。
- site.example.yaml 由 site.yaml 生成(gen_site_example.py --check 通过)。
成败判定:`ros2 launch` 的退出码**恒为 0**
------------------------------------------
实测(Humble):子进程 exit 3,`ros2 launch` 仍返回 0。用 `$?` 判成败的脚本会把
"装配失败"记成"成功"——没人会去查一个说成功的脚本。改成:
- 节点把结论**落盘**(--result-file,默认 /tmp/gauge_assembly_result.json,原子写、
先落盘再发话题),任何"没跑到收尾"的退出也会补一份 ok=false;
- 脚本读结论文件判成败:0=成功、1=失败、**文件不存在也算失败**并说清;
- 新增 test_gauge_result_gate.py:把脚本里那段判成败的 Python 抽出来,
对成功/失败/干跑/缺文件/坏 JSON 五种输入断言退出码 —— 判据本身也有测试。
顺带修掉两个"看着像玄学"的现象
------------------------------
- **流程打完了终端不回提示符**:launch 里的静态 TF 发布者是常驻的,主节点退出
带不走它 —— 挂 OnProcessExit → Shutdown。
- **"已装 1/2 孔"**:结果里 hole_index 是 0 基当前孔号,不是"装完几个",
新增 holes_done 给人和脚本看。
文档(全部中文,含"怎么调参/还没做")
------------------------------------
- 新增 docs/GAUGE_ASSEMBLY.md:状态机全图、每步判据表、E1 掉件处理(**按实现**:
掉件后该孔作废、换下一个孔;最后一个孔掉件则序号被夹住=重试本孔)、
话题契约、调参顺序、验收命令、踩过的坑。
- FRAMEWORK_RULES 新增 R5.8(同一份数学只许一份 + 守卫测试)、R5.9(自检的
期望类型与调用处同源)、R5.10(缺依赖 skip,别留常年红)、R6.9(硬退出前打
traceback)、R6.10(ros2 launch 退出码恒 0)+ R5.7 补真机/仿真"生效方式不同"
(真机软链即改即生效,仿真拷贝必须重编)+ 5 条症状反例、.cursor/rules 同步。
- WORKSPACE / INTERFACES / SITE_CONFIG / HANDBOOK / 两份 README 索引与决策表补齐。
验证(本机真跑)
----------------
pytest(仿真环境) 436 passed, 14 skipped(缺 onnx 跳过)
干跑 --sim --dry-run → 脚本退出码 0,结论 ok=true 已装 2/2 孔
不干跑 --sim → 脚本退出码 1,S00 拦住,未发任何运动指令
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-24 10:30:11 +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
|
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
|
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
|
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
|
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
|
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
|
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 |
|