arm_dev
|
561d7da21f
|
首轮顺时针进孔改回 10°/s,空回固定做 4 轮。
首轮咬合的顺时针进孔又走了接口上限,螺纹还没咬住就转完了。这一段改回读
extreme_speed_deg_s(现场 10°/s)。首轮逆时针出孔,以及空回每一轮的正反转,
仍走 0.90 rad/s 上限。
空回原先只做一轮。首轮咬合结束后,张开、微抬、逆时针、按本轮实测上抬降回、
合爪、顺时针这一整段固定重复 UNWIND_ROUNDS=4。抬和降留在轮内,下降量用这一轮
记住的上抬,不用上一轮的数。日志打空回第 i/4 轮;某一轮没走完就停,后面不再做。
10:40 合爪命令已经发到话题,节点被 Serial.flush/tcdrain 堵住 120s 才执行,
流水线 8s 时手指还停在 pos=400,第 2–4 轮没开始。发送改成等发送缓冲排空,
0.25s 排不空就失败返回,不再调用 tcdrain。同一次开合状态没跟上再发一次。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-20 11:40:57 +08:00 |
|
arm_dev
|
04137fb6e5
|
修复平行夹爪串口通讯问题
今天三次实测都在"首轮咬合结束→张开到 400"之后停住,微抬 Z 3mm 那条
厂家 MoveL 从未下发。日志只写"夹爪 pos 后 8s 未到位(没有 /gripper/states)",
照着去查夹爪开合,方向是错的。
真实原因是串口会话坏了:旧节点从昨天 18:25 一直跑,写命令(FC06)有回显
——ctrl pos=400 有记录、手指也确实张开——读 0x0201 却连续 264 次无应答
(0x0200 和写都有应答)。_on_timer 读失败就 return,一帧状态都不发,
流水线拿不到状态,就在 _shift_flange_z 之前退出了。
改法(都按"读不到要如实说读不到"):
- dh_pgea:串口 EIO/termios 错误从 DhPgeaError 里分出来,单独抛
DhPgeaBusError。以前 termios.error(5) 从 timer 回调冒出去,整个节点带
traceback 退出。
- dh_pgea_node:读失败也发 /gripper/states,data 增到 6 个元素,最后一位
online(1=刚读回,0=离线、前 5 个是旧值);5 元素老消费者不受影响。
离线时不发 /gripper/joint_states(关节状态没有离线位,不能拿旧开口冒充
现在的)。EIO 立刻重开串口,连续 30 拍读不到也重开一次,1 秒最多一次。
- pipeline_node:超时不再一律说"未到位",三种情况分开——真没到位 /
只有离线帧(总线无应答,不是没张开)/ 一帧都没收到(节点没在发)。
- gauge_gripper、operator_gripper_state:离线帧不当有效状态,夹爪页显示
"总线无应答"。
- gauge_config:登记 unwind_lift_mm 和 unwind_open_permille。这两个键
上一次只写进了 site.yaml,加载器按"不认识的键"直接拒绝启动
gauge_assembly 段(test_shipped_site_yaml_s07_step_cannot_deadlock 当时
是红的)。
- site_io:恢复 7af9e3d 误删的 listed_arms / absolutize_site_paths 等
(operator_gripper_state 还在导入 listed_arms,操作台夹爪页一导入就
ImportError)。按 76c9b9c 恢复,只增不减。
实测验收:重启节点后 --cli probe 读回
data=[1.0, 2.0, 9.0, 0.0, 20.0, 1.0],online=1、grip=2(咬住)、pos=9,
0x0201 不再无应答。
单测 931 passed。新增负向用例:离线帧不能被当成有效状态、EIO 必须抛
DhPgeaBusError、夹爪超时不能报成"未到位"。剩余 6 个失败与本次无关
(伺服限速、回调遮蔽、operator_camera_view 直接继承 Node、
site.yaml 缺 screw_stage_depth_mm、臂控制器未激活,以及 parallel_cli
用例因当前有真实夹爪节点占着串口而走了另一条分支)。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-20 10:10:37 +08:00 |
|
arm_dev
|
e80edd7b5e
|
旋转操作加速版本
|
2026-10-20 09:18:24 +08:00 |
|
arm_dev
|
55acdb638d
|
空回微抬改成一条厂家 MoveL,并按实测抬高降回。默认 Z50 会在还差约 0.5mm 时就报到位,微升降改用 ZFine 和到位等级 1。合到 0 时固件常报 grip=1,手指已在合拢端就继续顺时针,避免最后一转永远不发。快转样条按峰速不超过 0.90 rad/s 排程,避免时间到了还差几十度。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-10-19 19:02:55 +08:00 |
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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 |
|