arm_dev
|
d8942defd1
|
初步完成平行夹爪旋转入孔-不断的张开转到逆时针极限再闭合转到顺时针极限
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-19 15:44:09 +08:00 |
|
arm_dev
|
7af9e3d4b6
|
贴面后不再抬回悬高;MoveL 切回关节后不再把整段位移当前进速度。
搜孔开局会抬到悬高再落回 hole_hover_z_m,螺纹还没咬合臂就离开孔口。没有接触传感器,到位只认 final_gap 算出的法兰高度,贴面后停在这个 Z。09:25 厂家 MoveL 切回 NRT_JOINT_POSITION 的第一拍把整段关节差算成 1.686 rad/s,前瞻再推 0.07 rad,光心从 5 mm 冲到 276 mm 后急停。切回后钉住结束瞬间的关节角并清零速度,JTC 还指着出发前的关节时不放行。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-19 11:30:49 +08:00 |
|
arm_dev
|
8c4dccccb1
|
命令挤在一条队列里,上一条没返回后面的就点不了;软拖拽又被位置环顶回去。相机、夹爪、触觉分开走,停止只杀本界面登记的进程组。左指右指挪到画面外面,标题条改成实底,图像不再把字盖住。
验收:test_command_lanes、test_listed_arms、test_camera_preview 共 26 项通过。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-15 16:36:18 +08:00 |
|
arm_dev
|
7942014763
|
主界面一屏要同时看臂、夹爪、相机和触觉,按钮再被拉高就会叠到画面上。相机按原分辨率走回调取最新帧,场景一动不再等红外齐帧把彩色丢掉。模型和相机都留在工位窗口里,启动默认不再弹 RViz。
验收:test_listed_arms 与 test_camera_preview 共 20 项通过。D435 1280×720 发布约 29 帧/秒。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-15 16:20:35 +08:00 |
|
arm_dev
|
76c9b9cc49
|
主界面要直接下发夹爪并看仿真,再开夹爪页会把相机和触觉挡住。开口、力度和旋转收到左栏,壳体、抱闸、手指改成对比色,黑指在深色底上会看不见。臂清单从 site.yaml 读,界面不再写死哪一台。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-15 15:12:46 +08:00 |
|
arm_dev
|
80983954a4
|
克隆后还缺仿真安装树和两边的编译目录。把 build、build_sim、install_sim 一并放进仓库;仿真入口不再写死本机 conda,build 里指向源码的链接改成相对路径。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-15 11:07:04 +08:00 |
|
arm_dev
|
926a7f9796
|
别人克隆后没有 install,source 会直接失败。把展开后的 install 放进仓库,符号链接和指向本机 build 的 egg-link 收成真实文件,Python 入口改走当前环境的解释器,这样不用先 colcon build 也能启动。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-15 11:04:43 +08:00 |
|
arm_dev
|
3c86441fb0
|
换台电脑克隆后启动失败,是因为 site 和启动脚本写死了 /home/cc/ros2。仓库内路径改成相对仓库根,加载时再展开;缺 install 时先说明要编译,而不是 source 一个不存在的文件。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-15 10:51:24 +08:00 |
|
arm_dev
|
ab73399426
|
工位操作改成桌面窗口:夹爪位置、力度、速度和旋转角可以调,阶段能分步或整体跑,并能软拖拽记点后回到该点。相机预览按窗口一次铺满,不再跟着每帧慢慢放大。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-15 10:23:56 +08:00 |
|
arm_dev
|
5c7966d8cd
|
悬停走完后常数前馈会往 +X 过补约 1.3mm、插不进,贴面改成笛卡尔锁 XY、只降 Z 的厂家 MoveL;D435 交回也不再杀栈,只等法兰停稳 0.5 秒。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-15 10:23:52 +08:00 |
|
arm_dev
|
37b85aa09b
|
悬停一次走完后下降会漂几毫米,这一保存点把观察高补交、接触门控和厂家 MoveL 接到能插进去的状态,后面好对照锁 XY 纯 Z。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-13 19:27:19 +08:00 |
|
arm_dev
|
1b264c0654
|
把现场确认能用的对孔方案落成保存点:小孔只用视觉关键点交到工件面、多走多拍闭环;D435 走到点后杀栈冷启动再停稳 3 秒才交 D405,避免赶路时带臂晃。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-13 14:57:54 +08:00 |
|
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
|
16d2912a9a
|
真机断连不再静默"成功":加连接存活看门狗 + 说清"臂没跟随"
2026-09-28 真机实测:S05 精拍定位报 code=2(垂直化或悬停失败:工具轴没竖直、或悬停
高度到不了),看着像几何/标定问题。真因是 RDK 在 14:53:44 就与机器人断开了
(Disconnected from the robot, waiting for reconnection ...),此后 robot_->states()
只返回最后一帧缓存、robot_timestamp 推进量恒为 0(/joint_states 永久冻结),而
rizon_arm_controller 是本地插值、不校验硬件反馈 —— 于是控制器照报 Goal reached,
success!,臂却一步没动。
最要命的是整条链路没有一个环节报错:断连不报、写命令不报、控制器报成功,只有
"实测位移为 0"这一个真信号,而它还被归因成了"悬停高度到不了"。
- flexiv_hardware: read() 加双重看门狗 —— connected()(必须放在 operational()
之前判:断连后 operational() 会一直保持 true)+ 状态帧时间戳停滞 500ms。
任一失效即返回 ERROR,让 ros2_control 把栈停下来报错。
- flexiv_hardware: on_deactivate 里 robot_->Stop() 加保护 —— 看门狗正是把断连
场景送到这里,此时 Stop() 发不出去且大概率抛异常,不能让异常抛穿
controller_manager 的下线流程。
- stage2_locate: 实测位移为 0 时单独报一条指向"连接/关节状态"的错,不再混进
"垂直化或悬停失败"里把人带去调标定。
- 新增 test_stage2_no_follow.py(4 例)。
验证:全量单测 1136 passed / 1 skipped;flexiv_hardware 单独编译通过。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-28 15:57:41 +08:00 |
|
arm_dev
|
55e5f8f20b
|
--sim 时按仿真台别名导出手眼真值:--arm 89 --sim 照着文档敲必然失败
`--arm 89 --sim` 是个**合法且有意的**用法(run_gauge_assembly.sh 里
_HAD_ARM 那段写着"想用仿真核对某台真机的点位时会这么用"),但手眼真值导出
传的是 ${ROBOT_SN} —— 于是帧名成了 Rizon4-063989_flange,而仿真 URDF 里是
sim_flange。导出先干等 20s,再报"仿真栈起来了吗?",把人引去重启一个明明在
跑的仿真。文档 §7 里写的就是这条命令,所以照着敲 100% 失败。
真值必须按**仿真台**的帧名导出,与"用哪台臂的点位"无关 → 固定用仿真台别名
(SIM_ARM_ALIAS 可覆盖,默认 sim)。
顺带把 §7 那组验收命令补成真能跑的:
· 以前只写"先编",没说**怎么起仿真**,也没说**起之前要清干净** ——
这次就是在这两步上栽的,新增 §6.5 把三步(编 / 清 / 起)写全;
· "arm_application 子集 454 passed"配的那条裸 `PYTHONPATH=... pytest`
命令实测已经收集阶段就 error(导入不到 ament),改成走 run_unit_tests.sh
并更新数字;总测试数 1118 → 1128。
验证:`bash scripts/run_gauge_assembly.sh --arm 89 --sim --dry-run` 现在
state=DONE ok=True 2/2 孔、退出码 0(修改前必然退出码 1)。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-28 12:25:26 +08:00 |
|
arm_dev
|
baf7601995
|
预检补上 arm_control 孤儿与 mock 的 joint_state_publisher
上一轮残留的那四条孤儿(real_trajectory_adapter / named_pose_server /
arm_status_node / joint_state_publisher)在预检里**一条都报不出来**:
老的六条模式只覆盖 ros2_control_node 和 stack.launch,而它们的特征恰恰是
"栈的主体已经没了、只剩它们",所以两条老模式全都匹配不到。
补两条:
· `install/arm_control/lib/arm_control/` —— 一次覆盖该包全部节点
(用 install/ 而不是 install_sim/:仿真同名节点不该被真机体检误报,
"install_sim/" 不含子串 "install/",这条性质用测试钉住了);
· `joint_state_publisher` —— mock 专用节点,真机上出现 = 上一轮 mock 栈的
残留。它发的**静态** /joint_states 会盖掉真实关节角,是"控制器一切正常、
TF 显示臂没动"这个假象的元凶,所以说明里把它写清楚。
人工动作也改成先指向新的 stop_real.sh(它真能带走这些孤儿),保留手动 pkill 兜底。
新增 3 项单测(含"不许误报 install_sim/"的负向用例)。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-28 12:25:20 +08:00 |
|
arm_dev
|
6baf989595
|
真机侧补 stop_real.sh 与域隔离:残留的 mock 栈会把仿真/真机的 TF 盖成"静止"
实测(2026-09-28)在仿真里上料失败,一路查到 IK / TF / 时钟上,全是被冤枉的。
根因是两个真机侧一直缺的东西:
1) **真机侧没有停止脚本**。仿真侧早有 stop_sim_gz.sh(R4.10/R4.11:残留会伪造出
"代码写错了"的现场),真机侧只有 run_real.sh 里两行 pkill。而真机栈里有一族
**独立于 ros2_control_node 存活**的进程 —— 上一轮栈的主体早没了,只剩
real_trajectory_adapter / named_pose_server / arm_status_node /
joint_state_publisher 四条,两条 pkill 一条都匹配不到。
危害不是占资源而是**冒名顶替**:残留的 adapter 照样订阅 /trajectory、
残留的 joint_state_publisher 照样发 /joint_states(mock 的静态关节角)。
现象极具误导性:控制器 feedback 与 reference 完全一致(控制器没坏),
TF 却显示末端一动不动。
新增 stop_real.sh + lib/real_procs.sh:扫描 = 点名表 + `install/` 前缀兜底,
按 ROS_DOMAIN_ID 判别真机/仿真(两边有同名的 robot_state_publisher,
否则停真机会把正在跑的仿真杀一半),杀完复查残留并非零退出。
run_real.sh 启动前的清理改用它,不再各写一份会漂移的模式表。
真机侧**有意不**用仿真的"进程组"法:真机栈是前台交互跑的,没有独立进程组,
它的 PGID 就是调用它的 shell —— 记下来再 kill 会杀掉自己的终端。
2) **真机侧不强制 ROS_DOMAIN_ID**。sim_env.sh 早就强制 77 并写明"隔离不能靠
『环境里恰好没设』",真机侧没有对应保护:先 source 过 sim_env.sh 的 shell 里
再起真机栈,真机栈就落到**仿真的域**上,节点名撞车、/joint_states 与 /tf
各有两个来源互相盖。补 lib/ros_domain.sh(真机 0 / 覆盖用 REAL_ROS_DOMAIN_ID),
env.sh、run_real.sh、run_sim.sh 三处共用一份实现 —— 成对强制,否则一侧的
强制只是把运气挪到了另一侧。
run_sim.sh 特别容易漏:它名为 sim,用的却是**真机的 install/**、节点名与真机
完全一样,而且当时不管域;这次也钉到真机域 0。run_sim_gz.sh 文件头那句
"两者互不影响"是错的,一并改掉。
验证:stop_real.sh --list 在有仿真在跑时为空(REAL_PROC_INCLUDE_SIM=1 能看到
并按"域 77"标出);伪装一个 install/arm_control/lib/arm_control/arm_status_node
的孤儿进程能被点名。新增 6 项单测钉住域的四种输入 + 一个不许误报。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-28 12:25:16 +08:00 |
|
arm_dev
|
bb6f792922
|
复位命令必须带 timeout:实测它自己会永久挂住,还留下占着 USB 的僵尸进程
上一版把触觉 wedged 的处置改成「首选 `sudo -n orisys-usb-reset`(免密,无人值守
可直接跑)」。真跑了一次,发现这句话是错的:
sudo -n /usr/local/bin/orisys-usb-reset 1-3.4.1
跑了 **155s 都没返回**,进程停在 `S` 状态(卡在写
`/sys/bus/usb/drivers/usb/unbind`),`timeout 90` 都收不掉它 —— 现场留下了三个
残留进程。对无人值守来说这是最坏的结果:那一轮在这里永久卡死,而且这个进程还
占着 USB,后面重试触觉只会继续失败。
复位之后 `1-3.4.1` 直接从 sysfs **消失**了(复位前它还在总线上、只是没有
/dev/video*)—— 也就是设备比复位前掉得更彻底。所以这条命令**不是**"跑一下试试"
那种安全操作,必须当有副作用的操作对待。
改动:
· 两处给现场的命令一律写成 `timeout 15 sudo -n /usr/local/bin/orisys-usb-reset
<devpath>`,并把「不加 timeout 会永久卡住 + 留僵尸进程」写进提示原文。
这与 `orisys_tactile.py` 的自动自愈口径一致(那里本来就是 `timeout=15.0`,
所以自动路径不受影响,只有手工执行的建议是错的)。
· 复位后设备仍不在总线上(或消失)→ 明确归到**物理**处置:拔插该相机 /
给这个 Hub 断电重启。
测试:test_v4l_health 新增两条 —— 断言给出的复位命令里含 `timeout 15`,
以及提示里必须写出"会挂住"这件事(只给命令不说风险,人就会裸跑它)。
全仓库 1119 passed / 1 skipped。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-28 10:46:06 +08:00 |
|
arm_dev
|
02c26cfb86
|
测试运行器:位置参数真的只跑指定路径;文档改成实际能跑通的命令与数字
scripts/run_unit_tests.sh
用法注释里写的是「bash run_unit_tests.sh src/<pkg>/test/test_x.py -x」,但实现是
`pytest "${test_dirs[@]}" "$@"` —— 传进来的路径只是被**追加**到「全部测试目录」
后面,实际仍然跑整仓库。用法示例就是承诺,它不成立会让人以为「我只改了 x,
怎么蹦出别的包的失败」。改成:位置参数里**存在的路径**当作「只跑这些」,
其余(-k / -x / --tb=… 及其取值)原样透传。
docs/HANDBOOK.md §4.3
原文让人逐个包手敲 `python3 -m pytest src/xxx/test`。这条命令在本仓库**会失败**,
而且是两种最容易被误读的失败:
· 少挂一个 src 包 → 收集阶段 ModuleNotFoundError,现象是「0 个测试 / 1 个 error」,
像测试环境坏了;
· 更隐蔽:install/ 里那份**旧的**同名模块会先被找到。实测直接跑
src/arm_sim_gazebo/test 报 `cannot import name 'RESET_GIVE_UP'`,导的是
install/ 里的旧副本,源码里明明有这个符号 —— 测的根本不是你改的那份。
改为统一用 run_unit_tests.sh(它把 src/* 挂到 PYTHONPATH 最前面),并把这段
原因写进文档。顺带把过期的统计数字更新为实测值:608 → 1118 项,
分项 arm_application 233→547、arm_sim_gazebo 236→400、arm_interfaces 46→78。
实测:src/arm_sim_gazebo/test 直跑 = 2 个收集错误;经运行器 = 400 passed。
单文件 28 passed;-k real_preflight = 28 passed / 1 skipped;
无参数全量 = 1118 passed / 1 skipped。
docs/QUICKSTART.md
「第一次实机」以体检开场(它只读、不需要机械臂能动),并列出四项判据各抓什么,
免得现场跳过它直接起栈。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-28 10:42:56 +08:00 |
|
arm_dev
|
352270d1cc
|
真机开工体检的编排:把「不报错但不对」的故障源排成现场能照着做的顺序
前一个提交给了四个单项判据(USB / V4L / RDK / 手眼)。这一批把它们的**判据入口**
补齐并编排成一条命令,产出「结论 + 要人工做的事」:
scripts/lib/real_preflight.py
run_all() 按**现场该处理的顺序**排出结论,不是按代码顺序:
现场配置 → 遗留进程 → 网络 → 机械臂 RDK → 腕部相机 → USB/触觉/几何
顺序是有理由的:遗留的 ros2_control 占着 RDK,会让后面每一项的现象都指向别处,
所以先查它;机械臂能不能动是唯一的人工硬阻断,排在相机之前。
新增 check_cameras —— **本臂配置的那只腕部相机在不在 USB 上**。
这是本次实测暴露的判据缺口:2026-09-28 现场 D405 整个从 lsusb 消失
(只剩顶置 D435),而旧体检**全绿** —— check_usb 只看协商速率,遍历的是
「发现到的」设备,对「这只根本不在了」无话可说。缺 D405 之后踩的是完全不同
的一类错误(点云为空 / 找不到工件),排查方向会被带偏很远。
按 site.yaml 的配置意图核对:robots[<sn>].camera.name → cameras.<name>.model,
比**型号**而不是序列号 —— 实测这台 D435 的 sysfs serial 是空串,按序列号判会
永远判「不在」;序列号只在型号对上时做一次「是不是本臂那只」的提醒(WARN)。
修掉三处会误导现场的输出:
· _run() 改成三返回值(分离 stdout/stderr,RDK 的 C++ 日志会混进 stdout)
之后,_ps_output/网络两处还在按两返回值解包 —— 体检**第一步就崩**,
什么结论都给不出来。现在有 test_run_all_completes_without_crashing 钉住。
· 人工动作里「去查网线」出现过两条(check_network 与 rdk_status 各写一句
意思相同、字面不同的话),去重是按整串文字做的,于是人读到两条、还得判断
「这是一个问题还是两个」。措辞抽到 manual_actions.py,两边字面一致。
· 触觉 wedged 的处置与 v4l_health 同步(免密的 reset 是首选,重插是兜底)。
scripts/lib/manual_actions.py(新)
人工动作措辞的**唯一**来源。判据分在两个文件、还跑在两个不同的 Python 上
(3.11 / 3.10,见 rdk_status 的模块说明),互相 import 不了,所以抽成只依赖
标准库的公共模块。
scripts/run_real_preflight.sh(新)
一条命令的入口:只读、不动臂,不需要机械臂能连上。
实测(2026-09-28,arm 89):体检跑通并给出 NO-GO + 7 条互不重复的人工动作,
新判据抓到「D405 不在 USB 上」(旧版全绿)。同时确认了本机可用的部分:
夹爪 PGEA 初始化完成 / 全开 20.00mm、右指触觉 SDK 300 帧 @29.9Hz、
手眼矩阵自洽、enp4s0 千兆全双工。
测试:test_real_preflight 28 项通过(含跨模块的措辞去重回归、
编排端到端冒烟、腕部相机四种情形)。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-28 10:42:49 +08:00 |
|
arm_dev
|
0fadd26ca2
|
体检判据硬化:sysfs 读取带超时、复位命令不再写成"要密码"
两处都是「体检工具自己失效」——本仓库最贵的那类坑:判据看起来在跑,其实
结论是假的或者干脆没有结论。
scripts/lib/usb_health.py
Hub 在内核里卡死时,读它的 sysfs 属性会**永久阻塞**(实测
/sys/bus/usb/devices/1-3.4/product 读 20 秒不返回)。于是体检脚本自己挂住:
本该报出故障的那一次,恰恰什么都不输出。新增 SysfsReader —— 所有 sysfs 读取
带 1s 超时,卡死的路径只尝试一次并记入 blocked(Python 杀不掉阻塞在系统调用
里的线程,不记就会在 --watch 反复采样时攒出几十个),blocked 映射回父设备
并按 error 报出「给这个 Hub 断电重启 / 改插主板直连」。
scripts/lib/v4l_health.py
· 提示语写「到真终端里带密码跑 orisys-usb-reset」,与实际不符:sudoers 对这个
脚本本来就是 NOPASSWD 白名单(`sudo -n -l` 可见;orisys_tactile.py 的自动
复位用的也是 sudo -n)。现场按这句会以为无人值守时做不了这件事。
改成 `sudo -n /usr/local/bin/orisys-usb-reset <devpath>` 并说明免密来源。
· wedged 的处置写反了:以前是「物理重插;reset 只在没完全卡死时有效」,
而设备还在 sysfs 里正是 unbind/bind 能生效的前提,先说 reset。
· main() 增加可注入的 v4l_root/usb_root,用例不再读真机 sysfs。
测试:test_main_exit_code_is_nonzero_on_error 以前直接读真 sysfs,本机这只
相机没插(unplugged)时会变红 —— 它本该只在 wedged 下断言复位提示。改为注入
临时 sysfs(设备在总线上、无 /dev/video*),用例变成确定性的。
test_usb_health + test_v4l_health 共 28 项通过。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-28 10:42:37 +08:00 |
|
arm_dev
|
98f4277849
|
真机开工体检四件套:USB 链路 / V4L 设备 / RDK 状态 / 手眼几何
这四个都是「现场看着正常、结果不对」的故障源,共同特征是**不报错**,所以要把它们
从流程里拎出来单独判,并且给出可执行的下一步,而不是只报一个失败。
scripts/lib/usb_health.py
RealSense 协商到 480Mbps(USB 2.0)时能出图、不报错,只是帧率掉到 1/2 且
深度不稳定。这里解析 sysfs 拓扑,把「速率对不对」独立于「有没有图」来判,
并在 --watch 下统计掉线/重连次数(线/口/供电 Hub 的问题会表现为反复抖)。
scripts/lib/v4l_health.py
触觉相机有两类完全不同的坏法,处置方式相反,以前都只表现为「没有 /dev/video*」:
· unplugged —— sysfs 里也没有,是没插/线坏
· wedged —— sysfs 里在、/dev/video* 没有,是 USB 端点了卡死(要复位或重插)
2026-09-28 现场左指就是 wedged(1-3.4.1 在 uvcvideo 下,video 节点缺失)。
scripts/lib/rdk_status.py
ros2_control 卡在 wait_for_operational 时,现象是「栈起来了但关节全是 0、
流程一直等」。原因全在 OperationalStatus 上,而它只有 RDK 读得到。
这里把状态翻成人话 + 处置(Auto(Remote) / 松急停 / 清故障)。
判据取自厂商头文件原话:IN_MANUAL_MODE = "need to switch to Auto (Remote) mode"。
scripts/lib/real_geometry_check.py
手眼矩阵与悬停几何。手眼写坏不会报错,只会让位姿整体偏几毫米到几厘米;
判据:R 正交(|RᵀR−I| ≤ 2e-3)、det(R)=+1、平移量级在 10–500mm。
悬停几何直接复用 gauge_fsm.hover_z_guard(流程里唯一判定处,不另写一套);
工件面未标定(site.yaml 写 0.0 = 未知)时**明确报 FAIL 并说明判不了**,
而不是按 0.0 算出一个假的「已修正」结论。
测试:test_usb_health / test_v4l_health / test_real_geometry_check 共 33 例,
全部钉在「判据要能报错」上(故意弄坏的输入必须被抓到)。
顺带 .gitignore 补 log_real/:真机各终端日志是运行产物,与 log_sim/ 对称。
|
2026-09-28 10:17:32 +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
|
c1cd862eea
|
真机落地前的仿真补齐:夹持/腕力/真值位姿,让仿真能验证 S07–S09
这批是 09-24 那轮仿真工作的收尾,一直没提交。内容按目的分四块:
1) 仿真真能「夹住」工件
sim_grasp_lock:给规与夹爪之间加接触锁(默认关,run_gauge_assembly --sim 才开)。
没有它时仿真里规只靠摩擦,S08 棘轮一转就滑出指间,于是把「仿真不真」误读成
「力判据不对」。新增 test_sim_grasp_lock.py。
2) 腕部力传感器进仿真
sim_wrist_ft(+ config/wrist_ft.yaml):S07 的首选判据是腕部 FT 的轴向突变,
而仿真里以前根本没有腕部力,S07 只能退到触觉兜底,两套判据从没在同一场景下比过。
现在两条通道都在,接触判定可对照。
3) 真值位姿可查(不再靠猜)
sim_live_pose / sim_pose_service:把 Gazebo 里刚体的实测位姿发布出来,
stage2 的真值路径按**实测**位姿算孔位,而不是按场景文件里的标称位姿。
顺带把真值孔位的推导(truth_holes)独立出来并补测。
4) 工具几何成为可配置项
config/sim_tool_geometry.yaml + stage2 的 tool_tip_below_flange_m 启动参数:
仿真里规会在指间下滑,工具长每次装夹都不同(实测见过 243/254mm),
悬停高度与 S07 下探预算必须按实测值算,不能再写死。
应用层同时跟进:gauge_fsm / gauge_config 把「悬停时工具尖端入没入工件」的判据
(hover_z_guard)与拖件判定(drag_verdict)收成纯函数并补测;stage3 搜孔、
site 导出、run_gauge_assembly 与若干文档同步。
新增 test_gauge_exit_lift / test_gauge_gripper_client / test_node_handler_collisions。
测试:全仓库 1085 passed。
|
2026-09-28 10:17:06 +08:00 |
|
arm_dev
|
1b4d6168d9
|
仿真手眼真值改为不跟踪:它是从 URDF/TF 推导的生成物,不是标定结果
文件里有一行"生成时间",run_gauge_assembly.sh --sim 每次重生成 →
每跑一次就脏一次仓库,让人分不清"这次改动是代码改的还是只是跑了一遍"。
区别对待(这条界线以后照此办):
- handeye/results/89_/91_*.yaml **要跟踪**:真机实测标定数据,必须留档
- handeye/results/sim_*.yaml **不跟踪**:由 gen_sim_handeye.py 从
URDF/TF 真值导出(仿真不需要标定),仿真启动时自动重生成
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-24 14:53:39 +08:00 |
|
arm_dev
|
b0fc5d7194
|
文档:把 S07 过不去的根因写进 GAUGE_ASSEMBLY(防再次误判成阈值问题)
这一轮走过一次弯路:S07 ABORT 报"没触发接触判据",第一反应是阈值标高了,
于是调预算、开台面通槽 —— 那些确实是独立的 bug(该修,已修),但**没有
解决 S07**。真实根因是「工具靠夹持摩擦抱住 → 轴向力传不到指垫触觉」,
µ·N ≈ 23N 而台肩落面一步就产生 25N,必然打滑。
把完整因果链(5 步,每步附日志证据)和三个可选修法写进 §6,并标明推荐
第 1 条(改读腕部 FT)—— 避免下一个人又对着触觉阈值试半天。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-24 14:53:12 +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
|
36a59c81bf
|
应用层修复:S00 自检假阴性、S07 触觉假断流、S07 下探静默卡死
三处都是"看着像硬件问题、其实是判据问题"的假警报:
1. S00 自检假阴性(gauge_boot.py)
探测等待时用 time.sleep 不 spin → 订阅回调根本没机会执行,
于是**健康的硬件也会被判"没有数据"**。改成等待期间持续 spin。
2. S07 触觉假断流(stage3_search_node.py)
判据原本是 |fnormal| > 1.0,但力本来就可能是零或很小(真机轻触、
仿真刚接触)→ 明明在发数据却判"触觉断流"。
改成"数据够新(TACTILE_MAX_AGE_S 内)+ connected" —— 判活看的是
**有没有在发**,不是**数值大不大**。
3. S07 下探静默卡死(gauge_fsm.py / gauge_assembly_node.py)
_goto 的到位容差 ≥ 步长时,每一步都被判成"已在容差内",臂一步都不走,
而外层只看到"没进度"→ 死循环到超时,日志里看不出原因。
新增 descend_tol_m / descend_iter_budget / descend_progress:
容差从步长推导(不该手工调大),并显式判"连续 N 步零位移"。
容差 ≥ 步长的反例已进 site 回归测试,防止再犯。
4. site 分段化(export_site_env.py):89/91 两臂的 servo/handeye/点位
各一段,避免"改 91 影响 89"。
另:run_unit_tests.sh 作为统一测试入口(932→933 项),
devices/sim/gazebo/README.md 同步。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-24 14:43:19 +08:00 |
|
arm_dev
|
b29339e522
|
仿真感知:真值孔位 + 真值相机 + 补 /home,让流程不改代码就能跑仿真
三块补齐,目标是"同一个流程节点,仿真/真机都不改代码":
1. 真值孔位(truth_holes.py):把 scene.yaml 与模型 SDF 里已知的孔几何
直接投影到相机像素坐标,替代 HoughCircles。perception.source 选:
yolo / truth / external —— truth 就走这条路。
为什么不用 Hough:仿真里孔是"方孔 + 贴片亮环"的近似(SDF 没有布尔运算),
靠图像门槛去凑 radus 区间很脆;而孔位本来就是场景里已知量,直接投影
既准又稳,还能当真实检测算法的对照基线。
2. 真值相机(sim_truth_perception.py):同理,直接算物体位姿,供
visual_servo/filtered_point 用,绕开 ONNX。
3. 补 /home 与 set_named_pose 服务(sim_home_service.py):真机上这两个
由 Flexiv 驱动提供,仿真里没有 —— 缺了 S01 直接超时。
4. sim_tactile_adapter 容错导入 ros_gz_interfaces:那一路只是"接触点",
力本身走 geometry_msgs/Wrench。typesupport 加载不了时退化成
contact 恒 False + centroid 恒 0,三力判据(S07 用的)不受影响 ——
宁可用退化模式跑,也不要整个适配器起不来。
5. repo_root 解析(已装包要能找回源码仓库根,见 test_repo_root.py 的用例)
与 use_sim_time 向子 launch 的传递。
另:scripts/gen_sim_handeye.py 把 URDF 真值导出手眼文件(仿真不标定)。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-24 14:43:13 +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
|
811e71df1f
|
仿真上料:夹爪真能夹住通止规(新增 sim_load_gauge.py + 夹爪出力钳位)
仿真里没有"人把规塞进夹爪"这一步,之前整条流程拖到 S07 才暴露(下探 25mm
什么也没碰到,看着像力阈值问题)。新增 scripts/sim_load_gauge.py 顶这一步。
上料为什么难(四个原因叠加,都实测踩过):
1. URDF 里夹爪关节 effort=80Nm → 位置控制器把规顶飞。urdf_normalize 新增
_clamp_grip_effort:按握力 45N 与指节杠杆反推,指节 4.79Nm / 主关节 45N。
2. **指腹接触面不在 tcp_frame 原点上**:URDF 里 inner_finger_pad_joint
z=0.0235、finger_tcp_joint z=0.0360(同父链节同方向)→ 指腹中心比
tcp_frame 高 12.5mm。所以规尖→指腹中心是 0.043,不是夹持段中心的
0.0555。用错值的现象极迷惑:gz 报得出 Contacts、也能读到接触,但规
就是托不住、一路滑下去。ΔZ 扫描实测:0/±5mm 掉、+10~+15mm 夹住。
3. 摆完位就自由落体:ROS 往返 100ms+ 而物理步进 1ms,等合爪指令到达,
规已掉 125mm。改成**像人手一样托着** —— 合爪期间 50Hz 按当前指腹位姿
把规瞬移回两指之间,托到真夹住为止。
4. 合到多少不猜:从松到紧扫开口,扫到"松手后不掉"为止。指腹实际接触面
未必在 tcp_frame 原点,拿"指间间距=开口"反推闭合量会差好几毫米。
结果:漂移 0.4mm、两指 |fnormal|≈46.5N(正好是 45N 设计握力)。
另修 _set_gauge_pose 的 ign service 口径说明与 _gauge_pose_now 的解析。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-24 14:42:56 +08:00 |
|
arm_dev
|
aed5d326a0
|
仿真环境串味根治:清残留路径 + 补 conda lib + 修入口脚本 shebang
症状一(最隐蔽):同一 shell 里先 source 过真机环境(scripts/env.sh)再
source 仿真环境,install/ 与 ros_flexiv 的路径不会被 conda deactivate 撤掉,
长在 install_sim/ 前面:
- PYTHONPATH 里真机那份 arm_msgs 压住仿真那份 → 消息类型混用
- LD_LIBRARY_PATH 里没有 $CONDA_PREFIX/lib → sim_tactile_adapter 订阅
ros_gz_interfaces/Contacts 时抛
Type support not from this implementation ...
cannot open shared object file
而库其实就在 conda 里、ldd 全齐。整条触觉链静默死掉(话题不发)。
症状二:setuptools 写 console_scripts 用的是**构建那一刻**的 python。
若在真机环境里跑过 build_sim.sh,install_sim/ 里 7 个入口脚本的 shebang
会指向 ros_flexiv —— 而 ros2 run 是照 shebang 执行的,于是**仿真节点一直
跑在真机 conda 环境里**。两个环境都是 Humble+py3.11,所以"居然能跑",
只有偶发 dlopen / 反序列化怪错。
修法:
- conda_switch.sh 新增 path_drop_patterns:按 glob 从冒号列表里删条目
- sim_env.sh 清掉真机残留 + 末尾补 $CONDA_PREFIX/lib(优先级最低)
- 新增 lib/fix_shebangs.sh,build.sh / build_sim.sh 收尾各扫一遍(幂等)
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-24 14:42:50 +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
|
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 |
|