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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
e4f513392d
|
P3b:手眼标定支持 --mode 模式选择与 --points 目标点数
--mode eye_in_hand|eye_to_hand
- 切换读写的文件为 <臂号>_<模式>.yaml,两种模式的采点与结果互不覆盖、可共存
- 臂号从 Flexiv 序列号后两位取(Rizon4-063989 → 89),不做硬编码映射
- 非法模式立即报错退出。不静默退回默认值 —— 那会造成"以为标的是
eye-to-hand、其实算的是 eye-in-hand",是这类工具最难排查的错误
- 求解侧本来就支持(calibrate_hand_eye(eye_to_hand=...) 对 gripper2base 求逆),
这次把入口补上,让模式不再只存在于参数文件里
--points N(仅 teach)
- teach 新增 target_points 参数并实现计数:显示 [3/12] ... 还差 9 个点,
采够时提示可以收工
- 刻意不强制关窗:实际采点常出现"某个角度还没覆盖",强制退出要重开
usage 重写:路径改为 handeye/,补上全局选项与组合示例;
usage() 的 sed 行号随之同步(原 2,18p 已不覆盖新头部)。
handeye/README.md 补第 5 节:模式切换与点数的完整用法,
并说明为什么非法模式要报错而不是兜底。
测试:224 passed
|
2026-09-23 11:48:11 +08:00 |
|
arm_dev
|
a3a589b9e3
|
P3a:手眼标定集中到 handeye/,修复三套板尺寸矛盾与从未生效的稳定性参数
新建 handeye/ 专用文件夹(用户要求:专用文件夹 + 使用说明)
- handeye/README.md:从小白视角讲清楚两种模式怎么选、板尺寸怎么量、
完整流程、出问题先看哪里。以前这些知识散在 docs/ 和代码注释里。
- 结构:calibrate.yaml(参数)/ poses/(采点输入)/ results/(结果输出)/ tmp/(临时,不进 git)
- 文件用 git mv 迁移,历史保留
命名约定改为 <臂号>_<模式>.yaml
- 如 89_eye_in_hand.yaml、91_eye_to_hand.yaml
- 臂号 = Flexiv 序列号后两位;模式 = eye_in_hand | eye_to_hand
- 换模式 = 改 site.yaml 里 paths.handeye_file 一行,不动代码
- 迁移只需改 site.yaml(4 个 launch + servo_node 都经 paths 读取,一行没改)
修复:三套互相矛盾的标定板尺寸(真实 bug)
同一个"板子多大"分散在三处,且值各不相同:
handeye_calibrate.py 默认 9x6 / 0.025
handeye/calibrate.yaml 11x16 / 0.011
site.yaml + 结果文件头 8x11 / 0.015 ← 实测的真实板子
谁生效取决于覆盖顺序。症状是"标定流程跑得很顺、不报错,但结果明显不对",
极难排查。已全部统一为 8x11 / 0.015,README 里写明换板必须三处同改。
修复:site.yaml 里五项稳定性配置从未生效
run_handeye.sh 一直在传 settle_qd_max / settle_stable_sec / settle_timeout_sec /
pin_holds / pin_hold_sec,但 handeye_calibrate.py 从未声明这些参数 ——
ROS 2 会静默忽略未声明的覆盖参数,所以"等机械臂停稳再拍照"这件事其实没实现,
实际只有一次固定 settle_sec。现已声明并实现:
- _wait_settled():用关节速度 |qd|_max 判定"真的停了",比固定 sleep 可靠
(负载/姿态不同刹车距离差别很大;等太短会在臂还动时拍照导致角点抖动)
- _pin_hold():静止后再保持几拍,抑制到位后的微小回弹
新增:自动标定进度条
一条可视化进度条([ 4/12] [████░░░░] 33.3%),无人值守的长流程能一眼看出还剩多久。
清理重复文件(内容逐字节比对确认后删除)
- config/handeye_result.yaml 与 _89 逐字节相同 → 删
- config/handeye_poses.yaml 与 _89 逐字节相同 → 删
迁移后 src/arm_application/config/ 下已无 handeye 文件。
测试:224 passed(原 198,新增 26)
新增 src/arm_application/test/test_handeye_lib.py —— 手眼模块此前零测试。
关键用例是合成数据往返:用人为设定的 X_true 反推出每个视图的
(gripper→base, target→cam),再让求解器还原。坐标链方向/求逆/转置
只要有一处写错就必然失败。
- 真值恢复(旋转+平移,atol 1e-6)
- 单独验证平移精确(旋转对而平移错是最易漏的一类 bug)
- 结果必须是 det=+1 的正交阵(排除镜像解)
- 退化输入(全同位姿)不得悄悄给出"看起来能用"的结果
- 稳定性判据:窗口未满不判稳、丢帧后重新计数、抖动超阈不判稳
- 写盘再读回不漂移
|
2026-09-23 11:46:03 +08:00 |
|
arm_dev
|
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 |
|