From 7af9e3d4b64c07194a6b55303cab883d15c61e89 Mon Sep 17 00:00:00 2001 From: arm_dev Date: Mon, 19 Oct 2026 11:30:49 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B4=B4=E9=9D=A2=E5=90=8E=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E6=8A=AC=E5=9B=9E=E6=82=AC=E9=AB=98=EF=BC=9BMoveL=20=E5=88=87?= =?UTF-8?q?=E5=9B=9E=E5=85=B3=E8=8A=82=E5=90=8E=E4=B8=8D=E5=86=8D=E6=8A=8A?= =?UTF-8?q?=E6=95=B4=E6=AE=B5=E4=BD=8D=E7=A7=BB=E5=BD=93=E5=89=8D=E8=BF=9B?= =?UTF-8?q?=E9=80=9F=E5=BA=A6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 搜孔开局会抬到悬高再落回 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 --- config/site.example.yaml | 94 +- config/site.yaml | 81 +- docs/HANDBOOK.md | 28 +- docs/HOLE_DETECT.md | 29 +- handeye/poses/89_eye_in_hand.yaml | 56 +- handeye/poses/89_eye_to_hand.yaml | 44 +- handeye/results/89_d435_eye_to_hand.yaml | 18 +- handeye/results/89_eye_in_hand.yaml | 25 +- .../lib/arm_application/assembly_pipeline | 8 +- .../lib/arm_application/demo_named_poses | 8 +- .../lib/arm_application/dh_pgea | 8 +- .../lib/arm_application/dh_pgea_cli | 8 +- .../lib/arm_application/dual_gripper | 8 +- .../lib/arm_application/dual_gripper_cli | 8 +- .../lib/arm_application/gauge_assembly | 8 +- .../lib/arm_application/goto_ee_plane | 8 +- .../lib/arm_application/handeye_calibrate | 8 +- .../lib/arm_application/handeye_teach | 8 +- .../lib/arm_application/handeye_waypoints | 8 +- .../lib/arm_application/hole_detect | 8 +- .../lib/arm_application/move_to_pose_client | 8 +- .../lib/arm_application/perception_node | 8 +- .../lib/arm_application/realsense_color_pub | 8 +- .../lib/arm_application/realsense_view | 8 +- .../lib/arm_application/run_joint_trajectory | 8 +- .../lib/arm_application/servo_node | 8 +- .../lib/arm_application/stage2_locate | 8 +- .../lib/arm_application/stage3_search | 8 +- .../lib/arm_application/tactile | 8 +- .../lib/arm_application/tactile_cli | 8 +- .../site-packages/arm_application/dh_pgea.py | 63 +- .../arm_application/handeye_calibrate.py | 4 +- .../arm_application/handeye_teach.py | 21 +- .../arm_application/operator_app.py | 2136 ++++++++++++++--- .../arm_application/operator_arm_mesh.py | 339 +++ .../arm_application/operator_arm_state.py | 595 +++++ .../arm_application/operator_camera_view.py | 651 ++++- .../arm_application/operator_console.py | 406 +++- .../arm_application/operator_gripper_mesh.py | 138 ++ .../arm_application/operator_gripper_state.py | 436 ++++ .../arm_application/operator_handeye_board.py | 91 + .../arm_application/operator_teach.py | 118 +- .../arm_application/overhead_oneshot.py | 30 +- .../arm_application/perception_node.py | 20 +- .../arm_application/pipeline_node.py | 71 +- .../arm_application/realsense_color_pub.py | 134 +- .../arm_application/servo_node.py | 82 +- .../site-packages/arm_application/site_io.py | 76 +- .../arm_application/stage2_locate_node.py | 58 +- .../arm_application/stage3_search_node.py | 3 +- .../arm_application/config/perception.yaml | 2 +- .../share/arm_application/package.ps1 | 1 - .../colcon-core/packages/arm_application | 2 +- .../flexiv_dual_hardware_interface.hpp | 140 +- .../flexiv_hardware_interface.hpp | 157 +- .../flexiv_hardware/visibility_control.h | 36 +- .../flexiv_hardware/lib/libflexiv_hardware.so | Bin 1595352 -> 57 bytes .../flexiv_hardware | 2 +- .../package_run_dependencies/flexiv_hardware | 2 +- .../resource_index/packages/flexiv_hardware | 1 + .../parent_prefix_path/flexiv_hardware | 2 +- ...ent_cmake_export_dependencies-extras.cmake | 95 +- ...ke_export_include_directories-extras.cmake | 17 +- .../ament_cmake_export_libraries-extras.cmake | 142 +- .../cmake/flexiv_hardwareConfig-version.cmake | 15 +- .../cmake/flexiv_hardwareConfig.cmake | 43 +- .../environment/ament_prefix_path.dsv | 2 +- .../environment/ament_prefix_path.sh | 5 +- .../flexiv_hardware/environment/path.dsv | 2 +- .../share/flexiv_hardware/environment/path.sh | 6 +- .../flexiv_hardware_interface_plugin.xml | 21 +- .../share/flexiv_hardware/local_setup.bash | 47 +- .../share/flexiv_hardware/local_setup.dsv | 3 +- .../share/flexiv_hardware/local_setup.sh | 185 +- .../share/flexiv_hardware/local_setup.zsh | 60 +- .../share/flexiv_hardware/package.xml | 26 +- install/setup.bash | 15 +- install/setup.zsh | 15 +- scripts/gen_site_example.py | 4 +- scripts/lib/export_site_env.py | 10 +- scripts/rdk_drag_bridge.py | 123 - scripts/rdk_release_movel.py | 4 +- scripts/run_all.sh | 20 +- scripts/run_camera_d405.sh | 8 +- scripts/run_camera_d435.sh | 2 - scripts/run_real.sh | 15 +- .../arm_application/handeye_calibrate.py | 4 +- .../arm_application/handeye_teach.py | 21 +- .../arm_application/overhead_oneshot.py | 30 +- .../arm_application/perception_node.py | 20 +- .../arm_application/pipeline_node.py | 83 +- .../arm_application/servo_node.py | 82 +- .../arm_application/site_io.py | 150 +- .../arm_application/stage2_locate_node.py | 58 +- .../arm_application/stage3_search_node.py | 3 +- src/arm_application/config/perception.yaml | 2 +- src/arm_application/launch/pipeline.launch.py | 2 +- .../test/test_overhead_oneshot.py | 109 +- .../test/test_overhead_track.py | 2 +- .../test/test_pipeline_stage1_diag.py | 60 +- .../test/test_stage2_coarse_guard.py | 24 +- .../flexiv_hardware_interface.hpp | 15 +- .../src/flexiv_hardware_interface.cpp | 174 +- 103 files changed, 5594 insertions(+), 2498 deletions(-) create mode 100644 install/arm_application/lib/python3.11/site-packages/arm_application/operator_arm_mesh.py create mode 100644 install/arm_application/lib/python3.11/site-packages/arm_application/operator_arm_state.py create mode 100644 install/arm_application/lib/python3.11/site-packages/arm_application/operator_gripper_mesh.py create mode 100644 install/arm_application/lib/python3.11/site-packages/arm_application/operator_gripper_state.py create mode 100644 install/arm_application/lib/python3.11/site-packages/arm_application/operator_handeye_board.py mode change 100644 => 120000 install/flexiv_hardware/include/flexiv_hardware/flexiv_dual_hardware_interface.hpp mode change 100644 => 120000 install/flexiv_hardware/include/flexiv_hardware/flexiv_hardware_interface.hpp mode change 100644 => 120000 install/flexiv_hardware/include/flexiv_hardware/visibility_control.h mode change 100755 => 120000 install/flexiv_hardware/lib/libflexiv_hardware.so mode change 100644 => 120000 install/flexiv_hardware/share/ament_index/resource_index/hardware_interface__pluginlib__plugin/flexiv_hardware mode change 100644 => 120000 install/flexiv_hardware/share/ament_index/resource_index/package_run_dependencies/flexiv_hardware mode change 100644 => 120000 install/flexiv_hardware/share/ament_index/resource_index/packages/flexiv_hardware mode change 100644 => 120000 install/flexiv_hardware/share/ament_index/resource_index/parent_prefix_path/flexiv_hardware mode change 100644 => 120000 install/flexiv_hardware/share/flexiv_hardware/cmake/ament_cmake_export_dependencies-extras.cmake mode change 100644 => 120000 install/flexiv_hardware/share/flexiv_hardware/cmake/ament_cmake_export_include_directories-extras.cmake mode change 100644 => 120000 install/flexiv_hardware/share/flexiv_hardware/cmake/ament_cmake_export_libraries-extras.cmake mode change 100644 => 120000 install/flexiv_hardware/share/flexiv_hardware/cmake/flexiv_hardwareConfig-version.cmake mode change 100644 => 120000 install/flexiv_hardware/share/flexiv_hardware/cmake/flexiv_hardwareConfig.cmake mode change 100644 => 120000 install/flexiv_hardware/share/flexiv_hardware/environment/ament_prefix_path.dsv mode change 100644 => 120000 install/flexiv_hardware/share/flexiv_hardware/environment/ament_prefix_path.sh mode change 100644 => 120000 install/flexiv_hardware/share/flexiv_hardware/environment/path.dsv mode change 100644 => 120000 install/flexiv_hardware/share/flexiv_hardware/environment/path.sh mode change 100644 => 120000 install/flexiv_hardware/share/flexiv_hardware/flexiv_hardware_interface_plugin.xml mode change 100644 => 120000 install/flexiv_hardware/share/flexiv_hardware/local_setup.bash mode change 100644 => 120000 install/flexiv_hardware/share/flexiv_hardware/local_setup.dsv mode change 100644 => 120000 install/flexiv_hardware/share/flexiv_hardware/local_setup.sh mode change 100644 => 120000 install/flexiv_hardware/share/flexiv_hardware/local_setup.zsh mode change 100644 => 120000 install/flexiv_hardware/share/flexiv_hardware/package.xml diff --git a/config/site.example.yaml b/config/site.example.yaml index 116f291..23a87d0 100644 --- a/config/site.example.yaml +++ b/config/site.example.yaml @@ -10,8 +10,8 @@ # 用法:复制本文件为 config/site.yaml,然后把所有 <占位符> 换成现场实际值。 # 现场值包括:机械臂 SN / IP、相机序列号、夹爪串口、传感器 USB 路径。 # -# 说明:仓库里的文件用相对仓库根的路径(例如 models/yolo/...)。 -# 不要改回某个用户的绝对家目录,换台电脑就会找不到文件。 +# 说明:site.yaml 里的路径出现 /home/USER 是因为生成时匿名化了用户名; +# 部署时请改成实际用户的 home 目录。 # ============================================================================= # ============================================================================= @@ -61,17 +61,15 @@ robot: # (status 里的 init=need_init 只说明"绝对位置标定没做",不影响夹持) # 想让开机必初始化(无人值守的上电流程)就显式改 true,并确认爪里是空的。 auto_init: false - named_poses_file: "src/arm_control/config/named_poses_89.yaml" + named_poses_file: "/home/USER/ros2/src/arm_control/config/named_poses_89.yaml" paths: - handeye_file: "handeye/results/89_eye_in_hand.yaml" - handeye_waypoints: "handeye/poses/89_eye_in_hand.yaml" - handeye_workdir: "handeye/tmp/89" + handeye_file: "/home/USER/ros2/handeye/results/89_eye_in_hand.yaml" + handeye_waypoints: "/home/USER/ros2/handeye/poses/89_eye_in_hand.yaml" + handeye_workdir: "/home/USER/ros2/handeye/tmp/89" handeye: # Studio tcp_location(法兰系,米 + qwxyz)。与 rdk Tool.params 必须一致。 - # 眼在手上:AX=XB 的手是这份 TCP,再折成 cam→法兰写 T_4x4。 - # 眼在手外:AX=XB 的手同样是这份 TCP(板夹在工具上);T_4x4 仍是 cam→基座,不折。 gripper_tool: "tool10120938" - gripper_tcp_xyz: [-0.0005, 0.0067, 0.2245] + gripper_tcp_xyz: [0.000537, -0.000264, 0.226587] gripper_tcp_qwxyz: [1.0, 0.0, 0.0, 0.0] # 左臂跟踪高度(只在 --arm 89 时生效,改 91 的 servo 不影响这里) # 锚点 `&servo_left`:下面的"仿真台"条目直接引用它 —— 仿真建的就是左臂, @@ -179,9 +177,8 @@ robot: # 落在本文件下方注释记录的合理区间 0.204~0.233 内。 # 复核:python3 scripts/sense_tool_length.py --sn Rizon4-XXXXXX # python3 scripts/lib/real_geometry_check.py --sn Rizon4-XXXXXX - # 2026-10-12:下臂安全距离改按 Studio 工具 tool10120938 的 TCP +Z。 - # 上面 0.223 是旧 tool1(夹爪 TCP 0.147 + 规露出 0.076),这把工具不再用。 - tool_tip_below_flange_m: 0.2245 + # 下臂安全距离按 tool10120938 的 TCP +Z,不再用旧 tool1 的 0.223。 + tool_tip_below_flange_m: 0.226587 # >>> 仿真那台臂(Gazebo)也登记成 robots[] 里的一台 <<< # 为什么必须登记:应用是按"当前臂"的名字去查 frame / joint / 相机话题的 #(`sim_env.sh` 注释里说的"仿真与真机共用同一套 site.yaml")。仿真的 TF 与关节 @@ -211,7 +208,7 @@ robot: # 为什么必须给文件而不能留空:visual_servo 的 servo_node 需要 # T_flange_cam 矩阵做像素→笛卡尔换算;留空它就直接拒绝启动 #("手眼标定结果缺失"),S04 跟踪起不来。 - handeye_file: "handeye/results/sim_eye_in_hand.yaml" + handeye_file: "/home/USER/ros2/handeye/results/sim_eye_in_hand.yaml" # 运动参数:**大部分**与 89 共用(锚点),但**高度类**必须按仿真台的几何改写。 # # 为什么不能直接共用:真机是"机械臂装在台面同一高度",所以"法兰相对基座" @@ -305,11 +302,11 @@ robot: port: "/dev/ttyUSB1" stroke_mm: 30.0 auto_init: false - named_poses_file: "src/arm_control/config/named_poses_91.yaml" + named_poses_file: "/home/USER/ros2/src/arm_control/config/named_poses_91.yaml" paths: - handeye_file: "handeye/results/91_eye_in_hand.yaml" - handeye_waypoints: "handeye/poses/91_eye_in_hand.yaml" - handeye_workdir: "handeye/tmp/91" + handeye_file: "/home/USER/ros2/handeye/results/91_eye_in_hand.yaml" + handeye_waypoints: "/home/USER/ros2/handeye/poses/91_eye_in_hand.yaml" + handeye_workdir: "/home/USER/ros2/handeye/tmp/91" # >>> 右臂跟踪高度:只改这里,89 仍用上面那段 <<< # 法兰 Z;RGI 比平行爪高,通常要比 89 略抬一点。改完重启: # bash ~/ros2/scripts/run_visual_servo_rt.sh --arm 91 --control @@ -330,7 +327,7 @@ robot: paths: # >>> YOLO 权重(标准放法,不必改代码)<<< - # 1) 把 .onnx / .pt 放到 models/yolo/ + # 1) 把 .onnx / .pt 放到 /home/USER/ros2/models/yolo/ # 2) 这一行改成那个绝对路径(写 .onnx 即可;TensorRT 会找同名 .engine) # 3) backend=tensorrt 时必须先导出引擎(只需做一次): # bash ~/ros2/scripts/weights.sh convert <源文件> --name <名字> --group yolo @@ -344,18 +341,18 @@ paths: # ultralytics 解不了 pose 的 TensorRT engine,运行时走 # arm_application/pose_detector.py 的自解码(见该模块头)。 # 仍然用 .engine(实测 ~2.6 ms/帧;onnx 只有 CPU provider,约 27 ms)。 - yolo_weights: "models/yolo/gongjian0929.onnx" + yolo_weights: "/home/USER/ros2/models/yolo/gongjian0929.onnx" # 换模型:把 models/yolo/ 下别的 .onnx / .pt 绝对路径填进来即可。 # 未在用的模型已移出仓库(体积大且干扰 `weights.sh list`),要恢复见 models/README.md。 # 入孔模型(backend=yolo 时把孔当框)。two_stage 用下面的工件权重。 # pose 模型两处共用同一份 —— 它既能框工件,又直接给 k1/k2 关键点。 # 查悬空引用:bash ~/ros2/scripts/weights.sh doctor - hole_weights: "models/yolo/gongjian0929.onnx" + hole_weights: "/home/USER/ros2/models/yolo/gongjian0929.onnx" # 下面是缺省(未匹配到 robots[] 时)。真机以 robots[].paths 为准:89 / 91 各一份。 - handeye_file: "handeye/results/89_eye_in_hand.yaml" - handeye_waypoints: "handeye/poses/89_eye_in_hand.yaml" - handeye_calibrate: "handeye/calibrate.yaml" - handeye_workdir: "handeye/tmp/89" + handeye_file: "/home/USER/ros2/handeye/results/89_eye_in_hand.yaml" + handeye_waypoints: "/home/USER/ros2/handeye/poses/89_eye_in_hand.yaml" + handeye_calibrate: "/home/USER/ros2/handeye/calibrate.yaml" + handeye_workdir: "/home/USER/ros2/handeye/tmp/89" # 空=默认 DDS(SHM);勿强制 UDP-only,否则相机预览收不到 /workpiece/detection fastdds_profile: "" @@ -595,9 +592,8 @@ servo: lock_hold_sec: 0.7 # 旧死区驻留(混合模式不再用它进二阶段) # 混合模式必填。交回 D405 的等待不再用它,见 d435_handback_sec。 lock_still_sec: 3.0 - # 进程内 MoveL 到位后,法兰 XY 连续停稳这么久才交回 D405。 - # 旧 3s 是杀栈冷启动的垫时间;不杀栈后只留机械停稳。晃了把数加大,不许再杀栈。 - d435_handback_sec: 0.5 + # 厂家 MoveL 结束后,法兰连续这么久没再动,才打开 D405 框心。 + d435_handback_sec: 2.0 # 框心对准并且法兰停稳这么久,才精拍一次小孔关键点、进对孔。实时点到此为止。 wrist_align_sec: 0.5 lock_still_move_m: 0.015 # 15 mm;超过就重计时,避免"晃一下刚好满 3 秒" @@ -679,10 +675,10 @@ gripper: hole_detect: backend: "two_stage" # two_stage | yolo | hough # backend=yolo 时用。 - weights: "models/yolo/gongjian0929.onnx" + weights: "/home/USER/ros2/models/yolo/gongjian0929.onnx" # 二阶段工件检测权重。pose 模型既框工件、又直接给 k1/k2 关键点, # 所以和上面的跟踪权重是同一份。 - workpiece_weights: "models/yolo/gongjian0929.onnx" + workpiece_weights: "/home/USER/ros2/models/yolo/gongjian0929.onnx" # pose 关键点优先:检测器给了 k1/k2 就直接用,不用再找圆。 # 关键点不可信(低于 keypoint_min_conf / 两点几乎重合)时自动回落到 # 亮环 + Hough 那套老路径。 @@ -690,7 +686,7 @@ hole_detect: keypoint_min_conf: 0.30 keypoint_min_dist_px: 15.0 keypoint_hole_radius_px: 12.0 # 关键点没有半径;反投影时按这个半径取孔周深度环 - config: "src/arm_application/config/hole_detect.yaml" + config: "/home/USER/ros2/src/arm_application/config/hole_detect.yaml" conf: 0.25 iou: 0.7 imgsz: 640 @@ -768,26 +764,22 @@ hole_detect: # bbox=(0,142,97,222) 反投影出盒外的 (0.280,-0.747)(偏差 211.5mm),旧逻辑 # **不判误检、只把目标截断到盒边**,臂照样横移 56mm 离开工件 ⇒ 精定位再也 # 看不到 k1/k2 ⇒ exit=4。判据改成"盒外/偏移过大 ⇒ 判误检、原地不动"。 - coarse_max_offset_m: 0.12 # 目标离夹爪轴线超过此值就不像粗居中量 [m] + coarse_max_offset_m: 0.08 # 目标离夹爪轴线超过此值就不像粗居中量 [m];0.12 放行了 2026-10-16 的 110mm 回撤 coarse_border_px: 2 # bbox 贴边(被画面切)即弃用 [px];见 _bbox_on_border # —— 柔顺运动(夹爪持规,防急加速/急停/过冲把规震脱):速度放慢 + 平滑刹车 —— align_speed: 0.014 # 对准段笛卡尔速度 [m/s](原0.03,放缓防冲) move_speed: 0.022 # 升降/粗居中段速度 [m/s](原0.05,放缓防震规) - # 对小孔关键点:只用像素射线交到 workpiece_surface_z_m,不读深度图。 hole_visual_only: true - # 本分支实验(exp/hole-keypoint-once):true=悬停拍一次,框心→k1 一次走完 - # (40mm 上限)。main 保存点是 false(多走多拍)。 + # 本分支实验:true=悬停拍一次并走完到孔;main 保存点应保持 false。 hole_keypoint_once: true - # true=悬停走完后一条厂家 MoveL 贴面(不补拍)。对照 37b85aa 改回 false。 + # true=悬停走完后一条厂家 MoveL 贴面。对照观察高补交时改 false。 hole_once_lock_xy_descend: true - # 贴面相对悬停:沿 -look 走 k×剩余下降 [m/m]。0=笛卡尔锁 XY 只降 Z。 - # 0.018 在框心改平面交点后两条链路都 +1.3mm X 过补,插不进。 + # 0=笛卡尔锁 XY 只降 Z。k>0 才沿 -look 加前馈。 once_ff_look_per_m: 0.0 - final_gap_mm: 2.0 # 对准后贴面目标:规尖离面 [mm](不接触)。笛卡尔锁 XY - # 时法兰 Z = 工具长 + 工件面 + 本值。CLI --final-gap 覆盖。 + final_gap_mm: 2.0 # 对准后贴面目标:规尖离面 [mm]。CLI --final-gap 覆盖。 observe_gap_mm: 15.0 # --no-descent 时的观测下降:只降到离面这么高 [mm], # 方便看规尖偏没偏。必须 ≥ final_gap_mm。 - # 左臂89:悬停间隙约 73.5mm(0.320−0.2245−0.022)⇒ 观测下降约 58mm。 + # 左臂89:悬停间隙约 75mm(0.320−0.223−0.022)⇒ 大约下降 60mm。 final_descent_speed: 0.005 # 最后轻降速度 [m/s](很慢,柔顺) # ✅ 2026-10-08 定稿:下降量**只由几何决定**(法兰 FK − 工具长 − 工件面 − final_gap), # 深度读数只做交叉核对、不再参与任何决策。两段真机事故方向相反、都指向这条: @@ -800,7 +792,7 @@ hole_detect: # ⇒ 读数往**小**错一次就永远到不了目标。 # 同一套读数两个方向各错一次、幅度 150mm 量级、随位姿乱跳 —— 它对"能降多少" # 和"该不该停"都没有发言权。现在它只在偏差 >10mm 时喊一句"深度测距与几何不符"。 - # 现行几何:悬停 0.320 − 工具长 0.2245 − 工件面 0.022 = 73.5mm 间隙 ⇒ 降 71.5mm 到 2mm。 + # 现行几何:悬停 0.320 − 工具长 0.223 − 工件面 0.022 = 75mm 间隙 ⇒ 降 73mm 到 2mm。 # 三道**物理/几何**保险:descent_budget_m(配置上限)+ 腕部 FT 接触检测(停+退 1mm) # + 任一步 _goto 走不到位就停。 # 缺几何(工具长/工件面没配)时**拒绝下降**并报错(保持悬停),绝不拿测距去赌。 @@ -812,9 +804,11 @@ hole_detect: max_depth_m: 0.60 linger_sec: 8.0 # 完成后继续发布结果/叠图的时长 - # ===== 旋转装配仍读这里的力阈值(stage3_search_node)===== - # 搜孔轻压已取消:不带 --screw 的入口会直接拒绝,整体流程也不再进入这一步。 - # bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --screw + # ===== 三阶段柔顺搜孔(stage3_search_node,Orisys 触觉 + 力闭环螺旋)===== + # 前置:二阶段已完成,臂悬停在目标孔上方。先 run_tactile.sh --cli 看力读数量级再调阈值。 + # bash ~/ros2/scripts/run_stage3_search.sh --arm 89 # 搜孔+落座 + # bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --depth 0 # 只搜孔定心不插入 + # bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --depth 6 --pitch 1.0 # 螺纹规旋入 stage3: # —— 力阈值全是「相对静止均值的增量」(Orisys 读数是形变任意单位且基线会漂移, # 节点启动时自动测 1.2s 静止均值 μ0/方差 σ0,看日志「触觉基线/派生阈值」两行)—— @@ -841,12 +835,9 @@ hole_detect: drop_frames: 2 # 跌落需持续的帧数(防毛刺) center_scan: false # 入孔后十字微扫;默认关(0.8mm 扫会把已入孔的规刮偏) center_scan_mm: 0.3 - insert_depth_mm: 0.0 # 默认只搜孔+轻压,不拧入。旋转装配不读这项 + insert_depth_mm: 0.0 # 默认只搜孔+轻压,不拧入;拧入阶段再用 --depth insert_retry: 3 - screw_pitch_mm: 0.0 # 搜孔轻压不旋。旋转装配不读这项 - # 控制页「旋转装配」单独用。数值就是手册里的 M6 旋入:深度 6 mm、螺距 1.0 mm/圈。 - screw_stage_depth_mm: 6.0 - screw_stage_pitch_mm: 1.0 + screw_pitch_mm: 0.0 # 螺纹通止规螺距:>0 右旋 / <0 左旋 / 0=不旋(光面规) search_speed: 0.03 # 搜孔笛卡尔速度 [m/s] settle_sec: 0.25 # 每个微步后的静止时间 tactile_timeout_s: 3.0 # 触觉话题断流多久中止 @@ -1102,7 +1093,7 @@ tactile: # 传感器型号。必须与 devices/sensors/orisys/models/ 下的子目录名一致。 model: "dd03" # 型号配置随 SDK 一起放:devices/sensors/orisys/models/<型号>/config.json - config_file: "devices/sensors/orisys/models/dd03/config.json" + config_file: "/home/USER/ros2/devices/sensors/orisys/models/dd03/config.json" # 用 by-path 稳定路径(插哪个 USB 口就认哪个口,不受 video 编号漂移影响)。 # 当前:3.4.1=左指 3.4.2=右指;若左右反了,互换两行即可。 left_video: "/dev/v4l/by-path/pci-0000:00:14.0-usb-0:X.Y.Z:1.0-video-index0" @@ -1129,8 +1120,7 @@ go_home: network: # 2026-09-29 改线:两台臂 → 交换机 → USB-C 千兆网卡(ASIX AX88179) - # MAC 6c:1f:f7:d0:6f:17。内核名会在 eth0 和 enxXXXXXXXXXXXX 之间变, - # NetworkManager 按 MAC 绑定,不按名字。 + # MAC 6c:1f:f7:d0:6f:17。内核现在叫 eth0(不再用 enxXXXXXXXXXXXX)。 # 本机 ↔ <左臂SN>(左89)=.101 / <右臂SN>(右91)=.100 # NM 配置名 robot-usb-lan(静态 .10/24,不当默认路由)。 # 说明:enp4s0 仍是办公网(192.168.1.x);两台臂已不在 enp4s0 上。 diff --git a/config/site.yaml b/config/site.yaml index 1a84117..4942fa3 100644 --- a/config/site.yaml +++ b/config/site.yaml @@ -23,7 +23,7 @@ robot: # 本臂装了 Orisys 视触觉(只有左臂有)。脚本靠这一项决定要不要拉触觉服务, # 而不是靠"SN 等于某个具体值"这种写死的判断(换台臂就会失效)。 tactile: true - # 2026-10-12 示教的工具 TCP(tool + 1012 + 0938)。眼在手上 AX=XB 用这份 TCP。 + # 2026-10-16 下午 RDK 重读 Studio tool10120938(用户再次更新 TCP)。 tool_name: "tool10120938" camera: name: d405_left # 话题 /d405_left/... 与 91 分开 @@ -45,17 +45,17 @@ robot: # (status 里的 init=need_init 只说明"绝对位置标定没做",不影响夹持) # 想让开机必初始化(无人值守的上电流程)就显式改 true,并确认爪里是空的。 auto_init: false - named_poses_file: "src/arm_control/config/named_poses_89.yaml" + named_poses_file: "/home/cc/ros2/src/arm_control/config/named_poses_89.yaml" paths: - handeye_file: "handeye/results/89_eye_in_hand.yaml" - handeye_waypoints: "handeye/poses/89_eye_in_hand.yaml" - handeye_workdir: "handeye/tmp/89" + handeye_file: "/home/cc/ros2/handeye/results/89_eye_in_hand.yaml" + handeye_waypoints: "/home/cc/ros2/handeye/poses/89_eye_in_hand.yaml" + handeye_workdir: "/home/cc/ros2/handeye/tmp/89" handeye: # Studio tcp_location(法兰系,米 + qwxyz)。与 rdk Tool.params 必须一致。 # 眼在手上:AX=XB 的手是这份 TCP,再折成 cam→法兰写 T_4x4。 # 眼在手外:AX=XB 的手同样是这份 TCP(板夹在工具上);T_4x4 仍是 cam→基座,不折。 gripper_tool: "tool10120938" - gripper_tcp_xyz: [-0.0005, 0.0067, 0.2245] + gripper_tcp_xyz: [0.000537, -0.000264, 0.226587] gripper_tcp_qwxyz: [1.0, 0.0, 0.0, 0.0] # 左臂跟踪高度(只在 --arm 89 时生效,改 91 的 servo 不影响这里) # 锚点 `&servo_left`:下面的"仿真台"条目直接引用它 —— 仿真建的就是左臂, @@ -163,9 +163,11 @@ robot: # 落在本文件下方注释记录的合理区间 0.204~0.233 内。 # 复核:python3 scripts/sense_tool_length.py --sn Rizon4-063989 # python3 scripts/lib/real_geometry_check.py --sn Rizon4-063989 - # 2026-10-12:下臂安全距离改按 Studio 工具 tool10120938 的 TCP +Z。 - # 上面 0.223 是旧 tool1(夹爪 TCP 0.147 + 规露出 0.076),这把工具不再用。 - tool_tip_below_flange_m: 0.2245 + # 2026-10-16 下午:RDK Tool.params("tool10120938").tcp_location + # [x,y,z,qw,qx,qy,qz] = [0.000537, -0.000264, 0.226587, 1, 0, 0, 0] + # 示教器拟合误差:平均 0.58 / 最大 0.99 / 最小 0.29 mm。 + # 下臂安全距离用 TCP +Z。上午 0.226274;再往前 0.2245 是 10-12;0.223 是 tool1。 + tool_tip_below_flange_m: 0.226587 # >>> 仿真那台臂(Gazebo)也登记成 robots[] 里的一台 <<< # 为什么必须登记:应用是按"当前臂"的名字去查 frame / joint / 相机话题的 #(`sim_env.sh` 注释里说的"仿真与真机共用同一套 site.yaml")。仿真的 TF 与关节 @@ -195,7 +197,7 @@ robot: # 为什么必须给文件而不能留空:visual_servo 的 servo_node 需要 # T_flange_cam 矩阵做像素→笛卡尔换算;留空它就直接拒绝启动 #("手眼标定结果缺失"),S04 跟踪起不来。 - handeye_file: "handeye/results/sim_eye_in_hand.yaml" + handeye_file: "/home/cc/ros2/handeye/results/sim_eye_in_hand.yaml" # 运动参数:**大部分**与 89 共用(锚点),但**高度类**必须按仿真台的几何改写。 # # 为什么不能直接共用:真机是"机械臂装在台面同一高度",所以"法兰相对基座" @@ -289,11 +291,11 @@ robot: port: "/dev/ttyUSB1" stroke_mm: 30.0 auto_init: false - named_poses_file: "src/arm_control/config/named_poses_91.yaml" + named_poses_file: "/home/cc/ros2/src/arm_control/config/named_poses_91.yaml" paths: - handeye_file: "handeye/results/91_eye_in_hand.yaml" - handeye_waypoints: "handeye/poses/91_eye_in_hand.yaml" - handeye_workdir: "handeye/tmp/91" + handeye_file: "/home/cc/ros2/handeye/results/91_eye_in_hand.yaml" + handeye_waypoints: "/home/cc/ros2/handeye/poses/91_eye_in_hand.yaml" + handeye_workdir: "/home/cc/ros2/handeye/tmp/91" # >>> 右臂跟踪高度:只改这里,89 仍用上面那段 <<< # 法兰 Z;RGI 比平行爪高,通常要比 89 略抬一点。改完重启: # bash ~/ros2/scripts/run_visual_servo_rt.sh --arm 91 --control @@ -314,7 +316,7 @@ robot: paths: # >>> YOLO 权重(标准放法,不必改代码)<<< - # 1) 把 .onnx / .pt 放到 models/yolo/ + # 1) 把 .onnx / .pt 放到 /home/cc/ros2/models/yolo/ # 2) 这一行改成那个绝对路径(写 .onnx 即可;TensorRT 会找同名 .engine) # 3) backend=tensorrt 时必须先导出引擎(只需做一次): # bash ~/ros2/scripts/weights.sh convert <源文件> --name <名字> --group yolo @@ -328,18 +330,18 @@ paths: # ultralytics 解不了 pose 的 TensorRT engine,运行时走 # arm_application/pose_detector.py 的自解码(见该模块头)。 # 仍然用 .engine(实测 ~2.6 ms/帧;onnx 只有 CPU provider,约 27 ms)。 - yolo_weights: "models/yolo/gongjian0929.onnx" + yolo_weights: "/home/cc/ros2/models/yolo/gongjian0929.onnx" # 换模型:把 models/yolo/ 下别的 .onnx / .pt 绝对路径填进来即可。 # 未在用的模型已移出仓库(体积大且干扰 `weights.sh list`),要恢复见 models/README.md。 # 入孔模型(backend=yolo 时把孔当框)。two_stage 用下面的工件权重。 # pose 模型两处共用同一份 —— 它既能框工件,又直接给 k1/k2 关键点。 # 查悬空引用:bash ~/ros2/scripts/weights.sh doctor - hole_weights: "models/yolo/gongjian0929.onnx" + hole_weights: "/home/cc/ros2/models/yolo/gongjian0929.onnx" # 下面是缺省(未匹配到 robots[] 时)。真机以 robots[].paths 为准:89 / 91 各一份。 - handeye_file: "handeye/results/89_eye_in_hand.yaml" - handeye_waypoints: "handeye/poses/89_eye_in_hand.yaml" - handeye_calibrate: "handeye/calibrate.yaml" - handeye_workdir: "handeye/tmp/89" + handeye_file: "/home/cc/ros2/handeye/results/89_eye_in_hand.yaml" + handeye_waypoints: "/home/cc/ros2/handeye/poses/89_eye_in_hand.yaml" + handeye_calibrate: "/home/cc/ros2/handeye/calibrate.yaml" + handeye_workdir: "/home/cc/ros2/handeye/tmp/89" # 空=默认 DDS(SHM);勿强制 UDP-only,否则相机预览收不到 /workpiece/detection fastdds_profile: "" @@ -579,9 +581,8 @@ servo: lock_hold_sec: 0.7 # 旧死区驻留(混合模式不再用它进二阶段) # 混合模式必填。交回 D405 的等待不再用它,见 d435_handback_sec。 lock_still_sec: 3.0 - # 进程内 MoveL 到位后,法兰 XY 连续停稳这么久才交回 D405。 - # 旧 3s 是杀栈冷启动的垫时间;不杀栈后只留机械停稳。晃了把数加大,不许再杀栈。 - d435_handback_sec: 0.5 + # 厂家 MoveL 结束后,法兰连续这么久没再动,才打开 D405 框心。 + d435_handback_sec: 2.0 # 框心对准并且法兰停稳这么久,才精拍一次小孔关键点、进对孔。实时点到此为止。 wrist_align_sec: 0.5 lock_still_move_m: 0.015 # 15 mm;超过就重计时,避免"晃一下刚好满 3 秒" @@ -663,10 +664,10 @@ gripper: hole_detect: backend: "two_stage" # two_stage | yolo | hough # backend=yolo 时用。 - weights: "models/yolo/gongjian0929.onnx" + weights: "/home/cc/ros2/models/yolo/gongjian0929.onnx" # 二阶段工件检测权重。pose 模型既框工件、又直接给 k1/k2 关键点, # 所以和上面的跟踪权重是同一份。 - workpiece_weights: "models/yolo/gongjian0929.onnx" + workpiece_weights: "/home/cc/ros2/models/yolo/gongjian0929.onnx" # pose 关键点优先:检测器给了 k1/k2 就直接用,不用再找圆。 # 关键点不可信(低于 keypoint_min_conf / 两点几乎重合)时自动回落到 # 亮环 + Hough 那套老路径。 @@ -674,7 +675,7 @@ hole_detect: keypoint_min_conf: 0.30 keypoint_min_dist_px: 15.0 keypoint_hole_radius_px: 12.0 # 关键点没有半径;反投影时按这个半径取孔周深度环 - config: "src/arm_application/config/hole_detect.yaml" + config: "/home/cc/ros2/src/arm_application/config/hole_detect.yaml" conf: 0.25 iou: 0.7 imgsz: 640 @@ -752,7 +753,7 @@ hole_detect: # bbox=(0,142,97,222) 反投影出盒外的 (0.280,-0.747)(偏差 211.5mm),旧逻辑 # **不判误检、只把目标截断到盒边**,臂照样横移 56mm 离开工件 ⇒ 精定位再也 # 看不到 k1/k2 ⇒ exit=4。判据改成"盒外/偏移过大 ⇒ 判误检、原地不动"。 - coarse_max_offset_m: 0.12 # 目标离夹爪轴线超过此值就不像粗居中量 [m] + coarse_max_offset_m: 0.08 # 目标离夹爪轴线超过此值就不像粗居中量 [m];0.12 放行了 2026-10-16 的 110mm 回撤 coarse_border_px: 2 # bbox 贴边(被画面切)即弃用 [px];见 _bbox_on_border # —— 柔顺运动(夹爪持规,防急加速/急停/过冲把规震脱):速度放慢 + 平滑刹车 —— align_speed: 0.014 # 对准段笛卡尔速度 [m/s](原0.03,放缓防冲) @@ -771,7 +772,7 @@ hole_detect: # 时法兰 Z = 工具长 + 工件面 + 本值。CLI --final-gap 覆盖。 observe_gap_mm: 15.0 # --no-descent 时的观测下降:只降到离面这么高 [mm], # 方便看规尖偏没偏。必须 ≥ final_gap_mm。 - # 左臂89:悬停间隙约 73.5mm(0.320−0.2245−0.022)⇒ 观测下降约 58mm。 + # 左臂89:悬停间隙约 71.4mm(0.320−0.226587−0.022)⇒ 观测下降约 56.4mm。 final_descent_speed: 0.005 # 最后轻降速度 [m/s](很慢,柔顺) # ✅ 2026-10-08 定稿:下降量**只由几何决定**(法兰 FK − 工具长 − 工件面 − final_gap), # 深度读数只做交叉核对、不再参与任何决策。两段真机事故方向相反、都指向这条: @@ -784,7 +785,7 @@ hole_detect: # ⇒ 读数往**小**错一次就永远到不了目标。 # 同一套读数两个方向各错一次、幅度 150mm 量级、随位姿乱跳 —— 它对"能降多少" # 和"该不该停"都没有发言权。现在它只在偏差 >10mm 时喊一句"深度测距与几何不符"。 - # 现行几何:悬停 0.320 − 工具长 0.2245 − 工件面 0.022 = 73.5mm 间隙 ⇒ 降 71.5mm 到 2mm。 + # 现行几何:悬停 0.320 − 工具长 0.226587 − 工件面 0.022 = 71.4mm 间隙 ⇒ 降 69.4mm 到 2mm。 # 三道**物理/几何**保险:descent_budget_m(配置上限)+ 腕部 FT 接触检测(停+退 1mm) # + 任一步 _goto 走不到位就停。 # 缺几何(工具长/工件面没配)时**拒绝下降**并报错(保持悬停),绝不拿测距去赌。 @@ -796,9 +797,11 @@ hole_detect: max_depth_m: 0.60 linger_sec: 8.0 # 完成后继续发布结果/叠图的时长 - # ===== 旋转装配仍读这里的力阈值(stage3_search_node)===== - # 搜孔轻压已取消:不带 --screw 的入口会直接拒绝,整体流程也不再进入这一步。 - # bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --screw + # ===== 三阶段柔顺搜孔(stage3_search_node,Orisys 触觉 + 力闭环螺旋)===== + # 前置:二阶段已完成,臂悬停在目标孔上方。先 run_tactile.sh --cli 看力读数量级再调阈值。 + # bash ~/ros2/scripts/run_stage3_search.sh --arm 89 # 搜孔+落座 + # bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --depth 0 # 只搜孔定心不插入 + # bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --depth 6 --pitch 1.0 # 螺纹规旋入 stage3: # —— 力阈值全是「相对静止均值的增量」(Orisys 读数是形变任意单位且基线会漂移, # 节点启动时自动测 1.2s 静止均值 μ0/方差 σ0,看日志「触觉基线/派生阈值」两行)—— @@ -825,12 +828,9 @@ hole_detect: drop_frames: 2 # 跌落需持续的帧数(防毛刺) center_scan: false # 入孔后十字微扫;默认关(0.8mm 扫会把已入孔的规刮偏) center_scan_mm: 0.3 - insert_depth_mm: 0.0 # 默认只搜孔+轻压,不拧入。旋转装配不读这项 + insert_depth_mm: 0.0 # 默认只搜孔+轻压,不拧入;拧入阶段再用 --depth insert_retry: 3 - screw_pitch_mm: 0.0 # 搜孔轻压不旋。旋转装配不读这项 - # 控制页「旋转装配」单独用。数值就是手册里的 M6 旋入:深度 6 mm、螺距 1.0 mm/圈。 - screw_stage_depth_mm: 6.0 - screw_stage_pitch_mm: 1.0 + screw_pitch_mm: 0.0 # 螺纹通止规螺距:>0 右旋 / <0 左旋 / 0=不旋(光面规) search_speed: 0.03 # 搜孔笛卡尔速度 [m/s] settle_sec: 0.25 # 每个微步后的静止时间 tactile_timeout_s: 3.0 # 触觉话题断流多久中止 @@ -1086,7 +1086,7 @@ tactile: # 传感器型号。必须与 devices/sensors/orisys/models/ 下的子目录名一致。 model: "dd03" # 型号配置随 SDK 一起放:devices/sensors/orisys/models/<型号>/config.json - config_file: "devices/sensors/orisys/models/dd03/config.json" + config_file: "/home/cc/ros2/devices/sensors/orisys/models/dd03/config.json" # 用 by-path 稳定路径(插哪个 USB 口就认哪个口,不受 video 编号漂移影响)。 # 当前:3.4.1=左指 3.4.2=右指;若左右反了,互换两行即可。 left_video: "/dev/v4l/by-path/pci-0000:00:14.0-usb-0:3.4.1:1.0-video-index0" @@ -1113,8 +1113,7 @@ go_home: network: # 2026-09-29 改线:两台臂 → 交换机 → USB-C 千兆网卡(ASIX AX88179) - # MAC 6c:1f:f7:d0:6f:17。内核名会在 eth0 和 enx6c1ff7d06f17 之间变, - # NetworkManager 按 MAC 绑定,不按名字。 + # MAC 6c:1f:f7:d0:6f:17。内核现在叫 eth0(不再用 enx6c1ff7d06f17)。 # 本机 192.168.2.10 ↔ 063989(左89)=.101 / 063991(右91)=.100 # NM 配置名 robot-usb-lan(静态 .10/24,不当默认路由)。 # 说明:enp4s0 仍是办公网(192.168.1.x);两台臂已不在 enp4s0 上。 diff --git a/docs/HANDBOOK.md b/docs/HANDBOOK.md index ab2832b..1440686 100644 --- a/docs/HANDBOOK.md +++ b/docs/HANDBOOK.md @@ -34,7 +34,7 @@ ### 1.3 一次完整任务分几步 -代码里跟踪和对孔串成一条命令。搜孔轻压已取消,`run_all.sh` 不会再进入那一步,也不能用参数打开。旋入是单独的 `run_stage3_search.sh --screw`。 +代码里用 **stage1 / stage2 / stage3** 三段串起来,一条命令跑完: ``` run_all.sh @@ -46,13 +46,18 @@ run_all.sh │ YOLO 找工件外框 → Hough 找左右两个孔心 → │ 深度 + 内参 + 手眼 → 换算出孔的基座坐标 → │ 移动夹爪轴线到目标孔正上方 → 复拍验证 → 锁定 + │ + └─ stage3_search 柔顺搜孔:力闭环下的螺旋搜索,直到规的尖端"掉进"孔里, + 再十字微扫定心,最后在力限内下压(可选按螺距旋入) ``` -跟踪和对孔在同一个进程里交接。盯日志: +三段之间**在同一个进程里交接**,所以相机、触觉、手眼 TF 只初始化一次。 +盯日志就能知道跑到哪了 —— 三行关键输出: ``` [pipeline] === stage1_track === → 在等跟踪锁定 [pipeline] === stage2_locate === → 在拍照对孔 +[pipeline] === stage3_search === → 触面 → ★ 入孔 → 轻压到位 ``` 日常入口(三个终端,**必须同一个 `--arm`**): @@ -60,7 +65,7 @@ run_all.sh ```bash bash ~/ros2/scripts/run_real.sh --arm 89 # T1 控制栈,一直开着 bash ~/ros2/scripts/run_camera_d405.sh --arm 89 --preview # T2 相机预览,一直开着 -bash ~/ros2/scripts/run_all.sh --arm 89 # T3 跟踪 + 对孔(不含搜孔、不含旋入) +bash ~/ros2/scripts/run_all.sh --arm 89 # T3 三段串联(默认不拧入) bash ~/ros2/scripts/run_all.sh --arm 89 --init-z 0.35 --hover-z 0.34 ``` @@ -68,15 +73,15 @@ bash ~/ros2/scripts/run_all.sh --arm 89 --init-z 0.35 --hover-z 0.34 `D405_MISS_BEFORE_D435_S`(0.5 s)没有 conf≥`perception.wrist_handoff_conf`(0.5) 的目标,才用顶视 D435 **拍一张**,把点用眼在手外 T 变到基座(不再乘法兰)。 这一段运动是进程内厂家 `MoveL`(不杀 `run_real.sh`):当前厂家 `tcp_pose` 加 -ROS 相对 XY,`keep_z`。硬件在 MoveL 期间不灌关节指令。到位后法兰 XY 连续停稳 -`servo.d435_handback_sec`(0.5 s)才交回 D405。赶路期间不下发 D405(两套控制互抢会晃)。 -停稳之后改回 D405,跟踪点是工件框的矩形中心,不追 k0。 +ROS 相对 XY,`keep_z`。硬件在 MoveL 期间不灌关节指令。D435 这一点变到基座系后,只下发厂家 `MoveL`。赶路期间 D405 推理开着, +控制点截断。法兰连续停稳 `servo.d435_handback_sec`(2 s)后才打开 D405, +追工件框的矩形中心。框心对准并停稳后才精拍 k1/k2、下臂。 启动高度用 `--init-z`(法兰 Z,米),对孔悬停用 `--hover-z`。两个都是法兰高度。 下臂贴面用 `--final-gap`(规尖离面,毫米;site 默认 `final_gap_mm` 2)。笛卡尔锁 XY 时只改 Z:法兰 = 工具长 + 工件面 + 间隙。不能和 `--no-descent` 一起写。 会让工具尖进入工件、或超出工作盒的值直接拒绝,不会改成另一个高度再往下走。 不写则一阶段锁高仍按 site,对孔悬停仍是 `hole_hover_z_m`(89 为 0.320)。 -框心对准连续 `servo.wrist_align_sec`(0.5 s)→ `/visual_servo/locked`。 +框心对准连续 `servo.wrist_align_sec`(0.5 s)才 `/visual_servo/locked`,然后才精拍下臂。 本分支(`exp/hole-keypoint-once`)二阶段是**单次精拍**:悬停拍一次 k1/k2, 把框心到目标孔一次走完(步长上限 40mm)。现行实验 `hole_once_lock_xy_descend: true`: 走完后不补拍,一条厂家 MoveL 收到贴面。`once_ff_look_per_m: 0` 时 MoveL 只降 Z @@ -84,7 +89,8 @@ ROS 相对 XY,`keep_z`。硬件在 MoveL 期间不灌关节指令。到位后 不是 ROS 绝对坐标。保存点 `37b85aa` 是观察高 15mm 再斜线补 XY。看 `/tmp/stage2_result.json`。 `main` 保存点仍是多走多拍。 孔坐标把关键点像素交到 `workpiece_surface_z_m`,不读深度图。下臂停在 -`tool10120938` 的 TCP 长度(0.2245 m)加上 `final_gap_mm` 之上,不按旧 tool1 的 0.223 m。 +`tool10120938` 的 TCP 长度(0.226587 m,2026-10-16 下午 RDK 重读)加上 `final_gap_mm` 之上,不按旧 tool1 的 0.223 m。 +贴面之后就停在这个法兰高度,不抬回 `hole_hover_z_m`。没有接触传感器,到位只认 `final_gap_mm`,不另设一个 Z。 只测 D435 这一段位移时加 `--d435-only`:不交 D405、不进二阶段,并且要求先停 `run_real.sh`。 ```bash @@ -107,8 +113,8 @@ bash ~/ros2/scripts/run_visual_servo_rt.sh --arm 89 --control | 能力 | 状态 | 怎么自己确认 | |------|------|--------------| -| 跟踪 → 对孔 | ✅ 真机跑通过。搜孔轻压已取消,这条命令不再下压搜孔 | `run_all.sh --arm 89` | -| 按螺距旋入(螺纹规) | ✅ 单独阶段。不带 `--screw` 的旧搜孔命令会直接拒绝 | `run_stage3_search.sh --arm 89 --screw` | +| 三段串联(跟踪 → 对孔 → 搜孔轻压) | ✅ 真机跑通 | `run_all.sh --arm 89` | +| 按螺距旋入(螺纹规) | ✅ 有参数,`--depth`/`--pitch` 控制 | `run_stage3_search.sh --arm 89 --depth 6 --pitch 1.0` | | Orisys 双指触觉(法向 + 两路切向) | ⚠️ 能读,但**轴向映射尚未实测确认** | 见 [`TACTILE.md`](TACTILE.md) | | Orisys 的深度图 / 力矢量图 | ⚠️ 消息结构与话题已在,真机值待确认 | 同上 | | D435 手外相机(一阶段一次性 + S03) | ⚠️ 89 已按 `tool10120938` 重标(24 视角,10-09 旧结果在 `results/backup/`);S03 粗定位真机未对过 | `handeye/results/89_d435_eye_to_hand.yaml` | @@ -505,7 +511,7 @@ bash scripts/run_unit_tests.sh -k gauge # 只跑名字带 gauge 的 | **89 / 91** | 左臂 / 右臂的现场编号,**各有独立的手眼、相机、夹爪、锁高,禁止混用** | | **法兰** | 机械臂末端安装面;手眼 `T_4x4` 仍是相机→法兰。左臂 89 现行按 Studio `tool10120938` 的 TCP 求解,再折回法兰 | | **eye-in-hand / eye-to-hand** | 相机装在手上 / 装在手上之外(D405 在手上,D435 在外部) | -| **stage1/2** | 跟踪 / 对孔,见 1.3。搜孔轻压已取消 | +| **stage1/2/3** | 跟踪 / 对孔 / 搜孔,见 1.3 | | **S03 / S04 / S07 / S08** | 整体装配任务书里的阶段编号(S03 粗定位、S04 跟踪、S07 柔顺下压、S08 夹爪旋转循环)。代码里对应的是 stage1/2/3 的拆分实现,**没有一一对应**,别硬套 | | **基线** | 触觉静止时的读数均值。力的语义是"相对基线的增量",不是绝对牛顿值 | | **R1.1 / M1 …** | [`FRAMEWORK_RULES.md`](FRAMEWORK_RULES.md) 里的规则编号,文档和提交信息里直接引用 | diff --git a/docs/HOLE_DETECT.md b/docs/HOLE_DETECT.md index 5c984a3..fba4c2a 100644 --- a/docs/HOLE_DETECT.md +++ b/docs/HOLE_DETECT.md @@ -57,7 +57,8 @@ hole_detect: ## 三阶段串联(推荐日常入口) -一条命令跑完:**视觉跟踪锁定 → 拍照对孔**。搜孔轻压已取消,这条命令不再下压,也不能用参数重新打开。旋入是后面的单独命令。 +一条命令跑完:**视觉跟踪锁定 → 拍照对孔 → 搜孔并轻轻按压**。默认**不拧入** +(拧入是后面单独阶段)。三个阶段在同一个节点里交接,相机 / 触觉 / 手眼 TF 只起一次。 ```bash # 终端 1 —— 控制栈(一直开着) @@ -66,12 +67,12 @@ bash ~/ros2/scripts/run_real.sh --arm 89 # 终端 2 —— 相机预览(一直开着,看跟踪画面) bash ~/ros2/scripts/run_camera_d405.sh --arm 89 --preview -# 终端 3 —— 跟踪 + 对孔(到这里停,不搜孔) -bash ~/ros2/scripts/run_all.sh --arm 89 # 跟踪 + 对孔 +# 终端 3 —— 串联(搜到孔后轻压保持,不拧) +bash ~/ros2/scripts/run_all.sh --arm 89 # 跟踪 + 对孔 + 搜孔轻压 bash ~/ros2/scripts/run_all.sh --arm 89 --hole k2 # 换 k2 bash ~/ros2/scripts/run_all.sh --arm 89 --skip-stage1 # 臂已在工件上方,跳过跟踪 -# 旋入不写在这条命令里: -bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --screw +# 若这一次就要拧入(一般留给后续阶段): +bash ~/ros2/scripts/run_all.sh --arm 89 --depth 6 --pitch 1.0 ``` `run_pipeline.sh` 是 `run_all.sh` 的别名。 @@ -84,9 +85,9 @@ bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --screw 1. `[pipeline] === stage1_track ===` → 等跟踪锁定 2. `一阶段已锁定 — 停跟踪、钉住当前关节,直接进二阶段` 3. `[pipeline] === stage2_locate ===` → 降到孔悬停高,拍照对准 -对孔结束后臂停在当前高度。搜孔轻压不再接着跑。 +4. `[pipeline] === stage3_search ===` → `触面` → `★ 入孔` → `轻压到位`(不拧入) -急停:终端 3 Ctrl-C。失败时臂保持当前位姿,看 +急停:终端 3 Ctrl-C。搜孔中会先抬 5mm。失败时臂保持当前位姿,看 `/tmp/pipeline_stage1.log`、`/tmp/stage2_result.json`、`/tmp/stage3_result.json`。 高度接口:`robots[89].servo.ee_z_m`(跟踪锁高)、 @@ -148,10 +149,7 @@ bash ~/ros2/scripts/run_all.sh --arm 89 --final-gap 8 # 锁 XY,规 | `/stage2/result` | `std_msgs/String` JSON:k1/k2 基座坐标、残差、悬停高 | | `/stage2/overlay` | `sensor_msgs/Image` 标注图(同时落盘 /tmp/stage2_overlay.png) | -## 旋转装配(原三阶段节点;搜孔轻压入口已取消) - -不带 `--screw` 时 `run_stage3_search.sh` 直接退出,不会触面,也不会螺旋搜孔。 -下面是这个节点在旋入时仍会走的力控步骤,不是一条还能单独打开的搜孔命令。 +## 三阶段柔顺搜孔(stage3,Orisys 触觉 + 力闭环螺旋) 前置:二阶段刚跑完(臂悬停在目标孔上方、垂直朝下、夹持通止规)。流程: @@ -169,9 +167,12 @@ bash ~/ros2/scripts/run_all.sh --arm 89 --final-gap 8 # 锁 XY,规 7. **hold** — 锁定关节,发布结果 ```bash -# 前置:T1 run_real.sh --arm 89;对孔已完成(臂保持不动) -# 不带 --screw 会直接退出,不会搜孔。 -bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --screw +# 前置:T1 run_real.sh --arm 89;二阶段已完成(臂保持不动) +bash ~/ros2/scripts/run_stage3_search.sh --arm 89 # 搜孔+落座 +bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --depth 0 # 只搜孔定心,不插入 +bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --depth 6 --pitch 1.0 # M6 螺纹规旋入 +bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --r-max 5 # 临时改搜索半径 +bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --resnap # 搜前用 D405 复拍对准一次 ``` **力阈值是「相对静止均值的增量」,不用标定牛顿**:Orisys 读数是形变任意单位且 diff --git a/handeye/poses/89_eye_in_hand.yaml b/handeye/poses/89_eye_in_hand.yaml index c4642b3..67b189b 100644 --- a/handeye/poses/89_eye_in_hand.yaml +++ b/handeye/poses/89_eye_in_hand.yaml @@ -8,49 +8,59 @@ /**: ros__parameters: - num_poses: 21 - # 2026-09-02 通过 handeye_teach 示教(RDK 自由驱动) + num_poses: 26 + # 2026-10-16 重新示教 26 点(旧 21 点在 poses/backup/);A5 11×8,tool10120938 waypoint_positions: [ # pose 0 - -0.928421, -1.237477, 1.528823, 1.526334, -1.230146, 0.098562, 0.538531, + -0.211261, -0.571428, 0.247301, 1.552793, -0.119748, 0.614676, -0.023410, # pose 1 - -0.970203, -1.315554, 1.553230, 1.669383, -1.407869, 0.125907, 0.578173, + -0.169531, -0.647538, 0.404777, 1.568624, -0.268686, 0.700171, 0.395947, # pose 2 - -0.967819, -1.525572, 1.706057, 1.357062, -1.211051, 0.023232, 0.193541, + -0.759204, -0.846289, 0.735872, 1.387307, -0.539553, 0.576374, -0.060241, # pose 3 - -1.065106, -1.624218, 1.793644, 1.279402, -1.120929, 0.019151, 0.139658, + -0.925104, -1.394748, 1.532423, 0.826872, -0.686369, -0.074315, -0.145523, # pose 4 - -0.386279, -1.551838, 1.563578, 1.552444, -1.775337, -0.341974, 0.957445, + -1.004515, -1.491910, 1.596406, 1.245659, -0.864236, 0.212990, 0.460589, # pose 5 - -0.227779, -1.219414, 1.026882, 0.828801, -0.916809, -0.166985, 0.034154, + -0.373052, -1.441561, 1.624783, 0.325602, -1.361228, -0.404116, 0.511430, # pose 6 - -0.324968, -1.273082, 1.092344, 0.723132, -0.922095, -0.206413, 0.152082, + -0.281212, -1.452318, 1.627866, 0.517637, -1.412135, -0.420614, 0.217023, # pose 7 - -0.421966, -1.309101, 1.143065, 0.606456, -0.906673, -0.219282, 0.237953, + -0.184868, -1.473244, 1.670229, 0.827772, -1.522330, -0.529014, -0.212158, # pose 8 - -0.851598, -1.260600, 1.302895, 1.127033, -0.969809, 0.032856, 0.642673, + -0.246577, -1.532828, 1.679171, 1.204498, -1.697638, -0.607779, -0.517785, # pose 9 - -0.951418, -0.550124, 0.848598, 2.139787, -1.025778, 1.184418, 0.598394, + -0.463435, -1.549157, 1.650964, 1.948746, -1.841124, -0.638016, -0.842437, # pose 10 - -0.834498, -0.635767, 0.970038, 2.058410, -1.035138, 1.032040, 0.738619, + -1.573539, -0.359801, 1.261534, 2.423352, -0.405671, 1.620875, -0.045014, # pose 11 - -0.519528, -0.883321, 1.135035, 1.830274, -1.100901, 0.234065, -0.069068, + -1.549088, -0.710364, 1.347227, 2.111776, -0.606133, 1.116345, -0.567788, # pose 12 - -0.347358, -1.081426, 1.192233, 1.514557, -1.244594, -0.116668, -0.564362, + -1.512218, -0.983202, 1.445454, 1.808168, -0.602205, 0.699420, -0.803771, # pose 13 - -0.364832, -1.102689, 1.224844, 1.520320, -1.265760, -0.136855, -0.582459, + -1.023248, -1.342205, 1.465909, 1.092911, -0.843418, 0.099672, 1.287399, # pose 14 - -1.130796, -1.316186, 1.711089, 1.821409, -1.278003, 0.010885, -1.337210, + -0.870257, -1.473283, 1.657987, 0.831271, -1.034030, -0.144608, 1.263203, # pose 15 - -0.732222, -1.376363, 1.812339, 1.293854, -1.410826, -0.226771, -0.217230, + -0.384368, -1.417984, 1.403442, 2.075614, -1.952106, -0.394139, 1.022592, # pose 16 - -0.885017, -1.493416, 1.894070, 1.379021, -1.520015, -0.257115, 1.025331, + -0.320712, -1.513967, 1.508547, 1.652983, -1.854378, -0.409816, 1.024834, # pose 17 - -0.911769, -1.532695, 1.944123, 1.427787, -1.560513, -0.267425, 1.004398, + -0.046066, -1.630857, 1.639221, 1.105078, -1.971509, -0.601654, 1.076452, # pose 18 - -1.436271, -1.768595, 1.559397, 2.244717, -2.057112, 0.341785, 0.749883, + -0.183628, -1.588663, 1.989621, 0.918793, -1.873979, -0.629910, 0.092012, # pose 19 - -1.511828, -1.759337, 1.620075, 2.131574, -1.946595, 0.328227, 0.584657, + -1.300709, -1.159500, 1.611790, 1.761984, -0.963027, 0.391619, -0.468980, # pose 20 - -1.180482, -1.643241, 1.865851, 1.597699, -1.464982, 0.097605, -0.217780, + -0.750548, -1.221231, 1.566283, 1.128755, -1.163176, -0.102841, 0.958420, + # pose 21 + -0.874185, -1.297230, 1.655940, 1.303348, -1.334815, -0.047425, 2.047690, + # pose 22 + -0.671314, -1.510973, 1.813908, 1.235398, -1.778996, -0.462958, 2.899902, + # pose 23 + -0.710795, -1.522124, 1.653705, 1.316372, -1.574330, 0.086268, 1.453877, + # pose 24 + -0.533642, -1.545775, 1.837151, 1.467081, -1.463448, -0.161183, 0.387563, + # pose 25 + -0.850451, -1.523689, 1.982673, 1.082314, -1.771622, -0.354609, 0.583659, ] diff --git a/handeye/poses/89_eye_to_hand.yaml b/handeye/poses/89_eye_to_hand.yaml index 7620bbd..2ef7485 100644 --- a/handeye/poses/89_eye_to_hand.yaml +++ b/handeye/poses/89_eye_to_hand.yaml @@ -7,48 +7,38 @@ /**: ros__parameters: - num_poses: 21 + num_poses: 16 waypoint_positions: [ # pose 0 - -0.798667, -0.611079, 0.054912, 2.246988, 0.049199, 1.389219, 0.196135, + -0.850450, -1.523690, 1.982676, 1.082314, -1.771616, -0.354603, 0.583665, # pose 1 - -0.787304, -0.531383, 0.240108, 2.183440, 0.367026, 1.370311, 0.472489, + -2.098137, -0.606148, 1.213612, 2.290328, -0.795715, 0.618633, 0.213293, # pose 2 - -1.615896, -0.687070, 1.187600, 2.243693, -0.162789, 1.650045, 1.026121, + -2.115870, -0.711154, 1.204674, 2.128285, -0.937034, 0.514766, -0.356714, # pose 3 - -1.787431, -0.668004, 1.408195, 2.025431, -0.064718, 0.565415, 1.324635, + -2.017820, -0.798105, 1.516945, 2.002096, -0.392371, -0.035888, 0.657789, # pose 4 - -2.024343, -0.863793, 1.349640, 2.359469, -0.720629, 0.084467, 0.821412, + -2.021874, -0.805058, 1.533201, 2.001407, -0.399671, -0.045924, 0.653138, # pose 5 - -1.884100, -1.117584, 1.183093, 2.127937, -1.258013, 0.151934, 0.985555, + -2.135665, -1.020864, 1.346306, 2.334297, -1.268378, 0.406336, 0.897832, # pose 6 - -1.866465, -1.194137, 1.143263, 1.872811, -1.320028, 0.280719, 0.777663, + -1.793944, -1.143422, 1.244557, 1.942177, -1.315611, 0.473616, 1.619695, # pose 7 - -1.729402, -1.139685, 1.127574, 1.913544, -1.329630, -0.194162, 1.383834, + -2.075950, -1.215149, 1.322761, 2.142359, -1.372597, 0.398777, 1.008202, # pose 8 - -1.611937, -1.158158, 1.064049, 1.918313, -1.480676, -0.392994, 1.833894, + -2.153500, -1.215892, 1.345656, 2.306931, -1.412319, 0.415145, 0.997235, # pose 9 - -2.252978, -1.387014, 1.686186, 2.101298, -1.333349, -0.111994, 0.036817, + -2.127777, -1.256654, 1.370562, 2.009440, -1.369584, 0.219094, -0.132912, # pose 10 - -2.179722, -1.297659, 1.497171, 2.167675, -1.272158, 0.100548, 0.673603, + -2.138686, -1.270867, 1.591854, 2.150744, -0.898729, 0.079835, 0.840791, # pose 11 - -1.921897, -1.543024, 1.000863, 1.897273, -1.455972, 1.437388, -0.140968, + -2.063605, -1.300562, 1.582835, 2.080463, -0.904827, 0.243915, 1.110180, # pose 12 - -1.884059, -1.063347, 1.439248, 2.253200, -1.164963, -0.423306, 0.907197, + -1.867772, -1.316320, 1.376713, 1.888199, -1.274029, 0.284609, 1.405712, # pose 13 - -1.987022, -1.235459, 1.354108, 2.370862, -1.354044, 0.065378, 0.923079, + -1.829092, -1.355347, 1.315742, 1.808403, -1.413826, 0.338349, 1.459711, # pose 14 - -1.770869, -1.333861, 1.241996, 1.800723, -1.395819, -0.255590, 1.389660, + -2.192461, -1.374082, 1.330037, 2.154137, -1.528020, 0.279283, 0.657702, # pose 15 - -1.761759, -1.369860, 1.213137, 1.791639, -1.467956, -0.170537, 1.473557, - # pose 16 - -1.504294, -1.346227, 1.206550, 1.660186, -1.320782, -0.393676, 1.657215, - # pose 17 - -1.846546, -1.476930, 0.946255, 1.910615, -2.435429, 1.535168, 2.151544, - # pose 18 - -1.700067, -1.452760, 0.909035, 1.758222, -2.103930, 1.445249, 2.174743, - # pose 19 - -2.173713, -0.983647, 1.399849, 2.384939, -1.379509, -0.216471, 0.253561, - # pose 20 - -2.160187, -0.967904, 1.376288, 2.451713, -1.393524, -0.212278, 0.473606, + -2.137444, -1.468886, 1.525289, 2.348419, -1.025385, 1.011855, 1.148855, ] diff --git a/handeye/results/89_d435_eye_to_hand.yaml b/handeye/results/89_d435_eye_to_hand.yaml index 8039d08..f58c7a3 100644 --- a/handeye/results/89_d435_eye_to_hand.yaml +++ b/handeye/results/89_d435_eye_to_hand.yaml @@ -1,22 +1,22 @@ # 手眼标定结果 # mode: eye_to_hand # camera_source: d435 -# views: 24 -# utc: 2026-10-12T05:20:30.137123+00:00 +# views: 15 +# utc: 2026-10-16T03:02:49.214344+00:00 # board: 11x8 square_m=0.02 # gripper_tool: tool10120938 # T_4x4 = cam→基座(AX=XB 的手是 TCP,不折到法兰) # 旧结果备份在 results/backup/ gripper_tool: tool10120938 T_flange_to_tool: - - [1.00000000, 0.00000000, 0.00000000, -0.00050000] - - [0.00000000, 1.00000000, 0.00000000, 0.00670000] - - [0.00000000, 0.00000000, 1.00000000, 0.22450000] + - [1.00000000, 0.00000000, 0.00000000, 0.00075900] + - [0.00000000, 1.00000000, 0.00000000, -0.00008800] + - [0.00000000, 0.00000000, 1.00000000, 0.22627400] - [0.00000000, 0.00000000, 0.00000000, 1.00000000] # T_cam_to_base (eye_to_hand) -translation_m: [0.37094529, -0.52805928, 0.73745585] +translation_m: [0.38078822, -0.52208142, 0.70359964] T_4x4: - - [-0.00990083, 0.99909706, -0.04131632, 0.37094529] - - [0.99991380, 0.01024832, 0.00820703, -0.52805928] - - [0.00862304, -0.04123150, -0.99911241, 0.73745585] + - [-0.03086969, 0.99833255, -0.04877684, 0.38078822] + - [0.99951823, 0.03067553, -0.00472440, -0.52208142] + - [-0.00322027, -0.04889918, -0.99879853, 0.70359964] - [0.00000000, 0.00000000, 0.00000000, 1.00000000] diff --git a/handeye/results/89_eye_in_hand.yaml b/handeye/results/89_eye_in_hand.yaml index 9cfa770..fd4f670 100644 --- a/handeye/results/89_eye_in_hand.yaml +++ b/handeye/results/89_eye_in_hand.yaml @@ -1,28 +1,27 @@ # 手眼标定结果 # mode: eye_in_hand # camera_source: d405_left -# views: 21 -# utc: 2026-10-12T03:29:51.243487+00:00 +# views: 25 +# utc: 2026-10-16T02:29:07.572249+00:00 # board: 11x8 square_m=0.015 # gripper_tool: tool10120938 # T_4x4 = cam→法兰(AX=XB 在 TCP 上解,再左乘 T_flange_to_tool) # T_cam_to_tool 是原始求解;旧的法兰/tool1 备份在 results/backup/ -# 2026-10-12 第一次写出的 T_4x4 乘序反了(tz≈-296 mm),已按同一份 T_cam_to_tool 重折。 gripper_tool: tool10120938 T_flange_to_tool: - - [1.00000000, 0.00000000, 0.00000000, -0.00050000] - - [0.00000000, 1.00000000, 0.00000000, 0.00670000] - - [0.00000000, 0.00000000, 1.00000000, 0.22450000] + - [1.00000000, 0.00000000, 0.00000000, 0.00075900] + - [0.00000000, 1.00000000, 0.00000000, -0.00008800] + - [0.00000000, 0.00000000, 1.00000000, 0.22627400] - [0.00000000, 0.00000000, 0.00000000, 1.00000000] T_cam_to_tool: - - [-0.03435184, -0.86951953, 0.49270249, -0.05961995] - - [0.99932767, -0.03620511, 0.00577974, -0.01523625] - - [0.01281276, 0.49256977, 0.87017863, -0.09761517] + - [0.01117708, -0.86715374, 0.49791511, -0.06192365] + - [0.99992647, 0.01203574, -0.00148504, -0.00762539] + - [-0.00470501, 0.49789509, 0.86722450, -0.09779711] - [0.00000000, 0.00000000, 0.00000000, 1.00000000] # T_cam_to_flange (eye_in_hand,由工具 TCP 折算) -translation_m: [-0.06011995, -0.00853625, 0.12688483] +translation_m: [-0.06116465, -0.00771339, 0.12847689] T_4x4: - - [-0.03435184, -0.86951953, 0.49270249, -0.06011995] - - [0.99932767, -0.03620511, 0.00577974, -0.00853625] - - [0.01281276, 0.49256977, 0.87017863, 0.12688483] + - [0.01117708, -0.86715374, 0.49791511, -0.06116465] + - [0.99992647, 0.01203574, -0.00148504, -0.00771339] + - [-0.00470501, 0.49789509, 0.86722450, 0.12847689] - [0.00000000, 0.00000000, 0.00000000, 1.00000000] diff --git a/install/arm_application/lib/arm_application/assembly_pipeline b/install/arm_application/lib/arm_application/assembly_pipeline index 0b324dd..83e04c4 100755 --- a/install/arm_application/lib/arm_application/assembly_pipeline +++ b/install/arm_application/lib/arm_application/assembly_pipeline @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','assembly_pipeline' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','assembly_pipeline' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'assembly_pipeline')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'assembly_pipeline')()) diff --git a/install/arm_application/lib/arm_application/demo_named_poses b/install/arm_application/lib/arm_application/demo_named_poses index f7c52f6..7c827cb 100755 --- a/install/arm_application/lib/arm_application/demo_named_poses +++ b/install/arm_application/lib/arm_application/demo_named_poses @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','demo_named_poses' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','demo_named_poses' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'demo_named_poses')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'demo_named_poses')()) diff --git a/install/arm_application/lib/arm_application/dh_pgea b/install/arm_application/lib/arm_application/dh_pgea index cd342cb..ca6dd44 100755 --- a/install/arm_application/lib/arm_application/dh_pgea +++ b/install/arm_application/lib/arm_application/dh_pgea @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','dh_pgea' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','dh_pgea' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'dh_pgea')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'dh_pgea')()) diff --git a/install/arm_application/lib/arm_application/dh_pgea_cli b/install/arm_application/lib/arm_application/dh_pgea_cli index d7cfa18..81ecdd2 100755 --- a/install/arm_application/lib/arm_application/dh_pgea_cli +++ b/install/arm_application/lib/arm_application/dh_pgea_cli @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','dh_pgea_cli' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','dh_pgea_cli' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'dh_pgea_cli')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'dh_pgea_cli')()) diff --git a/install/arm_application/lib/arm_application/dual_gripper b/install/arm_application/lib/arm_application/dual_gripper index fbbbaf9..bf6c529 100755 --- a/install/arm_application/lib/arm_application/dual_gripper +++ b/install/arm_application/lib/arm_application/dual_gripper @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','dual_gripper' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','dual_gripper' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'dual_gripper')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'dual_gripper')()) diff --git a/install/arm_application/lib/arm_application/dual_gripper_cli b/install/arm_application/lib/arm_application/dual_gripper_cli index 2189632..1a713e1 100755 --- a/install/arm_application/lib/arm_application/dual_gripper_cli +++ b/install/arm_application/lib/arm_application/dual_gripper_cli @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','dual_gripper_cli' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','dual_gripper_cli' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'dual_gripper_cli')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'dual_gripper_cli')()) diff --git a/install/arm_application/lib/arm_application/gauge_assembly b/install/arm_application/lib/arm_application/gauge_assembly index cdeb1f2..5270ad3 100755 --- a/install/arm_application/lib/arm_application/gauge_assembly +++ b/install/arm_application/lib/arm_application/gauge_assembly @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','gauge_assembly' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','gauge_assembly' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'gauge_assembly')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'gauge_assembly')()) diff --git a/install/arm_application/lib/arm_application/goto_ee_plane b/install/arm_application/lib/arm_application/goto_ee_plane index 6cd4bbe..9b2bf55 100755 --- a/install/arm_application/lib/arm_application/goto_ee_plane +++ b/install/arm_application/lib/arm_application/goto_ee_plane @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','goto_ee_plane' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','goto_ee_plane' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'goto_ee_plane')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'goto_ee_plane')()) diff --git a/install/arm_application/lib/arm_application/handeye_calibrate b/install/arm_application/lib/arm_application/handeye_calibrate index e089854..05964cc 100755 --- a/install/arm_application/lib/arm_application/handeye_calibrate +++ b/install/arm_application/lib/arm_application/handeye_calibrate @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','handeye_calibrate' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','handeye_calibrate' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'handeye_calibrate')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'handeye_calibrate')()) diff --git a/install/arm_application/lib/arm_application/handeye_teach b/install/arm_application/lib/arm_application/handeye_teach index 3ff575f..cb30156 100755 --- a/install/arm_application/lib/arm_application/handeye_teach +++ b/install/arm_application/lib/arm_application/handeye_teach @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','handeye_teach' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','handeye_teach' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'handeye_teach')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'handeye_teach')()) diff --git a/install/arm_application/lib/arm_application/handeye_waypoints b/install/arm_application/lib/arm_application/handeye_waypoints index 89dbb86..461792e 100755 --- a/install/arm_application/lib/arm_application/handeye_waypoints +++ b/install/arm_application/lib/arm_application/handeye_waypoints @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','handeye_waypoints' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','handeye_waypoints' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'handeye_waypoints')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'handeye_waypoints')()) diff --git a/install/arm_application/lib/arm_application/hole_detect b/install/arm_application/lib/arm_application/hole_detect index c68accd..f2b2bc9 100755 --- a/install/arm_application/lib/arm_application/hole_detect +++ b/install/arm_application/lib/arm_application/hole_detect @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','hole_detect' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','hole_detect' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'hole_detect')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'hole_detect')()) diff --git a/install/arm_application/lib/arm_application/move_to_pose_client b/install/arm_application/lib/arm_application/move_to_pose_client index 1ce10d4..73cf946 100755 --- a/install/arm_application/lib/arm_application/move_to_pose_client +++ b/install/arm_application/lib/arm_application/move_to_pose_client @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','move_to_pose_client' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','move_to_pose_client' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'move_to_pose_client')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'move_to_pose_client')()) diff --git a/install/arm_application/lib/arm_application/perception_node b/install/arm_application/lib/arm_application/perception_node index d1e25ad..8679a28 100755 --- a/install/arm_application/lib/arm_application/perception_node +++ b/install/arm_application/lib/arm_application/perception_node @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','perception_node' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','perception_node' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'perception_node')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'perception_node')()) diff --git a/install/arm_application/lib/arm_application/realsense_color_pub b/install/arm_application/lib/arm_application/realsense_color_pub index c640e0c..1453e5a 100755 --- a/install/arm_application/lib/arm_application/realsense_color_pub +++ b/install/arm_application/lib/arm_application/realsense_color_pub @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','realsense_color_pub' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','realsense_color_pub' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'realsense_color_pub')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'realsense_color_pub')()) diff --git a/install/arm_application/lib/arm_application/realsense_view b/install/arm_application/lib/arm_application/realsense_view index 83717c6..48049cf 100755 --- a/install/arm_application/lib/arm_application/realsense_view +++ b/install/arm_application/lib/arm_application/realsense_view @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','realsense_view' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','realsense_view' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'realsense_view')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'realsense_view')()) diff --git a/install/arm_application/lib/arm_application/run_joint_trajectory b/install/arm_application/lib/arm_application/run_joint_trajectory index 442cb60..19b0f14 100755 --- a/install/arm_application/lib/arm_application/run_joint_trajectory +++ b/install/arm_application/lib/arm_application/run_joint_trajectory @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','run_joint_trajectory' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','run_joint_trajectory' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'run_joint_trajectory')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'run_joint_trajectory')()) diff --git a/install/arm_application/lib/arm_application/servo_node b/install/arm_application/lib/arm_application/servo_node index 55b7cc0..66bfece 100755 --- a/install/arm_application/lib/arm_application/servo_node +++ b/install/arm_application/lib/arm_application/servo_node @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','servo_node' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','servo_node' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'servo_node')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'servo_node')()) diff --git a/install/arm_application/lib/arm_application/stage2_locate b/install/arm_application/lib/arm_application/stage2_locate index 29e1b3f..56a842a 100755 --- a/install/arm_application/lib/arm_application/stage2_locate +++ b/install/arm_application/lib/arm_application/stage2_locate @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','stage2_locate' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','stage2_locate' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'stage2_locate')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'stage2_locate')()) diff --git a/install/arm_application/lib/arm_application/stage3_search b/install/arm_application/lib/arm_application/stage3_search index e08aa2d..f1a3a02 100755 --- a/install/arm_application/lib/arm_application/stage3_search +++ b/install/arm_application/lib/arm_application/stage3_search @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','stage3_search' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','stage3_search' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'stage3_search')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'stage3_search')()) diff --git a/install/arm_application/lib/arm_application/tactile b/install/arm_application/lib/arm_application/tactile index 3f600f9..952fc9f 100755 --- a/install/arm_application/lib/arm_application/tactile +++ b/install/arm_application/lib/arm_application/tactile @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','tactile' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','tactile' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'tactile')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'tactile')()) diff --git a/install/arm_application/lib/arm_application/tactile_cli b/install/arm_application/lib/arm_application/tactile_cli index 85e500c..570ae0f 100755 --- a/install/arm_application/lib/arm_application/tactile_cli +++ b/install/arm_application/lib/arm_application/tactile_cli @@ -1,10 +1,10 @@ -#!/usr/bin/env python3 -# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application','console_scripts','tactile_cli' +#!/home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 +# EASY-INSTALL-ENTRY-SCRIPT: 'arm-application==0.1.0','console_scripts','tactile_cli' import re import sys # for compatibility with easy_install; see #2198 -__requires__ = 'arm-application' +__requires__ = 'arm-application==0.1.0' try: from importlib.metadata import distribution @@ -30,4 +30,4 @@ globals().setdefault('load_entry_point', importlib_load_entry_point) if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(load_entry_point('arm-application', 'console_scripts', 'tactile_cli')()) + sys.exit(load_entry_point('arm-application==0.1.0', 'console_scripts', 'tactile_cli')()) diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/dh_pgea.py b/install/arm_application/lib/python3.11/site-packages/arm_application/dh_pgea.py index 3cfbbbf..117d46a 100644 --- a/install/arm_application/lib/python3.11/site-packages/arm_application/dh_pgea.py +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/dh_pgea.py @@ -213,6 +213,34 @@ WIRING_HINT = ( ) +def open_failure_text(port: str, exc: BaseException) -> str: + """串口打不开时把「被占用」和「没应答」分开。 + + 独占锁失败(errno 11)以前和真没上电写成同一句「需要 24 V DC」, + 界面上看起来像缺自动上电,实际是别的进程还握着这个口。 + """ + text = str(exc).lower() + errno = getattr(exc, "errno", None) + busy = errno in (11, 16) or any( + token in text + for token in ( + "temporarily unavailable", + "device or resource busy", + "could not exclusively lock", + "resource busy", + ) + ) + if busy: + return ( + f"无法独占打开 {port}:{exc}。" + "串口已被别的进程占用(夹爪驱动或状态读取)。" + ) + return ( + f"无法打开 {port}:{exc}。" + "需要 24 V DC 供电 + RS485 接线,且当前用户在 dialout 组内。" + ) + + class DhPgeaGripper: """面向 PGE / PGEA 的线程安全 Modbus RTU 客户端(官方寄存器映射)。""" @@ -257,10 +285,7 @@ class DhPgeaGripper: exclusive=True, ) except Exception as exc: - raise DhPgeaError( - f"无法打开 {self.port}:{exc}。" - "需要 24 V DC 供电 + RS485 接线,且当前用户在 dialout 组内。" - ) from exc + raise DhPgeaError(open_failure_text(self.port, exc)) from exc try: self._ser.dtr = True except Exception: @@ -562,6 +587,36 @@ def _scan_bus(port: str) -> int: return 1 +def ctrl_values( + cmd: str, + value: str = "", + force: int = 50, + speed: int = 50, +) -> Optional[tuple[float, float, float, float]]: + """节点已经占着串口时,改发 ``/gripper/ctrl`` 的四个数。 + + 顺序是初始化、位置千分比、力度、速度。``probe`` / ``status`` 只读,返回 None。 + """ + name = str(cmd or "").strip().lower() + if name in ("probe", "status"): + return None + if name == "init": + return (1.0, 0.0, float(force), float(speed)) + if name == "open": + pos = 1000 + elif name == "close": + pos = 0 + elif name in ("pos", "move"): + if str(value).strip() == "": + raise ValueError("pos 需要 0–1000") + pos = int(round(float(value))) + if pos < 0 or pos > 1000: + raise ValueError("位置只接受 0–1000") + else: + raise ValueError(f"节点在线时不支持 {name}") + return (0.0, float(pos), float(force), float(speed)) + + def _cli(argv: Optional[list[str]] = None) -> int: site = _site_defaults() ap = argparse.ArgumentParser( diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/handeye_calibrate.py b/install/arm_application/lib/python3.11/site-packages/arm_application/handeye_calibrate.py index a04c653..e7e827d 100644 --- a/install/arm_application/lib/python3.11/site-packages/arm_application/handeye_calibrate.py +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/handeye_calibrate.py @@ -137,9 +137,7 @@ class HandeyeCalibrate(TolerantNode): self.declare_parameter('pin_hold_sec', 0.45) # 每拍多长 self.declare_parameter('skip_unstable_poses', True) - from arm_application.site_io import ros2_root - - self.declare_parameter('output_dir', str(ros2_root() / 'handeye' / 'tmp')) + self.declare_parameter('output_dir', '/home/cc/ros2/handeye/tmp') self.declare_parameter('result_file', '') self.declare_parameter('cleanup_images', True) # 眼在手上:AX=XB 的 gripper 用 Studio 工具 TCP,再折成 cam→法兰写 T_4x4。 diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/handeye_teach.py b/install/arm_application/lib/python3.11/site-packages/arm_application/handeye_teach.py index c657a18..a78b0fc 100644 --- a/install/arm_application/lib/python3.11/site-packages/arm_application/handeye_teach.py +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/handeye_teach.py @@ -108,12 +108,10 @@ class RdkBridge: self.robot_sn = robot_sn self._last_q: Optional[List[float]] = None self._io = threading.Lock() - from arm_application.site_io import ros2_root - - py = os.environ.get('RDK_PYTHON', '').strip() or str( - Path.home() / 'rdk_py310' / 'bin' / 'python' - ) - bridge = ros2_root() / 'scripts' / 'rdk_drag_bridge.py' + py = os.environ.get('RDK_PYTHON', '/home/cc/rdk_py310/bin/python') + bridge = Path.home() / 'ros2' / 'scripts' / 'rdk_drag_bridge.py' + if not bridge.is_file(): + bridge = Path('/home/cc/ros2/scripts/rdk_drag_bridge.py') if not bridge.is_file(): raise FileNotFoundError(f'找不到 rdk_drag_bridge.py:{bridge}') @@ -222,13 +220,6 @@ class RdkBridge: return self._last_q return self._last_q - def move_joints(self, joints: List[float], speed: float = 0.15) -> dict: - """按关节阻抗把臂缓慢送到已记录的 7 个关节角。speed 单位 rad/s。""" - return self._ask( - {'cmd': 'move_joints', 'q': [float(v) for v in joints[:7]], 'speed': float(speed)}, - timeout=180.0, - ) - def close(self) -> None: try: self._ask({'cmd': 'quit'}, timeout=3.0) @@ -248,9 +239,7 @@ class RdkBridge: class HandeyeTeach(TolerantNode): def __init__(self) -> None: super().__init__('handeye_teach') - from arm_application.site_io import ros2_root - - self.declare_parameter('output_dir', str(ros2_root() / 'handeye' / 'tmp')) + self.declare_parameter('output_dir', '/home/cc/ros2/handeye/tmp') self.declare_parameter('width', 1280) self.declare_parameter('height', 720) self.declare_parameter('fps', 15) diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/operator_app.py b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_app.py index 2c38a27..0e93ba1 100644 --- a/install/arm_application/lib/python3.11/site-packages/arm_application/operator_app.py +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_app.py @@ -11,39 +11,69 @@ import sys import threading from pathlib import Path -from PySide6.QtCore import Qt, QSize, QThread, QTimer, Signal -from PySide6.QtGui import QColor, QFont, QPainter, QPen, QPixmap +from PySide6.QtCore import QPoint, QRect, QRectF, QSize, Qt, QThread, QTimer, Signal +from PySide6.QtGui import ( + QBrush, + QColor, + QFont, + QFontMetrics, + QIcon, + QImage, + QMatrix3x3, + QPainter, + QPainterPath, + QPen, + QPixmap, + QPolygon, + QSurfaceFormat, + QVector3D, +) +from PySide6.QtOpenGL import QOpenGLBuffer, QOpenGLShader, QOpenGLShaderProgram, QOpenGLVertexArrayObject +from PySide6.QtOpenGLWidgets import QOpenGLWidget from PySide6.QtWidgets import ( QApplication, + QCheckBox, QDoubleSpinBox, + QSpinBox, QFrame, QGridLayout, QHBoxLayout, QLabel, + QLayout, QLineEdit, QListWidget, QListWidgetItem, QMainWindow, - QMessageBox, QPlainTextEdit, QPushButton, QScrollArea, QSizePolicy, QSlider, + QStyle, + QStyleOptionButton, QStackedWidget, QVBoxLayout, QWidget, ) +from arm_application.site_io import arm_short_id, listed_arms, load_site_dict from arm_application.operator_console import ( _poll_tactile, catalog, + command_blocked_message, + command_lane, run_action, + stop_started, run_gripper_tune, status, ) +from arm_application import operator_arm_mesh as arm_mesh +from arm_application import operator_gripper_mesh as gripper_mesh +from arm_application import operator_arm_state as arm_state +from arm_application import operator_gripper_state as grip_state from arm_application import operator_teach as teach from arm_application import operator_camera_view as camera_view +from arm_application.operator_handeye_board import BoardWatch from arm_application import operator_tactile_view as tactile_view _LOGO = Path(__file__).resolve().parent / "operator_console_logo.png" @@ -83,14 +113,21 @@ class ValueTune(QWidget): decimals: int = 0, slider_low: float | None = None, slider_high: float | None = None, + compact: bool = False, ) -> None: super().__init__() self._scale = 10 ** decimals self._guard = False row = QHBoxLayout(self) row.setContentsMargins(0, 0, 0, 0) + row.setSpacing(4) label = QLabel(title) - label.setFixedWidth(88) + label.setFixedWidth(36 if compact else 88) + label.setAlignment( + Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter + if compact + else Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter + ) self.slider = QSlider(Qt.Orientation.Horizontal) slo = low if slider_low is None else slider_low shi = high if slider_high is None else slider_high @@ -105,8 +142,10 @@ class ValueTune(QWidget): self.spin.valueChanged.connect(self._from_spin) row.addWidget(label) row.addWidget(self.slider, 1) - self.spin.setFixedWidth(120) + self.spin.setFixedWidth(72 if compact else 120) row.addWidget(self.spin) + if compact: + self.setMaximumHeight(28) def _clamp_slider(self, value: float) -> int: raw = int(round(value * self._scale)) @@ -133,62 +172,212 @@ class ValueTune(QWidget): self.spin.setValue(value) -class FrameLabel(QLabel): - """预览框。尺寸不跟图像走,否则每贴一帧标签就更大,画面会慢慢放大。""" +class _PictureRow(QWidget): + """并排画面的高度跟格子宽度走,按 16:9 把画面铺满。 - def __init__(self, text: str) -> None: + 彩色是 848×480 和 1280×720,都接近 16:9。格子若被纵向拉高, + 画面上下会留一条黑边,那块空白本该给触觉。 + """ + + def __init__(self, columns: int) -> None: + super().__init__() + self._columns = max(1, columns) + self._lock = False + policy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) + policy.setHeightForWidth(True) + self.setSizePolicy(policy) + self.setMinimumHeight(96) + + def hasHeightForWidth(self) -> bool: # noqa: N802 + return True + + def heightForWidth(self, width: int) -> int: # noqa: N802 + gap = 6 * max(0, self._columns - 1) + cell = max(1, (max(1, width) - gap) // self._columns) + return max(96, int(round(cell * 9 / 16))) + + def resizeEvent(self, event) -> None: # noqa: N802 + super().resizeEvent(event) + if self._lock or self.width() < 40: + return + want = self.heightForWidth(self.width()) + if abs(self.minimumHeight() - want) <= 1 and abs(self.maximumHeight() - want) <= 1: + return + self._lock = True + self.setMinimumHeight(want) + self.setMaximumHeight(want) + self._lock = False + + +class FrameLabel(QLabel): + """预览框。尺寸不跟图像走,否则每贴一帧标签就更大,画面会慢慢放大。 + + 画面自己画。不调用 ``setPixmap``:那一下会 ``updateGeometry``, + 六路画面每帧都把整页布局重算一遍,窗口就卡。 + """ + + def __init__(self, text: str, *, compact: bool = False) -> None: super().__init__(text) + self._compact = compact self.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.setMinimumSize(320, 200) + self.setMinimumSize(96, 64) if compact else self.setMinimumSize(320, 200) self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) self.setObjectName("shot") self._source = QPixmap() + self._scaled = QPixmap() self._token = 0 self._drawn = QSize() self._busy = False + self._badge_title = "" + self._badge_detail = "" def sizeHint(self) -> QSize: - return QSize(480, 270) + return QSize(200, 112) if self._compact else QSize(480, 270) def minimumSizeHint(self) -> QSize: - return QSize(320, 200) + return QSize(96, 64) if self._compact else QSize(320, 200) + + def setText(self, text: str) -> None: # noqa: N802 + self._source = QPixmap() + self._scaled = QPixmap() + self._token = 0 + self._drawn = QSize() + super().setText(text) + + def set_badge(self, title: str, detail: str = "") -> None: + """画在画面底部的名字和分辨率、帧率。文字变了才重画。""" + title = str(title or "") + detail = str(detail or "") + if title == self._badge_title and detail == self._badge_detail: + return + self._badge_title = title + self._badge_detail = detail + self.update() def show_jpeg(self, blob: bytes) -> None: token = id(blob) - if token != self._token: - pix = QPixmap() - if not pix.loadFromData(blob, "JPG"): - return - self._source = pix - self._token = token - self._drawn = QSize() + if token == self._token and not self._source.isNull(): + self._draw(False) + return + pix = QPixmap() + if not pix.loadFromData(blob, "JPG"): + return + self._source = pix + self._token = token + self._drawn = QSize() self._draw(False) - def resizeEvent(self, event) -> None: + def show_rgb(self, blob: bytes, width: int, height: int) -> None: + """贴一块已经缩好的 RGB。不再在界面线程里解 JPEG。""" + token = id(blob) + if token == self._token and not self._source.isNull(): + self._draw(False) + return + if width < 2 or height < 2 or len(blob) < width * height * 3: + return + image = QImage(blob, width, height, width * 3, QImage.Format.Format_RGB888).copy() + if image.isNull(): + return + self._source = QPixmap.fromImage(image) + self._token = token + self._drawn = QSize() + self._draw(False) + + def resizeEvent(self, event) -> None: # noqa: N802 super().resizeEvent(event) self._drawn = QSize() self._draw(True) - def _draw(self, smooth: bool) -> None: + def paintEvent(self, event) -> None: # noqa: N802 + if self._scaled.isNull(): + super().paintEvent(event) + if self._badge_title or self._badge_detail: + painter = QPainter(self) + self._clip_round(painter) + self._paint_badge(painter) + painter.end() + return + painter = QPainter(self) + self._clip_round(painter) + painter.fillRect(self.rect(), QColor("#090c11")) + pix = self._scaled + frame = self._picture_rect() + painter.drawPixmap( + frame.x() + (frame.width() - pix.width()) // 2, + frame.y() + (frame.height() - pix.height()) // 2, + pix, + ) + self._paint_badge(painter) + painter.end() + + def _clip_round(self, painter: QPainter) -> None: + painter.setRenderHint(QPainter.RenderHint.Antialiasing) + path = QPainterPath() + path.addRoundedRect(QRectF(self.rect()), 10, 10) + painter.setClipPath(path) + + def _paint_badge(self, painter: QPainter) -> None: + if not self._badge_title and not self._badge_detail: + return + bar = self._caption_h() + top = max(0, self.height() - bar) + painter.fillRect(0, top, self.width(), bar, QColor("#0c1016")) + font = QFont(painter.font()) + font.setPixelSize(12) + painter.setFont(font) + painter.setPen(QColor("#f4f7fb")) + detail = self._badge_detail + if detail: + title_w = max(0, self.width() // 2 - 14) + detail_w = max(0, self.width() // 2 - 14) + else: + title_w = max(0, self.width() - 16) + detail_w = 0 + title = QFontMetrics(font).elidedText(self._badge_title, Qt.TextElideMode.ElideRight, title_w) + painter.drawText( + 8, top, title_w, bar, + int(Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft), + title, + ) + if detail_w: + detail_font = QFont("DejaVu Sans Mono") + detail_font.setPixelSize(11) + painter.setFont(detail_font) + painter.setPen(QColor("#d5deea")) + shown = QFontMetrics(detail_font).elidedText(detail, Qt.TextElideMode.ElideLeft, detail_w) + painter.drawText( + self.width() // 2, top, detail_w, bar, + int(Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignRight), + shown, + ) + + def _caption_h(self) -> int: + return 24 if (self._badge_title or self._badge_detail) else 0 + + def _picture_rect(self) -> QRect: + """画面停在标题条上面。标题条是实底,图像不再从字下面透出来。""" + bar = self._caption_h() + return QRect(0, 0, max(1, self.width()), max(1, self.height() - bar)) + + def _draw(self, _smooth: bool) -> None: if self._busy or self._source.isNull(): return - size = self.contentsRect().size() + size = self._picture_rect().size() if size.width() < 8 or size.height() < 8 or size == self._drawn: return self._busy = True try: - mode = ( - Qt.TransformationMode.SmoothTransformation - if smooth - else Qt.TransformationMode.FastTransformation - ) - self.setPixmap(self._source.scaled( + # 缩放用平滑。快缩放会把原图重新弄糊,帧率并不靠它省出来。 + mode = Qt.TransformationMode.SmoothTransformation + self._scaled = self._source.scaled( size, Qt.AspectRatioMode.KeepAspectRatio, mode, - )) - self.setText("") + ) self._drawn = QSize(size) + if self.text(): + super().setText("") + self.update() finally: self._busy = False @@ -273,21 +462,753 @@ def _title(text: str) -> QLabel: return label +def _section(text: str) -> QWidget: + """一小节的标题。左侧一条色标,用来把相机、触觉、操作分开。""" + row = QWidget() + row.setFixedHeight(36) + lay = QHBoxLayout(row) + lay.setContentsMargins(0, 0, 0, 0) + lay.setSpacing(6) + lay.setAlignment(Qt.AlignmentFlag.AlignVCenter) + mark = QFrame() + mark.setObjectName("sectionMark") + mark.setFixedSize(3, 12) + lay.addWidget(mark) + label = QLabel(text) + label.setObjectName("section") + lay.addWidget(label) + lay.addStretch(1) + return row + + +def _segment_box() -> QWidget: + """一对开关。外框固定高度,里面两颗按钮不再各自被拉成一条。""" + box = QWidget() + box.setObjectName("switch") + box.setFixedHeight(36) + box.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) + lay = QHBoxLayout(box) + lay.setContentsMargins(3, 3, 3, 3) + lay.setSpacing(2) + return box + + +def _segment_button(button: QPushButton) -> None: + button.setObjectName("seg") + button.setFixedHeight(30) + button.setMinimumWidth(52) + button.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) + button.setCursor(Qt.CursorShape.PointingHandCursor) + + def _btn(text: str, danger: bool = False) -> QPushButton: button = QPushButton(text) button.setObjectName("danger" if danger else "action") + button.setCursor(Qt.CursorShape.PointingHandCursor) return button +def _fit_text(label: QLabel) -> QLabel: + """长状态文字不能把窗口撑得比屏幕宽,否则标题栏拖不动。""" + label.setMinimumWidth(0) + label.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Preferred) + return label + + +def _action_icon(name: str) -> QPixmap: + """操作键上的图案。自己画,不靠系统图标主题,也比一个符号字清楚。""" + pix = QPixmap(64, 64) + pix.setDevicePixelRatio(2.0) + pix.fill(Qt.GlobalColor.transparent) + painter = QPainter(pix) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) + ink = QColor("#e8eef6") + accent = QColor("#8eb7ff") + painter.setPen(QPen(ink, 1.8, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin)) + painter.setBrush(Qt.BrushStyle.NoBrush) + if name in ("real_up",): + painter.drawEllipse(QRectF(7, 7, 18, 18)) + painter.setPen(QPen(accent, 2.2, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap)) + painter.drawLine(16, 8, 16, 16) + elif name in ("camera_d405", "camera_d435"): + painter.setBrush(QColor(94, 168, 255, 56)) + painter.drawRoundedRect(QRectF(4, 10, 24, 14), 3, 3) + painter.setBrush(QColor("#10141b")) + painter.drawEllipse(QRectF(11, 12, 10, 10)) + painter.setBrush(accent) + painter.drawEllipse(QRectF(14.2, 15.2, 3.6, 3.6)) + painter.setBrush(Qt.BrushStyle.NoBrush) + painter.setPen(QPen(ink, 1.6, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin)) + if name == "camera_d435": + painter.drawLine(10, 24, 8, 28) + painter.drawLine(22, 24, 24, 28) + painter.drawLine(8, 28, 24, 28) + else: + painter.drawRoundedRect(QRectF(12, 6, 8, 4), 1.2, 1.2) + elif name == "tactile_up": + painter.setBrush(QColor(94, 168, 255, 56)) + painter.drawRoundedRect(QRectF(10, 4, 12, 18), 6, 6) + painter.setBrush(accent) + painter.setPen(Qt.PenStyle.NoPen) + for row in (9, 15): + for col in (13, 18): + painter.drawEllipse(QRectF(col, row, 2.4, 2.4)) + painter.setPen(QPen(ink, 1.6, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap)) + painter.setBrush(Qt.BrushStyle.NoBrush) + painter.drawLine(12, 22, 12, 28) + painter.drawLine(20, 22, 20, 28) + painter.drawLine(10, 28, 22, 28) + elif name in ("gripper_up", "gripper_open", "gripper_close", "gripper_probe", "gripper_rotate"): + painter.drawRoundedRect(QRectF(6, 13, 20, 6), 1.5, 1.5) + left = 8 if name != "gripper_open" else 5 + right = 20 if name != "gripper_open" else 23 + if name == "gripper_close": + left, right = 12, 16 + painter.setBrush(QColor(94, 168, 255, 80)) + painter.drawRoundedRect(QRectF(left, 6, 4, 16), 1.2, 1.2) + painter.drawRoundedRect(QRectF(right, 6, 4, 16), 1.2, 1.2) + painter.setBrush(Qt.BrushStyle.NoBrush) + if name == "gripper_probe": + painter.setPen(QPen(accent, 1.6, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap)) + painter.drawArc(QRectF(18, 18, 10, 10), 40 * 16, 220 * 16) + elif name == "gripper_rotate": + painter.setPen(QPen(accent, 1.8, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap)) + painter.drawArc(QRectF(8, 20, 16, 10), 20 * 16, 200 * 16) + painter.drawLine(22, 24, 25, 22) + painter.drawLine(22, 24, 24, 27) + elif name == "pipeline": + painter.setBrush(accent) + painter.setPen(Qt.PenStyle.NoPen) + path = QPainterPath() + path.moveTo(11, 7) + path.lineTo(24, 16) + path.lineTo(11, 25) + path.closeSubpath() + painter.drawPath(path) + elif name == "stage_track": + painter.drawEllipse(QRectF(6, 6, 20, 20)) + painter.drawLine(16, 4, 16, 10) + painter.drawLine(16, 22, 16, 28) + painter.drawLine(4, 16, 10, 16) + painter.drawLine(22, 16, 28, 16) + painter.setBrush(accent) + painter.setPen(Qt.PenStyle.NoPen) + painter.drawEllipse(QRectF(14, 14, 4, 4)) + elif name == "stage_locate": + painter.drawEllipse(QRectF(5, 5, 22, 22)) + painter.drawEllipse(QRectF(10, 10, 12, 12)) + painter.setBrush(accent) + painter.setPen(Qt.PenStyle.NoPen) + painter.drawEllipse(QRectF(14, 14, 4, 4)) + elif name == "stage_screw": + painter.setPen(QPen(ink, 1.8, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap)) + painter.drawArc(QRectF(6, 6, 20, 20), 40 * 16, 280 * 16) + painter.setPen(QPen(accent, 1.8, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap)) + painter.drawLine(23, 8, 26, 6) + painter.drawLine(23, 8, 20, 5) + painter.setPen(QPen(ink, 1.6, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap)) + painter.drawLine(16, 12, 16, 20) + elif name in ("reset_home", "nav_main"): + path = QPainterPath() + path.moveTo(16, 5) + path.lineTo(27, 14) + path.lineTo(24, 14) + path.lineTo(24, 26) + path.lineTo(8, 26) + path.lineTo(8, 14) + path.lineTo(5, 14) + path.closeSubpath() + painter.setBrush(QColor(94, 168, 255, 56)) + painter.drawPath(path) + painter.setBrush(accent) + painter.drawRect(QRectF(13, 18, 6, 8)) + elif name == "nav_eye": + painter.drawEllipse(QRectF(4, 10, 24, 12)) + painter.setBrush(accent) + painter.drawEllipse(QRectF(12, 12, 8, 8)) + painter.setBrush(QColor("#10141b")) + painter.drawEllipse(QRectF(14.5, 14.5, 3, 3)) + elif name == "nav_pen": + painter.drawLine(8, 24, 20, 8) + painter.setPen(QPen(accent, 1.8, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap)) + painter.drawLine(20, 8, 24, 6) + painter.setPen(QPen(ink, 1.8, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap)) + painter.drawLine(6, 26, 12, 22) + elif name == "gauge_dry": + painter.drawRoundedRect(QRectF(6, 6, 20, 20), 3, 3) + painter.setBrush(accent) + painter.setPen(Qt.PenStyle.NoPen) + path = QPainterPath() + path.moveTo(13, 11) + path.lineTo(21, 16) + path.lineTo(13, 21) + path.closeSubpath() + painter.drawPath(path) + elif name == "gauge_run": + painter.setBrush(accent) + painter.setPen(Qt.PenStyle.NoPen) + painter.drawRoundedRect(QRectF(6, 6, 20, 20), 4, 4) + painter.setBrush(QColor("#10141b")) + path = QPainterPath() + path.moveTo(13, 11) + path.lineTo(21, 16) + path.lineTo(13, 21) + path.closeSubpath() + painter.drawPath(path) + else: + painter.drawEllipse(QRectF(8, 8, 16, 16)) + painter.end() + return pix + + +class CommandButton(QPushButton): + """操作键。背景自己画成圆角矩形,图标和文字都落在这块矩形里面。 + + ``bar`` 用在相机、触觉那一行:图标在左、字在右,高度跟旁边的开关一样。 + 操作区用上下排,避免和系统按钮样式叠出两层边、再把邻居挤变形。 + """ + + def __init__(self, text: str, kind: str, mark: str, *, bar: bool = False) -> None: + super().__init__(text) + self._bar = bar + self._pix = _action_icon(mark) + self.setIcon(QIcon(self._pix)) + self.setObjectName(kind) + self.setCursor(Qt.CursorShape.PointingHandCursor) + self.setMinimumWidth(0) + self.setFixedHeight(36 if bar else 60) + self.setSizePolicy( + QSizePolicy.Policy.Expanding if not bar else QSizePolicy.Policy.Preferred, + QSizePolicy.Policy.Fixed, + ) + + def sizeHint(self) -> QSize: + if self._bar: + width = QFontMetrics(self.font()).horizontalAdvance(self.text()) + 46 + return QSize(max(96, width), 36) + return QSize(108, 60) + + def minimumSizeHint(self) -> QSize: + return QSize(88 if self._bar else 96, self.height()) + + def paintEvent(self, _event) -> None: # noqa: N802 + painter = QPainter(self) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) + option = QStyleOptionButton() + self.initStyleOption(option) + rect = QRectF(self.rect()).adjusted(0.5, 0.5, -0.5, -0.5) + path = QPainterPath() + path.addRoundedRect(rect, 8, 8) + painter.setClipPath(path) + hover = bool(option.state & QStyle.StateFlag.State_MouseOver) + down = bool(option.state & QStyle.StateFlag.State_Sunken) + kind = self.objectName() + if kind == "primary": + fill = QColor("#2460ad" if hover else "#1d4f91") + border = QColor("#6ea2ea") + ink = QColor("#f4f7fb") + elif kind == "danger": + fill = QColor("#3a1c20" if hover else "#241416") + border = QColor("#c46a6a" if hover else "#8a4a4a") + ink = QColor("#f6d5d5") + else: + fill = QColor("#263041" if hover else "#1c2430") + border = QColor("#5c6b82" if hover else "#3d4b60") + ink = QColor("#e7ebf2") + if down: + fill = fill.darker(115) + painter.fillPath(path, fill) + painter.setPen(QPen(border, 1)) + painter.setBrush(Qt.BrushStyle.NoBrush) + painter.drawPath(path) + font = QFont(self.font()) + if self._bar: + icon_box = QRect(8, (self.height() - 18) // 2, 18, 18) + painter.drawPixmap(icon_box, self._pix) + font.setPixelSize(13) + text_rect = QRect(32, 0, max(0, self.width() - 40), self.height()) + align = Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft + else: + icon_box = QRect((self.width() - 22) // 2, 6, 22, 22) + painter.drawPixmap(icon_box, self._pix) + font.setPixelSize(12) + text_rect = QRect(6, 32, max(0, self.width() - 12), 22) + align = Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter + painter.setFont(font) + painter.setPen(ink) + shown = QFontMetrics(font).elidedText( + self.text(), Qt.TextElideMode.ElideRight, text_rect.width(), + ) + painter.drawText(text_rect, int(align), shown) + painter.end() + + +# Qt 的 QOpenGLFunctions 只带函数,不带这些枚举。数值是 OpenGL 规范里的固定值。 +_GL_TRIANGLES = 0x0004 +_GL_DEPTH_BUFFER_BIT = 0x00000100 +_GL_CULL_FACE = 0x0B44 +_GL_DEPTH_TEST = 0x0B71 +_GL_FLOAT = 0x1406 +_GL_COLOR_BUFFER_BIT = 0x00004000 +_AXIS_X = QColor("#e24b4b") +_AXIS_Y = QColor("#3dbe6a") +_AXIS_Z = QColor("#4c86d4") +# 打开界面时的观察角。双击只回到这里,不改关节。 +_VIEW_YAW = 0.9 +_VIEW_PITCH = -0.35 + + +_MESH_VERT = """ +#version 330 core +layout(location = 0) in vec3 a_pos; +layout(location = 1) in vec3 a_color; +uniform mat3 u_rot; +uniform vec3 u_center; +uniform float u_sx; +uniform float u_sy; +uniform float u_radius; +out vec3 v_color; +void main() { + vec3 p = u_rot * (a_pos - u_center); + gl_Position = vec4(p.x * u_sx, p.z * u_sy, p.y / (u_radius * 2.0), 1.0); + v_color = a_color; +} +""" + +_MESH_FRAG = """ +#version 330 core +in vec3 v_color; +uniform float u_tint; +out vec4 frag_color; +void main() { + frag_color = vec4(v_color * u_tint, 1.0); +} +""" + + +class MeshView(QOpenGLWidget): + """三角网格。左键拖拽绕模型转视角,三角面交给显卡画。""" + + def __init__(self, min_w: int, min_h: int) -> None: + super().__init__() + fmt = QSurfaceFormat() + fmt.setRenderableType(QSurfaceFormat.RenderableType.OpenGL) + fmt.setVersion(3, 3) + fmt.setProfile(QSurfaceFormat.OpenGLContextProfile.CoreProfile) + fmt.setSamples(4) + fmt.setDepthBufferSize(24) + self.setFormat(fmt) + self._tris: list = [] + self._note = "" + self._live = False + self._yaw = _VIEW_YAW + self._pitch = _VIEW_PITCH + self._press: QPoint | None = None + self._cache_key: tuple | None = None + self._batch: list[tuple[QColor, QPolygon]] = [] + self._gen = 0 + self._sig: tuple | None = None + self._gl_blob = b"" + self._gl_count = 0 + self._gl_center = (0.0, 0.0, 0.0) + self._gl_radius = 1.0 + self._gl_dirty = False + self._gl_program: QOpenGLShaderProgram | None = None + self._gl_buffer: QOpenGLBuffer | None = None + self._gl_vao: QOpenGLVertexArrayObject | None = None + self._frames: list = [] + self._thin_caption = False + self.setMinimumSize(min_w, min_h) + self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + self.setCursor(Qt.CursorShape.OpenHandCursor) + + def set_mesh(self, triangles: list, note: str, live: bool) -> None: + live = bool(live) + sig = (len(triangles), note, live) + if triangles: + sig = sig + (triangles[0][0], triangles[-1][0]) + if sig == self._sig: + return + self._sig = sig + self._tris = triangles + self._note = note + self._live = live + self._gen += 1 + self._gl_count = 0 + self.update() + + def set_gl_mesh( + self, + blob: bytes, + count: int, + center: tuple[float, float, float], + radius: float, + note: str, + live: bool, + ) -> None: + """大网格走显卡顶点缓冲。拖视角不再在 CPU 上重算每个三角。""" + self._gl_blob = blob + self._gl_count = count + self._gl_center = center + self._gl_radius = radius + self._gl_dirty = True + self._tris = [] + self._batch = [] + self._note = note + self._live = bool(live) + self._sig = None + self.update() + + def initializeGL(self) -> None: # noqa: N802 + program = QOpenGLShaderProgram(self) + ok = program.addShaderFromSourceCode(QOpenGLShader.ShaderTypeBit.Vertex, _MESH_VERT) + ok = program.addShaderFromSourceCode(QOpenGLShader.ShaderTypeBit.Fragment, _MESH_FRAG) and ok + program.bindAttributeLocation("a_pos", 0) + program.bindAttributeLocation("a_color", 1) + ok = program.link() and ok + if not ok: + self._note = "显卡着色器没链上,网格画不出来。" + self._gl_program = None + return + self._gl_program = program + self._gl_loc = { + name: program.uniformLocation(name.encode()) + for name in ("u_rot", "u_center", "u_sx", "u_sy", "u_radius", "u_tint") + } + buffer = QOpenGLBuffer(QOpenGLBuffer.Type.VertexBuffer) + buffer.create() + self._gl_buffer = buffer + vao = QOpenGLVertexArrayObject(self) + vao.create() + self._gl_vao = vao + + def mousePressEvent(self, event) -> None: # noqa: N802 + if event.button() == Qt.MouseButton.LeftButton: + self._press = event.position().toPoint() + self.setCursor(Qt.CursorShape.ClosedHandCursor) + event.accept() + return + super().mousePressEvent(event) + + def mouseMoveEvent(self, event) -> None: # noqa: N802 + if self._press is None: + super().mouseMoveEvent(event) + return + now = event.position().toPoint() + self._yaw += (now.x() - self._press.x()) * 0.012 + self._pitch = max(-1.35, min(1.35, self._pitch + (now.y() - self._press.y()) * 0.010)) + self._press = now + self.update() + event.accept() + + def mouseReleaseEvent(self, event) -> None: # noqa: N802 + self._press = None + self.setCursor(Qt.CursorShape.OpenHandCursor) + self.update() + event.accept() + + def paintGL(self) -> None: # noqa: N802 + if self._gl_count and self._gl_program is not None and self._gl_buffer is not None: + self._paint_gpu() + return + # 夹爪面少,仍用画笔。机械臂不走这里。 + key = (self.width(), self.height(), round(self._yaw, 3), round(self._pitch, 3), self._gen) + if key != self._cache_key: + self._batch = self._project() + self._cache_key = key + painter = QPainter(self) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) + painter.fillRect(self.rect(), QColor("#0c0e12")) + if not self._batch and not self._tris: + painter.setPen(QColor("#8b929d")) + painter.drawText(self.rect(), int(Qt.AlignmentFlag.AlignCenter), self._note or "没有网格") + painter.end() + return + painter.setPen(Qt.PenStyle.NoPen) + for color, poly in self._batch: + painter.setBrush(QBrush(color)) + painter.drawPolygon(poly) + self._paint_caption(painter) + painter.end() + + def _caption(self) -> str: + return "左键拖拽改视角。 " + self._note + + def _paint_gpu(self) -> None: + """一次画出全部三角。视角只改 uniform,不把面片搬回 CPU。""" + painter = QPainter(self) + painter.beginNativePainting() + functions = self.context().functions() + functions.glClearColor(12 / 255, 14 / 255, 18 / 255, 1.0) + functions.glClear(_GL_COLOR_BUFFER_BIT | _GL_DEPTH_BUFFER_BIT) + functions.glEnable(_GL_DEPTH_TEST) + functions.glDisable(_GL_CULL_FACE) + program = self._gl_program + buffer = self._gl_buffer + vao = self._gl_vao + assert program is not None and buffer is not None and vao is not None + vao.bind() + program.bind() + buffer.bind() + if self._gl_dirty: + buffer.allocate(self._gl_blob, len(self._gl_blob)) + self._gl_dirty = False + program.setAttributeBuffer(0, _GL_FLOAT, 0, 3, 24) + program.setAttributeBuffer(1, _GL_FLOAT, 12, 3, 24) + program.enableAttributeArray(0) + program.enableAttributeArray(1) + rot = arm_mesh.view_rotation(self._yaw, self._pitch) + # QMatrix3x3 按行读入。GLSL 再按列使用,和 view_rotation 的行向量乘法一致。 + flat = [rot[row][col] for row in range(3) for col in range(3)] + loc = self._gl_loc + program.setUniformValue(loc["u_rot"], QMatrix3x3(flat)) + cx, cy, cz = self._gl_center + program.setUniformValue(loc["u_center"], QVector3D(cx, cy, cz)) + width = max(self.width(), 1) + height = max(self.height(), 1) + sx, sy = arm_mesh.view_scale(self._gl_radius, width, height) + program.setUniformValue1f(loc["u_sx"], sx) + program.setUniformValue1f(loc["u_sy"], sy) + program.setUniformValue1f(loc["u_radius"], self._gl_radius) + program.setUniformValue1f(loc["u_tint"], 1.0 if self._live else 0.55) + functions.glDrawArrays(_GL_TRIANGLES, 0, self._gl_count) + buffer.release() + program.release() + vao.release() + painter.endNativePainting() + self._paint_ground(painter) + self._paint_frames(painter) + self._paint_caption(painter) + painter.end() + + def _caption_band(self) -> int: + if self._thin_caption: + return 16 + return 14 if self.height() < 160 else 60 + + def _paint_caption(self, painter: QPainter) -> None: + band = self._caption_band() + if band <= 16: + rect = QRect(6, self.height() - 16, self.width() - 12, 14) + else: + rect = QRect(12, self.height() - 72, self.width() - 24, 60) + painter.setPen(QColor("#8b929d")) + painter.drawText( + rect, + int(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBottom | Qt.TextFlag.TextWordWrap), + self._caption(), + ) + + def _paint_ground(self, painter: QPainter) -> None: + """地平面格子。只在机械臂仿真上画,线数固定,不跟模型一起重算。""" + if not self._frames or self.width() < 8 or self.height() < 8: + return + pen = QPen(QColor("#3a4150")) + pen.setWidth(1) + painter.setPen(pen) + width = self.width() + height = self.height() + for start, end in arm_mesh.ground_grid(): + x0, y0 = arm_mesh.screen_point( + start, self._yaw, self._pitch, self._gl_center, self._gl_radius, width, height, + ) + x1, y1 = arm_mesh.screen_point( + end, self._yaw, self._pitch, self._gl_center, self._gl_radius, width, height, + ) + painter.drawLine(int(x0), int(y0), int(x1), int(y1)) + + def _paint_frames(self, painter: QPainter) -> None: + """坐标系画在地面格子上面。轴长 0.15 m,和模型同一套米制。""" + if not self._frames or self.width() < 8 or self.height() < 8: + return + length = 0.15 + width = self.width() + height = self.height() + for frame in self._frames: + origin = frame["pos"] + rot = frame["rot"] + ox, oy = arm_mesh.screen_point( + origin, self._yaw, self._pitch, self._gl_center, self._gl_radius, width, height, + ) + for axis, color in enumerate((_AXIS_X, _AXIS_Y, _AXIS_Z)): + tip = arm_mesh.axis_tip(origin, rot, axis, length) + tx, ty = arm_mesh.screen_point( + tip, self._yaw, self._pitch, self._gl_center, self._gl_radius, width, height, + ) + pen = QPen(color) + pen.setWidth(3) + painter.setPen(pen) + painter.drawLine(int(ox), int(oy), int(tx), int(ty)) + painter.setPen(QColor("#e7ebf0")) + painter.drawText(int(ox) + 8, int(oy) - 8, frame["name"]) + + def _project(self) -> list[tuple[QColor, QPolygon]]: + triangles = self._tris + if not triangles or self.width() < 8 or self.height() < 8: + return [] + rot = arm_mesh.view_rotation(self._yaw, self._pitch) + projected = [] + for item in triangles: + a, b, c = item[0], item[1], item[2] + shade = float(item[3]) + rgb = item[4] if len(item) > 4 else (0.75, 0.76, 0.78) + pts = [arm_mesh._apply(rot, p) for p in (a, b, c)] + screen = [(p[0], -p[2]) for p in pts] + depth = pts[0][1] + pts[1][1] + pts[2][1] + projected.append((depth, shade, rgb, screen)) + projected.sort(key=lambda row: row[0], reverse=True) + xs = [p[0] for _d, _s, _c, pts in projected for p in pts] + ys = [p[1] for _d, _s, _c, pts in projected for p in pts] + span = max(max(xs) - min(xs), max(ys) - min(ys), 1e-4) + band = self._caption_band() + scale = min(self.width() - 20, max(8, self.height() - band - 8)) / span + cx = (min(xs) + max(xs)) / 2 + cy = (min(ys) + max(ys)) / 2 + origin_y = (self.height() - band) / 2 + tint = 1.0 if self._live else 0.55 + batch = [] + for _depth, shade, rgb, pts in projected: + light = (0.55 + 0.45 * max(0.0, min(1.0, shade))) * tint + color = QColor( + int(max(0, min(255, rgb[0] * 255 * light))), + int(max(0, min(255, rgb[1] * 255 * light))), + int(max(0, min(255, rgb[2] * 255 * light))), + ) + poly = QPolygon([ + QPoint( + int(self.width() / 2 + (point[0] - cx) * scale), + int(origin_y + (point[1] - cy) * scale), + ) + for point in pts + ]) + batch.append((color, poly)) + return batch + + +class ArmSketch(MeshView): + """用非夕 Rizon4 的外壳网格画当前姿态。没有关节角时摆在零位,并标明。 + + 双击把观察角放回打开界面时的角度。关节仍是实时的,真机不动。 + """ + + def __init__(self) -> None: + super().__init__(240, 180) + self._workpiece = None + self._pose_key = None + + def _caption(self) -> str: + return "左键拖拽改视角。双击把视角复位。 " + self._note + + def mouseDoubleClickEvent(self, event) -> None: # noqa: N802 + if event.button() != Qt.MouseButton.LeftButton: + return + self._yaw = _VIEW_YAW + self._pitch = _VIEW_PITCH + self._press = None + self.update() + event.accept() + + def set_pose(self, q: list | None, live: bool, workpiece: tuple | None = None) -> None: + self._workpiece = workpiece + if isinstance(q, list) and len(q) >= 7: + joints = [round(float(v), 3) for v in q[:7]] + draw_live = bool(live) + else: + joints = [0.0] * 7 + draw_live = False + try: + pose_key = (tuple(joints), draw_live, _workpiece_key(workpiece)) + if pose_key == self._pose_key: + return + self._pose_key = pose_key + blob, count, center, radius = arm_mesh.posed_gl(joints) + self._frames = arm_state.display_frames(joints, workpiece) + except Exception as exc: # noqa: BLE001 —— 网格缺失时把原因画出来,不退回另一套模型 + self._pose_key = None + self._frames = [] + self.set_mesh([], f"厂家网格读失败:{exc}", False) + return + names = "、".join(item["name"] for item in self._frames) + if not draw_live: + note = f"非夕 Rizon4 外壳,零位。还没有关节角,这不是当前姿态。坐标系:{names}。" + else: + note = f"非夕 Rizon4 外壳,按实时关节角摆放。白壳和灰蓝关节是厂家材质。坐标系:{names}。" + self.set_gl_mesh(blob, count, center, radius, note, draw_live) + + +def _workpiece_key(workpiece: tuple | None) -> tuple | None: + if workpiece is None: + return None + rot, pos = workpiece + return ( + tuple(round(float(v), 5) for row in rot for v in row), + tuple(round(float(v), 5) for v in pos), + ) + + +def _workpieces_by_arm(site: dict) -> dict[str, tuple | None]: + """每台真机臂的工件系。没配完整坐标系就是 None,法兰继续画在法兰上。""" + found: dict[str, tuple | None] = {} + robots = ((site or {}).get("robot") or {}).get("robots") or [] + for robot in robots: + if not isinstance(robot, dict): + continue + sn = str(robot.get("sn") or "") + if sn.lower() == "sim": + continue + arm_no = arm_short_id(sn) + if arm_no: + found[arm_no] = arm_state.workpiece_frame(robot) + return found + + +class GripperSketch(MeshView): + """按尺寸图画大寰夹爪。开口和旋转跟寄存器走。""" + + def __init__(self, min_w: int = 360, min_h: int = 280) -> None: + super().__init__(min_w, min_h) + self._thin_caption = True + + def _caption(self) -> str: + return "拖拽改视角" + + def set_pose( + self, + permille: float | None, + rotation_deg: float | None, + rotary: bool, + live: bool, + stroke_mm: float | None = None, + ) -> None: + if isinstance(permille, (int, float)): + permille = round(float(permille)) + if isinstance(rotation_deg, (int, float)): + rotation_deg = round(float(rotation_deg), 1) + pose_key = (bool(rotary), permille, rotation_deg, bool(live), stroke_mm) + if pose_key == getattr(self, "_pose_key", None): + return + self._pose_key = pose_key + triangles, note = gripper_mesh.triangles(bool(rotary), permille, rotation_deg, stroke_mm) + if live and permille is not None: + note = f"开口 {float(permille):.0f}‰ " + note + if rotary and isinstance(rotation_deg, (int, float)): + note = f"转角 {float(rotation_deg):.1f}° " + note + self.set_mesh(triangles, note, bool(live) and permille is not None) + + class StationApp(QMainWindow): def __init__(self) -> None: super().__init__() self.setWindowTitle("ORISYS 通止规工位") self.resize(1440, 900) - self._arm = "89" + site = load_site_dict() + self._catalog = listed_arms(site) + self._workpieces = _workpieces_by_arm(site) + self._board = BoardWatch() + self._arm = self._catalog[0]["id"] if self._catalog else "" self._mode = "eye_in_hand" self._snap: dict = {} - self._job: _Job | None = None + self._jobs: dict[str, _Job] = {} self._teach_sig: tuple | None = None self._pages: dict[str, QWidget] = {} self._nav: list[tuple[str, QPushButton]] = [] @@ -297,7 +1218,8 @@ class StationApp(QMainWindow): self._timer.start(800) self._images = QTimer(self) self._images.timeout.connect(self._paint_images) - self._images.start(200) + # 和相机预览同一档:大约 20 帧,推理框才跟得上画面。 + self._images.start(16) self._stack.currentChanged.connect(lambda _index: self._paint_images()) self._refresh() @@ -307,93 +1229,105 @@ class StationApp(QMainWindow): layout = QHBoxLayout(root) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) + # 子控件的最小宽度不许写进窗口。否则一串长状态会把窗口撑过屏幕,最大化后拖不动。 + layout.setSizeConstraint(QLayout.SizeConstraint.SetNoConstraint) layout.addWidget(self._sidebar()) right = QVBoxLayout() right.setContentsMargins(0, 0, 0, 0) right.setSpacing(0) right.addWidget(self._header()) self._stack = QStackedWidget() - for key, _label in ( - ("overview", "总览"), - ("tactile", "视触觉"), - ("handeye", "手眼"), - ("camera", "相机"), - ("gripper", "夹爪"), - ("control", "控制"), - ("teach", "示教"), - ("assembly", "装配"), - ("operate", "操作"), - ): - page = getattr(self, f"_page_{key}")() + handeye = self._page_handeye() + self._he_out.setFixedHeight(72) + built = ( + ("main", self._page_overview()), + ("handeye", self._with_back(handeye, "手眼")), + ("teach", self._with_back(self._scroll(self._page_teach()), "示教")), + ) + for key, page in built: self._pages[key] = page self._stack.addWidget(page) right.addWidget(self._stack, 1) self._detail = QPlainTextEdit() + self._detail.setObjectName("log") self._detail.setReadOnly(True) - self._detail.setFixedHeight(120) + self._detail.setFixedHeight(56) self._detail.setPlaceholderText("命令输出显示在这里") right.addWidget(self._detail) holder = QWidget() holder.setLayout(right) layout.addWidget(holder, 1) - self._show_page("overview") + self._show_page("main") + self.setMinimumSize(1100, 680) + + def _with_back(self, inner: QWidget, title: str) -> QWidget: + page = QWidget() + lay = QVBoxLayout(page) + lay.setContentsMargins(12, 8, 12, 8) + bar = QHBoxLayout() + back = _btn("← 返回主界面") + back.clicked.connect(lambda: self._show_page("main")) + bar.addWidget(back) + bar.addWidget(_title(title)) + bar.addStretch(1) + lay.addLayout(bar) + lay.addWidget(inner, 1) + return page def _sidebar(self) -> QWidget: bar = QWidget() bar.setObjectName("side") - bar.setFixedWidth(220) + bar.setFixedWidth(120) lay = QVBoxLayout(bar) + lay.setContentsMargins(10, 12, 10, 12) + lay.setSpacing(6) logo = QLabel() if _LOGO.is_file(): pix = QPixmap(str(_LOGO)) - logo.setPixmap(pix.scaledToWidth(180, Qt.TransformationMode.SmoothTransformation)) + logo.setPixmap(pix.scaledToWidth(88, Qt.TransformationMode.SmoothTransformation)) logo.setAlignment(Qt.AlignmentFlag.AlignCenter) lay.addWidget(logo) - station = QLabel("通止规工位") - station.setObjectName("sideNote") - station.setAlignment(Qt.AlignmentFlag.AlignCenter) - lay.addWidget(station) + brand = QLabel("通止规工位") + brand.setObjectName("brand") + brand.setAlignment(Qt.AlignmentFlag.AlignCenter) + lay.addWidget(brand) items = ( - ("overview", "总览", "设备与流程"), - ("tactile", "视触觉", "切向、热成像、曲线"), - ("handeye", "手眼", "眼在手上 / 手外"), - ("camera", "相机", "D405 与 D435"), - ("gripper", "夹爪", "位置、力度、速度"), - ("control", "控制", "分步、整体、复位"), - ("teach", "示教", "软拖拽记点"), - ("assembly", "装配", "S00 到 S11"), - ("operate", "操作", "启动与确认"), + ("main", "主界面", "nav_main"), + ("handeye", "手眼", "nav_eye"), + ("teach", "示教", "nav_pen"), ) - for key, name, hint in items: - button = QPushButton(f"{name}\n{hint}") + for key, name, mark in items: + button = QPushButton(name) + button.setIcon(QIcon(_action_icon(mark))) + button.setIconSize(QSize(18, 18)) button.setObjectName("nav") + button.setFixedHeight(36) button.clicked.connect(lambda _checked=False, k=key: self._show_page(k)) self._nav.append((key, button)) lay.addWidget(button) lay.addStretch(1) - note = QLabel("读的是本机 USB、进程和话题。动臂、张合夹爪都要再确认一次。") - note.setWordWrap(True) - note.setObjectName("sideNote") - lay.addWidget(note) return bar def _header(self) -> QWidget: bar = QWidget() bar.setObjectName("head") - bar.setFixedHeight(64) + bar.setFixedHeight(52) lay = QHBoxLayout(bar) - self._arm_line = QLabel("左臂 89") + first = next((row for row in self._catalog if row["id"] == self._arm), None) + self._arm_line = QLabel(first["label"] if first else "没有机械臂") self._arm_line.setObjectName("armLine") lay.addWidget(self._arm_line) lay.addStretch(1) self._chips = QLabel("") self._chips.setObjectName("chips") - lay.addWidget(self._chips) + _fit_text(self._chips) + lay.addWidget(self._chips, 1) self._arm_buttons: dict[str, QPushButton] = {} - for arm, text in (("89", "左臂89"), ("91", "右臂91")): - button = _btn(text) - button.clicked.connect(lambda _checked=False, a=arm: self._set_arm(a)) - self._arm_buttons[arm] = button + for row in self._catalog: + button = _btn(row["label"]) + button.setObjectName("seg") + button.clicked.connect(lambda _checked=False, a=row["id"]: self._set_arm(a)) + self._arm_buttons[row["id"]] = button lay.addWidget(button) self._clock = QLabel("--:--:--") self._clock.setObjectName("clock") @@ -408,53 +1342,429 @@ class StationApp(QMainWindow): return area def _page_overview(self) -> QWidget: + """主界面。状态、仿真、相机、触觉和操作键都在这一屏,不放滚动区。""" + page = QWidget() + root = QHBoxLayout(page) + root.setContentsMargins(8, 6, 8, 6) + root.setSpacing(8) + root.addWidget(self._main_arm_column(), 2) + root.addWidget(self._main_work_column(), 5) + return page + + def _main_arm_column(self) -> QWidget: page = QWidget() lay = QVBoxLayout(page) - self._lead = QLabel("") - self._lead.setObjectName("lead") - self._lead.setWordWrap(True) - lay.addWidget(self._lead) - grid = QGridLayout() - self._usb_labels: list[tuple[QLabel, QLabel, QLabel]] = [] - for index, name in enumerate(("腕部 D405", "手外 D435", "视触觉", "夹爪串口")): + lay.setContentsMargins(0, 0, 0, 0) + lay.setSpacing(6) + self._arm_banner = _fit_text(QLabel("")) + self._arm_banner.setObjectName("warn") + self._arm_banner.setFixedHeight(22) + lay.addWidget(self._arm_banner) + usb = QHBoxLayout() + usb.setSpacing(6) + self._usb_labels: list[tuple[QLabel, QLabel]] = [] + for name in ("腕部", "手外", "触觉", "串口"): card = _card() + card.setFixedHeight(48) box = QVBoxLayout(card) - box.addWidget(_title(name)) + box.setContentsMargins(8, 4, 8, 4) + box.setSpacing(0) + title = _title(name) + title.setAlignment(Qt.AlignmentFlag.AlignCenter) + box.addWidget(title) count = QLabel("—") - count.setObjectName("stat") + count.setObjectName("count") + count.setAlignment(Qt.AlignmentFlag.AlignCenter) flag = QLabel("") - detail = QLabel("") - detail.setObjectName("muted") - detail.setWordWrap(True) - box.addWidget(count) - box.addWidget(flag) - box.addWidget(detail) - grid.addWidget(card, index // 2, index % 2) - self._usb_labels.append((count, flag, detail)) - self._mini = [QLabel("左指 —"), QLabel("右指 —")] - for index, label in enumerate(self._mini): - label.setObjectName("muted") - label.setWordWrap(True) + flag.setAlignment(Qt.AlignmentFlag.AlignCenter) + line = QHBoxLayout() + line.setSpacing(4) + line.addStretch(1) + line.addWidget(count) + line.addWidget(flag) + line.addStretch(1) + box.addLayout(line) + usb.addWidget(card, 1) + self._usb_labels.append((count, flag)) + lay.addLayout(usb) + self._arm_prompt = _fit_text(QLabel("")) + self._arm_prompt.setObjectName("warn") + self._arm_prompt.setFixedHeight(22) + self._arm_prompt.setVisible(False) + lay.addWidget(self._arm_prompt) + self._arm_sketch = ArmSketch() + lay.addWidget(self._arm_sketch, 1) + cards = QHBoxLayout() + cards.setSpacing(6) + self._arm_cards: dict[str, QLabel] = {} + for key, title in ( + ("enable", "使能"), + ("mode", "模式"), + ("fault", "故障"), + ("source", "数据"), + ): card = _card() + card.setFixedHeight(48) box = QVBoxLayout(card) - box.addWidget(_title("左指" if index == 0 else "右指")) + box.setContentsMargins(6, 4, 6, 4) + box.setSpacing(0) + cap = _title(title) + cap.setAlignment(Qt.AlignmentFlag.AlignCenter) + box.addWidget(cap) + label = _fit_text(QLabel("—")) + label.setObjectName("count") + label.setAlignment(Qt.AlignmentFlag.AlignCenter) box.addWidget(label) - grid.addWidget(card, index, 2) - lay.addLayout(grid) - flow = QHBoxLayout() + self._arm_cards[key] = label + cards.addWidget(card, 1) + lay.addLayout(cards) + self._arm_joints = _fit_text(QLabel("")) + self._arm_joints.setObjectName("muted") + self._arm_joints.setAlignment(Qt.AlignmentFlag.AlignCenter) + self._arm_joints.setFixedHeight(22) + lay.addWidget(self._arm_joints) + lay.addWidget(self._grip_panel()) + return page + + def _grip_panel(self) -> QWidget: + """主界面上的夹爪仿真和下发。状态只留一行,滑条上下对齐。""" + panel = _card() + lay = QVBoxLayout(panel) + lay.setContentsMargins(8, 6, 8, 8) + lay.setSpacing(6) + head = QHBoxLayout() + head.setSpacing(8) + head.addWidget(_section("夹爪")) + self._grip_summary = _fit_text(QLabel("还没读到")) + self._grip_summary.setObjectName("warn") + self._grip_summary.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter) + head.addWidget(self._grip_summary, 1) + lay.addLayout(head) + self._grip_sketch = GripperSketch(160, 96) + self._grip_sketch.setFixedHeight(128) + self._grip_sketch.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + lay.addWidget(self._grip_sketch) + self._pos = ValueTune("位置", 0, 1000, 500, compact=True) + self._force = ValueTune("力度", 20, 100, 33, compact=True) + self._speed = ValueTune("速度", 1, 100, 25, compact=True) + for widget in (self._pos, self._force, self._speed): + lay.addWidget(widget) + row = QHBoxLayout() + row.setSpacing(6) + apply_btn = _btn("按此夹持", True) + open_btn = _btn("张开", True) + close_btn = _btn("闭合", True) + apply_btn.clicked.connect(lambda: self._grip_move("pos")) + open_btn.clicked.connect(lambda: self._grip_preset(1000)) + close_btn.clicked.connect(lambda: self._grip_preset(0)) + for button in (apply_btn, open_btn, close_btn): + button.setFixedHeight(36) + button.setMinimumWidth(0) + button.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + row.addWidget(button, 1) + lay.addLayout(row) + + self._rotary = QWidget() + rot = QVBoxLayout(self._rotary) + rot.setContentsMargins(0, 0, 0, 0) + rot.setSpacing(4) + rot.addWidget(_section("旋转")) + self._angle = ValueTune( + "绝对角", -32767, 32767, 0, step=0.1, decimals=1, + slider_low=-3600, slider_high=3600, compact=True, + ) + self._delta = ValueTune( + "相对角", -32767, 32767, 15, step=0.1, decimals=1, + slider_low=-720, slider_high=720, compact=True, + ) + self._turns = ValueTune( + "圈数", -50, 50, 0.25, step=0.01, decimals=2, + slider_low=-10, slider_high=10, compact=True, + ) + self._rot_speed = ValueTune("转速", 1, 100, 25, compact=True) + self._torque = ValueTune("力矩", 20, 100, 33, compact=True) + for widget in (self._angle, self._delta, self._turns, self._rot_speed, self._torque): + rot.addWidget(widget) + rot_grid = QGridLayout() + rot_grid.setSpacing(6) + for index, (title, kind) in enumerate(( + ("转到绝对角", "rotate"), + ("相对转动", "rotate_by"), + ("按圈数转", "revolutions"), + ("旋转回零", "rotate_home"), + )): + button = _btn(title, True) + button.setFixedHeight(36) + button.setMinimumWidth(0) + button.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + button.clicked.connect(lambda _checked=False, k=kind: self._grip_move(k)) + rot_grid.addWidget(button, index // 2, index % 2) + rot.addLayout(rot_grid) + self._rotary.setVisible(False) + lay.addWidget(self._rotary) + return panel + + def _main_work_column(self) -> QWidget: + page = QWidget() + lay = QVBoxLayout(page) + lay.setContentsMargins(0, 0, 0, 0) + lay.setSpacing(6) + cam_bar = QHBoxLayout() + cam_bar.setSpacing(6) + cam_bar.addWidget(_section("相机")) + source = _segment_box() + self._real_mode = QPushButton("真机") + self._sim_mode = QPushButton("仿真") + self._cam_source = "real" + for button, sim in ((self._real_mode, False), (self._sim_mode, True)): + _segment_button(button) + button.clicked.connect(lambda _checked=False, use_sim=sim: self._set_source(use_sim)) + source.layout().addWidget(button) + self._real_mode.setProperty("active", True) + cam_bar.addWidget(source) + switch = _segment_box() + self._color_mode = QPushButton("彩色") + self._depth_mode = QPushButton("深度") + self._depth_on = False + for button, on in ((self._color_mode, False), (self._depth_mode, True)): + _segment_button(button) + button.clicked.connect(lambda _checked=False, depth=on: self._set_depth(depth)) + switch.layout().addWidget(button) + self._color_mode.setProperty("active", True) + cam_bar.addWidget(switch) + for action in ("camera_d405", "camera_d435"): + button = self._node_button(action, bar=True) + cam_bar.addWidget(button) + cam_bar.addStretch(1) + self._infer_line = _fit_text(QLabel("腕部没有框 手外没有框")) + self._infer_line.setObjectName("ok") + self._infer_line.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter) + cam_bar.addWidget(self._infer_line) + lay.addLayout(cam_bar) + self._cam_note = _fit_text(QLabel("")) + self._cam_note.setObjectName("warn") + self._cam_note.setFixedHeight(22) + self._cam_note.setVisible(False) + lay.addWidget(self._cam_note) + self._cam_pic: dict[str, FrameLabel] = {} + cams = camera_view.targets() + self._slot_role = {cam["id"]: cam["role"] for cam in cams} + sim_rows = camera_view.sim_targets() + self._sim_id = {item["role"]: item["id"] for item in sim_rows} + self._sim_title = { + item["role"]: {"color": item["label"], "depth": f"{item['label']} 深度"} + for item in sim_rows + } + colors = QHBoxLayout() + colors.setSpacing(6) + colors.setContentsMargins(0, 0, 0, 0) + self._cam_title: dict[str, str] = {} + for cam in cams: + shot = FrameLabel("", compact=True) + shot.set_badge(cam["label"], "等待画面") + self._cam_pic[f"{cam['id']}_color"] = shot + self._cam_pic[f"{cam['id']}_depth"] = shot + self._cam_title[f"{cam['id']}_color"] = cam["label"] + self._cam_title[f"{cam['id']}_depth"] = f"{cam['label']} 深度" + colors.addWidget(shot, 1) + color_box = _PictureRow(len(cams) or 1) + color_box.setLayout(colors) + self._color_box = color_box + lay.addWidget(color_box, 0) + tac_bar = QHBoxLayout() + tac_bar.setSpacing(6) + tac_bar.addWidget(_section("视触觉")) + see = _btn("开始成像") + hide = _btn("停止成像") + for button in (see, hide): + button.setFixedHeight(36) + see.clicked.connect(lambda: self._run("tactile_view_on", False)) + hide.clicked.connect(lambda: self._run("tactile_view_off", False)) + tac_bar.addWidget(see) + tac_bar.addWidget(hide) + tac_bar.addWidget(self._node_button("tactile_up", bar=True)) + tac_bar.addStretch(1) + lay.addLayout(tac_bar) + self._view_note = _fit_text(QLabel("")) + self._view_note.setObjectName("warn") + self._view_note.setFixedHeight(22) + self._view_note.setVisible(False) + lay.addWidget(self._view_note) + self._charts: dict[str, ForceChart] = {} + self._pic: dict[str, FrameLabel] = {} + self._mini = [] + pics = QGridLayout() + pics.setSpacing(6) + pics.setContentsMargins(0, 0, 0, 0) + for side_index, (side, name) in enumerate((("left", "左指"), ("right", "右指"))): + head = QLabel(name) + head.setObjectName("finger") + head.setAlignment(Qt.AlignmentFlag.AlignCenter) + head.setFixedHeight(22) + pics.addWidget(head, 0, side_index * 2, 1, 2) + for kind_index, (kind, caption) in enumerate((("shear", "切向"), ("thermal", "热像"))): + column = side_index * 2 + kind_index + shot = FrameLabel("", compact=True) + shot.set_badge(caption, "") + self._pic[f"{side}_{kind}"] = shot + pics.addWidget(shot, 1, column) + force = _fit_text(QLabel("—")) + force.setObjectName("muted") + force.setFixedHeight(22) + force.setAlignment(Qt.AlignmentFlag.AlignCenter) + self._mini.append(force) + pics.addWidget(force, 2, side_index * 2, 1, 2) + for column in range(4): + pics.setColumnStretch(column, 1) + pics.setRowStretch(1, 1) + pics.setRowMinimumHeight(0, 22) + pics.setRowMinimumHeight(2, 22) + tac_box = QWidget() + tac_box.setLayout(pics) + tac_box.setMinimumHeight(160) + tac_box.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + self._tac_box = tac_box + lay.addWidget(tac_box, 1) self._flow_labels: list[QLabel] = [] - for name in ("跟踪", "对孔", "搜孔轻压"): + flow = QHBoxLayout() + flow.setSpacing(6) + for index, name in enumerate(("跟踪", "对孔", "旋转装配"), start=1): + label = _fit_text(QLabel(f"{index} {name}")) + label.setObjectName("step") + label.setAlignment(Qt.AlignmentFlag.AlignCenter) + label.setFixedHeight(34) + self._flow_labels.append(label) + flow.addWidget(label, 1) + lay.addLayout(flow) + ops = _card() + ops_lay = QVBoxLayout(ops) + ops_lay.setContentsMargins(8, 6, 8, 8) + ops_lay.setSpacing(6) + head = QHBoxLayout() + head.setSpacing(6) + head.addWidget(_section("操作")) + head.addStretch(1) + stop = _btn("停止命令", True) + stop.setFixedHeight(36) + stop.clicked.connect(lambda: self._start(stop_started, "stop")) + head.addWidget(stop) + ops_lay.addLayout(head) + grid = QGridLayout() + grid.setSpacing(6) + actions = ( + "real_up", "gripper_up", "gripper_probe", "gripper_open", + "gripper_close", "gripper_rotate", "pipeline", "stage_track", + "stage_locate", "stage_screw", "reset_home", "gauge_dry", + "gauge_run", + ) + for index, action in enumerate(actions): + grid.addWidget(self._node_button(action), index // 4, index % 4) + for column in range(4): + grid.setColumnStretch(column, 1) + ops_lay.addLayout(grid) + lay.addWidget(ops) + return page + + def _node_button(self, action: str, *, bar: bool = False) -> QPushButton: + spec = self._spec(action) or {} + if action == "pipeline": + kind = "primary" + elif spec.get("moves"): + kind = "danger" + else: + kind = "node" + button = CommandButton(str(spec.get("title") or action), kind, action, bar=bar) + button.clicked.connect(lambda _checked=False, a=action: self._ask(a)) + return button + + def _set_depth(self, on: bool) -> None: + """彩色和深度共用同一排画面,点过去就换,不往下再展开一排。""" + self._depth_on = bool(on) + for button, active in ((self._color_mode, not on), (self._depth_mode, on)): + button.setProperty("active", active) + button.style().unpolish(button) + button.style().polish(button) + suffix = "depth" if on else "color" + seen: set[int] = set() + for key, label in self._cam_pic.items(): + if not key.endswith("_" + suffix) or id(label) in seen: + continue + seen.add(id(label)) + _view_key, title = self._view_key(key) + label.setText("") + label.set_badge(title or self._cam_title.get(key, ""), "等待画面") + self._sync_camera_watch() + self._paint_images() + + def _set_source(self, sim: bool) -> None: + """真机和仿真共用这一排画面。仿真图像也进这里,不另开窗口。""" + self._cam_source = "sim" if sim else "real" + for button, active in ((self._real_mode, not sim), (self._sim_mode, sim)): + button.setProperty("active", active) + button.style().unpolish(button) + button.style().polish(button) + self._set_depth(self._depth_on) + + def _view_key(self, real_key: str) -> tuple[str, str]: + """这一格现在该订哪路话题,以及画面上写的名字。""" + suffix = "_depth" if real_key.endswith("_depth") else "_color" + real_id = real_key[: -len(suffix)] + role = self._slot_role.get(real_id, "") + if self._cam_source == "sim": + sim_id = self._sim_id.get(role) + if sim_id: + kind = "depth" if suffix == "_depth" else "color" + return f"{sim_id}{suffix}", self._sim_title.get(role, {}).get(kind, "") + return real_key, self._cam_title.get(real_key, "") + + def _page_arm(self) -> QWidget: + page = QWidget() + lay = QVBoxLayout(page) + self._arm_lead = QLabel( + "只读机械臂状态,不会在这里自动使能。Remote 只能在示教器上开。" + ) + self._arm_lead.setObjectName("lead") + self._arm_lead.setWordWrap(True) + lay.addWidget(self._arm_lead) + self._arm_prompt = QLabel(arm_state.REMOTE_PROMPT) + self._arm_prompt.setObjectName("warn") + self._arm_prompt.setWordWrap(True) + lay.addWidget(self._arm_prompt) + grid = QGridLayout() + self._arm_cards: dict[str, QLabel] = {} + for index, (key, title) in enumerate(( + ("enable", "使能"), + ("mode", "模式"), + ("fault", "故障 / 急停"), + ("source", "数据从哪来"), + )): card = _card() box = QVBoxLayout(card) - box.addWidget(_title(name)) - label = QLabel("") - label.setObjectName("muted") + box.addWidget(_title(title)) + label = QLabel("—") + label.setObjectName("stat" if key == "enable" else "muted") label.setWordWrap(True) box.addWidget(label) - self._flow_labels.append(label) - flow.addWidget(card) - lay.addLayout(flow) - lay.addStretch(1) + self._arm_cards[key] = label + grid.addWidget(card, 0, index) + lay.addLayout(grid) + body = QHBoxLayout() + self._arm_sketch = ArmSketch() + body.addWidget(self._arm_sketch, 1) + side = QVBoxLayout() + self._arm_summary = QLabel("") + self._arm_summary.setWordWrap(True) + self._arm_summary.setObjectName("muted") + side.addWidget(self._arm_summary) + self._arm_joints = QLabel("") + self._arm_joints.setWordWrap(True) + self._arm_joints.setObjectName("muted") + side.addWidget(self._arm_joints) + side.addStretch(1) + holder = QWidget() + holder.setLayout(side) + body.addWidget(holder, 1) + lay.addLayout(body, 1) return page def _page_tactile(self) -> QWidget: @@ -523,9 +1833,10 @@ class StationApp(QMainWindow): page = QWidget() lay = QVBoxLayout(page) lead = QLabel( - "眼在手上用腕部 D405 和 A5 板,结果是相机到法兰。" - "眼在手外用顶置 D435 和 A4 板,结果是相机到基座。两份文件不能混用。" - "示教和求解会动臂。" + "眼在手上用腕部相机和 A5 板,结果是相机到法兰。" + "眼在手外用手外相机和 A4 板,结果是相机到基座。两份文件不能混用。" + "先在这一页看画面,棋盘约占 30%–70% 再采点。示教时相机会被采点程序独占," + "求解前要重新拉起这路相机。示教和求解会动臂。" ) lead.setWordWrap(True) lead.setObjectName("lead") @@ -539,13 +1850,46 @@ class StationApp(QMainWindow): row.addWidget(button) row.addStretch(1) lay.addLayout(row) + body = QHBoxLayout() + preview = _card() + preview_box = QVBoxLayout(preview) + preview_box.addWidget(_title("当前相机")) + self._he_shot = FrameLabel("预览没开") + self._he_shot.setMinimumHeight(280) + preview_box.addWidget(self._he_shot, 1) + self._he_board = QLabel("还没有画面") + self._he_board.setWordWrap(True) + self._he_board.setObjectName("muted") + preview_box.addWidget(self._he_board) + body.addWidget(preview, 1) + info = _card() + info_box = QVBoxLayout(info) self._he_body = QLabel("") self._he_body.setWordWrap(True) self._he_body.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) - card = _card() - box = QVBoxLayout(card) - box.addWidget(self._he_body) - lay.addWidget(card) + info_box.addWidget(self._he_body) + info_box.addStretch(1) + body.addWidget(info, 1) + lay.addLayout(body, 1) + tools = QHBoxLayout() + open_cam = _btn("拉起这路相机") + open_cam.clicked.connect(self._handeye_camera) + see = _btn("看画面") + see.clicked.connect(self._handeye_preview_on) + hide = _btn("关掉画面") + hide.clicked.connect(self._handeye_preview_off) + tools.addWidget(open_cam) + tools.addWidget(see) + tools.addWidget(hide) + tools.addWidget(QLabel("目标点数")) + self._he_points = QSpinBox() + self._he_points.setRange(4, 30) + self._he_points.setValue(12) + tools.addWidget(self._he_points) + self._he_append = QCheckBox("接着上次的点") + tools.addWidget(self._he_append) + tools.addStretch(1) + lay.addLayout(tools) actions = QHBoxLayout() for action, danger in ( ("handeye_show", False), @@ -555,14 +1899,18 @@ class StationApp(QMainWindow): ): spec = self._spec(action) button = _btn(spec["title"] if spec else action, danger) - button.clicked.connect(lambda _checked=False, a=action: self._ask(a)) + if action == "handeye_teach": + button.clicked.connect(self._handeye_teach) + else: + button.clicked.connect(lambda _checked=False, a=action: self._ask(a)) actions.addWidget(button) actions.addStretch(1) lay.addLayout(actions) self._he_out = QPlainTextEdit() self._he_out.setReadOnly(True) - self._he_out.setPlaceholderText("查看配置后,脚本输出显示在这里。") - lay.addWidget(self._he_out, 1) + self._he_out.setPlaceholderText("查看配置、合并和求解的输出显示在这里。") + self._he_out.setFixedHeight(140) + lay.addWidget(self._he_out) return page def _page_camera(self) -> QWidget: @@ -603,94 +1951,19 @@ class StationApp(QMainWindow): lay.addWidget(card, 1) return self._scroll(page) - def _page_gripper(self) -> QWidget: - page = QWidget() - lay = QVBoxLayout(page) - self._grip_lead = QLabel("") - self._grip_lead.setWordWrap(True) - self._grip_lead.setObjectName("lead") - lay.addWidget(self._grip_lead) - tools = QHBoxLayout() - for action in ("gripper_probe", "gripper_up"): - spec = self._spec(action) - button = _btn(spec["title"] if spec else action, False) - button.clicked.connect(lambda _checked=False, a=action: self._ask(a)) - tools.addWidget(button) - tools.addStretch(1) - lay.addLayout(tools) - - hold = _card() - box = QVBoxLayout(hold) - box.addWidget(_title("夹持")) - note = QLabel("位置 0 是合拢,1000 是张开,单位是千分比。力度最低 20%。滑条和数字框是同一个值。") - note.setObjectName("muted") - note.setWordWrap(True) - box.addWidget(note) - self._pos = ValueTune("位置", 0, 1000, 500) - self._force = ValueTune("力度 %", 20, 100, 33) - self._speed = ValueTune("速度 %", 1, 100, 25) - box.addWidget(self._pos) - box.addWidget(self._force) - box.addWidget(self._speed) - row = QHBoxLayout() - apply_btn = _btn("按此夹持", True) - open_btn = _btn("张开", True) - close_btn = _btn("闭合", True) - apply_btn.clicked.connect(lambda: self._grip_move("pos")) - open_btn.clicked.connect(lambda: self._grip_preset(1000)) - close_btn.clicked.connect(lambda: self._grip_preset(0)) - row.addWidget(apply_btn) - row.addWidget(open_btn) - row.addWidget(close_btn) - row.addStretch(1) - box.addLayout(row) - lay.addWidget(hold) - - self._rotary = _card() - rot = QVBoxLayout(self._rotary) - rot.addWidget(_title("旋转")) - rot_note = QLabel( - "绝对角和相对角可以打到 ±32767°。滑条只覆盖常用段,数字框是完整范围。" - "圈数可以到小数。力矩和旋转速度跟上面的夹持力度、速度是分开的。" - ) - rot_note.setObjectName("muted") - rot_note.setWordWrap(True) - rot.addWidget(rot_note) - self._angle = ValueTune("绝对角 °", -32767, 32767, 0, step=0.1, decimals=1, slider_low=-3600, slider_high=3600) - self._delta = ValueTune("相对角 °", -32767, 32767, 15, step=0.1, decimals=1, slider_low=-720, slider_high=720) - self._turns = ValueTune("圈数", -50, 50, 0.25, step=0.01, decimals=2, slider_low=-10, slider_high=10) - self._rot_speed = ValueTune("转速 %", 1, 100, 25) - self._torque = ValueTune("力矩 %", 20, 100, 33) - for widget in (self._angle, self._delta, self._turns, self._rot_speed, self._torque): - rot.addWidget(widget) - rot_row = QHBoxLayout() - for title, kind in ( - ("转到绝对角", "rotate"), - ("相对转动", "rotate_by"), - ("按圈数转", "revolutions"), - ("旋转回零", "rotate_home"), - ): - button = _btn(title, True) - button.clicked.connect(lambda _checked=False, k=kind: self._grip_move(k)) - rot_row.addWidget(button) - rot_row.addStretch(1) - rot.addLayout(rot_row) - lay.addWidget(self._rotary) - lay.addStretch(1) - return self._scroll(page) - def _page_control(self) -> QWidget: page = QWidget() lay = QVBoxLayout(page) lead = QLabel( - "分步是单独拉起跟踪、对孔或搜孔。整体是三段一次跑完。" - "复位先停跟踪,再回到命名位姿 home,前提是控制栈已经起来。都会动臂。" + "分步是单独拉起跟踪、对孔或旋转装配。搜孔轻压已取消,整体只跑跟踪和对孔。" + "旋转装配按现场配置里的拧入深度和螺距走。复位先停跟踪,再回到命名位姿 home," + "前提是控制栈已经起来。都会动臂。" ) lead.setWordWrap(True) lead.setObjectName("lead") lay.addWidget(lead) groups = ( - ("分步测试", ("stage_track", "stage_locate", "stage_search")), + ("分步测试", ("stage_track", "stage_locate", "stage_screw")), ("整体测试", ("pipeline",)), ("复位", ("reset_home",)), ) @@ -708,14 +1981,14 @@ class StationApp(QMainWindow): button.clicked.connect(lambda _checked=False, a=action: self._ask(a)) box.addWidget(button) lay.addWidget(card) - lay.addStretch(1) - return self._scroll(page) + return page def _page_teach(self) -> QWidget: page = QWidget() lay = QVBoxLayout(page) lead = QLabel( - "软拖拽占用 RDK,要先停掉控制栈。记录的是当时的 7 个关节角,单位弧度。" + "软拖拽会先停下关节轨迹控制器,关掉后再恢复,不再另开一条去抢位置环。" + "记录的是当时的 7 个关节角,单位弧度。" "回到点位会先关掉拖拽,再按下面的关节速度走过去。和 home 复位不是同一条路。" ) lead.setWordWrap(True) @@ -760,8 +2033,7 @@ class StationApp(QMainWindow): back.addStretch(1) list_box.addLayout(back) lay.addWidget(listed) - lay.addStretch(1) - return self._scroll(page) + return page def _page_assembly(self) -> QWidget: page = QWidget() @@ -789,9 +2061,21 @@ class StationApp(QMainWindow): row.addWidget(real) row.addStretch(1) lay.addLayout(row) - lay.addStretch(1) return page + def _page_job(self) -> QWidget: + page = QWidget() + lay = QVBoxLayout(page) + for title, builder in ( + ("示教", self._page_teach), + ("装配", self._page_assembly), + ("控制", self._page_control), + ("操作", self._page_operate), + ): + lay.addWidget(_title(title)) + lay.addWidget(builder()) + return self._scroll(page) + def _page_operate(self) -> QWidget: page = QWidget() lay = QVBoxLayout(page) @@ -802,7 +2086,7 @@ class StationApp(QMainWindow): skip = { "gripper_open", "gripper_close", "gripper_rotate", "gripper_probe", "handeye_show", "handeye_teach", "handeye_merge", "handeye_calibrate", - "pipeline", "stage_track", "stage_locate", "stage_search", "reset_home", + "pipeline", "stage_track", "stage_locate", "stage_screw", "reset_home", } shown = [spec for spec in catalog()["actions"] if spec["id"] not in skip] for index, spec in enumerate(shown): @@ -813,13 +2097,12 @@ class StationApp(QMainWindow): blurb.setWordWrap(True) blurb.setObjectName("muted") box.addWidget(blurb) - button = _btn("确认后执行" if spec.get("moves") else "执行", bool(spec.get("moves"))) + button = _btn("执行", bool(spec.get("moves"))) button.clicked.connect(lambda _checked=False, a=spec["id"]: self._ask(a)) box.addWidget(button) grid.addWidget(card, index // 3, index % 3) lay.addLayout(grid) - lay.addStretch(1) - return self._scroll(page) + return page def _show_page(self, key: str) -> None: self._stack.setCurrentWidget(self._pages[key]) @@ -827,6 +2110,27 @@ class StationApp(QMainWindow): button.setProperty("active", name == key) button.style().unpolish(button) button.style().polish(button) + self._sync_camera_watch() + + def _sync_camera_watch(self) -> None: + """只有正在看的那一页才处理图像。别的页上相机回调直接丢掉。""" + page = self._stack.currentWidget() + if page is self._pages.get("main"): + suffix = "_depth" if self._depth_on else "_color" + keys = set() + seen: set[int] = set() + for key, label in self._cam_pic.items(): + if not key.endswith(suffix) or id(label) in seen: + continue + seen.add(id(label)) + keys.add(self._view_key(key)[0]) + camera_view.watch(keys, set()) + elif page is self._pages.get("handeye"): + name = str(self._handeye_card().get("preview_camera") or "") + keys = {f"{name}_color"} if name else set() + camera_view.watch(set(), keys) + else: + camera_view.watch(set(), set()) def _set_arm(self, arm: str) -> None: self._arm = arm @@ -840,35 +2144,6 @@ class StationApp(QMainWindow): def _spec(self, action: str) -> dict | None: return next((item for item in catalog()["actions"] if item["id"] == action), None) - def _cmd(self, action: str) -> str: - spec = self._spec(action) - if not spec: - return action - args = " ".join(part.replace("{arm}", self._arm).replace("{mode}", self._mode) for part in spec.get("args") or []) - script = { - "real_up": "run_real.sh", - "camera_d405": "run_camera_d405.sh", - "camera_d435": "run_camera_d435.sh", - "tactile_up": "run_tactile.sh", - "gripper_up": "run_gripper.sh", - "gripper_probe": "run_gripper.sh", - "gripper_open": "run_gripper.sh", - "gripper_close": "run_gripper.sh", - "gripper_rotate": "run_gripper.sh", - "pipeline": "run_all.sh", - "stage_track": "run_visual_servo_rt.sh", - "stage_locate": "run_stage2_locate.sh", - "stage_search": "run_stage3_search.sh", - "reset_home": "go_home.sh", - "gauge_dry": "run_gauge_assembly.sh", - "gauge_run": "run_gauge_assembly.sh", - "handeye_show": "run_handeye.sh", - "handeye_teach": "run_handeye.sh", - "handeye_merge": "run_handeye.sh", - "handeye_calibrate": "run_handeye.sh", - }.get(action, "") - return f"bash scripts/{script} {args}".strip() - def _grip_preset(self, pos: int) -> None: self._pos.set_value(pos) self._grip_move("pos") @@ -885,22 +2160,13 @@ class StationApp(QMainWindow): "torque": self._torque.value(), } send_kind = "rotate" if kind == "rotate_home" else kind - if send_kind != "pos" and self._arm != "91": - self.statusBar().showMessage("旋转只对右臂", 4000) - return - if send_kind == "pos": - text = f"位置 {numbers['pos']:.0f} 力度 {numbers['force']:.0f}% 速度 {numbers['speed']:.0f}%" - elif send_kind == "rotate": - text = f"绝对角 {numbers['angle']:.1f}° 力矩 {numbers['torque']:.0f}% 转速 {numbers['rot_speed']:.0f}%" - elif send_kind == "rotate_by": - text = f"相对 {numbers['delta']:.1f}° 力矩 {numbers['torque']:.0f}% 转速 {numbers['rot_speed']:.0f}%" - else: - text = f"{numbers['revolutions']:.2f} 圈 力矩 {numbers['torque']:.0f}% 转速 {numbers['rot_speed']:.0f}%" - if not self._confirm("夹爪动作", f"臂 {self._arm}。{text}\n夹爪会动。", ""): + current = next((row for row in self._catalog if row["id"] == self._arm), None) + if send_kind != "pos" and not (current and current.get("rotary")): + self.statusBar().showMessage("这台夹爪没有旋转轴", 4000) return captured = dict(numbers) arm = self._arm - self._start(lambda: run_gripper_tune(arm, send_kind, captured)) + self._start(lambda: run_gripper_tune(arm, send_kind, captured), "grip") def _selected_point(self) -> str: item = self._teach_list.currentItem() @@ -913,16 +2179,14 @@ class StationApp(QMainWindow): if not sn: self.statusBar().showMessage("这台臂没有序列号", 4000) return - if not self._confirm("开启软拖拽", f"臂 {self._arm}。会占用 RDK,控制栈必须先停掉。臂可以被手推动。", ""): - return - self._start(lambda: teach.drag_on(sn)) + self._start(lambda: teach.drag_on(sn), "teach") def _teach_off(self) -> None: sn = self._sn() if not sn: self.statusBar().showMessage("这台臂没有序列号", 4000) return - self._start(lambda: teach.drag_off(sn)) + self._start(lambda: teach.drag_off(sn), "teach") def _teach_record(self) -> None: sn = self._sn() @@ -931,7 +2195,7 @@ class StationApp(QMainWindow): self.statusBar().showMessage("这台臂没有序列号", 4000) return arm = self._arm - self._start(lambda: teach.record(sn, arm, name)) + self._start(lambda: teach.record(sn, arm, name), "teach") def _teach_delete(self) -> None: name = self._selected_point() @@ -940,7 +2204,7 @@ class StationApp(QMainWindow): return arm = self._arm self._teach_sig = None - self._start(lambda: teach.delete_point(arm, name)) + self._start(lambda: teach.delete_point(arm, name), "teach") def _teach_goto(self) -> None: sn = self._sn() @@ -949,48 +2213,54 @@ class StationApp(QMainWindow): if not sn or not name: self.statusBar().showMessage("先选臂和点位", 3000) return - if not self._confirm( - "回到示教点", - f"臂 {self._arm} 回到 {name}。关节速度 {speed:.2f} rad/s。会先关拖拽再动臂。", - "", - ): - return arm = self._arm - self._start(lambda: teach.goto(sn, arm, name, speed)) + self._start(lambda: teach.goto(sn, arm, name, speed), "teach") def _sn(self) -> str: arms = (self._snap or {}).get("arms") or [] current = next((arm for arm in arms if arm.get("id") == self._arm), None) return str((current or {}).get("sn") or "") - def _confirm(self, title: str, text: str, command: str) -> bool: - box = QMessageBox(self) - box.setWindowTitle(title) - box.setText(text) - if command: - box.setInformativeText(command) - box.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) - box.button(QMessageBox.StandardButton.Yes).setText("确认执行") - box.button(QMessageBox.StandardButton.No).setText("取消") - return box.exec() == QMessageBox.StandardButton.Yes + def _handeye_card(self) -> dict: + return ((self._snap.get("handeye") or {}).get(self._arm) or {}).get(self._mode) or {} + + def _handeye_camera(self) -> None: + action = "camera_d405" if self._mode == "eye_in_hand" else "camera_d435" + self._ask(action) + self.statusBar().showMessage(camera_view.start(), 4000) + + def _handeye_preview_on(self) -> None: + self.statusBar().showMessage(camera_view.start(), 4000) + + def _handeye_preview_off(self) -> None: + camera_view.stop() + self._he_shot.setText("预览没开") + self._he_board.setText("画面已关") + + def _handeye_teach(self) -> None: + spec = self._spec("handeye_teach") + confirm = bool(spec and spec.get("moves")) + arm, mode = self._arm, self._mode + extras = {"points": self._he_points.value(), "append": self._he_append.isChecked()} + self._start(lambda: run_action("handeye_teach", arm, confirm, mode, extras), "teach") def _ask(self, action: str) -> None: + """点下去就下发。动臂仍带 confirm,后台才肯执行,界面不再弹窗。""" spec = self._spec(action) - confirm = False - if spec and spec.get("moves"): - if not self._confirm(spec["title"], f"{spec['blurb']}\n当前臂 {self._arm}。", self._cmd(action)): - return - confirm = True + confirm = bool(spec and spec.get("moves")) arm, mode = self._arm, self._mode - self._start(lambda: run_action(action, arm, confirm, mode)) + self._start(lambda: run_action(action, arm, confirm, mode), command_lane(action)) - def _start(self, fn) -> None: - if self._job is not None and self._job.isRunning(): - self.statusBar().showMessage("上一个命令还没返回", 3000) + def _start(self, fn, lane: str) -> None: + running = {name for name, job in self._jobs.items() if job.isRunning()} + blocked = command_blocked_message(running, lane) + if blocked: + self.statusBar().showMessage(blocked, 3000) return - self._job = _Job(fn) - self._job.finished_ok.connect(self._ran) - self._job.start() + job = _Job(fn) + self._jobs[lane] = job + job.finished_ok.connect(self._ran) + job.start() def _ran(self, result: dict) -> None: text = result.get("message") or ("已发出" if result.get("ok") else "失败") @@ -1017,74 +2287,120 @@ class StationApp(QMainWindow): button.style().unpolish(button) button.style().polish(button) chips = [] + live = (self._snap.get("arm_live") or {}).get(self._arm) or {} + chips.append("已使能" if live.get("enabled") else "未使能") + grip = (self._snap.get("gripper_live") or {}).get(self._arm) or {} + if grip.get("connected") and grip.get("permille") is not None: + chips.append(f"夹爪 {grip['permille']:.0f}‰") + else: + chips.append("夹爪未连接") for service in self._snap.get("services") or []: chips.append(f"{service['label']} {'在跑' if service['up'] else '未开'}") self._chips.setText(" ".join(chips)) self._fill_overview(current) + self._fill_arm(live) self._fill_tactile() self._fill_handeye() self._fill_camera() - self._fill_gripper() + self._sync_camera_watch() + self._fill_gripper(grip) self._fill_teach() - self._op_lead.setText(f"当前臂 {self._arm}。红色按钮会动设备,先确认再执行。") def _fill_overview(self, current: dict | None) -> None: - if current: - tactile = "本臂有视触觉" if current.get("tactile") else "本臂无视触觉" - self._lead.setText( - f"{current.get('label')} · {current.get('sn') or ''} · 相机 {current.get('camera') or '—'} · " - f"夹爪 {current.get('gripper') or '—'} · {tactile}" - ) + del current + live = (self._snap.get("arm_live") or {}).get(self._arm) or {} + enable_word = "已使能" if live.get("enabled") else "未使能" + self._arm_banner.setText(f"{enable_word} {live.get('summary') or ''}".strip()) + self._arm_banner.setObjectName("ok" if live.get("enabled") and not live.get("prompt_hot") else "warn") + self._arm_banner.style().unpolish(self._arm_banner) + self._arm_banner.style().polish(self._arm_banner) usb = list(self._snap.get("usb") or []) ports = self._snap.get("grippers") or [] usb.append({"id": "tty", "items": [{"port": name, "speed": ""} for name in ports], "expect": 1}) - for (count, flag, detail), dev in zip(self._usb_labels, usb): + for (count, flag), dev in zip(self._usb_labels, usb): items = dev.get("items") or [] if dev.get("id") == "tty": count.setText(str(len(items))) ok = len(items) > 0 else: - count.setText(f"{len(items)} / {dev.get('expect')}") + count.setText(f"{len(items)}/{dev.get('expect')}") ok = len(items) >= int(dev.get("expect") or 0) - flag.setText("齐" if ok else ("缺一" if items else "缺失")) - detail.setText("\n".join( - f"{item['port']} · {item['speed']} Mbps" if item.get("speed") else item["port"] - for item in items - ) or "未检测到") + flag.setText("齐" if ok else "缺") + flag.setObjectName("ok" if ok else "warn") + flag.style().unpolish(flag) + flag.style().polish(flag) tactile = self._snap.get("tactile") or {} for label, side, name in ((self._mini[0], "left", "左指"), (self._mini[1], "right", "右指")): row = tactile.get(side) or {} label.setText(self._force_line(row)) pipe = next((s for s in self._snap.get("services") or [] if s["id"] == "pipeline"), None) - texts = ( - "D405 看住工件,锁住悬停", - "拍照找孔,移到孔上方", - "力闭环入孔,再轻压", + running = bool(pipe and pipe.get("up")) + for index, (label, name) in enumerate( + zip(self._flow_labels, ("跟踪", "对孔", "旋转装配")), start=1 + ): + label.setText(f"{index} {name}") + label.setProperty("on", running) + label.style().unpolish(label) + label.style().polish(label) + + def _fill_arm(self, live: dict) -> None: + enabled = bool(live.get("enabled")) + self._arm_cards["enable"].setText("已使能" if enabled else "未使能") + self._arm_cards["mode"].setText(str(live.get("mode") or "—")) + fault = live.get("fault") + estop = live.get("estop_released") + if estop is False: + fault_text = "急停" + elif fault: + fault_text = "有故障" + elif fault is False: + fault_text = "正常" + else: + fault_text = "—" + self._arm_cards["fault"].setText(fault_text) + source = str(live.get("source") or "—") + if source.startswith("RDK"): + source = "RDK" + elif source == "拖拽桥": + source = "拖拽" + self._arm_cards["source"].setText(source) + hot = bool(live.get("prompt_hot", True)) + prompt = str(live.get("prompt") or "") + sentence = prompt.split("。")[0] + self._arm_prompt.setVisible(hot and bool(sentence)) + self._arm_prompt.setText(f"{sentence}。" if sentence else "") + q = live.get("q") + if isinstance(q, list) and len(q) >= 7: + degrees = " ".join( + f"J{i + 1} {v * 180.0 / 3.141592653589793:.0f}°" for i, v in enumerate(q[:7]) + ) + else: + degrees = "零位" + self._arm_joints.setText(degrees) + self._arm_sketch.set_pose( + q if isinstance(q, list) else None, + bool(live.get("pose_live")), + self._workpieces.get(self._arm), ) - state = "进程在跑,具体阶段看终端日志。" if pipe and pipe.get("up") else "流程未启动。" - for label, text in zip(self._flow_labels, texts): - label.setText(f"{text}\n{state}") def _force_line(self, row: dict) -> str: def num(key: str) -> str: value = row.get(key) return f"{value:.1f}" if isinstance(value, (int, float)) else "—" - return f"法向 {num('fnormal')} 切向 X {num('fshearx')} 切向 Y {num('fsheary')} {num('fps')} fps" + return f"法向 {num('fnormal')} 切向 {num('fshearx')} / {num('fsheary')}" def _fill_tactile(self) -> None: view = self._snap.get("view") or {} - self._view_note.setText( - view.get("error") - or view.get("note") - or ("成像已开,等第一帧。" if view.get("on") else "成像没开。点开始成像后这里出切向力和热成像。") - ) + err = str(view.get("error") or "") + self._view_note.setVisible(bool(err)) + self._view_note.setText(err) history = self._snap.get("history") or {} for side, chart in self._charts.items(): chart.set_samples(history.get(side)) def _paint_images(self) -> None: current = self._stack.currentWidget() - if current is self._pages.get("tactile"): + if current is self._pages.get("main"): view = tactile_view.state() frames = view.get("frames") or {} if view.get("on") else {} for side in ("left", "right"): @@ -1095,15 +2411,35 @@ class StationApp(QMainWindow): blob = tactile_view.frame(side, kind) if blob: label.show_jpeg(blob) - if current is self._pages.get("camera"): - frames = camera_view.state().get("frames") or {} + shots = camera_view.previews() + meta = camera_view.preview_meta() + suffix = "_depth" if self._depth_on else "_color" + seen: set[int] = set() for key, label in self._cam_pic.items(): - if not frames.get(key): + if not key.endswith(suffix) or id(label) in seen: continue - name, _, kind = key.rpartition("_") - blob = camera_view.frame(name, kind) - if blob: - label.show_jpeg(blob) + seen.add(id(label)) + view_key, title = self._view_key(key) + info = meta.get(view_key) + if not title: + title = self._cam_title.get(key, "") + if info and info[2] >= 1.0: + detail = f"{info[0]}×{info[1]} {info[2]:.0f} 帧/秒" + elif info and info[0] > 0: + detail = f"{info[0]}×{info[1]}" + else: + detail = "等待画面" + label.set_badge(title, detail) + item = shots.get(view_key) + if not item: + continue + blob, width, height = item + label.show_rgb(blob, width, height) + infer = camera_view.inference_status() + if infer != self._infer_line.text(): + self._infer_line.setText(infer) + if current is self._pages.get("handeye"): + self._paint_handeye() def _fill_handeye(self) -> None: for mode, button in self._mode_buttons.items(): @@ -1119,40 +2455,71 @@ class StationApp(QMainWindow): mm = "—" name = "眼在手上" if self._mode == "eye_in_hand" else "眼在手外" poses = "没有位姿文件" if card.get("poses") is None else f"{card.get('poses')} 个" + target = self._he_points.value() + preview = card.get("preview_label") or "相机" + cam_id = card.get("preview_camera") or "—" self._he_body.setText( f"{name} {'有结果' if card.get('exists') else '还没有结果文件'}\n" - f"{card.get('meaning') or ''}\n" + f"{card.get('meaning') or '还没有求解结果'}\n" + f"预览 {preview} {cam_id}\n" f"文件 {card.get('file') or '—'}\n" f"视角 {card.get('views') or '—'} 时间 {card.get('utc') or '—'}\n" - f"相机 {card.get('camera') or '—'} 工具 {card.get('tool') or '—'}\n" + f"结果里的相机 {card.get('camera') or '—'} 工具 {card.get('tool') or '—'}\n" f"平移 X / Y / Z {mm}\n" - f"示教点 {poses}\n" + f"已有示教点 {poses} 这次目标 {target} 个\n" f"板 {board.get('cols') or '—'}×{board.get('rows') or '—'} " - f"方格 {board.get('square_m') if board.get('square_m') is not None else '—'} m" + f"方格 {board.get('square_m') if board.get('square_m') is not None else '—'} m\n" + "顺序:看画面 → 停控制栈后采点 → 合并 → 再开控制栈和相机 → 求解" ) + if not camera_view.state().get("on"): + self._he_board.setText("预览没开。点「看画面」。相机节点没起来时再点「拉起这路相机」。") + + def _paint_handeye(self) -> None: + if not camera_view.state().get("on"): + return + card = self._handeye_card() + name = str(card.get("preview_camera") or "") + blob = camera_view.frame(name, "color") if name else None + if not blob: + if not camera_view.state().get("on"): + self._he_board.setText("预览没开。点「看画面」。相机节点没起来时再点「拉起这路相机」。") + else: + self._he_board.setText(f"还没有 {card.get('preview_label') or '这路相机'} 的彩色帧") + return + cols = int((card.get("board") or {}).get("cols") or 0) + rows = int((card.get("board") or {}).get("rows") or 0) + self._board.submit(id(blob), blob, cols, rows) + drawn, text, token = self._board.latest() + if drawn and token == id(blob): + self._he_shot.show_jpeg(drawn) + self._he_board.setText(text) + else: + self._he_shot.show_jpeg(blob) + self._he_board.setText(text or "正在找棋盘") def _fill_camera(self) -> None: view = camera_view.state() - usb = {item["id"]: item for item in self._snap.get("usb") or []} - bits = [] - for key, name in (("d405", "腕部"), ("d435", "手外")): - dev = usb.get(key) or {"items": []} - bits.append(f"{name} {len(dev.get('items') or [])} 台") - err = view.get("error") or "" - ready = "、".join(key for key, ok in (view.get("frames") or {}).items() if ok) or "还没有帧" - self._cam_note.setText(f"{' '.join(bits)}。画面:{ready}。{err}") + err = str(view.get("error") or "") + self._cam_note.setVisible(bool(err)) + if err != self._cam_note.text(): + self._cam_note.setText(err) - def _fill_gripper(self) -> None: - ports = " ".join(self._snap.get("grippers") or []) or "没有 ttyUSB" - self._rotary.setVisible(self._arm == "91") - if self._arm == "91": - self._grip_lead.setText( - f"当前是右臂旋转爪。串口 {ports}。位置、力度、速度以及转角都会按数字下发。夹爪节点占着串口时,命令行会失败。" - ) - else: - self._grip_lead.setText( - f"当前是左臂平行爪。串口 {ports}。位置 0–1000,力度、速度按滑条下发。探测只读状态。" - ) + def _fill_gripper(self, live: dict) -> None: + current = next((row for row in self._catalog if row["id"] == self._arm), None) + rotary = bool(current and current.get("rotary")) + self._rotary.setVisible(rotary) + connected = bool(live.get("connected")) + self._grip_summary.setText(live.get("summary") or "没连上") + self._grip_summary.setObjectName("ok" if connected else "warn") + self._grip_summary.style().unpolish(self._grip_summary) + self._grip_summary.style().polish(self._grip_summary) + self._grip_sketch.set_pose( + live.get("permille"), + live.get("rotation_deg"), + bool(live.get("rotary")), + bool(live.get("pose_live")), + live.get("stroke_mm"), + ) def _fill_teach(self) -> None: listed = teach.list_points(self._arm) @@ -1177,42 +2544,85 @@ def main() -> None: atexit.register(camera_view.stop) atexit.register(teach.close) threading.Thread(target=_poll_tactile, daemon=True, name="tactile-poll").start() + arm_state.start() + grip_state.start() camera_view.start() + QApplication.setAttribute(Qt.ApplicationAttribute.AA_ShareOpenGLContexts) + QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseDesktopOpenGL) app = QApplication(sys.argv) app.setFont(QFont("Noto Sans SC", 11)) app.setStyleSheet(_STYLE) window = StationApp() - window.showMaximized() + screen = app.primaryScreen() + if screen is not None: + avail = screen.availableGeometry() + window.resize(min(1600, avail.width()), min(960, avail.height())) + # 不用最大化。桌面如果还记着上次的最大化,晚一拍取消,标题栏才能拖。 + window.showNormal() + QTimer.singleShot(300, window.showNormal) sys.exit(app.exec()) _STYLE = """ -QMainWindow, QWidget { background: #070708; color: #f3f4f6; } -QWidget#side, QWidget#head { background: #0b0c0e; } -QWidget#side { border-right: 1px solid #2a2e36; } -QWidget#head { border-bottom: 1px solid #2a2e36; } -QLabel#sideNote, QLabel#lead, QLabel#muted, QLabel#chips, QLabel#clock { color: #8b929d; } -QLabel#armLine { font-size: 16px; } -QLabel#cardTitle { color: #9aa3b2; letter-spacing: 1px; } -QLabel#stat { font-size: 28px; } -QLabel#shot { background: #0c0e12; border: 1px solid #243044; border-radius: 8px; color: #8b929d; } -QFrame#card { background: #101216; border: 1px solid #2a2e36; border-radius: 12px; } +QMainWindow, QWidget { background: #0c0f14; color: #e7ebf2; } +QWidget#side { background: #10141b; border-right: 1px solid #2a3240; } +QWidget#head { background: #10141b; border-bottom: 1px solid #2a3240; } +QLabel#brand { color: #8b95a7; font-size: 11px; letter-spacing: 1px; } +QLabel#sideNote, QLabel#lead, QLabel#muted, QLabel#chips, QLabel#clock { color: #8b95a7; } +QLabel#clock { padding: 0 8px; } +QLabel#warn { color: #e2b657; } +QLabel#ok { color: #7dcea0; } +QLabel#armLine { font-size: 16px; font-weight: 600; color: #f4f7fb; } +QLabel#count { color: #f4f7fb; font-weight: 600; } +QLabel#cardTitle { color: #8b95a7; font-size: 11px; } +QLabel#section { color: #d5dbe6; font-weight: 600; } +QLabel#finger { color: #f4f7fb; font-weight: 600; background: transparent; } +QFrame#sectionMark { background: #5b9bff; border: none; border-radius: 1px; } +QLabel#step { + background: #151a22; color: #9aa3b2; border: 1px solid #2a3240; + border-radius: 8px; padding: 2px 8px; +} +QLabel#step[on="true"] { background: #15263c; color: #d6e6ff; border-color: #2f5f99; } +QLabel#shot { background: #090c11; border: 1px solid #243044; border-radius: 10px; color: #8b95a7; } +QFrame#card { background: #161b24; border: 1px solid #2c3544; border-radius: 12px; } QPushButton { - background: #171a20; color: #f3f4f6; border: 1px solid #2a2e36; - border-radius: 8px; padding: 8px 12px; text-align: left; + background: #1a1f27; color: #e7ebf2; border: 1px solid #313844; + border-radius: 8px; padding: 0px 12px; font-size: 13px; text-align: left; } -QPushButton#nav { text-align: left; background: transparent; border: 1px solid transparent; } -QPushButton#nav[active="true"], QPushButton[active="true"] { background: #12315d; border-color: #1a4580; } -QPushButton#danger { background: #6e2a2a; border-color: #8a3a3a; } +QWidget#switch { background: #12171f; border: 1px solid #2c3544; border-radius: 8px; } +QPushButton:hover { background: #222833; border-color: #4a5568; } +QPushButton#nav { + text-align: left; background: transparent; border: none; border-radius: 8px; + padding: 0px 8px; color: #b7c0cc; font-size: 13px; +} +QPushButton#nav:hover { background: #1a2030; } +QPushButton#nav[active="true"] { + background: #182438; color: #f4f7fb; border-left: 3px solid #5b9bff; +} +QPushButton#node, QPushButton#danger, QPushButton#primary, QPushButton#action { text-align: center; padding: 0; } +QPushButton#node { background: #1c222c; border-color: #3a4558; } +QPushButton#seg { + text-align: center; padding: 0 12px; border: none; border-radius: 6px; + background: transparent; min-width: 0; min-height: 0; +} +QPushButton#seg[active="true"] { background: #1d4f91; color: #f4f7fb; } +QPushButton#primary { background: #1d4f91; border-color: #3d7ed4; color: #f4f7fb; font-weight: 600; } +QPushButton#primary:hover { background: #2460ad; } +QPushButton#danger { background: #241416; border: 1px solid #6e3a3a; color: #f0d0d0; } +QPushButton#danger:hover { background: #3a1c20; border-color: #a05050; } QPlainTextEdit, QLineEdit, QListWidget, QDoubleSpinBox { - background: #0c0e12; color: #f3f4f6; border: 1px solid #2a2e36; border-radius: 8px; padding: 4px 8px; + background: #10141a; color: #e7ebf2; border: 1px solid #2a3240; border-radius: 8px; padding: 4px 8px; } -QSlider::groove:horizontal { height: 6px; background: #2a2e36; border-radius: 3px; } +QPlainTextEdit#log { + background: #10141a; color: #b7c0cc; border: none; border-top: 1px solid #2a3240; border-radius: 0; +} +QDoubleSpinBox:focus, QLineEdit:focus { border-color: #3d7ed4; } +QSlider::groove:horizontal { height: 4px; background: #2a3240; border-radius: 2px; } QSlider::handle:horizontal { - width: 16px; margin: -6px 0; background: #4c86d4; border-radius: 8px; + width: 14px; margin: -6px 0; background: #5b9bff; border-radius: 7px; } -QScrollArea { background: transparent; } -QMessageBox { background: #12141a; } +QScrollArea { background: transparent; border: none; } +QStatusBar { background: #10141b; color: #8b95a7; } """ diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/operator_arm_mesh.py b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_arm_mesh.py new file mode 100644 index 0000000..20a7634 --- /dev/null +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_arm_mesh.py @@ -0,0 +1,339 @@ +"""用非夕给的 Rizon4 外壳网格摆出当前姿态。 + +网格和安装位姿来自 ``flexiv_description`` 的 ``visual_parameters.yaml`` +与 ``meshes/Rizon4/visual/*.obj``,不另画一套简图。面数按固定步长抽稀, +只为了界面里能转得动,几何仍是厂家的。 +""" + +from __future__ import annotations + +import math +from pathlib import Path + +import numpy as np +import yaml + +from arm_application.operator_arm_state import link_frames +from arm_application.site_io import ros2_root + +# 单个连杆原始面数大约 5000。再按 700 抽稀会在壳上留出三角洞, +# 白壳和灰蓝关节分不出来。这里保留全部面。 +_FACES_PER_LINK = 8000 +_MODELS: dict[str, list[tuple[list[tuple[float, float, float]], tuple]]] | None = None + + +def posed_triangles(q: list[float]) -> list[tuple]: + """返回基座系下的三角面 ``(a, b, c, 明暗, rgb)``。 + + ``rgb`` 来自同目录 ``.mtl`` 的 ``Kd``。外壳是白的,关节壳体是灰蓝, + 这是厂家导出的两种材质,不是另涂的颜色。 + """ + world, shade, rgb = _posed_arrays(q) + return [ + ( + (float(world[i, 0, 0]), float(world[i, 0, 1]), float(world[i, 0, 2])), + (float(world[i, 1, 0]), float(world[i, 1, 1]), float(world[i, 1, 2])), + (float(world[i, 2, 0]), float(world[i, 2, 1]), float(world[i, 2, 2])), + float(shade[i]), + (float(rgb[i, 0]), float(rgb[i, 1]), float(rgb[i, 2])), + ) + for i in range(shade.shape[0]) + ] + + +def posed_gl(q: list[float]) -> tuple[bytes, int, tuple[float, float, float], float]: + """给显卡的一份网格:交错 ``xyzrgb``、顶点数、中心、半径。 + + 拖视角只改观察矩阵,不再把四万个面在界面线程里重投影。 + """ + world, shade, rgb = _posed_arrays(q) + light = (0.55 + 0.45 * shade)[:, None] + colored = (rgb * light).astype(np.float32) + pos = world.reshape(-1, 3).astype(np.float32) + col = np.repeat(colored, 3, axis=0) + center = pos.mean(axis=0) + radius = float(np.linalg.norm(pos - center, axis=1).max()) + packed = np.concatenate([pos, col], axis=1).astype(np.float32, copy=False) + return ( + packed.tobytes(), + int(pos.shape[0]), + (float(center[0]), float(center[1]), float(center[2])), + max(radius, 1e-4), + ) + + +def _posed_arrays(q: list[float]): + """每个连杆做一次矩阵乘法,不再对每个顶点走 Python 循环。""" + frames = link_frames(q) + worlds: list[np.ndarray] = [] + shades: list[np.ndarray] = [] + colors: list[np.ndarray] = [] + for link, parts in _models().items(): + frame = frames.get(link) + if frame is None: + continue + rot = np.asarray(frame[0], dtype=np.float64) + origin = np.asarray(frame[1], dtype=np.float64) + for part in parts: + local = np.einsum("ij,fvj->fvi", part["erot"], part["verts"]) + part["epos"] + world = np.einsum("ij,fvj->fvi", rot, local) + origin + ab = world[:, 1] - world[:, 0] + ac = world[:, 2] - world[:, 0] + normal = np.cross(ab, ac) + norm = np.linalg.norm(normal, axis=1) + norm = np.where(norm > 0.0, norm, 1.0) + facing = np.abs(normal[:, 0] + normal[:, 1]) / (norm * math.sqrt(2.0)) + worlds.append(world) + shades.append(0.28 + 0.72 * facing) + colors.append(part["rgb"]) + if not worlds: + empty = np.zeros((0, 3, 3), dtype=np.float64) + return empty, np.zeros((0,), dtype=np.float64), np.zeros((0, 3), dtype=np.float64) + return np.concatenate(worlds), np.concatenate(shades), np.concatenate(colors) + + +def view_rotation(yaw: float, pitch: float): + """先绕竖直轴转,再俯仰。拖拽视角用这一份,不在界面里另写一套。""" + return _mul(_rx(pitch), _rz(yaw)) + + +def view_scale(radius: float, width: int, height: int) -> tuple[float, float]: + """和着色器里的 ``u_sx`` / ``u_sy`` 同一套缩放。宽屏时横向再缩一档。""" + fit = 0.85 / max(float(radius), 1e-4) + width = max(int(width), 1) + height = max(int(height), 1) + if width >= height: + return fit * height / width, fit + return fit, fit * width / height + + +def screen_point( + point: tuple[float, float, float], + yaw: float, + pitch: float, + center: tuple[float, float, float], + radius: float, + width: int, + height: int, +) -> tuple[float, float]: + """基座系的点投到控件像素。纵向和着色器里的 ``p.z`` 是同一套,+Z 朝画面上方。""" + shifted = ( + float(point[0]) - float(center[0]), + float(point[1]) - float(center[1]), + float(point[2]) - float(center[2]), + ) + viewed = _apply(view_rotation(yaw, pitch), shifted) + sx, sy = view_scale(radius, width, height) + ndc_x = viewed[0] * sx + # 着色器写的是 ``p.z * u_sy``。OpenGL 的 y 朝上,这里再换成 Qt 的像素 y。 + ndc_y = viewed[2] * sy + width = max(int(width), 1) + height = max(int(height), 1) + return (ndc_x * 0.5 + 0.5) * width, (1.0 - (ndc_y * 0.5 + 0.5)) * height + + +def ground_grid(extent: float = 1.0, step: float = 0.25) -> list[tuple[tuple[float, float, float], tuple[float, float, float]]]: + """地平面格子,z = 0,单位米。以前这张格子在 RViz 里,现在画在工位仿真上。""" + if step <= 0 or extent <= 0: + raise ValueError("地平面格子的间距和范围都要大于 0") + count = int(round(extent / step)) + lines = [] + for index in range(-count, count + 1): + coord = index * step + lines.append(((-extent, coord, 0.0), (extent, coord, 0.0))) + lines.append(((coord, -extent, 0.0), (coord, extent, 0.0))) + return lines + + +def axis_tip( + origin: tuple[float, float, float], + rot: tuple, + axis: int, + length: float, +) -> tuple[float, float, float]: + """坐标系某一轴的端点。``rot`` 把连杆坐标变到基座坐标,列向量就是三根轴。""" + basis = ((1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0))[axis] + direction = _apply(rot, basis) + return ( + origin[0] + direction[0] * length, + origin[1] + direction[1] * length, + origin[2] + direction[2] * length, + ) + + +def _place(rot, origin, erot, epos, vertex): + local = _add(epos, _apply(erot, vertex)) + return _add(origin, _apply(rot, local)) + + +def _shade(a, b, c) -> float: + ux, uy, uz = b[0] - a[0], b[1] - a[1], b[2] - a[2] + vx, vy, vz = c[0] - a[0], c[1] - a[1], c[2] - a[2] + nx = uy * vz - uz * vy + ny = uz * vx - ux * vz + nz = ux * vy - uy * vx + norm = math.sqrt(nx * nx + ny * ny + nz * nz) or 1.0 + facing = abs(nx + ny) / (norm * math.sqrt(2.0)) + return 0.28 + 0.72 * facing + + +def _models() -> dict[str, list]: + global _MODELS + if _MODELS is not None: + return _MODELS + root = _description_root() + visual = yaml.safe_load((root / "config/Rizon4/visual_parameters.yaml").read_text(encoding="utf-8")) + params = (visual or {}).get("visual_parameters") or {} + loaded: dict[str, list] = {} + for link, spec in params.items(): + shell = ((spec or {}).get("visual") or {}) + parts = [] + for kind in ("shell", "ring"): + item = shell.get(kind) + if not isinstance(item, dict): + continue + mesh = item.get("mesh") or {} + path = root / str(mesh.get("path") or "") + if not path.is_file(): + continue + scale = _scale(mesh.get("scale")) + origin = item.get("origin") or {} + erot = _rpy( + float(origin.get("roll") or 0.0), + float(origin.get("pitch") or 0.0), + float(origin.get("yaw") or 0.0), + ) + epos = ( + float(origin.get("x") or 0.0), + float(origin.get("y") or 0.0), + float(origin.get("z") or 0.0), + ) + parts.append(_part_arrays(_load_obj(path, scale, _materials(path)), erot, epos)) + if parts: + loaded[str(link)] = parts + if "base_link" not in loaded or "link7" not in loaded: + raise FileNotFoundError(f"Rizon4 外壳网格不完整:{root}") + _MODELS = loaded + return loaded + + +def _part_arrays(faces: list[tuple], erot, epos) -> dict: + """把一个零件收成数组,姿态更新时整块相乘。""" + if not faces: + verts = np.zeros((0, 3, 3), dtype=np.float64) + rgb = np.zeros((0, 3), dtype=np.float64) + else: + verts = np.asarray([[a, b, c] for a, b, c, _color in faces], dtype=np.float64) + rgb = np.asarray([color for _a, _b, _c, color in faces], dtype=np.float64) + return { + "verts": verts, + "rgb": rgb, + "erot": np.asarray(erot, dtype=np.float64), + "epos": np.asarray(epos, dtype=np.float64), + } + + +def _description_root() -> Path: + root = ros2_root() + candidates = ( + root / "src/flexiv_description", + root / "install/flexiv_description/share/flexiv_description", + ) + for item in candidates: + if (item / "config/Rizon4/visual_parameters.yaml").is_file(): + return item + raise FileNotFoundError("找不到 flexiv_description 的 Rizon4 外观") + + +def _materials(path: Path) -> dict[str, tuple[float, float, float]]: + mtl = path.with_suffix(".mtl") + colors: dict[str, tuple[float, float, float]] = {} + name = "" + if not mtl.is_file(): + return colors + for line in mtl.read_text(encoding="utf-8", errors="replace").splitlines(): + if line.startswith("newmtl "): + name = line.split(None, 1)[1].strip() + elif name and line.startswith("Kd "): + parts = line.split() + if len(parts) >= 4: + colors[name] = (float(parts[1]), float(parts[2]), float(parts[3])) + return colors + + +def _load_obj(path: Path, scale: tuple[float, float, float], materials: dict) -> list[tuple]: + fallback = (0.75, 0.76, 0.78) + rgb = fallback + vertices: list[tuple[float, float, float]] = [] + faces: list[tuple[int, int, int, tuple]] = [] + for line in path.read_text(encoding="utf-8", errors="replace").splitlines(): + if line.startswith("usemtl "): + rgb = materials.get(line.split(None, 1)[1].strip(), fallback) + elif line.startswith("v "): + _v, x, y, z, *_rest = line.split() + vertices.append((float(x) * scale[0], float(y) * scale[1], float(z) * scale[2])) + elif line.startswith("f "): + idx = [] + for token in line.split()[1:]: + raw = token.split("/")[0] + if not raw: + continue + number = int(raw) + idx.append(number - 1 if number > 0 else number) + for offset in range(1, len(idx) - 1): + faces.append((idx[0], idx[offset], idx[offset + 1], rgb)) + if not faces or not vertices: + return [] + step = max(1, len(faces) // _FACES_PER_LINK) + chosen = faces[::step] + out = [] + count = len(vertices) + for ia, ib, ic, color in chosen: + if not (0 <= ia < count and 0 <= ib < count and 0 <= ic < count): + continue + out.append((vertices[ia], vertices[ib], vertices[ic], color)) + return out + + +def _scale(raw) -> tuple[float, float, float]: + if raw is None: + return (1.0, 1.0, 1.0) + if isinstance(raw, (int, float)): + value = float(raw) + return (value, value, value) + text = str(raw).replace(",", " ").split() + nums = [float(item) for item in text[:3]] + while len(nums) < 3: + nums.append(nums[-1] if nums else 1.0) + return (nums[0], nums[1], nums[2]) + + +def _rpy(roll: float, pitch: float, yaw: float): + return _mul(_mul(_rz(yaw), _ry(pitch)), _rx(roll)) + + +def _rx(angle: float): + c, s = math.cos(angle), math.sin(angle) + return ((1.0, 0.0, 0.0), (0.0, c, -s), (0.0, s, c)) + + +def _ry(angle: float): + c, s = math.cos(angle), math.sin(angle) + return ((c, 0.0, s), (0.0, 1.0, 0.0), (-s, 0.0, c)) + + +def _rz(angle: float): + c, s = math.cos(angle), math.sin(angle) + return ((c, -s, 0.0), (s, c, 0.0), (0.0, 0.0, 1.0)) + + +def _mul(a, b): + return tuple(tuple(sum(a[i][k] * b[k][j] for k in range(3)) for j in range(3)) for i in range(3)) + + +def _apply(rot, vec): + return tuple(sum(rot[i][k] * vec[k] for k in range(3)) for i in range(3)) + + +def _add(a, b): + return (a[0] + b[0], a[1] + b[1], a[2] + b[2]) diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/operator_arm_state.py b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_arm_state.py new file mode 100644 index 0000000..d91bd37 --- /dev/null +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_arm_state.py @@ -0,0 +1,595 @@ +"""工位界面用的机械臂状态:只读使能 / Remote,并用关节角画出姿态。 + +不会调用 Enable,也不会在控制栈或拖拽桥已经占着 RDK 时再连一次。 +``robot.mode()`` 看不出 Manual,必须看 ``operational_status``。 +示教器上的 Auto(Remote)没有远程切换接口,这里只负责把这句话摆在界面上。 +""" + +from __future__ import annotations + +import json +import math +import os +import re +import subprocess +import threading +import time +from pathlib import Path +from typing import Any + +import yaml + +from arm_application.site_io import listed_arms, ros2_root + +# 和 scripts/lib/rdk_status.py 的 MANUAL_HINT 同一件事,界面上始终可见。 +REMOTE_PROMPT = ( + "请在示教器上打开 Auto(Remote)。" + "RDK 没有远程拨这个开关的接口,必须人到现场拨。" +) + +_AXES = ( + (0.0, 0.0, 1.0), + (0.0, 1.0, 0.0), + (0.0, 0.0, 1.0), + (0.0, 1.0, 0.0), + (0.0, 0.0, 1.0), + (0.0, 1.0, 0.0), + (0.0, 0.0, 1.0), +) +_JOINTS = tuple(f"joint{i}" for i in range(1, 8)) +_LOCK = threading.Lock() +_CACHE: dict[str, dict] = {} +_STOP = False +_STARTED = False +_HOLD = 0 +_HOLD_LOCK = threading.Lock() + + +def _empty(arm: str) -> dict: + return { + "arm": arm, + "level": "warn", + "enabled": False, + "summary": "还没读到这台臂的状态", + "prompt": REMOTE_PROMPT, + "prompt_hot": True, + "mode": "", + "op_status": "", + "fault": None, + "estop_released": None, + "q": None, + "points": [], + "source": "", + "stack_up": False, + "stamp": 0.0, + } + + +def snapshot(arm: str) -> dict: + with _LOCK: + row = _CACHE.get(arm) + return dict(row) if row else _empty(arm) + + +def present( + arm: str, + *, + stack_up: bool, + bridge_up: bool, + probe: dict | None, + joints: list[float] | None, + error: str = "", +) -> dict: + """把一次读数收成界面直接能画的字典。纯函数,不连机器人。""" + probe = probe or {} + q = _seven(joints) or _seven(probe.get("q")) + op_status = str(probe.get("op_status") or "") + mode = str(probe.get("mode") or "") + manual = "MANUAL" in op_status.upper() + if stack_up and q is not None: + enabled = True + level = "ok" + summary = "控制栈在发关节角,这台臂已经接上。" + if probe.get("_shared_stack"): + summary = "控制栈在发关节角,但看不出是 89 还是 91,两台显示的是同一份。" + source = "控制栈 /joint_states" + elif stack_up: + enabled = False + level = "fail" + summary = "控制栈在跑,但没有关节数据。通常是还没使能,一直在等 operational。" + source = "控制栈" + elif bridge_up and not probe: + enabled = False + level = "warn" + summary = "软拖拽桥占着 RDK,界面不再另开一条连接。" + source = "拖拽桥" + q = None + elif probe.get("connected"): + enabled = bool(probe.get("operational")) + level = str(probe.get("level") or ("ok" if enabled else "fail")) + summary = str(probe.get("summary") or ("已使能" if enabled else "未使能")) + source = "RDK 直读(未使能,只查询)" + else: + enabled = False + level = "fail" if error else "warn" + summary = error or str(probe.get("summary") or probe.get("error") or "还没连上 RDK") + source = "RDK 直读" if probe else "" + hot = (not enabled) or manual + return { + "arm": arm, + "level": level, + "enabled": enabled, + "summary": summary, + "prompt": REMOTE_PROMPT, + "prompt_hot": hot, + "mode": mode, + "op_status": op_status, + "fault": probe.get("fault") if probe else None, + "estop_released": probe.get("estop_released") if probe else None, + "q": q, + "points": fk_points(q) if q else fk_points([0.0] * 7), + "pose_live": q is not None, + "source": source, + "stack_up": stack_up, + "stamp": time.time(), + } + + +def link_frames(q: list[float]) -> dict[str, tuple]: + """各连杆坐标系,和 ``rizon_arm.xacro`` 的关节顺序一致。 + + 返回 ``base_link`` 与 ``link1``…``link7``。每个值是 ``(旋转, 原点)``, + 旋转把连杆坐标变到基座坐标。厂家网格就画在这些坐标系里。 + """ + kin = _kinematics() + rot = ((1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)) + pos = (0.0, 0.0, 0.0) + frames: dict[str, tuple] = {"base_link": (rot, pos)} + for index, angle in enumerate(q[:7]): + spec = kin[_JOINTS[index]] + pos = _add(pos, _apply(rot, (spec["x"], spec["y"], spec["z"]))) + rot = _mul(rot, _rpy(spec["roll"], spec["pitch"], spec["yaw"])) + rot = _mul(rot, _axis_rot(_AXES[index], float(angle))) + frames[f"link{index + 1}"] = (rot, pos) + return frames + + +def fk_points(q: list[float]) -> list[tuple[float, float, float]]: + """按 Rizon4 的 default_kinematics 做正运动学,返回基座到法兰的点(米)。 + + 关节轴与 ``rizon_arm.xacro`` 一致:1/3/5/7 绕 z,2/4/6 绕 y。 + URDF 顺序是先平移(父坐标系),再 rpy,再绕关节轴转 q。 + """ + frames = link_frames(q) + points = [frames["base_link"][1]] + points.extend(frames[f"link{index}"][1] for index in range(1, 8)) + _rot, pos = _flange_pose(frames) + points.append(pos) + return points + + +def display_frames(q: list[float], workpiece: tuple | None = None) -> list[dict]: + """界面要画的坐标系,都在基座系里。 + + 第一套是基座。第二套默认是法兰(旋转与 ``link7`` 相同,原点再加 + ``link7_to_flange``)。传入工件系时,第二套改画在工件上,不再画法兰。 + """ + frames = link_frames(q) + base_rot, base_pos = frames["base_link"] + shown = [{"name": "基座", "rot": base_rot, "pos": base_pos}] + if workpiece is None: + rot, pos = _flange_pose(frames) + shown.append({"name": "法兰", "rot": rot, "pos": pos}) + return shown + rot, pos = workpiece + if not isinstance(rot, tuple) or len(rot) != 3 or not isinstance(pos, tuple) or len(pos) != 3: + raise ValueError("工件坐标系要有旋转和原点") + shown.append({"name": "工件", "rot": rot, "pos": pos}) + return shown + + +def workpiece_frame(robot: dict | None) -> tuple | None: + """有完整工件系时返回 ``(旋转, 原点)``。没有就返回 None。 + + ``workpiece_surface_z_m`` 只是上表面高度,没有原点的 XY,也没有朝向, + 不能当成坐标系。配了 ``gauge_assembly.workpiece_frame`` 却缺 ``xyz`` 或 + ``rpy`` 时直接报错,不用单位阵把缺的朝向补上。 + ``xyz`` 单位米,``rpy`` 单位弧度,都写在基座系,旋转顺序与连杆 ``rpy`` 相同。 + """ + if not isinstance(robot, dict): + return None + gauge = robot.get("gauge_assembly") + if not isinstance(gauge, dict) or "workpiece_frame" not in gauge: + return None + raw = gauge.get("workpiece_frame") + if not isinstance(raw, dict): + raise ValueError("工件坐标系要写成 xyz 和 rpy,各三个数") + xyz = raw.get("xyz") + rpy = raw.get("rpy") + if not _three_floats(xyz) or not _three_floats(rpy): + raise ValueError("工件坐标系要有 xyz 和 rpy,各三个数,单位是米和弧度,写在基座系里") + return _rpy(float(rpy[0]), float(rpy[1]), float(rpy[2])), ( + float(xyz[0]), float(xyz[1]), float(xyz[2]), + ) + + +def _flange_pose(frames: dict) -> tuple: + """法兰位姿。旋转就是 ``link7`` 的旋转,平移来自 ``link7_to_flange``。""" + rot, pos = frames["link7"] + flange = _kinematics()["link7_to_flange"] + pos = _add(pos, _apply(rot, (float(flange["x"]), float(flange["y"]), float(flange["z"])))) + return rot, pos + + +def _three_floats(raw: Any) -> bool: + if not isinstance(raw, (list, tuple)) or len(raw) != 3: + return False + return all(isinstance(item, (int, float)) and not isinstance(item, bool) for item in raw) + + +def stack_running() -> bool: + return _stack_arm() is not None or _alive("ros2_control_node") + + +def control_holds_rdk() -> bool: + """有控制命令在用 RDK 时,状态轮询必须让开。""" + with _HOLD_LOCK: + return _HOLD > 0 + + +def release_for_control() -> None: + """停掉正在读状态的 RDK 子进程,把机器人交给控制命令。""" + with _HOLD_LOCK: + global _HOLD + _HOLD += 1 + _stop_status_probes() + deadline = time.time() + 3.0 + while time.time() < deadline and _status_probe_running(): + time.sleep(0.05) + + +def resume_after_control() -> None: + with _HOLD_LOCK: + global _HOLD + _HOLD = max(0, _HOLD - 1) + + +def hold_until_idle(pid: int, *, until_stack: bool = False) -> None: + """后台脚本还在时继续占着 RDK。进程退出,或控制栈已经起来,就放开。""" + def _watch() -> None: + while True: + if until_stack and stack_running(): + break + if not _pid_alive(pid): + break + time.sleep(0.3) + resume_after_control() + + threading.Thread(target=_watch, daemon=True, name="arm-rdk-hold").start() + + +def motion_blocked(action: str, *, stack_up: bool, op_status: str) -> str: + """控制栈没起来时,动臂按钮给一句能执行的原因,而不是把脚本挂在那儿。""" + needs_stack = { + "pipeline", "stage_track", "stage_locate", "stage_screw", + "reset_home", "gauge_run", "handeye_calibrate", + } + if action not in needs_stack or stack_up: + return "" + manual = "MANUAL" in str(op_status or "").upper() + tail = "示教器现在是 Manual,要先拨到 Auto(Remote)。" if manual else "" + return f"控制栈没起来,这个动作动不了臂。{tail}请先在操作页执行「上电控制栈」。" + + +def start() -> None: + global _STARTED + with _LOCK: + if _STARTED: + return + _STARTED = True + threading.Thread(target=_loop, daemon=True, name="arm-state").start() + + +def retain_pose(fresh: dict, previous: dict | None, *, now: float | None = None, hold_s: float = 15.0) -> dict: + """这一拍没读到关节角时,短时间沿用上一拍,避免模型被打回零位。 + + 超过 ``hold_s`` 仍没有新读数就放开,界面回到“没连上”,不把过期姿态一直当成当前。 + """ + if fresh.get("q") is not None or not previous or previous.get("q") is None: + return fresh + moment = time.time() if now is None else now + if moment - float(previous.get("stamp") or 0.0) > hold_s: + return fresh + kept = dict(fresh) + kept["q"] = list(previous["q"]) + kept["points"] = list(previous.get("points") or []) + kept["pose_live"] = True + note = "关节角沿用上次读数。" + summary = str(kept.get("summary") or "") + if note not in summary: + kept["summary"] = f"{summary} {note}".strip() + return kept + + +def _loop() -> None: + while not _STOP: + if control_holds_rdk(): + time.sleep(0.2) + continue + try: + arms = _arms() + if not arms: + time.sleep(2.0) + continue + stack_arm = _stack_arm() + stack_up = stack_arm is not None or _alive("ros2_control_node") + bridge_up = _alive("rdk_drag_bridge.py") + joints = _read_joints() if stack_up else None + if stack_up: + targets = [row for row in arms if stack_arm is None or row["id"] == stack_arm] + if not targets: + targets = arms + shared = stack_arm is None + for row in targets: + fresh = present( + row["id"], + stack_up=True, + bridge_up=bridge_up, + probe={"_shared_stack": True} if shared else None, + joints=joints, + ) + _store(retain_pose(fresh, snapshot(row["id"]))) + else: + for row in arms: + probe: dict | None = None + error = "" + if control_holds_rdk(): + break + if not bridge_up: + probe, error = _probe(row["sn"], row["iface"], row["pc_ip"]) + fresh = present( + row["id"], + stack_up=False, + bridge_up=bridge_up, + probe=probe, + joints=None, + error=error, + ) + _store(retain_pose(fresh, snapshot(row["id"]))) + except Exception as exc: # noqa: BLE001 —— 读状态失败不能把界面线程打死 + for row in _arms(): + _store(present( + row["id"], stack_up=False, bridge_up=False, probe=None, joints=None, error=str(exc), + )) + time.sleep(4.0) + + +def _stop_status_probes() -> None: + try: + subprocess.run( + ["pkill", "-f", "scripts/lib/rdk_status.py"], + capture_output=True, text=True, timeout=2, + ) + except (OSError, subprocess.TimeoutExpired): + return + + +def _status_probe_running() -> bool: + return "rdk_status.py" in _pgrep("scripts/lib/rdk_status.py") + + +def _pid_alive(pid: int) -> bool: + if pid <= 0: + return False + try: + os.kill(pid, 0) + except OSError: + return False + return True + + +def _store(row: dict) -> None: + with _LOCK: + _CACHE[str(row.get("arm") or "")] = row + + +def _arms() -> list[dict]: + path = ros2_root() / "config" / "site.yaml" + if not path.is_file(): + return [] + data = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + return [ + {"id": row["id"], "sn": row["sn"], "iface": row["iface"], "pc_ip": row["pc_ip"]} + for row in listed_arms(data) + ] + + +def _stack_arm() -> str | None: + text = _pgrep("run_real.sh") + ids = {row["id"] for row in _arms()} + for match in re.finditer(r"--arm(?:=|\s+)(\S+)", text): + token = match.group(1) + if token in ids: + return token + return None + + +def _alive(pattern: str) -> bool: + return bool(_pgrep(pattern).strip()) + + +def _pgrep(pattern: str) -> str: + try: + proc = subprocess.run( + ["pgrep", "-af", pattern], + capture_output=True, text=True, timeout=2, + ) + except (OSError, subprocess.TimeoutExpired): + return "" + lines = [] + for line in proc.stdout.splitlines(): + if "pgrep" in line: + continue + lines.append(line) + return "\n".join(lines) + + +def _read_joints() -> list[float] | None: + try: + proc = subprocess.run( + ["timeout", "4", "ros2", "topic", "echo", "/joint_states", "--once"], + capture_output=True, text=True, timeout=6, + ) + except (OSError, subprocess.TimeoutExpired): + return None + return _parse_joint_echo(proc.stdout) + + +def _parse_joint_echo(text: str) -> list[float] | None: + for chunk in text.split("---"): + if "position:" not in chunk: + continue + try: + data = yaml.safe_load(chunk) + except yaml.YAMLError: + continue + if not isinstance(data, dict): + continue + names = list(data.get("name") or []) + pos = list(data.get("position") or []) + if names and len(names) == len(pos): + by = {str(name): float(value) for name, value in zip(names, pos)} + q = [] + for joint in _JOINTS: + key = next((name for name in by if name == joint or name.endswith(joint)), None) + if key is None: + q = [] + break + q.append(by[key]) + if len(q) == 7: + return q + if len(pos) >= 7: + try: + return [float(v) for v in pos[:7]] + except (TypeError, ValueError): + return None + return None + + +def _probe(sn: str, iface: str, pc_ip: str = "") -> tuple[dict | None, str]: + python = os.environ.get("RDK_PYTHON", "").strip() or str(Path.home() / "rdk_py310" / "bin" / "python") + script = ros2_root() / "scripts" / "lib" / "rdk_status.py" + if not Path(python).is_file() or not script.is_file(): + return None, "找不到 RDK 解释器或 rdk_status.py,无法读使能状态" + cmd = _probe_cmd(python, script, sn, iface, pc_ip) + try: + proc = subprocess.run(cmd, capture_output=True, text=True, timeout=8) + except subprocess.TimeoutExpired: + return None, "读 RDK 超时。若控制栈没开,检查网线,并确认示教器已开 Remote。" + except OSError as exc: + return None, f"读 RDK 失败:{exc}" + payload = _json_object(proc.stdout) or _json_object(proc.stderr) + if not payload: + tail = (proc.stdout + proc.stderr).strip()[-240:] + return None, tail or "RDK 没有返回状态" + return payload, "" + + +def _probe_cmd(python: str, script: Path, sn: str, iface: str, pc_ip: str) -> list[str]: + """RDK 白名单要本机 IPv4。只传网卡名 ``eth0`` 时,内核改名叫 enx… 就永远连不上。""" + cmd = [python, str(script), "--sn", sn, "--json"] + if iface: + cmd += ["--iface", iface] + if pc_ip: + cmd += ["--pc-ip", pc_ip] + return cmd + + +def _json_object(text: str) -> dict: + decoder = json.JSONDecoder() + for index, char in enumerate(text): + if char != "{": + continue + try: + data, _end = decoder.raw_decode(text[index:]) + except json.JSONDecodeError: + continue + if isinstance(data, dict) and "connected" in data: + return data + return {} + + +_KIN: dict[str, dict[str, float]] | None = None + + +def _kinematics() -> dict[str, dict[str, float]]: + global _KIN + if _KIN is not None: + return _KIN + root = ros2_root() + candidates = ( + root / "src/flexiv_description/config/Rizon4/default_kinematics.yaml", + root / "install/flexiv_description/share/flexiv_description/config/Rizon4/default_kinematics.yaml", + ) + path = next((item for item in candidates if item.is_file()), None) + if path is None: + raise FileNotFoundError("找不到 Rizon4 default_kinematics.yaml") + data = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + kin = data.get("kinematics") or {} + _KIN = kin + return kin + + +def _rpy(roll: float, pitch: float, yaw: float) -> tuple: + return _mul(_mul(_rz(yaw), _ry(pitch)), _rx(roll)) + + +def _rx(angle: float) -> tuple: + c, s = math.cos(angle), math.sin(angle) + return ((1.0, 0.0, 0.0), (0.0, c, -s), (0.0, s, c)) + + +def _ry(angle: float) -> tuple: + c, s = math.cos(angle), math.sin(angle) + return ((c, 0.0, s), (0.0, 1.0, 0.0), (-s, 0.0, c)) + + +def _rz(angle: float) -> tuple: + c, s = math.cos(angle), math.sin(angle) + return ((c, -s, 0.0), (s, c, 0.0), (0.0, 0.0, 1.0)) + + +def _axis_rot(axis: tuple[float, float, float], angle: float) -> tuple: + x, y, z = axis + if abs(z) > 0.5: + return _rz(angle if z > 0 else -angle) + if abs(y) > 0.5: + return _ry(angle if y > 0 else -angle) + return _rx(angle if x > 0 else -angle) + + +def _mul(a: tuple, b: tuple) -> tuple: + return tuple( + tuple(sum(a[i][k] * b[k][j] for k in range(3)) for j in range(3)) + for i in range(3) + ) + + +def _apply(rot: tuple, vec: tuple[float, float, float]) -> tuple[float, float, float]: + return tuple(sum(rot[i][k] * vec[k] for k in range(3)) for i in range(3)) # type: ignore[return-value] + + +def _add(a: tuple[float, float, float], b: tuple[float, float, float]) -> tuple[float, float, float]: + return (a[0] + b[0], a[1] + b[1], a[2] + b[2]) + + +def _seven(raw: Any) -> list[float] | None: + if not isinstance(raw, list) or len(raw) < 7: + return None + try: + q = [float(v) for v in raw[:7]] + except (TypeError, ValueError): + return None + if any(v != v or abs(v) > 12.6 for v in q): + return None + return q diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/operator_camera_view.py b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_camera_view.py index da4744e..217a4ed 100644 --- a/install/arm_application/lib/python3.11/site-packages/arm_application/operator_camera_view.py +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_camera_view.py @@ -1,6 +1,12 @@ """工位程序里的相机预览。 只订彩色和已对齐深度,不订左右红外。画面留在本进程,不另开查看器窗口。 + +回调里先丢掉当前页没在看的流。彩色按相机原分辨率收进来,不按时钟抽帧, +也不在收图的线程里做颜色转换。另一条线只画最新的一张,所以帧率跟相机走, +848 和 1280 都不会为了帧率被缩成 480。深度是伪彩色,仍然抽稀, +而且没打开深度时不订阅,避免大图挤占彩色。 +手眼页要 JPEG 时也按原分辨率编码,只编正在看的那一路。 """ from __future__ import annotations @@ -11,33 +17,121 @@ import time import numpy as np +# 彩色上限。D405 是 848×480,D435 是 1280×720,都不超过它,所以不会被缩小。 +_COLOR_WIDTH = 1280 +# 深度上色不是要看的画质。全幅每帧上色会把刷新占满,所以这一路仍抽稀。 +_DEPTH_WIDTH = 480 +# 手眼只在正在看的那一页编码,宽度跟彩色预览一样,不另缩一档。 +_JPEG_WIDTH = 1280 +# 只留给还在用 ``accept_frame`` 的调用。彩色实况不再用它抽帧: +# 相机大约 30 帧,再按这个间隔丢一次,画面会掉到十几帧。 +_INTERVAL = 1.0 / 30.0 +_DET_HOLD_S = 1.5 + _LOCK = threading.Lock() _STOP = threading.Event() _THREAD: threading.Thread | None = None _JPEG: dict[str, bytes] = {} +_PREVIEW: dict[str, tuple[bytes, int, int]] = {} +_PREVIEW_WANT: set[str] = set() +_JPEG_WANT: set[str] = set() +_NEXT: dict[str, float] = {} +_DET: dict[str, tuple[float, list[float]]] = {} +# 没画框的预览。检测一到就在这张上重画,不必等下一帧相机。 +_CLEAN: dict[str, dict] = {} +# 每路最近一秒的到达时刻,用来算帧率。值是 (宽, 高, 帧/秒),宽高是相机原图。 +_META: dict[str, tuple[int, int, float]] = {} +_STAMPS: dict[str, list[float]] = {} +# 每路最新原图。收图线程只往这里放,画图线程只取最新的一张。 +_RAW: dict[str, dict] = {} +_SLOTS: dict[str, list] = {} +_SLOT_AT: dict[str, int] = {} +_PAINTED: dict[str, int] = {} +_WAKE = threading.Event() +_PAINT_THREAD: threading.Thread | None = None _STATE = {"on": False, "error": "", "frames": {}, "stamp": 0.0} -def targets() -> list[dict]: - """真机腕部相机和手外相机。仿真相机不进预览。""" - from arm_application.operator_console import _arms, _site - from arm_application.site_io import overhead_camera +def preview_targets(site: dict) -> list[dict]: + """主界面只要左臂腕部相机和共用的手外相机。 + + 右臂腕部不进这一屏。仿真相机名字以 ``sim`` 开头,也不进。 + 推理框的角色仍是 ``wrist`` / ``overhead``,跟话题对得上。 + """ + from arm_application.site_io import listed_arms, overhead_camera found: list[dict] = [] seen: set[str] = set() - for arm in _arms(): - name = str(arm.get("camera") or "").strip().strip("/") - if not name or name.startswith("sim") or name in seen: - continue + left = next((row for row in listed_arms(site) if row.get("side") == "left"), None) + name = str((left or {}).get("camera") or "").strip().strip("/") + if name and not name.startswith("sim"): + cams = site.get("cameras") if isinstance(site.get("cameras"), dict) else {} + cfg = cams.get(name) if isinstance(cams, dict) and isinstance(cams.get(name), dict) else {} + tag = str((cfg or {}).get("model") or "D405").strip().upper() or "D405" seen.add(name) - found.append({"id": name, "label": f"{arm.get('label') or name} 腕部"}) - cam, _others = overhead_camera(_site(), None) - name = str((cam or {}).get("name") or "").strip().strip("/") - if name and not name.startswith("sim") and name not in seen: - found.append({"id": name, "label": str((cam or {}).get("label") or "手外相机")}) + found.append({"id": name, "label": f"左腕 {tag}", "role": "wrist"}) + cam, _others = overhead_camera(site or {}, None) + oname = str((cam or {}).get("name") or "").strip().strip("/") + if oname and not oname.startswith("sim") and oname not in seen: + tag = str((cam or {}).get("model") or "D435").strip().upper() or "D435" + found.append({"id": oname, "label": f"手外 {tag}", "role": "overhead"}) return found +def sim_preview_targets(site: dict) -> list[dict]: + """仿真腕部和仿真手外。画面进主界面,不另开 Gazebo 的相机窗。""" + from arm_application.site_io import overhead_camera + + found: list[dict] = [] + robots = ((site or {}).get("robot") or {}).get("robots") or [] + cams = site.get("cameras") if isinstance(site.get("cameras"), dict) else {} + for robot in robots: + if not isinstance(robot, dict): + continue + if str(robot.get("sn") or "").strip().lower() != "sim": + continue + cam = robot.get("camera") if isinstance(robot.get("camera"), dict) else {} + name = str((cam or {}).get("name") or "").strip().strip("/") + if not name: + continue + cfg = cams.get(name) if isinstance(cams, dict) and isinstance(cams.get(name), dict) else {} + tag = str((cfg or {}).get("model") or "D405").strip().upper() or "D405" + found.append({"id": name, "label": f"仿真左腕 {tag}", "role": "wrist", "source": "sim"}) + break + overhead, _others = overhead_camera(site or {}, {"arm": "sim"}) + oname = str((overhead or {}).get("name") or "").strip().strip("/") + if oname.startswith("sim"): + tag = str((overhead or {}).get("model") or "D435").strip().upper() or "D435" + found.append({"id": oname, "label": f"仿真手外 {tag}", "role": "overhead", "source": "sim"}) + return found + + +def stream_targets(site: dict | None = None) -> list[dict]: + """真机和仿真都订上。主界面切换看哪一组,没在看的那组回调里直接丢掉。""" + if site is None: + from arm_application.operator_console import _site + + site = _site() + real = preview_targets(site) + for item in real: + item["source"] = "real" + seen = {item["id"] for item in real} + return real + [item for item in sim_preview_targets(site) if item["id"] not in seen] + + +def targets() -> list[dict]: + """当前站点的主界面真机相机。规则在 ``preview_targets``。""" + from arm_application.operator_console import _site + + return preview_targets(_site()) + + +def sim_targets() -> list[dict]: + from arm_application.operator_console import _site + + return sim_preview_targets(_site()) + + def state() -> dict: with _LOCK: return { @@ -53,141 +147,492 @@ def frame(name: str, kind: str) -> bytes | None: return _JPEG.get(f"{name}_{kind}") +def previews() -> dict[str, tuple[bytes, int, int]]: + """当前预览。值是 ``(rgb888, 宽, 高)``,同一帧反复取到的是同一块字节。""" + with _LOCK: + return dict(_PREVIEW) + + +def preview_meta() -> dict[str, tuple[int, int, float]]: + """每路相机原图的宽、高,以及最近一秒的帧率。还没来帧就是空。""" + with _LOCK: + return dict(_META) + + +def _mark_rate(key: str, width: int, height: int) -> None: + """记下这一帧的原图尺寸,并用最近一秒的到达间隔算帧率。""" + now = time.monotonic() + with _LOCK: + stamps = _STAMPS.setdefault(key, []) + stamps.append(now) + cut = now - 1.0 + while len(stamps) > 1 and stamps[0] < cut: + stamps.pop(0) + if len(stamps) >= 2 and stamps[-1] > stamps[0]: + fps = (len(stamps) - 1) / (stamps[-1] - stamps[0]) + else: + fps = 0.0 + _META[key] = (int(width), int(height), float(fps)) + + +def watch(preview: set[str], jpeg: set[str] | None = None) -> None: + """当前页要哪几路。没点名的流在回调里直接丢掉,不拷图像。 + + ``preview`` 走原分辨率 RGB,``jpeg`` 走手眼用的 JPEG。两边都空着就只保持订阅。 + """ + global _PREVIEW_WANT, _JPEG_WANT + jpeg = set(jpeg or ()) + preview = set(preview) + with _LOCK: + if preview == _PREVIEW_WANT and jpeg == _JPEG_WANT: + return + _PREVIEW_WANT = preview + _JPEG_WANT = jpeg + for key in preview | jpeg: + _NEXT.pop(key, None) + + +def wants(key: str) -> tuple[bool, bool]: + """返回 ``(要预览, 要 JPEG)``。""" + with _LOCK: + return key in _PREVIEW_WANT, key in _JPEG_WANT + + +def offer_color(key: str, image: np.ndarray, *, bgr: bool, role: str) -> None: + """收一帧彩色原图。没人看就丢掉。有人看就整幅留下,不按时钟抽帧。""" + preview, jpeg = wants(key) + if not preview and not jpeg: + return + if image.ndim != 3 or image.shape[2] != 3 or image.shape[0] < 1 or image.shape[1] < 1: + return + _offer(key, image, bgr=bgr, role=role, kind="color") + + +def offer_depth(key: str, image: np.ndarray) -> None: + """收一帧深度原图。伪彩色仍在画图线程里做,不堵收图。""" + preview, _jpeg = wants(key) + if not preview: + return + if image.ndim != 2 or image.shape[0] < 1 or image.shape[1] < 1: + return + _offer(key, image, bgr=False, role="", kind="depth") + + +def _offer(key: str, image: np.ndarray, *, bgr: bool, role: str, kind: str) -> None: + height, width = int(image.shape[0]), int(image.shape[1]) + with _LOCK: + slots = _SLOTS.setdefault(key, [None, None]) + index = _SLOT_AT.get(key, 0) + slot = slots[index] + if slot is None or slot.shape != image.shape or slot.dtype != image.dtype: + slot = np.empty(image.shape, dtype=image.dtype) + slots[index] = slot + np.copyto(slot, image) + seq = int(_RAW.get(key, {}).get("seq", 0)) + 1 + _RAW[key] = { + "seq": seq, + "frame": slot, + "bgr": bool(bgr), + "role": role, + "kind": kind, + "w": width, + "h": height, + } + _SLOT_AT[key] = 1 - index + _mark_rate(key, width, height) + _WAKE.set() + + +def publish_latest() -> None: + """把各路还没画的最新原图做成预览。中间帧直接跳过,避免越画越落后。""" + with _LOCK: + jobs = [] + for key, item in _RAW.items(): + if _PAINTED.get(key) == item["seq"]: + continue + jobs.append(( + key, + int(item["seq"]), + np.array(item["frame"], copy=True), + bool(item["bgr"]), + str(item["role"]), + str(item["kind"]), + int(item["w"]), + int(item["h"]), + )) + for key, seq, frame, bgr, role, kind, width, height in jobs: + try: + preview, jpeg = wants(key) + if kind == "depth": + if preview: + colored = depth_preview(frame) + _store(key, colored, preview=colored, jpeg=False) + elif preview or jpeg: + limit = _JPEG_WIDTH if jpeg else _COLOR_WIDTH + rgb = rgb_preview(frame, bgr=bgr, limit=limit) + shown = rgb + if preview and int(rgb.shape[1]) > _COLOR_WIDTH: + shown = rgb_preview(rgb, bgr=False, limit=_COLOR_WIDTH) + if preview: + _remember_clean(key, shown, role, width, height) + shown = _overlay_detection(shown, role, width, height) + _store(key, rgb, preview=shown if preview else None, jpeg=jpeg) + except Exception as exc: # noqa: BLE001 —— 坏的一帧留在状态里,下一帧继续 + with _LOCK: + _STATE["error"] = str(exc) + finally: + with _LOCK: + if _PAINTED.get(key, 0) < seq: + _PAINTED[key] = seq + + +def _paint_loop() -> None: + while not _STOP.is_set(): + _WAKE.wait(0.005) + _WAKE.clear() + if _STOP.is_set(): + return + try: + publish_latest() + except Exception as exc: # noqa: BLE001 —— 画图线程不能因为一帧坏掉就退出 + with _LOCK: + _STATE["error"] = str(exc) + + +def accept_frame(key: str) -> bool: + """这一帧要不要做进预览。没人看,或者距上一帧不到间隔,都返回 False。 + + 彩色实况不再走这里。固定间隔会把相机自己的帧率再砍掉一截。 + """ + now = time.monotonic() + with _LOCK: + if key not in _PREVIEW_WANT and key not in _JPEG_WANT: + return False + if now < _NEXT.get(key, 0.0): + return False + _NEXT[key] = now + _INTERVAL + return True + + def _close_external_viewers() -> None: """关掉独立的四窗格查看器。它会把左右红外挂出来,本程序不再用它。""" subprocess.run(["pkill", "-f", "lib/arm_application/realsense_view"], check=False) subprocess.run(["pkill", "-f", "arm_application.realsense_view"], check=False) -def _jpeg(img: np.ndarray) -> bytes | None: +def paint_detection( + rgb: np.ndarray, + xyxy: list[float] | tuple, + src_width: int, + src_height: int, +) -> np.ndarray: + """把推理结果画到相机预览上:绿框、画面中心、跟踪红点、置信度和距离。 + + ``xyxy`` 前四项是原图像素的框。够长时第 5 项是置信度,第 7、8 项是跟踪点, + 第 9 项是距离(米)。空框原样返回。 + """ import cv2 - if img.size == 0: + if rgb.ndim != 3 or src_width <= 0 or src_height <= 0 or len(xyxy) < 4: + return rgb + scale_x = rgb.shape[1] / float(src_width) + scale_y = rgb.shape[0] / float(src_height) + x1 = int(round(float(xyxy[0]) * scale_x)) + y1 = int(round(float(xyxy[1]) * scale_y)) + x2 = int(round(float(xyxy[2]) * scale_x)) + y2 = int(round(float(xyxy[3]) * scale_y)) + height, width = rgb.shape[:2] + x1 = max(0, min(width - 1, x1)) + x2 = max(0, min(width - 1, x2)) + y1 = max(0, min(height - 1, y1)) + y2 = max(0, min(height - 1, y2)) + if x2 <= x1 or y2 <= y1: + return rgb + out = np.array(rgb, copy=True) + thick = max(2, width // 180) + cv2.rectangle(out, (x1, y1), (x2, y2), (80, 220, 120), thick) + cv2.drawMarker( + out, + (width // 2, height // 2), + (255, 220, 60), + cv2.MARKER_CROSS, + max(8, width // 24), + 1, + ) + if len(xyxy) >= 8: + px = int(round(float(xyxy[6]) * scale_x)) + py = int(round(float(xyxy[7]) * scale_y)) + px = max(0, min(width - 1, px)) + py = max(0, min(height - 1, py)) + cv2.circle(out, (px, py), max(3, width // 80), (255, 48, 48), -1) + if len(xyxy) >= 5: + conf = float(xyxy[4]) + dist = f" z={float(xyxy[8]):.3f}m" if len(xyxy) >= 9 else "" + cv2.putText( + out, + f"{conf:.2f}{dist}", + (x1, max(14, y1 - 6)), + cv2.FONT_HERSHEY_SIMPLEX, + 0.45, + (80, 220, 120), + 1, + cv2.LINE_AA, + ) + return out + + +def inference_status() -> str: + """腕部和手外最近一次推理框。超过保持时间就当没有。""" + now = time.monotonic() + with _LOCK: + items = dict(_DET) + parts = [] + for role, title in (("wrist", "腕部"), ("overhead", "手外")): + item = items.get(role) + if item is None or now - item[0] > _DET_HOLD_S or len(item[1]) < 5: + parts.append(f"{title} 没有框") + continue + parts.append(f"{title} 置信 {float(item[1][4]):.2f}") + return "推理 " + " ".join(parts) + + +def _remember_detection(role: str, data: list[float]) -> None: + with _LOCK: + if len(data) < 4: + _DET.pop(role, None) + else: + _DET[role] = (time.monotonic(), data) + _stamp_role(role) + + +def _remember_clean(key: str, rgb: np.ndarray, role: str, src_w: int, src_h: int) -> None: + with _LOCK: + _CLEAN[key] = { + "rgb": np.array(rgb, copy=True), + "role": role, + "src_w": int(src_w), + "src_h": int(src_h), + } + + +def _overlay_detection(rgb: np.ndarray, role: str, src_width: int, src_height: int) -> np.ndarray: + now = time.monotonic() + with _LOCK: + item = _DET.get(role) + if item is None or now - item[0] > _DET_HOLD_S or len(item[1]) < 4: + return rgb + return paint_detection(rgb, item[1], src_width, src_height) + + +def _stamp_role(role: str) -> None: + """检测更新后,把框画回这一路相机已经在显示的画面上。""" + now = time.monotonic() + with _LOCK: + frames = [(key, dict(meta)) for key, meta in _CLEAN.items() if meta.get("role") == role] + item = _DET.get(role) + fresh = item is not None and now - item[0] <= _DET_HOLD_S and len(item[1]) >= 4 + for key, meta in frames: + shown = meta["rgb"] + if fresh: + shown = paint_detection(shown, item[1], meta["src_w"], meta["src_h"]) + with _LOCK: + _PREVIEW[key] = (np.ascontiguousarray(shown).tobytes(), int(shown.shape[1]), int(shown.shape[0])) + _STATE["frames"][key] = True + _STATE["stamp"] = time.time() + + +def rgb_preview(image: np.ndarray, *, bgr: bool, limit: int = _COLOR_WIDTH) -> np.ndarray: + """缩成宽不超过 ``limit`` 的 RGB。返回的数组自己持有内存,不跟调用方共用缓冲。""" + import cv2 + + if image.ndim != 3 or image.shape[2] != 3 or image.shape[0] < 1 or image.shape[1] < 1: + raise ValueError("彩色预览要的是高×宽×3") + view = image + width = int(view.shape[1]) + if width > limit: + height = max(1, int(round(int(view.shape[0]) * limit / float(width)))) + view = cv2.resize(view, (limit, height), interpolation=cv2.INTER_AREA) + if bgr: + return cv2.cvtColor(view, cv2.COLOR_BGR2RGB) + if not view.flags["OWNDATA"] or not view.flags["C_CONTIGUOUS"]: + return np.ascontiguousarray(view) + return view + + +def depth_preview(depth: np.ndarray, limit: int = _DEPTH_WIDTH) -> np.ndarray: + """uint16 深度画成 RGB。百分位只在抽稀后的样本上算,无效像素保持黑色。""" + import cv2 + + if depth.ndim != 2 or depth.shape[0] < 1 or depth.shape[1] < 1: + raise ValueError("深度预览要的是高×宽") + step = max(1, int(depth.shape[1]) // limit) + small = depth[::step, ::step] + valid = small > 0 + gray = np.zeros(small.shape, np.uint8) + if bool(valid.any()): + sample = small[valid] + if sample.size > 8000: + sample = sample[:: max(1, sample.size // 8000)] + lo, hi = np.percentile(sample.astype(np.float32, copy=False), [5, 95]) + hi = max(float(hi), float(lo) + 1.0) + norm = np.clip((small.astype(np.float32) - float(lo)) / (hi - float(lo)), 0.0, 1.0) + gray = (norm * 255.0).astype(np.uint8) + gray[~valid] = 0 + colored = cv2.applyColorMap(gray, cv2.COLORMAP_JET) + colored[~valid] = 0 + return rgb_preview(colored, bgr=True, limit=limit) + + +def _jpeg_bgr(rgb: np.ndarray) -> bytes | None: + import cv2 + + if rgb.size == 0: return None - view = img - if view.shape[1] > 640: - scale = 640.0 / float(view.shape[1]) - view = cv2.resize(view, (640, max(1, int(view.shape[0] * scale)))) - ok, buf = cv2.imencode(".jpg", view, [int(cv2.IMWRITE_JPEG_QUALITY), 70]) + ok, buf = cv2.imencode( + ".jpg", + cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR), + [int(cv2.IMWRITE_JPEG_QUALITY), 70], + ) if not ok: return None return buf.tobytes() -def _depth_bgr(depth: np.ndarray) -> np.ndarray: - import cv2 - - vis = np.zeros((depth.shape[0], depth.shape[1], 3), np.uint8) - valid = depth > 0 - if not bool(valid.any()): - return vis - values = depth.astype(np.float32) - lo, hi = np.percentile(values[valid], [5, 95]) - hi = max(float(hi), float(lo) + 1.0) - gray = (np.clip((values - lo) / (hi - lo), 0.0, 1.0) * 255.0).astype(np.uint8) - gray[~valid] = 0 - colored = cv2.applyColorMap(gray, cv2.COLORMAP_JET) - colored[~valid] = 0 - return colored +def _as_array(data, dtype, count: int) -> np.ndarray: + """优先直接看消息缓冲。缓冲不支持时才拷一份。""" + try: + arr = np.frombuffer(data, dtype=dtype, count=count) + except (TypeError, BufferError, ValueError): + arr = np.frombuffer(bytes(data), dtype=dtype, count=count) + if arr.size < count: + raise ValueError("图像字节数不够") + return arr -def _color_array(msg) -> np.ndarray | None: +def _color_view(msg) -> np.ndarray | None: if msg.encoding not in ("bgr8", "rgb8") or msg.height <= 0 or msg.width <= 0: return None - arr = np.frombuffer(bytes(msg.data), dtype=np.uint8) need = int(msg.height) * int(msg.width) * 3 - if arr.size < need: - return None - arr = arr[:need].reshape(int(msg.height), int(msg.width), 3) - if msg.encoding == "rgb8": - arr = arr[:, :, ::-1] - return np.ascontiguousarray(arr) + arr = _as_array(msg.data, np.uint8, need) + return arr.reshape(int(msg.height), int(msg.width), 3) -def _depth_array(msg) -> np.ndarray | None: +def _depth_view(msg) -> np.ndarray | None: if msg.encoding not in ("16UC1", "mono16") or msg.height <= 0 or msg.width <= 0: return None - arr = np.frombuffer(bytes(msg.data), dtype=np.uint16) need = int(msg.height) * int(msg.width) - if arr.size < need: - return None - return np.ascontiguousarray(arr[:need].reshape(int(msg.height), int(msg.width))) + arr = _as_array(msg.data, np.uint16, need) + return arr.reshape(int(msg.height), int(msg.width)) + + +def _store(key: str, rgb: np.ndarray, *, preview: np.ndarray | None, jpeg: bool) -> None: + encoded = _jpeg_bgr(rgb) if jpeg else None + with _LOCK: + if preview is not None: + _PREVIEW[key] = (preview.tobytes(), int(preview.shape[1]), int(preview.shape[0])) + if encoded: + _JPEG[key] = encoded + if preview is not None or encoded: + _STATE["frames"][key] = True + _STATE["stamp"] = time.time() + _STATE["error"] = "" def _loop(cams: list[dict]) -> None: import rclpy from rclpy.node import Node - from rclpy.qos import qos_profile_sensor_data + from rclpy.qos import HistoryPolicy, QoSProfile, ReliabilityPolicy, qos_profile_sensor_data from sensor_msgs.msg import Image - - latest: dict[str, np.ndarray] = {} - lock = threading.Lock() + from std_msgs.msg import Float32MultiArray class _Node(Node): def __init__(self) -> None: super().__init__("operator_camera_view") + det_qos = QoSProfile( + history=HistoryPolicy.KEEP_LAST, + depth=5, + reliability=ReliabilityPolicy.RELIABLE, + ) + self.create_subscription( + Float32MultiArray, "workpiece/detection", self._det("wrist"), det_qos + ) + self.create_subscription( + Float32MultiArray, + "workpiece/detection_overhead", + self._det("overhead"), + det_qos, + ) + self._depth_subs: dict[str, object] = {} for cam in cams: name = cam["id"] + role = str(cam.get("role") or "wrist") self.create_subscription( Image, f"/{name}/color/image_raw", - self._color(name), - qos_profile_sensor_data, - ) - self.create_subscription( - Image, - f"/{name}/aligned_depth_to_color/image_raw", - self._depth(name), + self._color(name, role), qos_profile_sensor_data, ) - def _color(self, name: str): + def sync_depth(self) -> None: + """深度没打开就不订。订上之后每一帧都会整幅拷进进程,彩色帧率会被拖下去。""" + with _LOCK: + want = { + cam["id"] + for cam in cams + if f"{cam['id']}_depth" in _PREVIEW_WANT + } + for cam in cams: + name = cam["id"] + if name in want and name not in self._depth_subs: + self._depth_subs[name] = self.create_subscription( + Image, + f"/{name}/aligned_depth_to_color/image_raw", + self._depth(name), + qos_profile_sensor_data, + ) + elif name not in want and name in self._depth_subs: + self.destroy_subscription(self._depth_subs.pop(name)) + + def _det(self, role: str): + def _cb(msg: Float32MultiArray) -> None: + _remember_detection(role, [float(v) for v in msg.data]) + return _cb + + def _color(self, name: str, role: str): + key = f"{name}_color" + def _cb(msg: Image) -> None: - arr = _color_array(msg) - if arr is None: - return - with lock: - latest[f"{name}_color"] = arr + # 这里只留最新原图。颜色转换和画框放到画图线程,避免把后面的帧堵住。 + try: + view = _color_view(msg) + if view is None: + return + offer_color(key, view, bgr=(msg.encoding == "bgr8"), role=role) + except Exception as exc: # noqa: BLE001 —— 坏的一帧留在状态里,订阅不停 + with _LOCK: + _STATE["error"] = str(exc) return _cb def _depth(self, name: str): + key = f"{name}_depth" + def _cb(msg: Image) -> None: - arr = _depth_array(msg) - if arr is None: - return - with lock: - latest[f"{name}_depth"] = arr + try: + view = _depth_view(msg) + if view is None: + return + offer_depth(key, view) + except Exception as exc: # noqa: BLE001 —— 坏的一帧留在状态里,订阅不停 + with _LOCK: + _STATE["error"] = str(exc) return _cb rclpy.init() node = _Node() - next_encode = 0.0 try: while not _STOP.is_set(): - rclpy.spin_once(node, timeout_sec=0.05) - now = time.monotonic() - if now < next_encode: - continue - next_encode = now + 0.12 - with lock: - snap = dict(latest) - latest.clear() - frames = {} - blobs = {} - for key, arr in snap.items(): - name, _, kind = key.rpartition("_") - img = _depth_bgr(arr) if kind == "depth" else arr - blob = _jpeg(img) - if not blob: - continue - blobs[key] = blob - frames[key] = True - if blobs: - with _LOCK: - _JPEG.update(blobs) - _STATE["frames"].update(frames) - _STATE["stamp"] = time.time() - _STATE["error"] = "" + node.sync_depth() + rclpy.spin_once(node, timeout_sec=0.01) except Exception as exc: with _LOCK: _STATE["error"] = str(exc) @@ -200,8 +645,8 @@ def _loop(cams: list[dict]) -> None: def start() -> str: - global _THREAD - cams = targets() + global _THREAD, _PAINT_THREAD + cams = stream_targets() if not cams: with _LOCK: _STATE["error"] = "site 里没有可预览的真机相机" @@ -213,14 +658,18 @@ def start() -> str: _STATE["on"] = True _STATE["error"] = "" _STOP.clear() + _WAKE.clear() _THREAD = threading.Thread(target=_loop, args=(cams,), daemon=True, name="cam-view") _THREAD.start() + _PAINT_THREAD = threading.Thread(target=_paint_loop, daemon=True, name="cam-paint") + _PAINT_THREAD.start() names = "、".join(cam["label"] for cam in cams) - return f"预览订了 {names} 的彩色和深度,不含左右红外。" + return f"预览订了 {names} 的彩色原图,深度点开才订,不含左右红外。" def stop() -> None: _STOP.set() - thread = _THREAD - if thread is not None: - thread.join(timeout=1.5) + _WAKE.set() + for thread in (_THREAD, _PAINT_THREAD): + if thread is not None: + thread.join(timeout=1.5) diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/operator_console.py b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_console.py index 7aed4c1..e83c1bc 100644 --- a/install/arm_application/lib/python3.11/site-packages/arm_application/operator_console.py +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_console.py @@ -12,6 +12,7 @@ from __future__ import annotations import json import os import re +import signal import subprocess import threading import time @@ -23,7 +24,7 @@ from urllib.parse import urlparse import yaml from arm_application import operator_tactile_view as tactile_view -from arm_application.site_io import eye_to_hand_result_name, overhead_camera +from arm_application.site_io import eye_to_hand_result_name, listed_arms, overhead_camera, usb_expectations HOST = "127.0.0.1" PORT = 8766 @@ -36,11 +37,60 @@ else: ROOT = Path.home() / "ros2" LOG_DIR = Path("/tmp/orisys_console") + +# 动臂和软拖拽都要这台臂,不能一边下发位置一边再开拖拽。 +# 相机、夹爪、触觉不碰这条,也不该排在它后面等。 +_ROBOT_LANES = frozenset({"arm", "teach"}) +_ARM_ACTIONS = frozenset({ + "pipeline", "stage_track", "stage_locate", "stage_screw", + "reset_home", "gauge_run", "real_up", "handeye_calibrate", +}) +_LANE_WAIT = { + "grip": "夹爪上一条还没到位", + "camera": "相机命令还没返回", + "tactile": "触觉命令还没返回", + "arm": "这台臂上一条运动还没结束", + "teach": "软拖拽还没返回", +} + + +def command_lane(action: str) -> str: + """每类命令一条通道。同一条还没返回才拒绝,不让相机去等夹爪。""" + if action == "stop_commands": + return "stop" + if action in ("camera_d405", "camera_d435"): + return "camera" + if action.startswith("tactile"): + return "tactile" + if action.startswith("gripper"): + return "grip" + if action == "handeye_teach": + return "teach" + if action in _ARM_ACTIONS: + return "arm" + return action + + +def command_blocked_message(running: set[str], lane: str) -> str: + """这条通道,或同一台臂上的另一条运动,还没结束时返回要显示的句子。""" + if lane == "stop": + return "" + if lane in running: + return _LANE_WAIT.get(lane, "这一条命令还没返回") + if lane in _ROBOT_LANES: + other = next((name for name in _ROBOT_LANES if name != lane and name in running), "") + if other == "arm": + return "这台臂还在动,软拖拽先不开" + if other == "teach": + return "软拖拽占着这台臂,运动先不开" + return "" ASSETS = Path(__file__).resolve().parent _LOCK = threading.Lock() _TACTILE = {"left": {}, "right": {}, "stamp": 0.0} _HIST = {"left": deque(maxlen=360), "right": deque(maxlen=360)} _PROCS: dict[str, int] = {} +_LIVE: dict[str, subprocess.Popen] = {} +_PROC_LOCK = threading.Lock() def _push_force(side: str, sample: dict) -> None: @@ -76,29 +126,26 @@ def _site() -> dict: def _arms() -> list[dict]: - robots = (_site().get("robot") or {}).get("robots") or [] - out = [] - for robot in robots: - if not isinstance(robot, dict): - continue - sn = str(robot.get("sn") or "") - arm_no = "89" if sn.endswith("063989") else "91" if sn.endswith("063991") else "" - if not arm_no: - continue - out.append({ - "id": arm_no, - "label": str(robot.get("label") or f"臂{arm_no}"), - "sn": sn, - "tactile": bool(robot.get("tactile")), - "camera": str((robot.get("camera") or {}).get("name") or ""), - "gripper": str((robot.get("gripper") or {}).get("model") or ""), - }) - return out or [ - {"id": "89", "label": "左臂89", "sn": "", "tactile": True, "camera": "d405_left", "gripper": ""}, - {"id": "91", "label": "右臂91", "sn": "", "tactile": False, "camera": "d405_right", "gripper": ""}, + """真机臂来自 site.yaml。没有清单就空着,不补本机的 89/91。""" + return [ + { + "id": row["id"], + "label": row["label"], + "sn": row["sn"], + "tactile": row["tactile"], + "camera": row["camera"], + "gripper": row["gripper"], + "rotary": row["rotary"], + } + for row in listed_arms(_site()) ] +def _default_arm() -> str: + rows = _arms() + return str(rows[0]["id"]) if rows else "" + + def _usb() -> list[dict]: found = {"d405": [], "d435": [], "orisys": []} want = { @@ -119,10 +166,11 @@ def _usb() -> list[dict]: continue speed = (dev / "speed").read_text().strip() if (dev / "speed").is_file() else "?" found[key].append({"port": dev.name, "speed": speed}) + expect = usb_expectations(_site()) return [ - {"id": "d405", "label": "腕部 D405", "expect": 2, "items": found["d405"]}, - {"id": "d435", "label": "手外 D435", "expect": 1, "items": found["d435"]}, - {"id": "orisys", "label": "视触觉", "expect": 2, "items": found["orisys"]}, + {"id": "d405", "label": "腕部 D405", "expect": expect["d405"], "items": found["d405"]}, + {"id": "d435", "label": "手外 D435", "expect": expect["d435"], "items": found["d435"]}, + {"id": "orisys", "label": "视触觉", "expect": expect["orisys"], "items": found["orisys"]}, ] @@ -214,13 +262,16 @@ def _poll_tactile() -> None: def _robot(arm: str) -> dict: + from arm_application.site_io import arm_short_id + robots = (_site().get("robot") or {}).get("robots") or [] for robot in robots: if not isinstance(robot, dict): continue sn = str(robot.get("sn") or "") - short = "89" if sn.endswith("063989") else "91" if sn.endswith("063991") else "" - if short == arm: + if sn.lower() == "sim": + continue + if arm_short_id(sn) == arm: return robot return {} @@ -305,12 +356,30 @@ def _handeye(arm: str) -> dict: or shared.get("handeye_waypoints_eye_to_hand") or (ROOT / "handeye" / "poses" / f"{arm}_eye_to_hand.yaml") )) + wrist = _result_card(wrist_file, wrist_pose, boards.get("eye_in_hand") or {}) + wrist_name = "" + if isinstance(robot.get("camera"), dict): + wrist_name = str(robot["camera"].get("name") or "").strip().strip("/") + wrist["preview_camera"] = wrist_name + wrist["preview_label"] = "腕部相机" + outside = _result_card(e2h_file, e2h_pose, boards.get("eye_to_hand") or {}) + outside["preview_camera"] = cam_name.strip().strip("/") + outside["preview_label"] = "手外相机" return { - "eye_in_hand": _result_card(wrist_file, wrist_pose, boards.get("eye_in_hand") or {}), - "eye_to_hand": _result_card(e2h_file, e2h_pose, boards.get("eye_to_hand") or {}), + "eye_in_hand": wrist, + "eye_to_hand": outside, } +def _by_arm(reader) -> dict: + return {row["id"]: reader(row["id"]) for row in _arms()} + + +def _gripper_live() -> dict: + from arm_application.operator_gripper_state import snapshot as grip_snapshot + return _by_arm(grip_snapshot) + + def status() -> dict: with _LOCK: tactile = { @@ -322,20 +391,23 @@ def status() -> dict: "left": list(_HIST["left"]), "right": list(_HIST["right"]), } + from arm_application.operator_arm_state import snapshot as arm_snapshot return { "arms": _arms(), + "arm_live": _by_arm(arm_snapshot), + "gripper_live": _gripper_live(), "usb": _usb(), "services": _services(), "grippers": _grippers(), "tactile": tactile, "history": history, "view": tactile_view.state(), - "handeye": {"89": _handeye("89"), "91": _handeye("91")}, + "handeye": _by_arm(_handeye), "now": time.time(), } -# 会动真机的动作必须 confirm。argv 里的 {arm} 只接受 89 或 91。 +# 会动真机的动作必须 confirm。臂号必须是 site.yaml 里某台真机的末两位。 _ACTIONS = { "real_up": { "title": "上电控制栈", @@ -377,21 +449,21 @@ _ACTIONS = { "script": "run_gripper.sh", "args": ["--arm", "{arm}", "--cli", "probe"], "moves": False, - "blurb": "读夹爪是否在线,不动手指。", + "blurb": "读夹爪是否在线,不动手指。驱动没在跑时先拉起,不回零。", }, "gripper_open": { "title": "夹爪张开", "script": "run_gripper.sh", "args": ["--arm", "{arm}", "--cli", "open"], "moves": True, - "blurb": "张开当前臂夹爪。", + "blurb": "张开当前臂夹爪。驱动没在跑时先拉起,不回零。", }, "gripper_close": { "title": "夹爪闭合", "script": "run_gripper.sh", "args": ["--arm", "{arm}", "--cli", "close"], "moves": True, - "blurb": "闭合当前臂夹爪。", + "blurb": "闭合当前臂夹爪。驱动没在跑时先拉起,不回零。", }, "gripper_rotate": { "title": "右爪转 90°", @@ -401,11 +473,11 @@ _ACTIONS = { "blurb": "右臂旋转爪转到 90°。左臂平行爪没有这一轴。", }, "pipeline": { - "title": "整体三段", + "title": "跟踪并对孔", "script": "run_all.sh", "args": ["--arm", "{arm}"], "moves": True, - "blurb": "跟踪、对孔、搜孔轻压一次跑完。会动臂。", + "blurb": "跟踪和对孔一次跑完。搜孔轻压已取消,这一步不含旋入。会动臂。", }, "stage_track": { "title": "只测跟踪", @@ -421,12 +493,12 @@ _ACTIONS = { "moves": True, "blurb": "只跑二阶段:朝下、悬停、拍照、对准孔。会动臂。", }, - "stage_search": { - "title": "只测搜孔", + "stage_screw": { + "title": "只测旋转装配", "script": "run_stage3_search.sh", - "args": ["--arm", "{arm}"], + "args": ["--arm", "{arm}", "--screw"], "moves": True, - "blurb": "只跑三阶段:触面、搜孔、轻压。会动臂。臂应已停在孔上方。", + "blurb": "按现场配置的拧入深度和螺距把螺纹规旋进去。会动臂。臂应已在孔口。", }, "reset_home": { "title": "复位到 home", @@ -462,9 +534,9 @@ _ACTIONS = { "handeye_teach": { "title": "示教采点", "script": "run_handeye.sh", - "args": ["--arm", "{arm}", "teach", "--mode", "{mode}", "--points", "12"], + "args": ["--arm", "{arm}", "teach", "--mode", "{mode}", "--points", "{points}"], "moves": True, - "blurb": "软拖拽示教,目标 12 个姿态。要先停控制栈,会动臂。", + "blurb": "软拖拽示教。点数和是否接着采由手眼页上的控件决定。要先停控制栈,会动臂。", }, "handeye_merge": { "title": "合并示教点", @@ -507,6 +579,63 @@ def catalog() -> dict: } +def _remember(action: str, proc: subprocess.Popen) -> None: + with _PROC_LOCK: + _LIVE[action] = proc + _PROCS[action] = proc.pid + + +def _forget(action: str) -> None: + with _PROC_LOCK: + _LIVE.pop(action, None) + + +def _pid_alive(pid: int) -> bool: + if pid <= 0: + return False + try: + os.kill(pid, 0) + except OSError: + return False + return True + + +def stop_started() -> dict: + """停掉本界面拉起的命令。只向登记过的进程组发信号,不扫全机。""" + with _PROC_LOCK: + items = list(_LIVE.items()) + if not items: + return {"ok": True, "message": "没有正在跑的命令"} + names: list[str] = [] + for action, proc in items: + pid = int(getattr(proc, "pid", 0) or 0) + if not _pid_alive(pid): + _forget(action) + continue + try: + os.killpg(os.getpgid(pid), signal.SIGTERM) + except ProcessLookupError: + _forget(action) + continue + except OSError as exc: + return {"ok": False, "message": f"停不了 {action}:{exc}"} + names.append(action) + time.sleep(0.3) + for action, proc in items: + pid = int(getattr(proc, "pid", 0) or 0) + if not _pid_alive(pid): + _forget(action) + continue + try: + os.killpg(os.getpgid(pid), signal.SIGKILL) + except OSError: + pass + _forget(action) + if not names: + return {"ok": True, "message": "没有正在跑的命令"} + return {"ok": True, "message": "已停止:" + "、".join(names)} + + def _stop_tactile() -> str: subprocess.run(["pkill", "-f", "lib/arm_application/tactile"], check=False) subprocess.run(["pkill", "-f", "arm_application/tactile_node.py"], check=False) @@ -514,7 +643,39 @@ def _stop_tactile() -> str: return "已发送停止视触觉节点" -def run_action(action: str, arm: str, confirm: bool, mode: str = "eye_in_hand") -> dict: +def render_action_args( + template: list[str], arm: str, mode: str, extras: dict | None = None, +) -> list[str]: + """把动作模板里的臂号、模式、点数换成这次界面上的值。""" + extra = extras or {} + needs_points = any("{points}" in str(token) for token in template) + points_text = "" + if needs_points: + try: + points = int(extra.get("points", 12)) + except (TypeError, ValueError): + raise ValueError("目标点数不是整数") from None + if points < 4 or points > 30: + raise ValueError("目标点数只接受 4–30") + points_text = str(points) + repl = {"{arm}": arm, "{mode}": mode, "{points}": points_text} + out: list[str] = [] + for token in template: + text = str(token) + for key, value in repl.items(): + text = text.replace(key, value) + out.append(text) + if extra.get("append"): + if "teach" not in out: + raise ValueError("只有示教采点能接着采") + out.append("--append") + return out + + +def run_action( + action: str, arm: str, confirm: bool, mode: str = "eye_in_hand", + extras: dict | None = None, +) -> dict: if action == "tactile_down": tactile_view.stop() return {"ok": True, "message": _stop_tactile()} @@ -528,8 +689,8 @@ def run_action(action: str, arm: str, confirm: bool, mode: str = "eye_in_hand") spec = _ACTIONS.get(action) if spec is None: return {"ok": False, "message": "没有这个动作"} - if arm not in ("89", "91"): - return {"ok": False, "message": "臂号只接受 89 或 91"} + if arm not in {row["id"] for row in _arms()}: + return {"ok": False, "message": f"臂号 {arm} 不在 site.yaml 里"} if mode not in ("eye_in_hand", "eye_to_hand"): return {"ok": False, "message": "手眼模式只接受 eye_in_hand 或 eye_to_hand"} if spec["moves"] and not confirm: @@ -537,40 +698,86 @@ def run_action(action: str, arm: str, confirm: bool, mode: str = "eye_in_hand") script = ROOT / "scripts" / spec["script"] if not script.is_file(): return {"ok": False, "message": f"找不到 {script}"} - args = [a.replace("{arm}", arm).replace("{mode}", mode) for a in spec["args"]] + from arm_application.operator_arm_state import ( + hold_until_idle, + motion_blocked, + release_for_control, + resume_after_control, + snapshot as arm_snapshot, + stack_running, + ) + blocked = motion_blocked( + action, + stack_up=stack_running(), + op_status=str((arm_snapshot(arm) or {}).get("op_status") or ""), + ) + if blocked: + return {"ok": False, "message": blocked} + uses_rdk = action in { + "real_up", "handeye_teach", "pipeline", "stage_track", "stage_locate", + "stage_screw", "reset_home", "gauge_run", "handeye_calibrate", + } + if uses_rdk: + release_for_control() + from arm_application.operator_gripper_state import hold_serial + grip_port = action in { + "gripper_probe", "gripper_open", "gripper_close", "gripper_rotate", "gripper_up", + } + try: + args = render_action_args(list(spec["args"]), arm, mode, extras) + except ValueError as exc: + return {"ok": False, "message": str(exc)} LOG_DIR.mkdir(parents=True, exist_ok=True) log_path = LOG_DIR / f"{action}_{arm}.log" shown = " ".join(["bash", f"scripts/{spec['script']}", *args]) - if spec.get("sync"): - try: - proc = subprocess.run( + try: + if spec.get("sync"): + proc = subprocess.Popen( ["bash", str(script), *args], cwd=str(ROOT), - capture_output=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, - timeout=float(spec.get("timeout") or 40), + start_new_session=True, ) - except subprocess.TimeoutExpired: - return {"ok": False, "message": "命令超时还没返回", "detail": shown} - detail = ((proc.stdout or "") + (proc.stderr or "")).strip() - log_path.write_text(detail) - ok = proc.returncode == 0 - return { - "ok": ok, - "message": shown if ok else f"退出码 {proc.returncode}", - "detail": detail[-4000:], - "log": str(log_path), - } - log_f = log_path.open("ab") - proc = subprocess.Popen( - ["bash", str(script), *args], - cwd=str(ROOT), - stdout=log_f, - stderr=subprocess.STDOUT, - start_new_session=True, - ) - _PROCS[action] = proc.pid - return {"ok": True, "message": shown, "pid": proc.pid, "log": str(log_path)} + _remember(action, proc) + try: + detail, _ = proc.communicate(timeout=float(spec.get("timeout") or 40)) + except subprocess.TimeoutExpired: + try: + os.killpg(os.getpgid(proc.pid), signal.SIGKILL) + except OSError: + pass + return {"ok": False, "message": "命令超时还没返回", "detail": shown} + finally: + _forget(action) + detail = (detail or "").strip() + log_path.write_text(detail) + ok = proc.returncode == 0 + return { + "ok": ok, + "message": shown if ok else f"退出码 {proc.returncode}", + "detail": detail[-4000:], + "log": str(log_path), + } + log_f = log_path.open("ab") + proc = subprocess.Popen( + ["bash", str(script), *args], + cwd=str(ROOT), + stdout=log_f, + stderr=subprocess.STDOUT, + start_new_session=True, + ) + _remember(action, proc) + if grip_port: + hold_serial(proc.pid) + if uses_rdk: + hold_until_idle(proc.pid, until_stack=(action == "real_up")) + uses_rdk = False + return {"ok": True, "message": shown, "pid": proc.pid, "log": str(log_path)} + finally: + if uses_rdk: + resume_after_control() def _bounded(name: str, raw, low: float, high: float) -> float: @@ -585,12 +792,12 @@ def _bounded(name: str, raw, low: float, high: float) -> float: def run_gripper_tune(arm: str, kind: str, numbers: dict) -> dict: """按界面上的位置、力度、速度下发夹爪。右臂另有角度、圈数。""" - if arm not in ("89", "91"): - return {"ok": False, "message": "臂号只接受 89 或 91"} + if arm not in {row["id"] for row in _arms()}: + return {"ok": False, "message": f"臂号 {arm} 不在 site.yaml 里"} if kind not in ("pos", "rotate", "rotate_by", "revolutions"): return {"ok": False, "message": "没有这种夹爪调节"} - if kind != "pos" and arm != "91": - return {"ok": False, "message": "左臂平行爪没有旋转轴"} + if kind != "pos" and not any(row["id"] == arm and row.get("rotary") for row in _arms()): + return {"ok": False, "message": "这台夹爪没有旋转轴"} try: if kind == "pos": pos = int(round(_bounded("位置", numbers.get("pos"), 0, 1000))) @@ -615,7 +822,17 @@ def run_gripper_tune(arm: str, kind: str, numbers: dict) -> dict: summary = f"转 {turns:.3f} 圈 力矩 {force}% 速度 {speed}%" except ValueError as exc: return {"ok": False, "message": str(exc)} + from arm_application.operator_gripper_state import pause_serial, resume_serial + script = ROOT / "scripts" / "run_gripper.sh" + pause_serial() + try: + return _run_gripper_argv(script, arm, force, speed, cli, summary) + finally: + resume_serial() + + +def _run_gripper_argv(script, arm: str, force: int, speed: int, cli: list[str], summary: str) -> dict: argv = [ "bash", str(script), "--arm", arm, @@ -624,19 +841,40 @@ def run_gripper_tune(arm: str, kind: str, numbers: dict) -> dict: "--cli", *cli, ] shown = " ".join(argv) + proc = subprocess.Popen( + argv, cwd=str(ROOT), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + text=True, start_new_session=True, + ) + _remember(f"gripper_{arm}", proc) try: - proc = subprocess.run(argv, cwd=str(ROOT), capture_output=True, text=True, timeout=90) - except subprocess.TimeoutExpired: - return {"ok": False, "message": "夹爪 90 秒还没到位", "detail": shown} - detail = ((proc.stdout or "") + (proc.stderr or "")).strip() + try: + detail, _ = proc.communicate(timeout=90) + except subprocess.TimeoutExpired: + try: + os.killpg(os.getpgid(proc.pid), signal.SIGKILL) + except OSError: + pass + return {"ok": False, "message": "夹爪 90 秒还没到位", "detail": shown} + finally: + _forget(f"gripper_{arm}") + detail = (detail or "").strip() ok = proc.returncode == 0 return { "ok": ok, - "message": summary if ok else f"夹爪退出码 {proc.returncode}", + "message": summary if ok else _grip_failure_message(proc.returncode, detail), "detail": (summary + "\n" + shown + "\n" + detail)[-4000:], } +def _grip_failure_message(code: int, detail: str) -> str: + """退出码旁边带上脚本打出来的原因,别只剩一个数字。""" + for line in reversed(detail.splitlines()): + text = line.strip() + if text.startswith("ERROR:") or text.startswith("无法"): + return text + return f"夹爪退出码 {code}" + + class Handler(BaseHTTPRequestHandler): def log_message(self, fmt: str, *args) -> None: return @@ -700,12 +938,12 @@ class Handler(BaseHTTPRequestHandler): return if path == "/api/tactile/view": action = "tactile_view_on" if body.get("on") else "tactile_view_off" - result = run_action(action, "89", False) + result = run_action(action, _default_arm(), False) self._json(200 if result.get("ok") else 400, result) return result = run_action( str(body.get("action") or ""), - str(body.get("arm") or "89"), + str(body.get("arm") or _default_arm()), bool(body.get("confirm")), str(body.get("mode") or "eye_in_hand"), ) @@ -715,6 +953,10 @@ class Handler(BaseHTTPRequestHandler): def main() -> None: import atexit atexit.register(tactile_view.stop) + from arm_application.operator_arm_state import start as start_arm_state + from arm_application.operator_gripper_state import start as start_gripper_state + start_arm_state() + start_gripper_state() threading.Thread(target=_poll_tactile, daemon=True).start() server = ThreadingHTTPServer((HOST, PORT), Handler) print(f"Orisys 工位界面 http://{HOST}:{PORT}", flush=True) diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/operator_gripper_mesh.py b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_gripper_mesh.py new file mode 100644 index 0000000..523abf5 --- /dev/null +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_gripper_mesh.py @@ -0,0 +1,138 @@ +"""大寰夹爪的一比一外形。 + +左臂按下载包 ``PGEA-3D-Model-and-2D-Drawing-4`` 里的 +``PGEA-100-26-W-F`` 尺寸图。图签就是这个型号,总长 138、宽 55、厚 29, +安装端抱闸约 20,主体 78,手指伸出约 40,闭合缝 1.2。 +现场装的是 ``PGEA-100-20-W``,这包里没有 100-20 的图,开口仍用 +``site.yaml`` 的 20 mm,不用图上的 26。 + +同包另一张完整的带抱闸图是 ``PGEA-100-40-W-F``(手指更长,张开约 40.8)。 +右臂是 ``RGI-100-30``,这包里没有旋转爪的图,仍按手册外形。 +""" + +from __future__ import annotations + +import math + +# 米。来源见模块说明。长指向指尖,宽是开合方向,厚是图上的 29。 +PGEA_SIZE = (0.138, 0.055, 0.029) +PGEA_STROKE_MM = 20.0 +PGEA_CLOSED_GAP_M = 0.0012 +PGEA_FINGER_M = 0.040 +RGI_SIZE = (0.158, 0.0755, 0.047) +RGI_STROKE_MM = 30.0 + +# 深色底上要能分开:银壳体、蓝抱闸、橙手指、绿导轨。 +# 手指以前是 0.10 的黑,乘上未连接时的 0.55 就沉进背景里。 +_BODY = (0.86, 0.88, 0.91) +_BRAKE = (0.16, 0.42, 0.90) +_FINGER = (0.95, 0.46, 0.10) +_RAIL = (0.18, 0.78, 0.28) + + +def triangles( + rotary: bool, + permille: float | None, + rotation_deg: float | None, + stroke_mm: float | None, +) -> tuple[list[tuple], str]: + """返回 ``(三角面, 说明)``。三角面是 ``(a, b, c, 明暗占位, rgb)``,明暗由界面再乘。""" + if rotary: + length, width, thick = RGI_SIZE + stroke = (float(stroke_mm) if stroke_mm else RGI_STROKE_MM) / 1000.0 + note = ( + f"RGI-100-30 外形 {length*1000:.0f}×{width*1000:.1f}×{thick*1000:.0f} mm" + f" 行程 {stroke*1000:.0f} mm。这包图纸里没有旋转爪,外形仍按手册。" + ) + body_end = 0.118 + brake = None + else: + length, width, thick = PGEA_SIZE + stroke = (float(stroke_mm) if stroke_mm else PGEA_STROKE_MM) / 1000.0 + note = ( + f"PGEA-100-26-W-F 尺寸图 外形 {length*1000:.0f}×{width*1000:.0f}×{thick*1000:.0f} mm" + f" 现场行程 {stroke*1000:.0f} mm。包里没有 100-20 的图,开口不用图上的 26。" + ) + body_end = length - PGEA_FINGER_M + brake = (0.0, 0.020) + gap = stroke * (0.5 if permille is None else max(0.0, min(1.0, float(permille) / 1000.0))) + if not rotary: + gap += PGEA_CLOSED_GAP_M + parts = _solid(length, width, thick, body_end, gap, brake, rotary) + if rotary and rotation_deg: + parts = _spin_fingers(parts, math.radians(float(rotation_deg)), body_end) + out = [] + for a, b, c, rgb in parts: + out.append((a, b, c, _face_shade(a, b, c), rgb)) + if permille is None: + note = "没有读数。下面是半开,不是当前开口。 " + note + return out, note + + +def _solid(length, width, thick, body_end, gap, brake, rotary: bool): + hx, hy = thick / 2.0, width / 2.0 + faces = [] + z0 = 0.0 + if brake is not None: + z0 = brake[1] + faces.extend(_box(0.0, brake[1], -hy, hy, -hx, hx, _BRAKE)) + faces.extend(_box(z0, body_end, -hy, hy, -hx, hx, _BODY)) + if rotary: + finger_t = min(0.010, hy * 0.4) + finger_h = thick * 0.72 + else: + # 尺寸图端视图:指厚 6.5,指高 16,都落在 55×29 的外形里面。 + finger_t = 0.0065 + finger_h = 0.016 + rail = 0.003 + faces.extend(_box(z0, body_end, -hy * 0.62, -hy * 0.62 + rail, hx - rail, hx, _RAIL)) + faces.extend(_box(z0, body_end, hy * 0.62 - rail, hy * 0.62, hx - rail, hx, _RAIL)) + half = gap / 2.0 + faces.extend(_box(body_end, length, -(half + finger_t), -half, -finger_h / 2, finger_h / 2, _FINGER)) + faces.extend(_box(body_end, length, half, half + finger_t, -finger_h / 2, finger_h / 2, _FINGER)) + return faces + + +def _spin_fingers(parts, angle: float, body_end: float): + c, s = math.cos(angle), math.sin(angle) + spun = [] + for a, b, c0, rgb in parts: + if a[0] >= body_end - 1e-6 and b[0] >= body_end - 1e-6 and c0[0] >= body_end - 1e-6: + a, b, c0 = (_rot_z(a, c, s), _rot_z(b, c, s), _rot_z(c0, c, s)) + spun.append((a, b, c0, rgb)) + return spun + + +def _face_shade(a, b, c) -> float: + """顶面亮、底面暗。零件颜色已经分开,明暗再把面分出来。""" + ux, uy, uz = b[0] - a[0], b[1] - a[1], b[2] - a[2] + vx, vy, vz = c[0] - a[0], c[1] - a[1], c[2] - a[2] + nx = uy * vz - uz * vy + ny = uz * vx - ux * vz + nz = ux * vy - uy * vx + length = math.sqrt(nx * nx + ny * ny + nz * nz) or 1.0 + dot = (nx * 0.15 + ny * -0.25 + nz * 0.95) / length + return 0.42 + 0.58 * max(0.0, min(1.0, dot)) + + +def _rot_z(point, c, s): + x, y, z = point + return (x, y * c - z * s, y * s + z * c) + + +def _box(x0, x1, y0, y1, z0, z1, rgb) -> list[tuple]: + """轴向盒子。x 指向指尖,y 是开合方向,z 是厚度。""" + p = ( + (x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0), + (x0, y0, z1), (x1, y0, z1), (x1, y1, z1), (x0, y1, z1), + ) + quads = ( + (0, 1, 2, 3), (4, 7, 6, 5), + (0, 4, 5, 1), (3, 2, 6, 7), + (0, 3, 7, 4), (1, 5, 6, 2), + ) + out = [] + for a, b, c, d in quads: + out.append((p[a], p[b], p[c], rgb)) + out.append((p[a], p[c], p[d], rgb)) + return out diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/operator_gripper_state.py b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_gripper_state.py new file mode 100644 index 0000000..b31d263 --- /dev/null +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_gripper_state.py @@ -0,0 +1,436 @@ +"""工位界面用的夹爪状态:只读开口和转角,并交给界面画出来。 + +夹爪节点已经占着串口时,只订它发布的话题,不再自己打开串口。 +没有节点时才短连一次。连上之后只读寄存器,不写初始化,避免手指回零把工件松开。 +""" + +from __future__ import annotations + +import os +import subprocess +import threading +import time +from typing import Any + +import yaml + +from arm_application.site_io import listed_arms, ros2_root + +_GRIP = {0: "运动中", 1: "已到位", 2: "已夹住", 3: "已掉落"} +_ROT = {0: "转动中", 1: "已到位", 2: "受阻", 3: "曾经受阻"} + +_LOCK = threading.Lock() +_CACHE: dict[str, dict] = {} +_STARTED = False +_PORT_HOLD = 0 +_PORT_BUSY = 0 +_PORT_LOCK = threading.Lock() + + +def snapshot(arm: str) -> dict: + with _LOCK: + row = _CACHE.get(arm) + return dict(row) if row else _empty(arm) + + +def _empty(arm: str) -> dict: + row = next((item for item in _arms() if item["id"] == arm), None) + return present( + arm, + rotary=bool(row and row["rotary"]), + stroke_mm=float(row["stroke_mm"]) if row else 20.0, + reading=None, + error="还没读到夹爪", + ) + + +def present( + arm: str, + *, + rotary: bool, + stroke_mm: float, + reading: dict | None, + error: str = "", +) -> dict: + """把一次读数收成界面能画的字典。不打开串口。""" + stroke = float(stroke_mm) if stroke_mm and stroke_mm > 0 else 20.0 + row = reading or {} + permille = _unit(row.get("permille"), 0, 1000) + connected = bool(row.get("connected")) and permille is not None + opening = None if permille is None else permille / 1000.0 * stroke + rotation = _finite(row.get("rotation_deg")) if rotary else 0.0 + grip_state = row.get("grip_state") + rot_state = row.get("rotation_state") + if not connected: + summary = error or str(row.get("error_msg") or "夹爪没连上") + else: + bits = [_GRIP.get(int(grip_state), "状态未知") if _finite(grip_state) is not None else "状态未知"] + bits.append(f"开口 {opening:.1f} mm" if opening is not None else "") + if rotary and rotation is not None: + bits.append(f"转角 {rotation:.1f}°") + if _finite(rot_state) is not None: + bits.append(_ROT.get(int(rot_state), "")) + summary = " ".join(bit for bit in bits if bit) + return { + "arm": arm, + "rotary": rotary, + "connected": connected, + "initialized": bool(row.get("initialized")) if connected else False, + "permille": permille, + "opening_mm": opening, + "stroke_mm": stroke, + "rotation_deg": rotation if rotary else None, + "force": _finite(row.get("force")), + "speed": _finite(row.get("speed")), + "grip_state": int(grip_state) if _finite(grip_state) is not None else None, + "grip_text": _GRIP.get(int(grip_state), "—") if _finite(grip_state) is not None else "—", + "rotation_text": _ROT.get(int(rot_state), "—") if rotary and _finite(rot_state) is not None else "—", + "moving": bool(row.get("moving")) if connected else False, + "gripped": bool(row.get("gripped")) or (_finite(grip_state) == 2) if connected else False, + "summary": summary, + "error": "" if connected else (error or str(row.get("error_msg") or "")), + "source": str(row.get("source") or ""), + "pose_live": connected, + "stamp": time.time(), + } + + +def retain_reading( + fresh: dict, + previous: dict | None, + *, + now: float | None = None, + hold_s: float = 15.0, +) -> dict: + """这一拍没读到开口时,短时间沿用上一拍。 + + 和机械臂同一件事:话题 echo 偶发超时会把刚读到的开口清成“没连上”, + 模型和顶栏就停在那里。超过 ``hold_s`` 仍没有新读数才放开。 + """ + if fresh.get("connected") or not previous or not previous.get("connected"): + return fresh + moment = time.time() if now is None else now + if moment - float(previous.get("stamp") or 0.0) > hold_s: + return fresh + kept = dict(fresh) + for key in ( + "connected", "initialized", "permille", "opening_mm", "stroke_mm", + "rotation_deg", "force", "speed", "grip_state", "grip_text", + "rotation_text", "moving", "gripped", "pose_live", "rotary", "source", + ): + if key in previous: + kept[key] = previous[key] + kept["summary"] = str(previous.get("summary") or fresh.get("summary") or "") + note = "沿用上次读数。" + if note not in kept["summary"]: + kept["summary"] = f"{kept['summary']} {note}".strip() + return kept + + +def pause_serial() -> None: + """控制命令要自己开串口,或把驱动拉起来。先拦住状态线程,等它把口放开。 + + 一次短读要过好几个寄存器,1.5 秒经常还没结束。提前返回会让新驱动 + 去抢还被占着的口,然后以「需要 24 V」失败。 + """ + global _PORT_HOLD + with _PORT_LOCK: + _PORT_HOLD += 1 + deadline = time.time() + 6.0 + while time.time() < deadline: + with _PORT_LOCK: + busy = _PORT_BUSY + if not busy: + return + time.sleep(0.05) + + +def hold_serial(pid: int) -> None: + """后台夹爪脚本还活着时,状态线程不要去开串口。""" + pause_serial() + + def _watch() -> None: + while pid > 0: + try: + os.kill(pid, 0) + except OSError: + break + time.sleep(0.2) + resume_serial() + + threading.Thread(target=_watch, daemon=True, name="gripper-port-hold").start() + + +def resume_serial() -> None: + global _PORT_HOLD + with _PORT_LOCK: + _PORT_HOLD = max(0, _PORT_HOLD - 1) + + +def serial_paused() -> bool: + with _PORT_LOCK: + return _PORT_HOLD > 0 + + +def start() -> None: + global _STARTED + with _LOCK: + if _STARTED: + return + _STARTED = True + threading.Thread(target=_loop, daemon=True, name="gripper-state").start() + + +def _loop() -> None: + while True: + try: + arms = _arms() + nodes = _nodes() + for arm in arms: + reading, error = _read_one(arm, nodes) + fresh = present( + arm["id"], + rotary=arm["rotary"], + stroke_mm=arm["stroke_mm"], + reading=reading, + error=error, + ) + _store(retain_reading(fresh, snapshot(arm["id"]))) + except Exception as exc: # noqa: BLE001 —— 读夹爪失败不能拖死界面 + for row in _arms(): + _store(present( + row["id"], rotary=bool(row.get("rotary")), stroke_mm=float(row.get("stroke_mm") or 20.0), + reading=None, error=str(exc), + )) + time.sleep(2.5) + + +def _store(row: dict) -> None: + with _LOCK: + _CACHE[str(row.get("arm") or "")] = row + + +def _arms() -> list[dict]: + path = ros2_root() / "config" / "site.yaml" + if not path.is_file(): + return [] + data = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + shared = data.get("gripper") if isinstance(data.get("gripper"), dict) else {} + baud = int(shared.get("baud") or 115200) + slave = int(shared.get("slave_id") or 1) + rts = str(shared.get("rs485_rts") or "") + out = [] + for row in listed_arms(data): + item = dict(row) + item["baud"] = baud + item["slave"] = slave + item["rts"] = rts + out.append(item) + return out + + +def _nodes() -> dict[str, bool]: + text = _pgrep("dh_pgea_node|dual_gripper") + return { + "pgea": "dh_pgea_node" in text or "lib/arm_application/dh_pgea" in text, + "dual": "dual_gripper" in text, + } + + +def _read_one(arm: dict, nodes: dict[str, bool]) -> tuple[dict | None, str]: + if nodes.get("dual") and arm.get("side") in ("left", "right"): + topic = f"/dual_gripper_driver/{arm['side']}/status" + parsed = _parse_status_echo(_echo(topic)) + if parsed is not None: + parsed["source"] = f"话题 {topic}" + return parsed, "" + return None, "夹爪节点在跑,但这侧话题没有数据" + if not arm.get("rotary") and nodes.get("pgea"): + parsed = _parse_states_echo(_echo("/gripper/states")) + if parsed is not None: + parsed["source"] = "话题 /gripper/states" + return parsed, "" + return None, "平行爪节点在跑,但 /gripper/states 没有数据" + if not arm.get("port"): + return None, "site.yaml 里这台夹爪没有串口" + if serial_paused(): + return None, "控制命令正在用串口" + return _read_serial(arm) + + +def _read_serial(arm: dict) -> tuple[dict | None, str]: + """短连串口只读。auto_initialize 必须关掉,否则旋转爪一连上就会找零。""" + global _PORT_BUSY + if serial_paused(): + return None, "控制命令正在用串口" + with _PORT_LOCK: + _PORT_BUSY += 1 + try: + return _read_serial_open(arm) + finally: + with _PORT_LOCK: + _PORT_BUSY = max(0, _PORT_BUSY - 1) + + +def _read_serial_open(arm: dict) -> tuple[dict | None, str]: + try: + if arm["rotary"]: + from arm_application.dual_gripper import RotaryGripper, RotaryGripperConfig + grip = RotaryGripper(RotaryGripperConfig( + port=arm["port"], + slave_id=int(arm["slave"]), + baudrate=int(arm["baud"]), + timeout=0.25, + auto_initialize=False, + )) + grip.connect() + try: + status = grip.get_status() + finally: + grip.disconnect() + if not status.connected: + return None, status.error_msg or "旋转爪没有应答" + return { + "connected": True, + "initialized": bool(status.initialized), + "permille": float(status.finger_position_per_mille), + "rotation_deg": float(status.rotation_deg), + "force": float(status.force), + "speed": float(status.velocity), + "grip_state": int(status.gripper_state), + "rotation_state": int(status.rotation_state), + "moving": bool(status.is_moving), + "gripped": bool(status.is_gripped), + "error_msg": status.error_msg or "", + "source": f"串口 {arm['port']} 只读", + }, "" + from arm_application.dh_pgea import DhPgeaError, DhPgeaGripper + grip = DhPgeaGripper( + port=arm["port"], + baudrate=int(arm["baud"]), + slave_id=int(arm["slave"]), + stroke_mm=float(arm["stroke_mm"]), + rs485_rts=str(arm["rts"]), + ) + grip.open() + try: + state = grip.get_run_states(retries=1) + finally: + grip.close() + return { + "connected": True, + "initialized": bool(state.initialized), + "permille": float(state.position), + "rotation_deg": 0.0, + "force": float(state.target_force), + "speed": None, + "grip_state": int(state.grip_state), + "rotation_state": None, + "moving": int(state.grip_state) == 0, + "gripped": int(state.grip_state) == 2, + "error_msg": "", + "source": f"串口 {arm['port']} 只读", + }, "" + except Exception as exc: # noqa: BLE001 —— 串口被节点占着、没上电,都要变成一句人话 + text = str(exc).strip() or exc.__class__.__name__ + if "DhPgeaError" in exc.__class__.__name__ or "Permission" in text or "busy" in text.lower(): + return None, text + return None, text + + +def _echo(topic: str) -> str: + try: + proc = subprocess.run( + ["timeout", "3", "ros2", "topic", "echo", topic, "--once"], + capture_output=True, text=True, timeout=5, + ) + except (OSError, subprocess.TimeoutExpired): + return "" + return proc.stdout + + +def _parse_status_echo(text: str) -> dict | None: + for chunk in text.split("---"): + if "finger_position_per_mille:" not in chunk and "connected:" not in chunk: + continue + try: + data = yaml.safe_load(chunk) + except yaml.YAMLError: + continue + if not isinstance(data, dict) or "connected" not in data: + continue + return { + "connected": bool(data.get("connected")), + "initialized": bool(data.get("initialized")), + "permille": data.get("finger_position_per_mille"), + "rotation_deg": data.get("rotation_deg"), + "force": data.get("force"), + "speed": data.get("velocity"), + "grip_state": data.get("gripper_state"), + "rotation_state": data.get("rotation_state"), + "moving": bool(data.get("is_moving")), + "gripped": bool(data.get("is_gripped")), + "error_msg": str(data.get("error_msg") or ""), + } + return None + + +def _parse_states_echo(text: str) -> dict | None: + for chunk in text.split("---"): + if "data:" not in chunk: + continue + try: + data = yaml.safe_load(chunk) + except yaml.YAMLError: + continue + if not isinstance(data, dict): + continue + raw = data.get("data") + if not isinstance(raw, list) or len(raw) < 5: + continue + try: + init, grip, pos, _target, force = (float(v) for v in raw[:5]) + except (TypeError, ValueError): + continue + return { + "connected": True, + "initialized": init >= 0.5, + "permille": pos, + "rotation_deg": 0.0, + "force": force, + "speed": None, + "grip_state": int(grip), + "rotation_state": None, + "moving": int(grip) == 0, + "gripped": int(grip) == 2, + "error_msg": "", + } + return None + + +def _pgrep(pattern: str) -> str: + try: + proc = subprocess.run( + ["pgrep", "-af", pattern], + capture_output=True, text=True, timeout=2, + ) + except (OSError, subprocess.TimeoutExpired): + return "" + return "\n".join(line for line in proc.stdout.splitlines() if "pgrep" not in line) + + +def _finite(raw: Any) -> float | None: + try: + value = float(raw) + except (TypeError, ValueError): + return None + if value != value: + return None + return value + + +def _unit(raw: Any, low: float, high: float) -> float | None: + value = _finite(raw) + if value is None: + return None + return max(low, min(high, value)) diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/operator_handeye_board.py b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_handeye_board.py new file mode 100644 index 0000000..d7cb7f5 --- /dev/null +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_handeye_board.py @@ -0,0 +1,91 @@ +"""手眼页的棋盘预览。只在已有彩色画面上画角点,不打开相机,也不动臂。""" + +from __future__ import annotations + +import threading +import time + +import numpy as np + +from arm_application.handeye_lib import BoardConfig, detect_chessboard + + +def coverage_fraction(corners: np.ndarray, width: int, height: int) -> float: + """角点外接框占画面的比例。宽或高为 0 时返回 0。""" + if width <= 0 or height <= 0: + return 0.0 + pts = np.asarray(corners, dtype=float).reshape(-1, 2) + if pts.size == 0: + return 0.0 + area = float(pts[:, 0].max() - pts[:, 0].min()) * float(pts[:, 1].max() - pts[:, 1].min()) + return area / float(width * height) + + +def coverage_words(fraction: float) -> str: + """30%–70% 是示教时棋盘该占的画面。再小对不准,再大姿态拉不开。""" + if fraction < 0.30: + return "偏小,再靠近一点" + if fraction > 0.70: + return "偏大,再离远一点" + return "大小合适" + + +def annotate_board(jpeg: bytes, cols: int, rows: int) -> tuple[bytes, str]: + """返回(可能画了角点的 JPEG,一行中文状态)。检测失败时 JPEG 原样返回。""" + import cv2 + + if cols <= 0 or rows <= 0: + return jpeg, "配置里没有这块板的内角点" + buf = np.frombuffer(jpeg, dtype=np.uint8) + image = cv2.imdecode(buf, cv2.IMREAD_COLOR) + if image is None: + return jpeg, "这一帧解不出来" + found = detect_chessboard(image, BoardConfig(int(cols), int(rows), 1.0)) + if found is None: + return jpeg, "画面里没有这块板" + corners, pattern = found + drawn = image.copy() + cv2.drawChessboardCorners(drawn, pattern, corners, True) + fraction = coverage_fraction(corners, drawn.shape[1], drawn.shape[0]) + ok, out = cv2.imencode(".jpg", drawn, [int(cv2.IMWRITE_JPEG_QUALITY), 70]) + blob = out.tobytes() if ok else jpeg + percent = int(round(fraction * 100)) + return blob, f"棋盘已找到,约占画面 {percent}%。{coverage_words(fraction)}" + + +class BoardWatch: + """后台画角点。界面线程只提交最新一帧,检测再慢也不卡住窗口。""" + + def __init__(self) -> None: + self._lock = threading.Lock() + self._pending: tuple | None = None + self._result = {"token": 0, "jpeg": b"", "text": ""} + self._stop = threading.Event() + self._thread = threading.Thread(target=self._loop, daemon=True, name="handeye-board") + self._thread.start() + + def submit(self, token: int, jpeg: bytes, cols: int, rows: int) -> None: + with self._lock: + if self._result["token"] == token and not self._pending: + return + self._pending = (token, jpeg, int(cols), int(rows)) + + def latest(self) -> tuple[bytes, str, int]: + with self._lock: + return self._result["jpeg"], self._result["text"], int(self._result["token"]) + + def _loop(self) -> None: + while not self._stop.is_set(): + with self._lock: + job = self._pending + self._pending = None + if job is None: + time.sleep(0.05) + continue + token, jpeg, cols, rows = job + try: + blob, text = annotate_board(jpeg, cols, rows) + except Exception as exc: # noqa: BLE001 —— 检测失败不能拖死预览 + blob, text = jpeg, f"棋盘检测失败:{exc}" + with self._lock: + self._result = {"token": token, "jpeg": blob, "text": text} diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/operator_teach.py b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_teach.py index 6761ad8..98c2871 100644 --- a/install/arm_application/lib/python3.11/site-packages/arm_application/operator_teach.py +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/operator_teach.py @@ -8,6 +8,8 @@ from __future__ import annotations +import re +import subprocess import threading from datetime import datetime, timezone from pathlib import Path @@ -17,13 +19,17 @@ import yaml _LOCK = threading.Lock() _BRIDGE = None _SN = "" +_YIELDED = False +_ARM_CONTROLLER = "rizon_arm_controller" def _file(arm: str) -> Path: from arm_application.operator_console import ROOT + from arm_application.site_io import listed_arms, load_site_dict - if arm not in ("89", "91"): - raise ValueError("臂号只接受 89 或 91") + known = {row["id"] for row in listed_arms(load_site_dict())} + if arm not in known: + raise ValueError(f"臂号 {arm} 不在 site.yaml 里") return ROOT / "handeye" / "tmp" / arm / "operator_teach.yaml" @@ -36,9 +42,14 @@ def _bridge_for(sn: str): return _BRIDGE close() from arm_application.handeye_teach import RdkBridge + from arm_application.operator_arm_state import release_for_control, resume_after_control - _BRIDGE = RdkBridge(sn) - _SN = sn + release_for_control() + try: + _BRIDGE = RdkBridge(sn) + _SN = sn + finally: + resume_after_control() return _BRIDGE @@ -55,19 +66,95 @@ def close() -> None: pass +def _switch_arm_controller(activate: bool) -> str: + """停或恢复关节轨迹控制器。硬件还在,只是不再下发关节位置。 + + 控制栈在 ``NRT_JOINT_POSITION`` 里持续 ``SendJointPosition``, + 模式一旦被改走,写循环两秒内又切回去。软拖拽要低阻抗,必须先停这条控制器。 + """ + if activate: + payload = ( + "{activate_controllers: ['%s'], deactivate_controllers: [], " + "strictness: 2, activate_asap: true, timeout: {sec: 3, nanosec: 0}}" + % _ARM_CONTROLLER + ) + else: + payload = ( + "{activate_controllers: [], deactivate_controllers: ['%s'], " + "strictness: 1, activate_asap: true, timeout: {sec: 3, nanosec: 0}}" + % _ARM_CONTROLLER + ) + try: + proc = subprocess.run( + [ + "ros2", "service", "call", + "/controller_manager/switch_controller", + "controller_manager_msgs/srv/SwitchController", + payload, + ], + capture_output=True, + text=True, + timeout=15, + ) + except FileNotFoundError: + return "找不到 ros2,停不了关节轨迹控制器" + except subprocess.TimeoutExpired: + return "关节轨迹控制器没回来" if activate else "停关节轨迹控制器超时" + text = ((proc.stdout or "") + (proc.stderr or "")).strip() + if proc.returncode == 0 and re.search(r"ok\s*[:=]\s*true", text, re.IGNORECASE): + return "" + tail = text.splitlines()[-1] if text else f"退出码 {proc.returncode}" + verb = "恢复" if activate else "停下" + return f"没能{verb}关节轨迹控制器:{tail}" + + +def _yield_position_stream() -> str: + """控制栈没起来就什么都不做。起来了就先把位置控制器让开。""" + global _YIELDED + from arm_application.operator_arm_state import stack_running + + if not stack_running(): + return "" + err = _switch_arm_controller(False) + if err: + return err + "。位置环还在推,软拖拽切不过去。" + _YIELDED = True + return "" + + +def _restore_position_stream() -> str: + global _YIELDED + if not _YIELDED: + return "" + err = _switch_arm_controller(True) + _YIELDED = False + return err + + def drag_on(sn: str) -> dict: with _LOCK: try: + yielded = _yield_position_stream() + if yielded: + return {"ok": False, "message": yielded} bridge = _bridge_for(sn) result = bridge.drag_on(mode="soft") except Exception as exc: - return {"ok": False, "message": str(exc)} - if not result.get("ok"): - return {"ok": False, "message": str(result.get("error") or result)} - return { - "ok": True, - "message": "软拖拽已开启。轻推机械臂,再记点。这一步要先停掉控制栈。", - } + restore = _restore_position_stream() + message = str(exc) + if restore: + message = f"{message};{restore}" + return {"ok": False, "message": message} + if not result.get("ok"): + restore = _restore_position_stream() + message = str(result.get("error") or result) + if restore: + message = f"{message};{restore}" + return {"ok": False, "message": message} + note = "软拖拽已开启。轻推机械臂,再记点。" + if _YIELDED: + note += "关节轨迹控制器已停下,关掉拖拽后会回来。" + return {"ok": True, "message": note} def drag_off(sn: str) -> dict: @@ -76,9 +163,16 @@ def drag_off(sn: str) -> dict: bridge = _bridge_for(sn) result = bridge.drag_off() except Exception as exc: - return {"ok": False, "message": str(exc)} + restore = _restore_position_stream() + message = str(exc) + if restore: + message = f"{message};{restore}" + return {"ok": False, "message": message} if not result.get("ok"): return {"ok": False, "message": str(result.get("error") or result)} + restore = _restore_position_stream() + if restore: + return {"ok": False, "message": f"软拖拽已关闭,但{restore}"} return {"ok": True, "message": "软拖拽已关闭,臂保持当前位置。"} diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/overhead_oneshot.py b/install/arm_application/lib/python3.11/site-packages/arm_application/overhead_oneshot.py index fa1d3b1..e8863dd 100644 --- a/install/arm_application/lib/python3.11/site-packages/arm_application/overhead_oneshot.py +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/overhead_oneshot.py @@ -45,6 +45,11 @@ from arm_application.path_profile import ( # 伺服和测试从本模块再导 # 上一跳实际少走这么多,就认定冻结 J 在当前姿态走不动,不许按同一剩差再刷。 D435_MIN_PROGRESS_FRAC = 0.40 +# SwitchMode 返回不等于已经进 primitive。连试仍失败必须让一阶段立刻中止, +# 不许空等 150s 装成"还在粗定位"(2026-10-16 17:27)。 +D435_MOVEL_RETRY = 3 +D435_MOVEL_RETRY_S = 0.3 +D435_MOVEL_GAVE_UP = "厂家 MoveL 连试 3 次没走成" def best_det_conf(dets: Optional[Iterable]) -> Optional[float]: @@ -154,16 +159,27 @@ def d405_commands_arm( ) -> bool: """D405 这一帧的点能不能下发给左臂。 - 推理一直开着。正在用 D435 那一个点时,进程内 MoveL 结束并且停稳之前不下发。 - 停稳用 ``d435_handback_sec`` 和 ``lock_still_move_m``。赶路时 XY 已经进 15 mm - 也不算到位。不许在 MoveL 期间下发 D405(两套控制互抢会晃)。 - ``move_only``:只测 D435 位移,D405 点一律不下发(到位之后也不交)。 + 推理从启动就开着。没有高置信度目标、或 D435 厂家 MoveL 还没结束、 + 法兰还没连续停稳时,这帧**不下发**(输出截断,不许拿去控臂)。 + 厂家移动结束并且法兰停稳(``settled``,时长是 ``d435_handback_sec``) + 之后才放行,伺服用它追工件框的矩形中心。框心对准停稳后才进精拍下臂。 + ``move_only``:只测 D435 位移,D405 点一律不下发。 """ if move_only: return False - if not using_d435: - return True - return bool(arrived) and bool(settled) + if using_d435: + return bool(arrived) and bool(settled) + return True + + +def retract_goal_z(z_now: float, z_target: float) -> float: + """收臂目标 Z:已经高于目标就钉在当前高度,不许为了追 5 mm 往下补。 + + 2026-10-16:贴面 MoveL 之后 JTC 还握着悬停关节,收臂 hop1 计划 +5 mm + 实际抬了 18–25 mm;hop2 把「抬过头」当成误差 ``Δz=−13 mm`` 往回压, + 看起来就是进孔再弹出。抬过了 = 成功。 + """ + return max(float(z_now), float(z_target)) def oneshot_needs_verticalize( diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/perception_node.py b/install/arm_application/lib/python3.11/site-packages/arm_application/perception_node.py index cb94271..21510b1 100644 --- a/install/arm_application/lib/python3.11/site-packages/arm_application/perception_node.py +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/perception_node.py @@ -105,7 +105,7 @@ class PerceptionNode(TolerantNode): self.declare_parameter("image_topic", "") self.declare_parameter("camera_info_topic", "") self.declare_parameter("depth_topic", "/d405/aligned_depth_to_color/image_raw") - self.declare_parameter("weights", "models/yolo/gongjian0929.onnx") + self.declare_parameter("weights", "/home/cc/ros2/models/yolo/gongjian0929.onnx") self.declare_parameter("conf", 0.10) self.declare_parameter("iou", 0.45) self.declare_parameter("imgsz", 640) @@ -336,9 +336,7 @@ class PerceptionNode(TolerantNode): r_pos=float(self.get_parameter("kf_r_pos").value), ) - from arm_application.site_io import resolve_repo_path - - weights_raw = resolve_repo_path(str(self.get_parameter("weights").value)) + weights_raw = str(self.get_parameter("weights").value) weights = str(resolve_weights(weights_raw, self.backend_name)) yolo_cfg = { "weights": weights, @@ -1141,8 +1139,8 @@ class PerceptionNode(TolerantNode): filt[2] = z_use self._last_filt = filt.copy() - if handoff: - # 画面里没有 conf≥门槛的目标:绿框可以留,三维点不许发 —— 伺服会拿去追。 + if handoff or self._d435_commanding: + # 没有高置信度,或 D435 厂家移动还没交接:推理留着,控制点不许发。 return self._publish_point(filt, stamp) self.get_logger().info( @@ -1427,15 +1425,11 @@ class PerceptionNode(TolerantNode): self._z_locked = bool(msg.data) def _on_d435_commanding(self, msg: Bool) -> None: - """伺服还在执行 D435 那一个点时,不再提交下一张。 + """伺服还在执行 D435 那一个点时,不再提交下一张,D405 控制点也不发。 - 这一段结束(到位并停稳)后清掉「已拍过」,漏检重新计 0.5 s。 + 这一轮已经拍过并走完。停稳后打开 D405 框心,不因交接瞬间漏检再 MoveL。 """ - commanding = bool(msg.data) - if self._d435_commanding and not commanding: - self._oneshot_shot = False - self._low_since = None - self._d435_commanding = commanding + self._d435_commanding = bool(msg.data) def _publish_overhead_k0(self, p_base: np.ndarray, is_k0: bool) -> None: pt = PointStamped() diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/pipeline_node.py b/install/arm_application/lib/python3.11/site-packages/arm_application/pipeline_node.py index fe4f9f8..a27c828 100644 --- a/install/arm_application/lib/python3.11/site-packages/arm_application/pipeline_node.py +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/pipeline_node.py @@ -15,6 +15,7 @@ import os import signal import subprocess import time +from pathlib import Path from typing import Optional import numpy as np @@ -23,6 +24,7 @@ from geometry_msgs.msg import PointStamped from rclpy.qos import HistoryPolicy, QoSProfile, ReliabilityPolicy from std_msgs.msg import Bool, String +from arm_application.overhead_oneshot import D435_MOVEL_GAVE_UP from arm_application.stage3_search_node import Stage3Search @@ -70,6 +72,19 @@ def init_z_refusal( return "" +def hold_at_insert_z(final, *, skip3: bool) -> bool: + """贴面已经按 ``final_gap_mm`` 停下时,不再进三阶段。 + + 三阶段开局会抬到悬高上方测触觉,再落回 ``hole_hover_z_m``。 + 没有接触传感器。到位高度就是这次的贴面间隙,不另设一个 Z。 + """ + if skip3: + return True + if not isinstance(final, dict): + return False + return bool(final.get("insert_held")) + + def track_lock_z(hover_z: float, tool_len_m: float, surface_z_m: float) -> float: """一阶段锁高。 @@ -85,6 +100,20 @@ def track_lock_z(hover_z: float, tool_len_m: float, surface_z_m: float) -> float return hover + TRACK_CLEARANCE_M +def stage1_log_fatal(log_path: str) -> str: + """一阶段日志里已经写明失败时,不许再空等锁定。空串 = 还没到这一步。""" + try: + text = Path(log_path).read_text(encoding="utf-8", errors="replace") + except OSError: + return "" + if D435_MOVEL_GAVE_UP in text: + return ( + f"D435 {D435_MOVEL_GAVE_UP}(RDK 控制模式没切到 primitive)," + f"一阶段中止。不是视野/标定。看 {log_path}" + ) + return "" + + def stage1_timeout_reason(points_seen: int, timeout_s: float, log_path: str) -> str: """一阶段超时了,到底是"感知没给点"还是"给了点但没收敛"。 @@ -99,6 +128,9 @@ def stage1_timeout_reason(points_seen: int, timeout_s: float, log_path: str) -> 以前两种情况共用一句"没锁定(视野里没工件?)",于是"perception_node 因为缺依赖早退了"被读成"算法不收敛",白查半天。 """ + fatal = stage1_log_fatal(log_path) + if fatal: + return fatal if points_seen <= 0: return ( f"一阶段 {timeout_s:.0f}s 没锁定:**一个视觉目标点都没收到**" @@ -308,6 +340,11 @@ class AssemblyPipeline(Stage3Search): self._spin(0.1) if self._locked: break + fatal = stage1_log_fatal(log_path) + if fatal: + self._stop_stage1() + self.get_logger().error(fatal) + return 1 if self._s1.poll() is not None: self.get_logger().error(f"一阶段进程提前退出,看 {log_path}") return 1 @@ -363,24 +400,42 @@ class AssemblyPipeline(Stage3Search): if code != 0: self.get_logger().error(f"二阶段失败 exit={code},停在当前位姿") return code - self.get_logger().info( - f"二阶段 OK 残差 {final.get('residual_mm', -1)}mm" - + ( - " — 跳过搜孔,停在观测高度(看规尖是否对中;进程退出后臂保持)" - if skip3 - else " — 立刻下压搜孔并轻压(不拧入)" + stay = hold_at_insert_z(final, skip3=skip3) + if stay and final.get("insert_held"): + self.get_logger().info( + f"二阶段 OK 残差 {final.get('residual_mm', -1)}mm — " + f"插孔停在法兰 Z={final.get('flange_z_m')}m," + f"规尖离面 {final.get('tip_gap_mm')}mm" + f"(目标 {final.get('commanded_gap_mm')}mm,来自 final_gap_mm)。" + "没有接触传感器,不另设 Z,不抬回悬高。" + ) + else: + self.get_logger().info( + f"二阶段 OK 残差 {final.get('residual_mm', -1)}mm" + + ( + " — 跳过搜孔,停在观测高度(看规尖是否对中;进程退出后臂保持)" + if skip3 + else " — 立刻下压搜孔并轻压(不拧入)" + ) ) - ) else: self._state("skip_stage2") + stay = hold_at_insert_z(final, skip3=skip3) - if skip3: + if stay: self._state("skip_stage3") if self.d435_move_only: self.get_logger().info( "D435 位移测试结束 —— 臂停在当前高度,不交 D405、不对孔;" "15s 后本进程退出,臂保持不动" ) + elif isinstance(final, dict) and final.get("insert_held"): + if q is not None: + self._pin(q) + self.get_logger().info( + "插孔已停在贴面高度,不进搜孔、不抬回悬高。" + "15s 后本进程退出,臂保持这个 Z。" + ) else: self.get_logger().info( "跳过搜孔 —— 臂停在当前高度。请看规尖与孔是否对中;" diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/realsense_color_pub.py b/install/arm_application/lib/python3.11/site-packages/arm_application/realsense_color_pub.py index 1d8464a..c95b46a 100644 --- a/install/arm_application/lib/python3.11/site-packages/arm_application/realsense_color_pub.py +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/realsense_color_pub.py @@ -176,6 +176,10 @@ class RealsenseColorPub(TolerantNode): self._overlay: Optional[list] = None self._overlay_stamp = 0.0 self._capture_started = False + self._cap_lock = threading.Lock() + self._cap_event = threading.Event() + self._pending = None + self._last_frame_mono = 0.0 self._usb_desc = '' self._last_ok = None self._site_yaml = _site_yaml_path() @@ -309,23 +313,26 @@ class RealsenseColorPub(TolerantNode): self._info_pub.publish(info) if depth_u16 is not None: - dh, dw = int(depth_u16.shape[0]), int(depth_u16.shape[1]) - dmsg = Image() - dmsg.header.stamp = stamp - dmsg.header.frame_id = self._frame_id - dmsg.height = dh - dmsg.width = dw - dmsg.encoding = '16UC1' - dmsg.is_bigendian = 0 - dmsg.step = dw * 2 - dmsg.data = array.array('B', np.ascontiguousarray(depth_u16).tobytes()) - self._depth_pub.publish(dmsg) - if not self._logged_depth: - self._logged_depth = True - self.get_logger().info( - f'首个对齐深度 {dw}x{dh} → ' - f'/{self._name}/aligned_depth_to_color/image_raw' - ) + self._publish_depth(depth_u16, stamp) + + def _publish_depth(self, depth_u16: np.ndarray, stamp=None) -> None: + dh, dw = int(depth_u16.shape[0]), int(depth_u16.shape[1]) + dmsg = Image() + dmsg.header.stamp = stamp or self.get_clock().now().to_msg() + dmsg.header.frame_id = self._frame_id + dmsg.height = dh + dmsg.width = dw + dmsg.encoding = '16UC1' + dmsg.is_bigendian = 0 + dmsg.step = dw * 2 + dmsg.data = array.array('B', np.ascontiguousarray(depth_u16).tobytes()) + self._depth_pub.publish(dmsg) + if not self._logged_depth: + self._logged_depth = True + self.get_logger().info( + f'首个对齐深度 {dw}x{dh} → ' + f'/{self._name}/aligned_depth_to_color/image_raw' + ) def _start_pipeline(self, reconnect: bool = False) -> None: try: @@ -457,7 +464,9 @@ class RealsenseColorPub(TolerantNode): trial.enable_stream( rs.stream.infrared, 2, w, h, rs.format.y8, fps ) - profile = pipe.start(trial) + # 回调里只留最新一帧。wait_for_frames 会等彩色、深度、红外齐套, + # 场景一动红外对不齐,彩色帧就在这里被丢掉。 + profile = pipe.start(trial, self._on_sdk_frame) have_depth = with_depth have_infra = infra_mode != 'none' self._infra_mode = infra_mode @@ -508,6 +517,13 @@ class RealsenseColorPub(TolerantNode): self._pipe = pipe self._profile = profile + try: + sensor = profile.get_device().first_color_sensor() + if sensor.supports(rs.option.frames_queue_size): + # 队列只留最新一张,避免越积越慢,动起来画面还停在旧帧。 + sensor.set_option(rs.option.frames_queue_size, 1) + except Exception: # noqa: BLE001 + pass self._have_depth = have_depth self._have_infra = have_infra self._logged_depth = False @@ -609,7 +625,7 @@ class RealsenseColorPub(TolerantNode): if not getattr(self, '_capture_started', False): self._capture_started = True - self._thread = threading.Thread(target=self._capture_loop, daemon=True) + self._thread = threading.Thread(target=self._publish_loop, daemon=True, name="rs-publish") self._thread.start() def _enable_color_ae_fast(self, profile, rs) -> None: @@ -702,18 +718,60 @@ class RealsenseColorPub(TolerantNode): ) return False - def _capture_loop(self) -> None: + def _on_sdk_frame(self, frame) -> None: + """SDK 回调。只拷走彩色并握住这一组帧,立刻返回。 + + 对齐、深度换算和 ROS 发布若做在这个回调里,下一帧来不及取, + 动起来时彩色帧号会跳。那些工作放到 ``_publish_loop``。 + """ + try: + frames = frame.as_frameset() + color = frames.get_color_frame() + if not color: + return + bgr = np.array(np.asanyarray(color.get_data()), copy=True) + frames.keep() + with self._cap_lock: + self._pending = (bgr, frames) + self._last_frame_mono = time.monotonic() + self._cap_event.set() + except Exception as exc: # noqa: BLE001 + try: + self.get_logger().warn(f'收帧:{exc}', throttle_duration_sec=2.0) + except Exception: # noqa: BLE001 + pass + + def _publish_loop(self) -> None: got = False - misses = 0 while self._running and rclpy.ok(): if self._pipe is None: if not self._reconnect(): time.sleep(2.0) continue + if not self._cap_event.wait(0.2): + silent = self._last_frame_mono and time.monotonic() - self._last_frame_mono > 2.0 + if silent: + usb = getattr(self, '_usb_desc', '?') + self.get_logger().error( + f'相机超过 2 秒没有新帧(USB={usb})。正在重连 —— ' + '请给 USB 线缆留点余量,别让机械臂把它拽掉。' + ) + self._last_frame_mono = 0.0 + got = False + self._reconnect() + continue + self._cap_event.clear() + with self._cap_lock: + item = self._pending + self._pending = None + if item is None: + continue + bgr, frames = item try: - t_wait0 = time.monotonic() - frames = self._pipe.wait_for_frames(timeout_ms=2000) - self._t_wait = time.monotonic() - t_wait0 + t_pub = time.monotonic() + # 彩色先发出去。深度对齐再慢,也不能把这一帧彩色拖掉。 + self._publish_frame(bgr, None) + self._t_pub = time.monotonic() - t_pub ir_l = ( frames.get_infrared_frame(1) if self._have_infra and self._infra_mode in ('both', 'left') @@ -724,44 +782,45 @@ class RealsenseColorPub(TolerantNode): if self._have_infra and self._infra_mode in ('both', 'right') else None ) + aligned = frames if self._align is not None: t_al = time.monotonic() - frames = self._align.process(frames) + aligned = self._align.process(frames) self._t_align = time.monotonic() - t_al + depth = aligned.get_depth_frame() color = frames.get_color_frame() except Exception as exc: # noqa: BLE001 - misses += 1 text = str(exc) dropped = ( 'disconnect' in text.lower() or 'ioctl' in text.lower() or 'errno=5' in text.lower() ) - if dropped or misses >= 4: + if dropped: usb = getattr(self, '_usb_desc', '?') self.get_logger().error( f'相机数据流中断(USB={usb}):{text[:120]}。' '正在重连 —— 请给 USB 线缆留点余量,别让机械臂把它拽掉。' ) got = False - misses = 0 + self._last_frame_mono = 0.0 self._reconnect() - continue - if misses == 1: - self.get_logger().warn(f'wait_for_frames:{exc}') + else: + self.get_logger().warn(f'整理帧:{exc}', throttle_duration_sec=2.0) continue - if not color: - misses += 1 - continue - misses = 0 - bgr = np.asanyarray(color.get_data()) - depth = frames.get_depth_frame() depth_u16 = np.asanyarray(depth.get_data()) if depth else None if depth_u16 is not None and self._depth_to_mm != 1.0: # 原始设备单位 → 真实 mm(D405 原始为 0.1 mm/单位) depth_u16 = np.clip( depth_u16.astype(np.float32) * self._depth_to_mm, 0, 65535 ).astype(np.uint16) + if depth_u16 is not None: + try: + self._publish_depth(depth_u16) + except Exception as exc: # noqa: BLE001 + if not self._running: + break + self.get_logger().warn(f'发布深度失败:{exc}', throttle_duration_sec=2.0) self._frame_i += 1 self._fps_meter(color, depth, depth_u16) # 预览相关的工作(CPU 上色、IR 拷贝、四窗格绘制)每 N 帧才做一次 @@ -794,7 +853,6 @@ class RealsenseColorPub(TolerantNode): ) try: t_pub = time.monotonic() - self._publish_frame(bgr, depth_u16) if self._ir1_pub is not None and ir_l is not None: self._publish_ir(ir_l, self._ir1_pub) if self._ir2_pub is not None and ir_r is not None: diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/servo_node.py b/install/arm_application/lib/python3.11/site-packages/arm_application/servo_node.py index 848dbcd..10eb349 100644 --- a/install/arm_application/lib/python3.11/site-packages/arm_application/servo_node.py +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/servo_node.py @@ -50,6 +50,9 @@ from arm_application.geom_ik import ( ) from arm_application.handeye_lib import cam_point_to_base, cam_to_base_T from arm_application.overhead_oneshot import ( + D435_MOVEL_GAVE_UP, + D435_MOVEL_RETRY, + D435_MOVEL_RETRY_S, D435_NEAR_CRUISE_MPS, D435_SETTLE_S, D435_VENDOR_MOVEL_VEL_MPS, @@ -126,7 +129,7 @@ class ServoNode(TolerantNode): self.declare_parameter("point_topic", "visual_servo/filtered_point") # 默认值只作兜底:真机运行一定由 launch 传入 site.yaml 的 paths.handeye_file。 # 指向 /tmp 是历史遗留(标定临时产物),会让人误以为"标定结果在那里"。 - self.declare_parameter("handeye_file", "handeye/results/89_eye_in_hand.yaml") + self.declare_parameter("handeye_file", "/home/cc/ros2/handeye/results/89_eye_in_hand.yaml") # eye_in_hand = cam→法兰;eye_to_hand = cam→基座。写错会把固定相机当腕部用。 self.declare_parameter("handeye_mode", "eye_in_hand") self.declare_parameter("use_static_handeye_tf", False) @@ -224,8 +227,7 @@ class ServoNode(TolerantNode): self.declare_parameter("lock_hold_sec", 2.0) # D405 跟住后法兰静止多久进二阶段。哨兵 -1 = 没从 site 接入。 # lock_still_sec 仍须从 site 接上(缺了就报错)。 - # 交回 D405:进程内 MoveL 到位后,再叠 d435_handback_sec(法兰 XY 停稳)。 - # 旧杀栈冷启动曾把这 3s 垫在控制器重启后面;不杀栈后只留机械停稳。 + # 厂家 MoveL 结束后,法兰连续停稳 d435_handback_sec 才打开 D405 框心。 self.declare_parameter("lock_still_sec", -1.0) self.declare_parameter("d435_handback_sec", -1.0) self.declare_parameter("wrist_align_sec", -1.0) @@ -329,7 +331,7 @@ class ServoNode(TolerantNode): and self.wrist_handoff_conf >= 0.0 and self.overhead_hold_s >= 0.0 ) - # 框心对准进对孔用 wrist_align_sec。D435 交回只用 d435_handback_sec,两段不要合成一个数。 + # 框心对准进精拍用 wrist_align_sec。D435 交接只用 d435_handback_sec,两段不要合成一个数。 self._still = StillLock( still_sec=max(0.0, self.wrist_align_sec), still_move_m=max(0.0, self.lock_still_move_m), @@ -342,6 +344,8 @@ class ServoNode(TolerantNode): self._d435_vendor_done = False self._d435_vendor_busy = False self._d435_vendor_failed = False + self._d435_vendor_tries = 0 + self._d435_retry_after = 0.0 self._d435_settled = False self._d435_exec_until = 0.0 self._d435_pinned = False @@ -374,10 +378,9 @@ class ServoNode(TolerantNode): self.ws_min = np.array([float(v) for v in ws_min[:3]], dtype=float) self.ws_max = np.array([float(v) for v in ws_max[:3]], dtype=float) - from arm_application.site_io import resolve_repo_path - - handeye_path = resolve_repo_path(str(self.get_parameter("handeye_file").value)) - self.T_cam_to_gripper = load_handeye_T(handeye_path) + self.T_cam_to_gripper = load_handeye_T( + str(self.get_parameter("handeye_file").value) + ) self.handeye_mode = str( self.get_parameter("handeye_mode").value or "eye_in_hand" ).strip().lower() @@ -400,7 +403,7 @@ class ServoNode(TolerantNode): t_he = np.asarray(self.T_cam_to_gripper[:3, 3], dtype=float) axis_z = float(np.clip(R_he[2, 2], -1.0, 1.0)) self._cam_tilt_deg = float(np.degrees(np.arccos(axis_z))) - he_path = handeye_path + he_path = str(self.get_parameter("handeye_file").value) if self.handeye_mode == "eye_to_hand": self.get_logger().warn( f"HANDEYE yaml={he_path} mode=eye_to_hand " @@ -709,6 +712,8 @@ class ServoNode(TolerantNode): self._d435_vendor_done = False self._d435_vendor_busy = False self._d435_vendor_failed = False + self._d435_vendor_tries = 0 + self._d435_retry_after = 0.0 self._overhead_t = now_m self._last_seen_time = now_m self._stamp_sec = now_sec @@ -732,6 +737,8 @@ class ServoNode(TolerantNode): self._d435_vendor_done = False self._d435_vendor_busy = False self._d435_vendor_failed = False + self._d435_vendor_tries = 0 + self._d435_retry_after = 0.0 self._publish_d435_commanding(True) self.get_logger().info( f"D435 一次性设定 feat_base=({p_base[0]:.3f},{p_base[1]:.3f},{p_base[2]:.3f}) " @@ -759,9 +766,9 @@ class ServoNode(TolerantNode): settled=settled, move_only=self.d435_move_only, ): - # 推理服务不关。这一帧的点不下发给臂,目标仍是 D435 那一个点。 + # 推理不关。没到「法兰停稳」之前,这帧不能进运动控制。 self.get_logger().info( - "D435 位置还没到并且停稳,D405 这帧不下发", + "D405 输出已截断:D435 厂家移动还没结束或法兰还没停稳", throttle_duration_sec=1.0, ) return @@ -1407,9 +1414,12 @@ class ServoNode(TolerantNode): return geometric_jacobian(link_R, link_p, p_ee, RIZON_LINK_AXES) def _d435_oneshot_holds_the_arm(self) -> bool: - """D435 到点平移:action 短跳,等结果再重测。D405 主跟踪仍走原来的伺服。""" - if self.d435_move_only and self._hybrid and self._track_source == "overhead" and self._d435_settled: - # 位移测完了:守着,不许掉进 D405 的 _execute_twist。hold 只在到位那一拍发一次。 + """D435 粗定位只走厂家 MoveL。停稳后先钉住,等 D405 框心接上再放开。 + + 这段不许再用流式伺服去追 D435 那个点。D405 点被 ``_on_point`` 接上后 + 跟踪源变成腕部,这里返回 False,框心才开始带臂。 + """ + if self._hybrid and self._track_source == "overhead" and self._d435_settled: return True if not (self._hybrid and self._track_source == "overhead" and not self._d435_settled): return False @@ -1519,7 +1529,7 @@ class ServoNode(TolerantNode): """停或重新打开关节轨迹控制器。关节状态广播留着,硬件接口不下线。 用节点里的客户端,不走 ``ros2 service call``:那条命令自己就要一两秒, - 交回 D405 的预算会被它吃掉。 + 停稳锁定的预算会被它吃掉。 """ from controller_manager_msgs.srv import SwitchController @@ -1659,18 +1669,32 @@ class ServoNode(TolerantNode): if self._d435_vendor_failed or self._d435_vendor_busy: return True if not self._d435_vendor_done: - # busy 先于调用置位:另一条控制回调不能再开一次 MoveL,也不能提前交回 D405。 + if time.monotonic() < float(self._d435_retry_after): + return True + if int(self._d435_vendor_tries) >= int(D435_MOVEL_RETRY): + self._d435_vendor_failed = True + self.get_logger().error( + f"{D435_MOVEL_GAVE_UP} —— 一阶段中止,D405 也不带臂" + ) + return True + # busy 先于调用置位:另一条控制回调不能再开一次 MoveL,也不能提前锁定。 self._d435_vendor_busy = True ok = self._d435_vendor_movel() self._d435_vendor_busy = False if not ok: - self._d435_vendor_failed = True + self._d435_vendor_tries = int(self._d435_vendor_tries) + 1 + self._d435_retry_after = time.monotonic() + float(D435_MOVEL_RETRY_S) self.get_logger().error( - "厂家 MoveL 没走成 —— 停在原地,D405 的点继续不下发" + f"厂家 MoveL 第{self._d435_vendor_tries}次没走成," + f"{D435_MOVEL_RETRY_S:.1f}s 后再试" ) else: self._d435_vendor_done = True self._arrive.reset() + # MoveL 切回关节后 JTC 还握着出发前的悬停。不钉住的话 + # 硬件 3 s 窗口一过,臂会按悬停关节走回去(比交回 D405 的 10 cm 更远)。 + self._send_hold() + self._d435_pinned = True return True return True @@ -1729,7 +1753,7 @@ class ServoNode(TolerantNode): ): self._d435_pin_and_stop_hops( f"D435 上一跳几乎没走近,剩差还是 {dist*1000:.0f}mm " - f"—— 钉住不再补刀,等停稳后交回 D405" + f"—— 钉住不再补刀,等停稳后锁定进二阶段" ) return True if not at: @@ -1836,7 +1860,7 @@ class ServoNode(TolerantNode): return True def _tick_d435_arrive(self) -> None: - """走到 D435 那个点,并且法兰按一阶段的静止判据停稳,才允许下发 D405。""" + """厂家 MoveL 已结束:看法兰还在不在动。连续停稳后才打开 D405 框心。""" if not self._hybrid: return if ( @@ -1858,11 +1882,15 @@ class ServoNode(TolerantNode): return xy = np.asarray(T[:2, 3], dtype=float) dist = float(np.hypot(float(xy[0]) - goal[0], float(xy[1]) - goal[1])) - arrived = dist <= float(self.lock_still_move_m) or self._d435_give_up - settled = self._arrive.update( - xy, time.monotonic(), tracking_ok=arrived - ) + # 到位看法兰是否还在动,不看离视觉点还差几毫米。 + settled = self._arrive.update(xy, time.monotonic(), tracking_ok=True) if not settled: + self.get_logger().info( + f"D435 厂家 MoveL 已结束,法兰还在动或未满 " + f"{self.d435_handback_sec:.1f}s(离点 {dist*1000:.0f}mm)," + "D405 输出仍截断", + throttle_duration_sec=1.0, + ) return with self._lock: self._d435_settled = True @@ -1885,10 +1913,12 @@ class ServoNode(TolerantNode): self._lock_pub_t = now_m self._lock_pub.publish(Bool(data=True)) return + # 法兰已停稳:打开 D405。不在这里锁定;框心对准停稳后才精拍下臂。 self._publish_d435_commanding(False) self.get_logger().info( - f"已到 D435 位置(还差 {dist*1000:.0f}mm)并停稳 " - f"{self.d435_handback_sec:.1f}s —— 改回下发 D405 框心" + f"法兰已连续停稳 {self.d435_handback_sec:.1f}s" + f"(离 D435 点 {dist*1000:.0f}mm)" + " —— 打开 D405 输出,追工件框矩形中心;对准后才精拍下臂" ) def _tick_still_lock(self) -> None: diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/site_io.py b/install/arm_application/lib/python3.11/site-packages/arm_application/site_io.py index 8ddc507..9243123 100644 --- a/install/arm_application/lib/python3.11/site-packages/arm_application/site_io.py +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/site_io.py @@ -10,83 +10,13 @@ import yaml def ros2_root() -> Path: - """仓库根目录。先看环境变量,再从本文件向上找 ``config/site.yaml``。 - - 不能默认 ``~/ros2``:别人把仓库克隆到别的目录时,写死的家目录路径是空的, - 启动会报文件不存在。 - """ for key in ("SITE_ROS2_ROOT", "ROS2_WS"): v = os.environ.get(key, "").strip() if v: return Path(v).expanduser() - here = Path(__file__).resolve() - for parent in here.parents: - if (parent / "config" / "site.yaml").is_file() and (parent / "scripts" / "env.sh").is_file(): - return parent return Path.home() / "ros2" -_PATH_KEYS = frozenset({ - "named_poses_file", - "handeye_file", - "handeye_waypoints", - "handeye_workdir", - "handeye_calibrate", - "yolo_weights", - "hole_weights", - "weights", - "workpiece_weights", - "config_file", - "append_poses_file", -}) - - -def resolve_repo_path(value: str, root: Path | None = None) -> str: - """仓库内的相对路径展开成绝对路径。已经是绝对路径的原样保留。""" - text = str(value or "").strip() - if not text: - return "" - path = Path(text).expanduser() - if path.is_absolute(): - return str(path) - base = root or ros2_root() - return str((base / path).resolve()) - - -def _is_path_key(key: str) -> bool: - return ( - key in _PATH_KEYS - or key.startswith("handeye_file") - or key.startswith("handeye_waypoints") - or key.endswith("_weights") - or key.endswith("_file") - or key.endswith("_workdir") - ) - - -def absolutize_site_paths(data: Any, root: Path | None = None) -> Any: - """把 site 里的仓库相对路径展开。只动路径字段,不动别的字符串。""" - base = root or ros2_root() - if isinstance(data, dict): - out: dict[Any, Any] = {} - for key, val in data.items(): - name = str(key) - if isinstance(val, str) and _is_path_key(name): - out[key] = resolve_repo_path(val, base) - elif ( - name == "config" - and isinstance(val, str) - and (val.endswith((".yaml", ".yml", ".json")) or "/" in val) - ): - out[key] = resolve_repo_path(val, base) - else: - out[key] = absolutize_site_paths(val, base) - return out - if isinstance(data, list): - return [absolutize_site_paths(item, base) for item in data] - return data - - def site_path() -> Path: env = os.environ.get("SITE_CONFIG", "").strip() if env: @@ -99,11 +29,7 @@ def load_site_dict(path: str | Path | None = None) -> dict[str, Any]: if not p.is_file(): return {} data = yaml.safe_load(p.read_text(encoding="utf-8")) or {} - if not isinstance(data, dict): - return {} - resolved = p.resolve() - root = resolved.parent.parent if resolved.parent.name == "config" else ros2_root() - return absolutize_site_paths(data, root) + return data if isinstance(data, dict) else {} def deep_merge(base: dict[str, Any], overlay: dict[str, Any]) -> dict[str, Any]: diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/stage2_locate_node.py b/install/arm_application/lib/python3.11/site-packages/arm_application/stage2_locate_node.py index 670bacb..a3575d2 100644 --- a/install/arm_application/lib/python3.11/site-packages/arm_application/stage2_locate_node.py +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/stage2_locate_node.py @@ -69,6 +69,7 @@ from arm_application.overhead_oneshot import ( once_ff_tcp_xyz, oneshot_align_step_m, pixel_on_base_plane, + retract_goal_z, ) from arm_application.handeye_realsense import resolve_realsense_topics from arm_application.gauge_boot import perception_source_of @@ -249,7 +250,7 @@ class Stage2Locate(TolerantNode): # _coarse_target_reject / _bbox_on_border 的注释):粗居中的目的是把臂 # 挪到工件正上方,**挪错比不挪危险得多** —— 一挪错精定位就再也看不到 # k1/k2,而流程会把它当"孔检测失败",现场根本想不到是粗居中把臂带走的。 - self.coarse_max_offset_m = float(stage2.get("coarse_max_offset_m") or 0.12) + self.coarse_max_offset_m = float(stage2.get("coarse_max_offset_m") or 0.08) self.coarse_border_px = int(stage2.get("coarse_border_px") or 2) # 最后轻降到孔面上方 final_gap_mm(不接触)。启动参数 ≥0 覆盖 site。 self._final_gap_refused = "" @@ -901,10 +902,13 @@ class Stage2Locate(TolerantNode): min_dur: float = 1.6, hold_xy: bool = False, strict_xy: bool = False, + z_up_only: bool = False, ) -> Tuple[bool, np.ndarray, np.ndarray]: """闭环点到点:规划短 hop → 执行 → 重新看 TF → 重复,直到容差内。 hold_xy=True:只收 Z/姿态(悬停降锁高),不把下降中漂出的 XY 再拽回去。 + z_up_only=True:当前已经高于目标 Z 就停,不许为了追 5 mm 往下补 + (贴面 MoveL 后收臂 hop1 抬过头、hop2 回压进孔,2026-10-16)。 """ target = self._clamp_ws(target) last_dist: Optional[float] = None @@ -912,7 +916,14 @@ class Stage2Locate(TolerantNode): stall = 0 for hop in range(max_hops): p = Tee[:3, 3] - goal = np.array([p[0], p[1], target[2]]) if hold_xy else target + z_tgt = float(target[2]) + if z_up_only: + if float(p[2]) >= z_tgt - float(tol_m): + return True, q, Tee + z_tgt = retract_goal_z(float(p[2]), z_tgt) + goal = np.array([p[0], p[1], z_tgt]) if hold_xy else np.array( + [target[0], target[1], z_tgt], dtype=float + ) if hold_xy: goal = self._clamp_ws(goal) dp = goal - p @@ -1491,14 +1502,15 @@ class Stage2Locate(TolerantNode): 1. 目标点落在 ``workspace_xyz_*`` 的 XY 之外(含 5mm 余量)—— 一阶段刚锁 定时臂就在工件上方,工件**不该**在盒外; - 2. 目标点离当前夹爪轴线超过 ``coarse_max_offset_m``(缺省 0.12m)—— 粗居中 - 是"把臂挪到工件正上方",合理位移是厘米级;十几厘米只可能是坏读数。 + 2. 目标点离当前夹爪轴线超过 ``coarse_max_offset_m``(缺省 0.08m)—— 粗居中 + 是"把臂挪到工件正上方",合理位移是厘米级;十几厘米只可能是坏读数 + (2026-10-16 三次 110mm 回撤,0.12 放行了)。 注意:判误检后**原地不动**,由精定位(直接量 k1/k2)继续。不动的代价只是 "少挪一次",而挪错的代价是精定位彻底看不到孔 —— 所以这里必须选不动。 """ if max_offset_m is None: - max_offset_m = float(getattr(self, "coarse_max_offset_m", 0.12)) + max_offset_m = float(getattr(self, "coarse_max_offset_m", 0.08)) p = np.asarray(p_base, dtype=float).reshape(3) if not np.all(np.isfinite(p[:2])): return "目标点非有限值(深度/三角化失败)" @@ -2241,7 +2253,9 @@ class Stage2Locate(TolerantNode): ) fl0 = (float(Tee[0, 3]), float(Tee[1, 3]), float(Tee[2, 3])) ok = self._vendor_movel_tcp(movel_xyz, ONCE_FF_MOVEL_VEL_MPS) - q2, Tee2 = self._wait_state(2.0) + # MoveL 刚切回关节模式:JTC 还握着贴面前的悬停关节。等 2 s 再钉 + # 等于让 vmax=0.05 往悬停留 0.1 rad(2026-10-16 收臂实测多抬 13–20 mm)。 + q2, Tee2 = self._wait_state(0.3) if q2 is not None and Tee2 is not None: q, Tee = q2, Tee2 self._pin(q) @@ -3392,14 +3406,36 @@ class Stage2Locate(TolerantNode): ) # ---- 5) 锁定保持 ---- + # 贴面之后就停在这个法兰 Z。没有接触传感器,到位只认 final_gap_mm + # (规尖离面)。三阶段开局会抬回悬高,这里标明已经插到位,流水线不再上抬。 + if ( + self.descent_final_gap_m is not None + and self.final_descent_enable + and not self.hover_only + ): + final = dict(final) + final["insert_held"] = True + final["flange_z_m"] = round(float(Tee[2, 3]), 4) + final["tip_gap_mm"] = round(float(self.descent_final_gap_m) * 1000.0, 2) + final["commanded_gap_mm"] = round(float(self.final_gap_m) * 1000.0, 2) + final["insert_z_source"] = "final_gap_mm" self._state("hold") q_fin = self._q() self._pin(q_fin if q_fin is not None else q) - self.get_logger().info( - f"=== 二阶段定位完成: {self.target_hole} 残差 {final.get('residual_mm', -1)}mm," - f"规尖悬在孔面上方 ≈{gap_txt}mm(不接触)。" - f"结果: /stage2/result, /tmp/stage2_result.json ===" - ) + if final.get("insert_held"): + self.get_logger().info( + f"=== 二阶段定位完成: {self.target_hole} 残差 {final.get('residual_mm', -1)}mm," + f"法兰 Z={final.get('flange_z_m')}m,规尖离面 {gap_txt}mm" + f"(final_gap {self.final_gap_m * 1000:.1f}mm,无接触传感器)。" + "停在这一高度,不抬回悬高。" + f"结果: /stage2/result, /tmp/stage2_result.json ===" + ) + else: + self.get_logger().info( + f"=== 二阶段定位完成: {self.target_hole} 残差 {final.get('residual_mm', -1)}mm," + f"规尖悬在孔面上方 ≈{gap_txt}mm(不接触)。" + f"结果: /stage2/result, /tmp/stage2_result.json ===" + ) return 0, q_fin if q_fin is not None else q, Tee, final def _linger_hold(self, final: Dict[str, Any], seconds: Optional[float] = None) -> None: diff --git a/install/arm_application/lib/python3.11/site-packages/arm_application/stage3_search_node.py b/install/arm_application/lib/python3.11/site-packages/arm_application/stage3_search_node.py index e6e9bbf..acf1a50 100644 --- a/install/arm_application/lib/python3.11/site-packages/arm_application/stage3_search_node.py +++ b/install/arm_application/lib/python3.11/site-packages/arm_application/stage3_search_node.py @@ -526,7 +526,8 @@ class Stage3Search(Stage2Locate): tgt[2] += dz_m self._goto(q, Tee, tgt, verticalize=True, tol_m=0.001, tol_tilt=self.vert_tol_rad * 3.0, tag="retract", - v=0.03, max_hops=3, settle_s=0.2, min_dur=0.6) + v=0.03, max_hops=2, settle_s=0.2, min_dur=0.6, + hold_xy=True, z_up_only=True) except Exception: pass diff --git a/install/arm_application/share/arm_application/config/perception.yaml b/install/arm_application/share/arm_application/config/perception.yaml index c2411e9..5685bf9 100644 --- a/install/arm_application/share/arm_application/config/perception.yaml +++ b/install/arm_application/share/arm_application/config/perception.yaml @@ -12,7 +12,7 @@ perception_node: image_topic: "" camera_info_topic: "" depth_topic: "/d405/aligned_depth_to_color/image_raw" - weights: "models/yolo/gongjian0929.onnx" + weights: "/home/cc/ros2/models/yolo/gongjian0929.onnx" conf: 0.10 iou: 0.45 imgsz: 640 diff --git a/install/arm_application/share/arm_application/package.ps1 b/install/arm_application/share/arm_application/package.ps1 index c4a4018..44f3ad6 100644 --- a/install/arm_application/share/arm_application/package.ps1 +++ b/install/arm_application/share/arm_application/package.ps1 @@ -112,6 +112,5 @@ $env:COLCON_CURRENT_PREFIX=(Get-Item $PSCommandPath).Directory.Parent.Parent.Ful colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX/share/arm_application/hook/pythonpath.ps1" colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX/share/arm_application/hook/ament_prefix_path.ps1" -colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX/../../build/arm_application/share/arm_application/hook/pythonpath_develop.ps1" Remove-Item Env:\COLCON_CURRENT_PREFIX diff --git a/install/arm_application/share/colcon-core/packages/arm_application b/install/arm_application/share/colcon-core/packages/arm_application index a46ab6f..f7f9267 100644 --- a/install/arm_application/share/colcon-core/packages/arm_application +++ b/install/arm_application/share/colcon-core/packages/arm_application @@ -1 +1 @@ -arm_kinematics:arm_msgs:geometry_msgs:python3-serial:python3-yaml:rclpy:sensor_msgs:std_msgs:std_srvs:tf2_ros:trajectory_msgs \ No newline at end of file +arm_kinematics:arm_msgs:control_msgs:geometry_msgs:python3-serial:python3-yaml:rclpy:sensor_msgs:std_msgs:std_srvs:tf2_ros:trajectory_msgs \ No newline at end of file diff --git a/install/flexiv_hardware/include/flexiv_hardware/flexiv_dual_hardware_interface.hpp b/install/flexiv_hardware/include/flexiv_hardware/flexiv_dual_hardware_interface.hpp deleted file mode 100644 index 457aee2..0000000 --- a/install/flexiv_hardware/include/flexiv_hardware/flexiv_dual_hardware_interface.hpp +++ /dev/null @@ -1,139 +0,0 @@ -/** - * @file flexiv_dual_hardware_interface.hpp - * @brief Hardware interface to a pair of Flexiv robots for ROS 2 control. - * @copyright Copyright (C) 2016-2025 Flexiv Ltd. All Rights Reserved. - * @author Flexiv - */ - -#ifndef FLEXIV_HARDWARE__FLEXIV_DUAL_HARDWARE_INTERFACE_HPP_ -#define FLEXIV_HARDWARE__FLEXIV_DUAL_HARDWARE_INTERFACE_HPP_ - -#include -#include -#include -#include - -// ROS -#include -#include -#include -#include -#include -#include - -// ros2_control hardware_interface -#include -#include -#include - -#include "flexiv_hardware/visibility_control.h" - -// Flexiv -#include "flexiv/drdk/robot_pair.hpp" - -namespace flexiv_hardware { - -enum StoppingInterface -{ - NONE, - STOP_POSITION, - STOP_VELOCITY, - STOP_EFFORT -}; - -class FlexivDualHardwareInterface : public hardware_interface::SystemInterface -{ -public: - RCLCPP_SHARED_PTR_DEFINITIONS(FlexivDualHardwareInterface) - - FLEXIV_HARDWARE_PUBLIC - hardware_interface::CallbackReturn on_init( - const hardware_interface::HardwareInfo& info) override; - - FLEXIV_HARDWARE_PUBLIC - std::vector export_state_interfaces() override; - - FLEXIV_HARDWARE_PUBLIC - std::vector export_command_interfaces() override; - - FLEXIV_HARDWARE_PUBLIC - hardware_interface::return_type prepare_command_mode_switch( - const std::vector& start_interfaces, - const std::vector& stop_interfaces) override; - - FLEXIV_HARDWARE_PUBLIC - hardware_interface::return_type perform_command_mode_switch( - const std::vector& start_interfaces, - const std::vector& stop_interfaces) override; - - FLEXIV_HARDWARE_PUBLIC - hardware_interface::CallbackReturn on_activate( - const rclcpp_lifecycle::State& previous_state) override; - - FLEXIV_HARDWARE_PUBLIC - hardware_interface::CallbackReturn on_deactivate( - const rclcpp_lifecycle::State& previous_state) override; - - FLEXIV_HARDWARE_PUBLIC - hardware_interface::return_type read( - const rclcpp::Time& time, const rclcpp::Duration& period) override; - - FLEXIV_HARDWARE_PUBLIC - hardware_interface::return_type write( - const rclcpp::Time& time, const rclcpp::Duration& period) override; - -private: - // Flexiv DRDK - std::unique_ptr robot_pair_; - - // RDK control mode for joint position and velocity interfaces - flexiv::rdk::Mode rdk_control_mode_; - - // External axis type - std::string external_axis_type_ = ""; - - // Joint commands - std::vector hw_commands_joint_positions_; - std::vector hw_commands_joint_velocities_; - std::vector hw_commands_joint_efforts_; - - // Joint states - std::vector hw_states_joint_positions_; - std::vector hw_states_joint_velocities_; - std::vector hw_states_joint_efforts_; - - // Robot States - flexiv::rdk::RobotStates hw_flexiv_robot_states_left_; - flexiv::rdk::RobotStates hw_flexiv_robot_states_right_; - flexiv::rdk::RobotStates* hw_flexiv_robot_states_addr_left_ = &hw_flexiv_robot_states_left_; - flexiv::rdk::RobotStates* hw_flexiv_robot_states_addr_right_ = &hw_flexiv_robot_states_right_; - - // GPIO commands and states - std::vector hw_commands_gpio_out_; - std::vector hw_states_gpio_in_; - - // Current digital output map - std::map current_digital_outputs_left_; - std::map current_digital_outputs_right_; - - // Joint mapping - struct JointMap - { - int robot_index; // 0: Left, 1: Right - int dof_index; // Index in the robot's q vector - }; - std::vector joint_map_; - - static rclcpp::Logger getLogger(); - - // Control modes - bool controllers_initialized_; - std::vector stop_modes_; - std::vector start_modes_; - bool position_controller_running_; - bool velocity_controller_running_; - bool torque_controller_running_; -}; - -} /* namespace flexiv_hardware */ -#endif /* FLEXIV_HARDWARE__FLEXIV_DUAL_HARDWARE_INTERFACE_HPP_ */ diff --git a/install/flexiv_hardware/include/flexiv_hardware/flexiv_dual_hardware_interface.hpp b/install/flexiv_hardware/include/flexiv_hardware/flexiv_dual_hardware_interface.hpp new file mode 120000 index 0000000..c8349fb --- /dev/null +++ b/install/flexiv_hardware/include/flexiv_hardware/flexiv_dual_hardware_interface.hpp @@ -0,0 +1 @@ +/home/cc/ros2/src/flexiv_hardware/include/flexiv_hardware/flexiv_dual_hardware_interface.hpp \ No newline at end of file diff --git a/install/flexiv_hardware/include/flexiv_hardware/flexiv_hardware_interface.hpp b/install/flexiv_hardware/include/flexiv_hardware/flexiv_hardware_interface.hpp deleted file mode 100644 index adf1b7a..0000000 --- a/install/flexiv_hardware/include/flexiv_hardware/flexiv_hardware_interface.hpp +++ /dev/null @@ -1,156 +0,0 @@ -/** - * @file flexiv_hardware_interface.hpp - * @brief Hardware interface to Flexiv robots for ROS 2 control. Adapted from - * ros2_control_demos/example_3/hardware/include/ros2_control_demo_example_3/rrbot_system_multi_interface.hpp - * @copyright Copyright (C) 2016-2024 Flexiv Ltd. All Rights Reserved. - * @author Flexiv - */ - -#ifndef FLEXIV_HARDWARE__FLEXIV_HARDWARE_INTERFACE_HPP_ -#define FLEXIV_HARDWARE__FLEXIV_HARDWARE_INTERFACE_HPP_ - -#include -#include -#include -#include - -// ROS -#include -#include -#include -#include -#include -#include - -// ros2_control hardware_interface -#include -#include -#include - -#include "flexiv_hardware/visibility_control.h" - -// Flexiv -#include "flexiv/rdk/robot.hpp" - -namespace flexiv_hardware { - -enum StoppingInterface -{ - NONE, - STOP_POSITION, - STOP_VELOCITY, - STOP_EFFORT -}; - -class FlexivHardwareInterface : public hardware_interface::SystemInterface -{ -public: - RCLCPP_SHARED_PTR_DEFINITIONS(FlexivHardwareInterface) - - FLEXIV_HARDWARE_PUBLIC - hardware_interface::CallbackReturn on_init( - const hardware_interface::HardwareInfo& info) override; - - FLEXIV_HARDWARE_PUBLIC - std::vector export_state_interfaces() override; - - FLEXIV_HARDWARE_PUBLIC - std::vector export_command_interfaces() override; - - FLEXIV_HARDWARE_PUBLIC - hardware_interface::return_type prepare_command_mode_switch( - const std::vector& start_interfaces, - const std::vector& stop_interfaces) override; - - FLEXIV_HARDWARE_PUBLIC - hardware_interface::return_type perform_command_mode_switch( - const std::vector& start_interfaces, - const std::vector& stop_interfaces) override; - - FLEXIV_HARDWARE_PUBLIC - hardware_interface::CallbackReturn on_activate( - const rclcpp_lifecycle::State& previous_state) override; - - FLEXIV_HARDWARE_PUBLIC - hardware_interface::CallbackReturn on_deactivate( - const rclcpp_lifecycle::State& previous_state) override; - - FLEXIV_HARDWARE_PUBLIC - hardware_interface::return_type read( - const rclcpp::Time& time, const rclcpp::Duration& period) override; - - FLEXIV_HARDWARE_PUBLIC - hardware_interface::return_type write( - const rclcpp::Time& time, const rclcpp::Duration& period) override; - -private: - // Flexiv RDK - std::unique_ptr robot_; - - // RDK control mode for joint position and velocity interfaces - flexiv::rdk::Mode rdk_control_mode_; - - // Joint commands - std::vector hw_commands_joint_positions_; - std::vector hw_commands_joint_velocities_; - std::vector hw_commands_joint_efforts_; - - // Joint states - std::vector hw_states_joint_positions_; - std::vector hw_states_joint_velocities_; - std::vector hw_states_joint_efforts_; - - // Robot States - flexiv::rdk::RobotStates hw_flexiv_robot_states_; - flexiv::rdk::RobotStates* hw_flexiv_robot_states_addr_ = &hw_flexiv_robot_states_; - - // GPIO commands and states - std::vector hw_commands_gpio_out_; - std::vector hw_states_gpio_in_; - - // Map from RDK joint index to ROS joint index - // RDK expects: [ext_axis_1, ..., ext_axis_N, arm_joint_1, ..., arm_joint_7] - std::vector rdk_to_ros_map_; - - // Current digital output map - std::map current_digital_outputs_; - - static rclcpp::Logger getLogger(); - - // Control modes - bool controllers_initialized_; - std::vector stop_modes_; - std::vector start_modes_; - bool position_controller_running_; - bool velocity_controller_running_; - bool torque_controller_running_; - - // ===== 连接存活看门狗(2026-09-28 真机踩到)===== - // - // 现象:RDK 断连后 `operational()` 会**一直保持 true**,于是 read() 照跑却只 - // 返回最后一帧缓存(关节状态永久冻结),write() 把命令发进虚空也不抛异常, - // 而 rizon_arm_controller 是本地插值、不校验反馈 —— 最终表现为 - // **「控制器报 Goal reached/success、关节状态纹丝不动、臂一步没动」**。 - // - // 实测:14:53:44 RDK 打印 "Disconnected from the robot, waiting for reconnection", - // 此后 50 分钟里 /joint_states 的 robot_timestamp 推进量恒为 0,而 `op=1` 没变过。 - // 根因是两台臂出厂 MAC 相同(88:88:88:88:87:88)在同一 L2 段抢转发,链路丢包 40~53%。 - // - // 这里用 connected() + states().timestamp 双重判活:任一失效就返回 ERROR, - // 让 ros2_control 把栈停下来报错,而不是静默地"成功"。 - int64_t last_state_stamp_ns_ = 0; // 上一帧的机器人时间戳 - int64_t last_state_change_ns_ = 0; // 上一帧时间戳发生变化的本地时刻 - - // D435 粗定位的 MoveL 走已经连上的这份 RDK。write() 里一步一步推进, - // 不另开线程,也不在这段里往关节接口灌点。请求文件是 /tmp/flexiv_movel_req。 - enum class VendorMovelPhase { kIdle, kRunning }; - VendorMovelPhase vendor_movel_phase_ = VendorMovelPhase::kIdle; - long vendor_movel_seq_ = -1; - long vendor_movel_done_seq_ = -1; - std::chrono::steady_clock::time_point vendor_movel_deadline_ {}; - bool poll_vendor_movel(); - void finish_vendor_movel(long seq, bool ok, const std::string& detail); -}; - -} /* namespace flexiv_hardware */ -#endif /* FLEXIV_HARDWARE__FLEXIV_HARDWARE_INTERFACE_HPP_ */ diff --git a/install/flexiv_hardware/include/flexiv_hardware/flexiv_hardware_interface.hpp b/install/flexiv_hardware/include/flexiv_hardware/flexiv_hardware_interface.hpp new file mode 120000 index 0000000..2ac643f --- /dev/null +++ b/install/flexiv_hardware/include/flexiv_hardware/flexiv_hardware_interface.hpp @@ -0,0 +1 @@ +/home/cc/ros2/src/flexiv_hardware/include/flexiv_hardware/flexiv_hardware_interface.hpp \ No newline at end of file diff --git a/install/flexiv_hardware/include/flexiv_hardware/visibility_control.h b/install/flexiv_hardware/include/flexiv_hardware/visibility_control.h deleted file mode 100644 index 2c26a9e..0000000 --- a/install/flexiv_hardware/include/flexiv_hardware/visibility_control.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef FLEXIV_HARDWARE__VISIBILITY_CONTROL_H_ -#define FLEXIV_HARDWARE__VISIBILITY_CONTROL_H_ - -// This logic was borrowed (then namespaced) from the examples on the gcc wiki: -// https://gcc.gnu.org/wiki/Visibility - -#if defined _WIN32 || defined __CYGWIN__ -#ifdef __GNUC__ -#define FLEXIV_HARDWARE_EXPORT __attribute__((dllexport)) -#define FLEXIV_HARDWARE_IMPORT __attribute__((dllimport)) -#else -#define FLEXIV_HARDWARE_EXPORT __declspec(dllexport) -#define FLEXIV_HARDWARE_IMPORT __declspec(dllimport) -#endif -#ifdef FLEXIV_HARDWARE_BUILDING_DLL -#define FLEXIV_HARDWARE_PUBLIC FLEXIV_HARDWARE_EXPORT -#else -#define FLEXIV_HARDWARE_PUBLIC FLEXIV_HARDWARE_IMPORT -#endif -#define FLEXIV_HARDWARE_PUBLIC_TYPE FLEXIV_HARDWARE_PUBLIC -#define FLEXIV_HARDWARE_LOCAL -#else -#define FLEXIV_HARDWARE_EXPORT __attribute__((visibility("default"))) -#define FLEXIV_HARDWARE_IMPORT -#if __GNUC__ >= 4 -#define FLEXIV_HARDWARE_PUBLIC __attribute__((visibility("default"))) -#define FLEXIV_HARDWARE_LOCAL __attribute__((visibility("hidden"))) -#else -#define FLEXIV_HARDWARE_PUBLIC -#define FLEXIV_HARDWARE_LOCAL -#endif -#define FLEXIV_HARDWARE_PUBLIC_TYPE -#endif - -#endif // FLEXIV_HARDWARE__VISIBILITY_CONTROL_H_ diff --git a/install/flexiv_hardware/include/flexiv_hardware/visibility_control.h b/install/flexiv_hardware/include/flexiv_hardware/visibility_control.h new file mode 120000 index 0000000..ee4aec6 --- /dev/null +++ b/install/flexiv_hardware/include/flexiv_hardware/visibility_control.h @@ -0,0 +1 @@ +/home/cc/ros2/src/flexiv_hardware/include/flexiv_hardware/visibility_control.h \ No newline at end of file diff --git a/install/flexiv_hardware/lib/libflexiv_hardware.so b/install/flexiv_hardware/lib/libflexiv_hardware.so deleted file mode 100755 index bac94b6b85a418c722dc96f0027284bfac5ebb23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1595352 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°A}LhO^KV4wR!cA@y*6qio*e9`OHUuNY;D5m&> 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

vPMqm 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

E2v-?Kalx_i|My3)7N6=l6j3x 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 z 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

7vWkr(l`FzX!fYf2$4`lm^Hu8>j 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

>$_qcJYw@xWq(t(em5pW!H5> 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

    HAgw= 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 z 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 ssQJH@_D^IMS1qG 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 - - - - ROS 2 Control Hardware interface for the Flexiv robots. This driver - uses Flexiv RDK. Position, Velocity and Effort interfaces are supported. - - - - - - ROS 2 Control Hardware interface for a pair of Flexiv robots. This driver - uses Flexiv DRDK. Position, Velocity and Effort interfaces are supported. - - - diff --git a/install/flexiv_hardware/share/flexiv_hardware/flexiv_hardware_interface_plugin.xml b/install/flexiv_hardware/share/flexiv_hardware/flexiv_hardware_interface_plugin.xml new file mode 120000 index 0000000..deb10b3 --- /dev/null +++ b/install/flexiv_hardware/share/flexiv_hardware/flexiv_hardware_interface_plugin.xml @@ -0,0 +1 @@ +/home/cc/ros2/src/flexiv_hardware/flexiv_hardware_interface_plugin.xml \ No newline at end of file diff --git a/install/flexiv_hardware/share/flexiv_hardware/local_setup.bash b/install/flexiv_hardware/share/flexiv_hardware/local_setup.bash deleted file mode 100644 index 49782f2..0000000 --- a/install/flexiv_hardware/share/flexiv_hardware/local_setup.bash +++ /dev/null @@ -1,46 +0,0 @@ -# generated from ament_package/template/package_level/local_setup.bash.in - -# source local_setup.sh from same directory as this file -_this_path=$(builtin cd "`dirname "${BASH_SOURCE[0]}"`" && pwd) -# provide AMENT_CURRENT_PREFIX to shell script -AMENT_CURRENT_PREFIX=$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." && pwd) -# store AMENT_CURRENT_PREFIX to restore it before each environment hook -_package_local_setup_AMENT_CURRENT_PREFIX=$AMENT_CURRENT_PREFIX - -# trace output -if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then - echo "# . \"$_this_path/local_setup.sh\"" -fi -. "$_this_path/local_setup.sh" -unset _this_path - -# unset AMENT_ENVIRONMENT_HOOKS -# if not appending to them for return -if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then - unset AMENT_ENVIRONMENT_HOOKS -fi - -# restore AMENT_CURRENT_PREFIX before evaluating the environment hooks -AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX -# list all environment hooks of this package - -# source all shell-specific environment hooks of this package -# if not returning them -if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then - _package_local_setup_IFS=$IFS - IFS=":" - for _hook in $AMENT_ENVIRONMENT_HOOKS; do - # restore AMENT_CURRENT_PREFIX for each environment hook - AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX - # restore IFS before sourcing other files - IFS=$_package_local_setup_IFS - . "$_hook" - done - unset _hook - IFS=$_package_local_setup_IFS - unset _package_local_setup_IFS - unset AMENT_ENVIRONMENT_HOOKS -fi - -unset _package_local_setup_AMENT_CURRENT_PREFIX -unset AMENT_CURRENT_PREFIX diff --git a/install/flexiv_hardware/share/flexiv_hardware/local_setup.bash b/install/flexiv_hardware/share/flexiv_hardware/local_setup.bash new file mode 120000 index 0000000..673cd47 --- /dev/null +++ b/install/flexiv_hardware/share/flexiv_hardware/local_setup.bash @@ -0,0 +1 @@ +/home/cc/ros2/build/flexiv_hardware/ament_cmake_environment_hooks/local_setup.bash \ No newline at end of file diff --git a/install/flexiv_hardware/share/flexiv_hardware/local_setup.dsv b/install/flexiv_hardware/share/flexiv_hardware/local_setup.dsv deleted file mode 100644 index d4a37ec..0000000 --- a/install/flexiv_hardware/share/flexiv_hardware/local_setup.dsv +++ /dev/null @@ -1,2 +0,0 @@ -source;share/flexiv_hardware/environment/ament_prefix_path.sh -source;share/flexiv_hardware/environment/path.sh diff --git a/install/flexiv_hardware/share/flexiv_hardware/local_setup.dsv b/install/flexiv_hardware/share/flexiv_hardware/local_setup.dsv new file mode 120000 index 0000000..49afd59 --- /dev/null +++ b/install/flexiv_hardware/share/flexiv_hardware/local_setup.dsv @@ -0,0 +1 @@ +/home/cc/ros2/build/flexiv_hardware/ament_cmake_environment_hooks/local_setup.dsv \ No newline at end of file diff --git a/install/flexiv_hardware/share/flexiv_hardware/local_setup.sh b/install/flexiv_hardware/share/flexiv_hardware/local_setup.sh deleted file mode 100644 index 260497e..0000000 --- a/install/flexiv_hardware/share/flexiv_hardware/local_setup.sh +++ /dev/null @@ -1,184 +0,0 @@ -# generated from ament_package/template/package_level/local_setup.sh.in - -# since this file is sourced use either the provided AMENT_CURRENT_PREFIX -# or fall back to the destination set at configure time -: ${AMENT_CURRENT_PREFIX:="/home/cc/ros2/install/flexiv_hardware"} -if [ ! -d "$AMENT_CURRENT_PREFIX" ]; then - if [ -z "$COLCON_CURRENT_PREFIX" ]; then - echo "The compile time prefix path '$AMENT_CURRENT_PREFIX' doesn't " \ - "exist. Consider sourcing a different extension than '.sh'." 1>&2 - else - AMENT_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" - fi -fi - -# function to append values to environment variables -# using colons as separators and avoiding leading separators -ament_append_value() { - # arguments - _listname="$1" - _value="$2" - #echo "listname $_listname" - #eval echo "list value \$$_listname" - #echo "value $_value" - - # avoid leading separator - eval _values=\"\$$_listname\" - if [ -z "$_values" ]; then - eval export $_listname=\"$_value\" - #eval echo "set list \$$_listname" - else - # field separator must not be a colon - _ament_append_value_IFS=$IFS - unset IFS - eval export $_listname=\"\$$_listname:$_value\" - #eval echo "append list \$$_listname" - IFS=$_ament_append_value_IFS - unset _ament_append_value_IFS - fi - unset _values - - unset _value - unset _listname -} - -# function to append non-duplicate values to environment variables -# using colons as separators and avoiding leading separators -ament_append_unique_value() { - # arguments - _listname=$1 - _value=$2 - #echo "listname $_listname" - #eval echo "list value \$$_listname" - #echo "value $_value" - - # check if the list contains the value - eval _values=\$$_listname - _duplicate= - _ament_append_unique_value_IFS=$IFS - IFS=":" - if [ "$AMENT_SHELL" = "zsh" ]; then - ament_zsh_to_array _values - fi - for _item in $_values; do - # ignore empty strings - if [ -z "$_item" ]; then - continue - fi - if [ $_item = $_value ]; then - _duplicate=1 - fi - done - unset _item - - # append only non-duplicates - if [ -z "$_duplicate" ]; then - # avoid leading separator - if [ -z "$_values" ]; then - eval $_listname=\"$_value\" - #eval echo "set list \$$_listname" - else - # field separator must not be a colon - unset IFS - eval $_listname=\"\$$_listname:$_value\" - #eval echo "append list \$$_listname" - fi - fi - IFS=$_ament_append_unique_value_IFS - unset _ament_append_unique_value_IFS - unset _duplicate - unset _values - - unset _value - unset _listname -} - -# function to prepend non-duplicate values to environment variables -# using colons as separators and avoiding trailing separators -ament_prepend_unique_value() { - # arguments - _listname="$1" - _value="$2" - #echo "listname $_listname" - #eval echo "list value \$$_listname" - #echo "value $_value" - - # check if the list contains the value - eval _values=\"\$$_listname\" - _duplicate= - _ament_prepend_unique_value_IFS=$IFS - IFS=":" - if [ "$AMENT_SHELL" = "zsh" ]; then - ament_zsh_to_array _values - fi - for _item in $_values; do - # ignore empty strings - if [ -z "$_item" ]; then - continue - fi - if [ "$_item" = "$_value" ]; then - _duplicate=1 - fi - done - unset _item - - # prepend only non-duplicates - if [ -z "$_duplicate" ]; then - # avoid trailing separator - if [ -z "$_values" ]; then - eval export $_listname=\"$_value\" - #eval echo "set list \$$_listname" - else - # field separator must not be a colon - unset IFS - eval export $_listname=\"$_value:\$$_listname\" - #eval echo "prepend list \$$_listname" - fi - fi - IFS=$_ament_prepend_unique_value_IFS - unset _ament_prepend_unique_value_IFS - unset _duplicate - unset _values - - unset _value - unset _listname -} - -# unset AMENT_ENVIRONMENT_HOOKS -# if not appending to them for return -if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then - unset AMENT_ENVIRONMENT_HOOKS -fi - -# list all environment hooks of this package -ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/flexiv_hardware/environment/ament_prefix_path.sh" -ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/flexiv_hardware/environment/path.sh" - -# source all shell-specific environment hooks of this package -# if not returning them -if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then - _package_local_setup_IFS=$IFS - IFS=":" - if [ "$AMENT_SHELL" = "zsh" ]; then - ament_zsh_to_array AMENT_ENVIRONMENT_HOOKS - fi - for _hook in $AMENT_ENVIRONMENT_HOOKS; do - if [ -f "$_hook" ]; then - # restore IFS before sourcing other files - IFS=$_package_local_setup_IFS - # trace output - if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then - echo "# . \"$_hook\"" - fi - . "$_hook" - fi - done - unset _hook - IFS=$_package_local_setup_IFS - unset _package_local_setup_IFS - unset AMENT_ENVIRONMENT_HOOKS -fi - -# reset AMENT_CURRENT_PREFIX after each package -# allowing to source multiple package-level setup files -unset AMENT_CURRENT_PREFIX diff --git a/install/flexiv_hardware/share/flexiv_hardware/local_setup.sh b/install/flexiv_hardware/share/flexiv_hardware/local_setup.sh new file mode 120000 index 0000000..23657ac --- /dev/null +++ b/install/flexiv_hardware/share/flexiv_hardware/local_setup.sh @@ -0,0 +1 @@ +/home/cc/ros2/build/flexiv_hardware/ament_cmake_environment_hooks/local_setup.sh \ No newline at end of file diff --git a/install/flexiv_hardware/share/flexiv_hardware/local_setup.zsh b/install/flexiv_hardware/share/flexiv_hardware/local_setup.zsh deleted file mode 100644 index fe161be..0000000 --- a/install/flexiv_hardware/share/flexiv_hardware/local_setup.zsh +++ /dev/null @@ -1,59 +0,0 @@ -# generated from ament_package/template/package_level/local_setup.zsh.in - -AMENT_SHELL=zsh - -# source local_setup.sh from same directory as this file -_this_path=$(builtin cd -q "`dirname "${(%):-%N}"`" > /dev/null && pwd) -# provide AMENT_CURRENT_PREFIX to shell script -AMENT_CURRENT_PREFIX=$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd) -# store AMENT_CURRENT_PREFIX to restore it before each environment hook -_package_local_setup_AMENT_CURRENT_PREFIX=$AMENT_CURRENT_PREFIX - -# function to convert array-like strings into arrays -# to wordaround SH_WORD_SPLIT not being set -ament_zsh_to_array() { - local _listname=$1 - local _dollar="$" - local _split="{=" - local _to_array="(\"$_dollar$_split$_listname}\")" - eval $_listname=$_to_array -} - -# trace output -if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then - echo "# . \"$_this_path/local_setup.sh\"" -fi -# the package-level local_setup file unsets AMENT_CURRENT_PREFIX -. "$_this_path/local_setup.sh" -unset _this_path - -# unset AMENT_ENVIRONMENT_HOOKS -# if not appending to them for return -if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then - unset AMENT_ENVIRONMENT_HOOKS -fi - -# restore AMENT_CURRENT_PREFIX before evaluating the environment hooks -AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX -# list all environment hooks of this package - -# source all shell-specific environment hooks of this package -# if not returning them -if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then - _package_local_setup_IFS=$IFS - IFS=":" - for _hook in $AMENT_ENVIRONMENT_HOOKS; do - # restore AMENT_CURRENT_PREFIX for each environment hook - AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX - # restore IFS before sourcing other files - IFS=$_package_local_setup_IFS - . "$_hook" - done - unset _hook - IFS=$_package_local_setup_IFS - unset _package_local_setup_IFS - unset AMENT_ENVIRONMENT_HOOKS -fi - -unset _package_local_setup_AMENT_CURRENT_PREFIX -unset AMENT_CURRENT_PREFIX diff --git a/install/flexiv_hardware/share/flexiv_hardware/local_setup.zsh b/install/flexiv_hardware/share/flexiv_hardware/local_setup.zsh new file mode 120000 index 0000000..8c19084 --- /dev/null +++ b/install/flexiv_hardware/share/flexiv_hardware/local_setup.zsh @@ -0,0 +1 @@ +/home/cc/ros2/build/flexiv_hardware/ament_cmake_environment_hooks/local_setup.zsh \ No newline at end of file diff --git a/install/flexiv_hardware/share/flexiv_hardware/package.xml b/install/flexiv_hardware/share/flexiv_hardware/package.xml deleted file mode 100644 index ebabe8d..0000000 --- a/install/flexiv_hardware/share/flexiv_hardware/package.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - flexiv_hardware - 1.9.1 - Hardware interfaces to Flexiv robots for ROS 2 control - Mun Seng Phoon - Apache 2.0 - Mun Seng Phoon - - ament_cmake - - rclcpp - rclcpp_lifecycle - hardware_interface - pluginlib - - ament_lint_auto - ament_lint_common - ament_cmake_gtest - - - ament_cmake - - diff --git a/install/flexiv_hardware/share/flexiv_hardware/package.xml b/install/flexiv_hardware/share/flexiv_hardware/package.xml new file mode 120000 index 0000000..adf94cf --- /dev/null +++ b/install/flexiv_hardware/share/flexiv_hardware/package.xml @@ -0,0 +1 @@ +/home/cc/ros2/src/flexiv_hardware/package.xml \ No newline at end of file diff --git a/install/setup.bash b/install/setup.bash index d3b480f..b2857a6 100644 --- a/install/setup.bash +++ b/install/setup.bash @@ -19,19 +19,8 @@ _colcon_prefix_chain_bash_source_script() { # source chained prefixes # setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced script -# 不写死某一台机器的 conda。已激活的环境优先,否则找当前用户的 ros_flexiv。 -if [ -n "${CONDA_PREFIX:-}" ] && [ -f "${CONDA_PREFIX}/local_setup.bash" ]; then - COLCON_CURRENT_PREFIX="${CONDA_PREFIX}" -elif [ -f "${HOME}/anaconda3/envs/ros_flexiv/local_setup.bash" ]; then - COLCON_CURRENT_PREFIX="${HOME}/anaconda3/envs/ros_flexiv" -elif [ -f "${HOME}/miniconda3/envs/ros_flexiv/local_setup.bash" ]; then - COLCON_CURRENT_PREFIX="${HOME}/miniconda3/envs/ros_flexiv" -else - COLCON_CURRENT_PREFIX="" -fi -if [ -n "${COLCON_CURRENT_PREFIX}" ]; then - _colcon_prefix_chain_bash_source_script "$COLCON_CURRENT_PREFIX/local_setup.bash" -fi +COLCON_CURRENT_PREFIX="/home/cc/anaconda3/envs/ros_flexiv" +_colcon_prefix_chain_bash_source_script "$COLCON_CURRENT_PREFIX/local_setup.bash" # source this prefix # setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced script diff --git a/install/setup.zsh b/install/setup.zsh index 9490adc..39dd846 100644 --- a/install/setup.zsh +++ b/install/setup.zsh @@ -19,19 +19,8 @@ _colcon_prefix_chain_zsh_source_script() { # source chained prefixes # setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced script -# 不写死某一台机器的 conda。已激活的环境优先,否则找当前用户的 ros_flexiv。 -if [ -n "${CONDA_PREFIX:-}" ] && [ -f "${CONDA_PREFIX}/local_setup.zsh" ]; then - COLCON_CURRENT_PREFIX="${CONDA_PREFIX}" -elif [ -f "${HOME}/anaconda3/envs/ros_flexiv/local_setup.zsh" ]; then - COLCON_CURRENT_PREFIX="${HOME}/anaconda3/envs/ros_flexiv" -elif [ -f "${HOME}/miniconda3/envs/ros_flexiv/local_setup.zsh" ]; then - COLCON_CURRENT_PREFIX="${HOME}/miniconda3/envs/ros_flexiv" -else - COLCON_CURRENT_PREFIX="" -fi -if [ -n "${COLCON_CURRENT_PREFIX}" ]; then - _colcon_prefix_chain_zsh_source_script "$COLCON_CURRENT_PREFIX/local_setup.zsh" -fi +COLCON_CURRENT_PREFIX="/home/cc/anaconda3/envs/ros_flexiv" +_colcon_prefix_chain_zsh_source_script "$COLCON_CURRENT_PREFIX/local_setup.zsh" # source this prefix # setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced script diff --git a/scripts/gen_site_example.py b/scripts/gen_site_example.py index 980682a..6852ebc 100644 --- a/scripts/gen_site_example.py +++ b/scripts/gen_site_example.py @@ -51,8 +51,8 @@ HEADER = """\ # 用法:复制本文件为 config/site.yaml,然后把所有 <占位符> 换成现场实际值。 # 现场值包括:机械臂 SN / IP、相机序列号、夹爪串口、传感器 USB 路径。 # -# 说明:仓库里的文件用相对仓库根的路径(例如 models/yolo/...)。 -# 不要改回某个用户的绝对家目录,换台电脑就会找不到文件。 +# 说明:site.yaml 里的路径出现 /home/USER 是因为生成时匿名化了用户名; +# 部署时请改成实际用户的 home 目录。 # ============================================================================= """ diff --git a/scripts/lib/export_site_env.py b/scripts/lib/export_site_env.py index e0485a1..c679ed3 100644 --- a/scripts/lib/export_site_env.py +++ b/scripts/lib/export_site_env.py @@ -42,13 +42,7 @@ def load_site(path: Path | None = None) -> dict: data = yaml.safe_load(text) or {} if not isinstance(data, dict): raise ValueError(f"无效的 site.yaml: {path}") - # 相对路径按仓库根展开。site.yaml 里不再写 /home/某用户/ros2。 - src = str(_root() / "src" / "arm_application") - if src not in sys.path: - sys.path.insert(0, src) - from arm_application.site_io import absolutize_site_paths - - return absolutize_site_paths(data, _root()) + return data def list_robots(data: dict) -> list[dict]: @@ -947,7 +941,7 @@ def servo_overrides(data: dict) -> dict: if "d435_handback_sec" not in servo: raise RuntimeError( "servo.d435_handback_sec 必须显式写" - "(厂家 MoveL 到位后,再确认多久才交回 D405)。不许用代码默认秒数。" + "(厂家 MoveL 结束后,法兰停稳多久才打开 D405 框心)。不许用代码默认秒数。" ) if "wrist_align_sec" not in servo: raise RuntimeError( diff --git a/scripts/rdk_drag_bridge.py b/scripts/rdk_drag_bridge.py index af19238..bde0e4e 100755 --- a/scripts/rdk_drag_bridge.py +++ b/scripts/rdk_drag_bridge.py @@ -33,7 +33,6 @@ RDK 的 FloatingJoint/FloatingCartesian 通常需要额外授权。 {"cmd":"drag_on","mode":"soft"} # soft | floating {"cmd":"drag_off"} {"cmd":"joints"} - {"cmd":"move_joints","q":[0,0,0,0,0,0,0],"speed":0.15} {"cmd":"quit"} 每个命令的应答都是单行 JSON,含 ``ok`` 字段;失败时另有 ``error``。 @@ -170,69 +169,6 @@ def _ensure_operational(robot) -> Optional[str]: return ensure_operational(robot) -def _restore_tracking_impedance(robot, flexivrdk) -> None: - """把关节阻抗从软拖拽拉回名义值,否则回放点位时臂跟不住指令。""" - try: - info = robot.info() - groups = dict(getattr(info, 'single_arm_groups', None) or {}) - except Exception as exc: - log(f'读名义阻抗失败: {exc}') - groups = {} - if groups: - for group in groups: - try: - nominal = [float(v) for v in list(info.K_q_nom[group])] - robot.SetJointImpedance(group, nominal) - log(f'恢复阻抗 {group} K={nominal}') - except Exception as exc: - log(f'恢复阻抗 {group}: {exc}') - return - try: - robot.SetJointImpedance([350.0] * 7, [7.0] * 7) - except Exception as exc: - log(f'恢复阻抗: {exc}') - - -def _send_joint_target(robot, flexivrdk, target: List[float], grouped: Optional[bool]) -> bool: - """下发 7 关节目标。返回是否走了分组接口。""" - if grouped is True: - _send_grouped_joint_target(robot, flexivrdk, target) - return True - try: - n = len(target) - robot.SendJointPosition(target, [0.0] * n, [1.5] * n, [2.0] * n) - return False - except Exception as exc: - if grouped is False: - raise - log(f'扁平关节指令失败,改分组: {exc}') - _send_grouped_joint_target(robot, flexivrdk, target) - return True - - -def _send_grouped_joint_target(robot, flexivrdk, target: List[float]) -> None: - info = robot.info() - groups = dict(getattr(info, 'single_arm_groups', None) or {}) - if not groups: - raise RuntimeError('没有分组,扁平关节指令也失败了') - states = robot.states() - cmds = {} - placed = False - for group in groups: - try: - current = [float(v) for v in list(states[group].q)] - except Exception: - current = list(target) - use = target if (not placed and len(current) == len(target)) else current - if use is target: - placed = True - n = len(use) - cmds[group] = flexivrdk.NrtJointPositionCmd(use, [0.0] * n, [1.5] * n, [2.0] * n) - if not placed: - raise RuntimeError('没有一组关节数和示教点一致,拒绝下发') - robot.SendJointPosition(cmds) - - def _idle_async(robot, lock: threading.Lock, flexivrdk, timeout: float = 4.0) -> bool: """切到 IDLE,同时避免把 stdin 循环永久阻塞住。""" done = threading.Event() @@ -487,65 +423,6 @@ def main() -> int: info['tool'] = '' reply(info) - elif cmd == 'move_joints': - if robot is None: - reply({'ok': False, 'error': '尚未连接'}) - continue - raw = msg.get('q') - if not isinstance(raw, list) or len(raw) < 7: - reply({'ok': False, 'error': '需要 7 个关节角(弧度)'}) - continue - try: - target = [float(v) for v in raw[:7]] - except (TypeError, ValueError): - reply({'ok': False, 'error': '关节角不是数字'}) - continue - if any(v != v or abs(v) > 12.6 for v in target): - reply({'ok': False, 'error': '关节角不是有限值,或超出约两圈'}) - continue - try: - speed = float(msg.get('speed', 0.15)) - except (TypeError, ValueError): - speed = 0.15 - speed = max(0.05, min(0.35, speed)) - if soft is not None: - soft.stop() - soft = None - drag_on = False - err = _ensure_operational(robot) - if err: - reply({'ok': False, 'error': err}) - continue - try: - with rlock: - robot.SwitchMode(flexivrdk.Mode.NRT_JOINT_IMPEDANCE) - current = [float(v) for v in list(robot.states().q)[:7]] - _restore_tracking_impedance(robot, flexivrdk) - max_delta = max(abs(a - b) for a, b in zip(current, target)) - step_s = 0.05 - steps = max(1, int(max_delta / (speed * step_s))) - if steps * step_s > 150.0: - reply({ - 'ok': False, - 'error': f'路程太长({max_delta:.2f} rad),请先拖近一点再回放', - }) - continue - grouped: Optional[bool] = None - for index in range(1, steps + 1): - alpha = index / steps - sample = [a + (b - a) * alpha for a, b in zip(current, target)] - with rlock: - grouped = _send_joint_target(robot, flexivrdk, sample, grouped) - time.sleep(step_s) - for _hold in range(8): - with rlock: - grouped = _send_joint_target(robot, flexivrdk, target, grouped) - time.sleep(step_s) - cached_q = list(target) - reply({'ok': True, 'q': target, 'from': current, 'steps': steps, 'speed': speed}) - except Exception as exc: - reply({'ok': False, 'error': f'回点失败: {exc}'}) - elif cmd == 'quit': if soft is not None: soft.stop() diff --git a/scripts/rdk_release_movel.py b/scripts/rdk_release_movel.py index 5e5e46b..fb8ea56 100644 --- a/scripts/rdk_release_movel.py +++ b/scripts/rdk_release_movel.py @@ -23,9 +23,7 @@ sys.path.insert(0, str(ROOT / "scripts" / "lib")) from rdk_release import is_control_stack_cmd # noqa: E402 -RDK_PYTHON = os.environ.get("RDK_PYTHON", "").strip() or str( - Path.home() / "rdk_py310" / "bin" / "python" -) +RDK_PYTHON = os.environ.get("RDK_PYTHON", "/home/cc/rdk_py310/bin/python") def _procs() -> list[tuple[int, str]]: diff --git a/scripts/run_all.sh b/scripts/run_all.sh index ef771a3..647484f 100755 --- a/scripts/run_all.sh +++ b/scripts/run_all.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# 跟踪 → 对孔。搜孔轻压已取消,本脚本不再进入那一步,也不能用参数打开。 +# 三阶段装配流程的完整入口:一阶段跟踪 → 二阶段对孔 → 三阶段搜孔轻压。 # # 为什么要单独有这个脚本: # pipeline.launch.py 只管两件事 —— 发布手眼静态 TF、起 assembly_pipeline 串联节点。 @@ -13,10 +13,10 @@ # 4) exec pipeline.launch.py,把下面的 CLI 参数映射成它的 launch 参数 # # 用法: -# bash ~/ros2/scripts/run_all.sh --arm 89 # 跟踪 + 对孔 +# bash ~/ros2/scripts/run_all.sh --arm 89 # 全流程 # bash ~/ros2/scripts/run_all.sh --arm 89 --hole k2 # 指定对 k2 孔 # bash ~/ros2/scripts/run_all.sh --arm 89 --skip-stage1 # 臂已在工件上方,跳过跟踪 -# 旋入不在这里。用 run_stage3_search.sh --arm 89 --screw +# bash ~/ros2/scripts/run_all.sh --arm 89 --depth 6 --pitch 1.0 # bash ~/ros2/scripts/run_all.sh --arm 89 --camera-preview # 相机带 Qt 预览窗 # bash ~/ros2/scripts/run_all.sh --arm 89 --no-camera # 相机已在别处拉起了 # bash ~/ros2/scripts/run_all.sh --arm 89 --no-tactile # 不起触觉服务 @@ -37,7 +37,7 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # ---- 默认值:全部沿用 site.yaml / launch 默认,CLI 只做覆盖 ---- SKIP_STAGE1=false SKIP_STAGE2=false -SKIP_STAGE3=true # 搜孔已取消。--skip-stage3 仍然接受,但没有开关能把它重新打开。 +SKIP_STAGE3=false HOLE="" DEPTH="" PITCH="" @@ -83,12 +83,6 @@ while [[ $# -gt 0 ]]; do esac done -if [[ -n "${DEPTH}" || -n "${PITCH}" ]]; then - echo "ERROR: 搜孔轻压已取消,整体流程不再搜孔,也不在这里旋入。" >&2 - echo " 旋入请用: bash ~/ros2/scripts/run_stage3_search.sh --arm ${SITE_ROBOT_ARM:-89} --screw" >&2 - exit 1 -fi - if [[ -n "${FINAL_GAP}" && "${NO_DESCENT}" == true ]]; then echo "ERROR: 同时写了 --final-gap 和 --no-descent,不知道听谁的。" >&2 exit 1 @@ -145,9 +139,7 @@ if [[ "${D435_ONLY}" == true ]]; then fi echo "=== MoveL:XY 对准 K0,工具尖高度保持当前(K0 的 z 是工件表面)===" # --go:不按直线距离拦截。800 mm 是脚本自己的保护,不是机械臂限位。 - # shellcheck disable=SC1091 - source "${ROOT}/scripts/lib/dev_tools.sh" - exec "$(dev_rdk_python)" "${ROOT}/scripts/rdk_goto_xyz.py" \ + exec /home/cc/rdk_py310/bin/python "${ROOT}/scripts/rdk_goto_xyz.py" \ --xyz "${kx}" "${ky}" "${kz}" --keep-z --go --vel 0.05 fi @@ -247,7 +239,7 @@ fi # 每次 echo 吃满超时 × 60 次 ≈ 3 分钟,终端上没有任何新行(2026-10-09 真机)。 _SKIP_TACTILE_REASON="" if [[ "${SKIP_STAGE3}" == true && "${WITH_TACTILE}" == true ]]; then - echo "=== 搜孔已取消:跟踪和对孔不用触觉,跳过拉起 ===" + echo "=== --skip-stage3:一/二阶段不用触觉,跳过拉起 ===" WITH_TACTILE=false _SKIP_TACTILE_REASON="stage3" fi diff --git a/scripts/run_camera_d405.sh b/scripts/run_camera_d405.sh index 829337c..4821e9a 100755 --- a/scripts/run_camera_d405.sh +++ b/scripts/run_camera_d405.sh @@ -31,8 +31,6 @@ done # shellcheck disable=SC1091 source "${ROOT}/scripts/env.sh" -# 用源码里的采集逻辑,不走 install 里那份旧的。install 的入口仍由 ros2 run 找到。 -export PYTHONPATH="${ROOT}/src/arm_application${PYTHONPATH:+:${PYTHONPATH}}" # shellcheck disable=SC1091 source "${ROOT}/scripts/lib/load_site_config.sh" @@ -129,14 +127,12 @@ DO_PREVIEW="${SITE_CAM_PREVIEW:-1}" if [[ "${PREVIEW}" -ge 0 ]]; then DO_PREVIEW="${PREVIEW}" fi -# 程序界面只看彩色和深度。红外是旧的四窗格查看器才要的。 -# 彩色要等红外齐帧时,场景一动,整组帧对不齐,彩色就被丢掉。 -if [[ "${DO_PREVIEW}" == "1" && "${SITE_CAM_ENABLE_INFRA:-1}" == "1" ]]; then +if [[ "${SITE_CAM_ENABLE_INFRA:-1}" == "1" ]]; then CAM_ARGS+=(-p enable_infra:=true) else CAM_ARGS+=(-p enable_infra:=false) fi -if [[ "${DO_PREVIEW}" == "1" && "${SITE_CAM_PREVIEW_STEREO:-1}" == "1" ]]; then +if [[ "${SITE_CAM_PREVIEW_STEREO:-1}" == "1" ]]; then CAM_ARGS+=(-p preview_stereo:=true) else CAM_ARGS+=(-p preview_stereo:=false) diff --git a/scripts/run_camera_d435.sh b/scripts/run_camera_d435.sh index 937a425..af6a5ca 100755 --- a/scripts/run_camera_d435.sh +++ b/scripts/run_camera_d435.sh @@ -17,8 +17,6 @@ while [[ $# -gt 0 ]]; do done # shellcheck disable=SC1091 source "${ROOT}/scripts/env.sh" -# 用源码里的采集逻辑,不走 install 里那份旧的。install 的入口仍由 ros2 run 找到。 -export PYTHONPATH="${ROOT}/src/arm_application${PYTHONPATH:+:${PYTHONPATH}}" # shellcheck disable=SC1091 source "${ROOT}/scripts/lib/load_site_config.sh" SN="${SITE_D435_SERIAL:-}" diff --git a/scripts/run_real.sh b/scripts/run_real.sh index fc45245..1517e97 100755 --- a/scripts/run_real.sh +++ b/scripts/run_real.sh @@ -19,22 +19,15 @@ while [[ $# -gt 0 ]]; do --list|-l) LIST_ONLY=true; shift ;; -h|--help) sed -n '2,8p' "$0" - echo " 机械臂模型在工位程序里看,这里默认不开 RViz。" - echo " 其余参数传给 ros2 launch。要临时打开 RViz:use_rviz:=true" + echo " Extra args are passed to ros2 launch (e.g. use_rviz:=false)." exit 0 ;; *) break ;; esac done -# shellcheck disable=SC1091 -source "${ROOT}/scripts/lib/dev_tools.sh" -dev_source_conda +source /home/cc/anaconda3/etc/profile.d/conda.sh conda activate ros_flexiv -if [[ ! -f "${ROOT}/install/setup.bash" ]]; then - echo "ERROR: ${ROOT}/install/setup.bash 不存在。先在仓库根目录 colcon build,不要在中文路径下编。" >&2 - exit 1 -fi # shellcheck disable=SC1091 source "${ROOT}/install/setup.bash" # shellcheck disable=SC1091 @@ -51,7 +44,7 @@ export LD_LIBRARY_PATH="${FLEXIV_PREFIX}/lib:${LD_LIBRARY_PATH:-}" ROBOT_SN="${SITE_ROBOT_SN:?run_real.sh 需要已配置的机械臂 SN —— 见 scripts/lib/load_site_config.sh 的报错}" ROBOT_IP="${ROBOT_IP:-${SITE_ROBOT_IP:-192.168.100.1}}" NET_IFACE="${SITE_NET_IFACE:-}" -RDK_PYTHON="$(dev_rdk_python)" +RDK_PYTHON="${RDK_PYTHON:-/home/cc/rdk_py310/bin/python}" if [[ "${LIST_ONLY}" == true ]]; then python3 "${ROOT}/scripts/lib/export_site_env.py" --print @@ -128,4 +121,4 @@ if ! "${RDK_PYTHON}" "${ROOT}/scripts/rdk_switch_tool.py" "${RDK_TOOL_ARGS[@]}"; exit 1 fi -exec ros2 launch arm_bringup stack.launch.py backend:=real robot_sn:="${ROBOT_SN}" use_rviz:=false "$@" +exec ros2 launch arm_bringup stack.launch.py backend:=real robot_sn:="${ROBOT_SN}" "$@" diff --git a/src/arm_application/arm_application/handeye_calibrate.py b/src/arm_application/arm_application/handeye_calibrate.py index a04c653..e7e827d 100644 --- a/src/arm_application/arm_application/handeye_calibrate.py +++ b/src/arm_application/arm_application/handeye_calibrate.py @@ -137,9 +137,7 @@ class HandeyeCalibrate(TolerantNode): self.declare_parameter('pin_hold_sec', 0.45) # 每拍多长 self.declare_parameter('skip_unstable_poses', True) - from arm_application.site_io import ros2_root - - self.declare_parameter('output_dir', str(ros2_root() / 'handeye' / 'tmp')) + self.declare_parameter('output_dir', '/home/cc/ros2/handeye/tmp') self.declare_parameter('result_file', '') self.declare_parameter('cleanup_images', True) # 眼在手上:AX=XB 的 gripper 用 Studio 工具 TCP,再折成 cam→法兰写 T_4x4。 diff --git a/src/arm_application/arm_application/handeye_teach.py b/src/arm_application/arm_application/handeye_teach.py index c657a18..a78b0fc 100644 --- a/src/arm_application/arm_application/handeye_teach.py +++ b/src/arm_application/arm_application/handeye_teach.py @@ -108,12 +108,10 @@ class RdkBridge: self.robot_sn = robot_sn self._last_q: Optional[List[float]] = None self._io = threading.Lock() - from arm_application.site_io import ros2_root - - py = os.environ.get('RDK_PYTHON', '').strip() or str( - Path.home() / 'rdk_py310' / 'bin' / 'python' - ) - bridge = ros2_root() / 'scripts' / 'rdk_drag_bridge.py' + py = os.environ.get('RDK_PYTHON', '/home/cc/rdk_py310/bin/python') + bridge = Path.home() / 'ros2' / 'scripts' / 'rdk_drag_bridge.py' + if not bridge.is_file(): + bridge = Path('/home/cc/ros2/scripts/rdk_drag_bridge.py') if not bridge.is_file(): raise FileNotFoundError(f'找不到 rdk_drag_bridge.py:{bridge}') @@ -222,13 +220,6 @@ class RdkBridge: return self._last_q return self._last_q - def move_joints(self, joints: List[float], speed: float = 0.15) -> dict: - """按关节阻抗把臂缓慢送到已记录的 7 个关节角。speed 单位 rad/s。""" - return self._ask( - {'cmd': 'move_joints', 'q': [float(v) for v in joints[:7]], 'speed': float(speed)}, - timeout=180.0, - ) - def close(self) -> None: try: self._ask({'cmd': 'quit'}, timeout=3.0) @@ -248,9 +239,7 @@ class RdkBridge: class HandeyeTeach(TolerantNode): def __init__(self) -> None: super().__init__('handeye_teach') - from arm_application.site_io import ros2_root - - self.declare_parameter('output_dir', str(ros2_root() / 'handeye' / 'tmp')) + self.declare_parameter('output_dir', '/home/cc/ros2/handeye/tmp') self.declare_parameter('width', 1280) self.declare_parameter('height', 720) self.declare_parameter('fps', 15) diff --git a/src/arm_application/arm_application/overhead_oneshot.py b/src/arm_application/arm_application/overhead_oneshot.py index fa1d3b1..e8863dd 100644 --- a/src/arm_application/arm_application/overhead_oneshot.py +++ b/src/arm_application/arm_application/overhead_oneshot.py @@ -45,6 +45,11 @@ from arm_application.path_profile import ( # 伺服和测试从本模块再导 # 上一跳实际少走这么多,就认定冻结 J 在当前姿态走不动,不许按同一剩差再刷。 D435_MIN_PROGRESS_FRAC = 0.40 +# SwitchMode 返回不等于已经进 primitive。连试仍失败必须让一阶段立刻中止, +# 不许空等 150s 装成"还在粗定位"(2026-10-16 17:27)。 +D435_MOVEL_RETRY = 3 +D435_MOVEL_RETRY_S = 0.3 +D435_MOVEL_GAVE_UP = "厂家 MoveL 连试 3 次没走成" def best_det_conf(dets: Optional[Iterable]) -> Optional[float]: @@ -154,16 +159,27 @@ def d405_commands_arm( ) -> bool: """D405 这一帧的点能不能下发给左臂。 - 推理一直开着。正在用 D435 那一个点时,进程内 MoveL 结束并且停稳之前不下发。 - 停稳用 ``d435_handback_sec`` 和 ``lock_still_move_m``。赶路时 XY 已经进 15 mm - 也不算到位。不许在 MoveL 期间下发 D405(两套控制互抢会晃)。 - ``move_only``:只测 D435 位移,D405 点一律不下发(到位之后也不交)。 + 推理从启动就开着。没有高置信度目标、或 D435 厂家 MoveL 还没结束、 + 法兰还没连续停稳时,这帧**不下发**(输出截断,不许拿去控臂)。 + 厂家移动结束并且法兰停稳(``settled``,时长是 ``d435_handback_sec``) + 之后才放行,伺服用它追工件框的矩形中心。框心对准停稳后才进精拍下臂。 + ``move_only``:只测 D435 位移,D405 点一律不下发。 """ if move_only: return False - if not using_d435: - return True - return bool(arrived) and bool(settled) + if using_d435: + return bool(arrived) and bool(settled) + return True + + +def retract_goal_z(z_now: float, z_target: float) -> float: + """收臂目标 Z:已经高于目标就钉在当前高度,不许为了追 5 mm 往下补。 + + 2026-10-16:贴面 MoveL 之后 JTC 还握着悬停关节,收臂 hop1 计划 +5 mm + 实际抬了 18–25 mm;hop2 把「抬过头」当成误差 ``Δz=−13 mm`` 往回压, + 看起来就是进孔再弹出。抬过了 = 成功。 + """ + return max(float(z_now), float(z_target)) def oneshot_needs_verticalize( diff --git a/src/arm_application/arm_application/perception_node.py b/src/arm_application/arm_application/perception_node.py index cb94271..21510b1 100644 --- a/src/arm_application/arm_application/perception_node.py +++ b/src/arm_application/arm_application/perception_node.py @@ -105,7 +105,7 @@ class PerceptionNode(TolerantNode): self.declare_parameter("image_topic", "") self.declare_parameter("camera_info_topic", "") self.declare_parameter("depth_topic", "/d405/aligned_depth_to_color/image_raw") - self.declare_parameter("weights", "models/yolo/gongjian0929.onnx") + self.declare_parameter("weights", "/home/cc/ros2/models/yolo/gongjian0929.onnx") self.declare_parameter("conf", 0.10) self.declare_parameter("iou", 0.45) self.declare_parameter("imgsz", 640) @@ -336,9 +336,7 @@ class PerceptionNode(TolerantNode): r_pos=float(self.get_parameter("kf_r_pos").value), ) - from arm_application.site_io import resolve_repo_path - - weights_raw = resolve_repo_path(str(self.get_parameter("weights").value)) + weights_raw = str(self.get_parameter("weights").value) weights = str(resolve_weights(weights_raw, self.backend_name)) yolo_cfg = { "weights": weights, @@ -1141,8 +1139,8 @@ class PerceptionNode(TolerantNode): filt[2] = z_use self._last_filt = filt.copy() - if handoff: - # 画面里没有 conf≥门槛的目标:绿框可以留,三维点不许发 —— 伺服会拿去追。 + if handoff or self._d435_commanding: + # 没有高置信度,或 D435 厂家移动还没交接:推理留着,控制点不许发。 return self._publish_point(filt, stamp) self.get_logger().info( @@ -1427,15 +1425,11 @@ class PerceptionNode(TolerantNode): self._z_locked = bool(msg.data) def _on_d435_commanding(self, msg: Bool) -> None: - """伺服还在执行 D435 那一个点时,不再提交下一张。 + """伺服还在执行 D435 那一个点时,不再提交下一张,D405 控制点也不发。 - 这一段结束(到位并停稳)后清掉「已拍过」,漏检重新计 0.5 s。 + 这一轮已经拍过并走完。停稳后打开 D405 框心,不因交接瞬间漏检再 MoveL。 """ - commanding = bool(msg.data) - if self._d435_commanding and not commanding: - self._oneshot_shot = False - self._low_since = None - self._d435_commanding = commanding + self._d435_commanding = bool(msg.data) def _publish_overhead_k0(self, p_base: np.ndarray, is_k0: bool) -> None: pt = PointStamped() diff --git a/src/arm_application/arm_application/pipeline_node.py b/src/arm_application/arm_application/pipeline_node.py index fab29f0..a27c828 100644 --- a/src/arm_application/arm_application/pipeline_node.py +++ b/src/arm_application/arm_application/pipeline_node.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 -"""跟踪锁定 → 孔定位。搜孔轻压已取消,本节点不再下压搜孔。 +"""三阶段串联:视觉跟踪锁定 → 孔定位 → 搜孔并轻轻按压(默认不拧入)。 流畅性(相对 bash 杀进程再起节点): - 相机 / 触觉 / 手眼 TF 全程只起一次 - 一阶段锁定后 SIGTERM 跟踪节点,立刻 pin 当前关节,不回零、不抬臂 - - 二阶段已垂直 / 已在悬停高则跳过对应动作;对准完就停,不再下压搜孔 - - 旋入不在这个节点里 + - 二阶段已垂直 / 已在悬停高则跳过对应动作;对准完立刻下压搜孔 + - 找到孔位后力闭环轻压保持;拧入是后续阶段(--depth 才插入) """ from __future__ import annotations @@ -15,6 +15,7 @@ import os import signal import subprocess import time +from pathlib import Path from typing import Optional import numpy as np @@ -23,6 +24,7 @@ from geometry_msgs.msg import PointStamped from rclpy.qos import HistoryPolicy, QoSProfile, ReliabilityPolicy from std_msgs.msg import Bool, String +from arm_application.overhead_oneshot import D435_MOVEL_GAVE_UP from arm_application.stage3_search_node import Stage3Search @@ -70,6 +72,19 @@ def init_z_refusal( return "" +def hold_at_insert_z(final, *, skip3: bool) -> bool: + """贴面已经按 ``final_gap_mm`` 停下时,不再进三阶段。 + + 三阶段开局会抬到悬高上方测触觉,再落回 ``hole_hover_z_m``。 + 没有接触传感器。到位高度就是这次的贴面间隙,不另设一个 Z。 + """ + if skip3: + return True + if not isinstance(final, dict): + return False + return bool(final.get("insert_held")) + + def track_lock_z(hover_z: float, tool_len_m: float, surface_z_m: float) -> float: """一阶段锁高。 @@ -85,6 +100,20 @@ def track_lock_z(hover_z: float, tool_len_m: float, surface_z_m: float) -> float return hover + TRACK_CLEARANCE_M +def stage1_log_fatal(log_path: str) -> str: + """一阶段日志里已经写明失败时,不许再空等锁定。空串 = 还没到这一步。""" + try: + text = Path(log_path).read_text(encoding="utf-8", errors="replace") + except OSError: + return "" + if D435_MOVEL_GAVE_UP in text: + return ( + f"D435 {D435_MOVEL_GAVE_UP}(RDK 控制模式没切到 primitive)," + f"一阶段中止。不是视野/标定。看 {log_path}" + ) + return "" + + def stage1_timeout_reason(points_seen: int, timeout_s: float, log_path: str) -> str: """一阶段超时了,到底是"感知没给点"还是"给了点但没收敛"。 @@ -99,6 +128,9 @@ def stage1_timeout_reason(points_seen: int, timeout_s: float, log_path: str) -> 以前两种情况共用一句"没锁定(视野里没工件?)",于是"perception_node 因为缺依赖早退了"被读成"算法不收敛",白查半天。 """ + fatal = stage1_log_fatal(log_path) + if fatal: + return fatal if points_seen <= 0: return ( f"一阶段 {timeout_s:.0f}s 没锁定:**一个视觉目标点都没收到**" @@ -125,7 +157,7 @@ class AssemblyPipeline(Stage3Search): super().__init__(node_name=node_name) self.declare_parameter("skip_stage1", False) self.declare_parameter("skip_stage2", False) - self.declare_parameter("skip_stage3", True) # 搜孔轻压已取消,写成 false 也不再下压 + self.declare_parameter("skip_stage3", False) # 默认搜孔+轻压;拧入靠 --depth self.declare_parameter("d435_move_only", False) self.declare_parameter("init_z_m", -1.0) # >0:启动后先到这个法兰高度 self.declare_parameter("stage1_timeout_sec", 150.0) @@ -308,6 +340,11 @@ class AssemblyPipeline(Stage3Search): self._spin(0.1) if self._locked: break + fatal = stage1_log_fatal(log_path) + if fatal: + self._stop_stage1() + self.get_logger().error(fatal) + return 1 if self._s1.poll() is not None: self.get_logger().error(f"一阶段进程提前退出,看 {log_path}") return 1 @@ -333,9 +370,7 @@ class AssemblyPipeline(Stage3Search): def run(self) -> int: skip1 = bool(self.get_parameter("skip_stage1").value) skip2 = bool(self.get_parameter("skip_stage2").value) - if not bool(self.get_parameter("skip_stage3").value): - self.get_logger().info("搜孔轻压已取消,忽略要求重新打开它的参数") - skip3 = True + skip3 = bool(self.get_parameter("skip_stage3").value) if self.d435_move_only: skip2 = True skip3 = True @@ -365,23 +400,45 @@ class AssemblyPipeline(Stage3Search): if code != 0: self.get_logger().error(f"二阶段失败 exit={code},停在当前位姿") return code - self.get_logger().info( - f"二阶段 OK 残差 {final.get('residual_mm', -1)}mm" - " — 搜孔已取消,停在这里。旋入请另跑。" - ) + stay = hold_at_insert_z(final, skip3=skip3) + if stay and final.get("insert_held"): + self.get_logger().info( + f"二阶段 OK 残差 {final.get('residual_mm', -1)}mm — " + f"插孔停在法兰 Z={final.get('flange_z_m')}m," + f"规尖离面 {final.get('tip_gap_mm')}mm" + f"(目标 {final.get('commanded_gap_mm')}mm,来自 final_gap_mm)。" + "没有接触传感器,不另设 Z,不抬回悬高。" + ) + else: + self.get_logger().info( + f"二阶段 OK 残差 {final.get('residual_mm', -1)}mm" + + ( + " — 跳过搜孔,停在观测高度(看规尖是否对中;进程退出后臂保持)" + if skip3 + else " — 立刻下压搜孔并轻压(不拧入)" + ) + ) else: self._state("skip_stage2") + stay = hold_at_insert_z(final, skip3=skip3) - if skip3: + if stay: self._state("skip_stage3") if self.d435_move_only: self.get_logger().info( "D435 位移测试结束 —— 臂停在当前高度,不交 D405、不对孔;" "15s 后本进程退出,臂保持不动" ) + elif isinstance(final, dict) and final.get("insert_held"): + if q is not None: + self._pin(q) + self.get_logger().info( + "插孔已停在贴面高度,不进搜孔、不抬回悬高。" + "15s 后本进程退出,臂保持这个 Z。" + ) else: self.get_logger().info( - "搜孔已取消 —— 臂停在当前高度。" + "跳过搜孔 —— 臂停在当前高度。请看规尖与孔是否对中;" "15s 后本进程退出,臂保持不动" ) if final: diff --git a/src/arm_application/arm_application/servo_node.py b/src/arm_application/arm_application/servo_node.py index 848dbcd..10eb349 100644 --- a/src/arm_application/arm_application/servo_node.py +++ b/src/arm_application/arm_application/servo_node.py @@ -50,6 +50,9 @@ from arm_application.geom_ik import ( ) from arm_application.handeye_lib import cam_point_to_base, cam_to_base_T from arm_application.overhead_oneshot import ( + D435_MOVEL_GAVE_UP, + D435_MOVEL_RETRY, + D435_MOVEL_RETRY_S, D435_NEAR_CRUISE_MPS, D435_SETTLE_S, D435_VENDOR_MOVEL_VEL_MPS, @@ -126,7 +129,7 @@ class ServoNode(TolerantNode): self.declare_parameter("point_topic", "visual_servo/filtered_point") # 默认值只作兜底:真机运行一定由 launch 传入 site.yaml 的 paths.handeye_file。 # 指向 /tmp 是历史遗留(标定临时产物),会让人误以为"标定结果在那里"。 - self.declare_parameter("handeye_file", "handeye/results/89_eye_in_hand.yaml") + self.declare_parameter("handeye_file", "/home/cc/ros2/handeye/results/89_eye_in_hand.yaml") # eye_in_hand = cam→法兰;eye_to_hand = cam→基座。写错会把固定相机当腕部用。 self.declare_parameter("handeye_mode", "eye_in_hand") self.declare_parameter("use_static_handeye_tf", False) @@ -224,8 +227,7 @@ class ServoNode(TolerantNode): self.declare_parameter("lock_hold_sec", 2.0) # D405 跟住后法兰静止多久进二阶段。哨兵 -1 = 没从 site 接入。 # lock_still_sec 仍须从 site 接上(缺了就报错)。 - # 交回 D405:进程内 MoveL 到位后,再叠 d435_handback_sec(法兰 XY 停稳)。 - # 旧杀栈冷启动曾把这 3s 垫在控制器重启后面;不杀栈后只留机械停稳。 + # 厂家 MoveL 结束后,法兰连续停稳 d435_handback_sec 才打开 D405 框心。 self.declare_parameter("lock_still_sec", -1.0) self.declare_parameter("d435_handback_sec", -1.0) self.declare_parameter("wrist_align_sec", -1.0) @@ -329,7 +331,7 @@ class ServoNode(TolerantNode): and self.wrist_handoff_conf >= 0.0 and self.overhead_hold_s >= 0.0 ) - # 框心对准进对孔用 wrist_align_sec。D435 交回只用 d435_handback_sec,两段不要合成一个数。 + # 框心对准进精拍用 wrist_align_sec。D435 交接只用 d435_handback_sec,两段不要合成一个数。 self._still = StillLock( still_sec=max(0.0, self.wrist_align_sec), still_move_m=max(0.0, self.lock_still_move_m), @@ -342,6 +344,8 @@ class ServoNode(TolerantNode): self._d435_vendor_done = False self._d435_vendor_busy = False self._d435_vendor_failed = False + self._d435_vendor_tries = 0 + self._d435_retry_after = 0.0 self._d435_settled = False self._d435_exec_until = 0.0 self._d435_pinned = False @@ -374,10 +378,9 @@ class ServoNode(TolerantNode): self.ws_min = np.array([float(v) for v in ws_min[:3]], dtype=float) self.ws_max = np.array([float(v) for v in ws_max[:3]], dtype=float) - from arm_application.site_io import resolve_repo_path - - handeye_path = resolve_repo_path(str(self.get_parameter("handeye_file").value)) - self.T_cam_to_gripper = load_handeye_T(handeye_path) + self.T_cam_to_gripper = load_handeye_T( + str(self.get_parameter("handeye_file").value) + ) self.handeye_mode = str( self.get_parameter("handeye_mode").value or "eye_in_hand" ).strip().lower() @@ -400,7 +403,7 @@ class ServoNode(TolerantNode): t_he = np.asarray(self.T_cam_to_gripper[:3, 3], dtype=float) axis_z = float(np.clip(R_he[2, 2], -1.0, 1.0)) self._cam_tilt_deg = float(np.degrees(np.arccos(axis_z))) - he_path = handeye_path + he_path = str(self.get_parameter("handeye_file").value) if self.handeye_mode == "eye_to_hand": self.get_logger().warn( f"HANDEYE yaml={he_path} mode=eye_to_hand " @@ -709,6 +712,8 @@ class ServoNode(TolerantNode): self._d435_vendor_done = False self._d435_vendor_busy = False self._d435_vendor_failed = False + self._d435_vendor_tries = 0 + self._d435_retry_after = 0.0 self._overhead_t = now_m self._last_seen_time = now_m self._stamp_sec = now_sec @@ -732,6 +737,8 @@ class ServoNode(TolerantNode): self._d435_vendor_done = False self._d435_vendor_busy = False self._d435_vendor_failed = False + self._d435_vendor_tries = 0 + self._d435_retry_after = 0.0 self._publish_d435_commanding(True) self.get_logger().info( f"D435 一次性设定 feat_base=({p_base[0]:.3f},{p_base[1]:.3f},{p_base[2]:.3f}) " @@ -759,9 +766,9 @@ class ServoNode(TolerantNode): settled=settled, move_only=self.d435_move_only, ): - # 推理服务不关。这一帧的点不下发给臂,目标仍是 D435 那一个点。 + # 推理不关。没到「法兰停稳」之前,这帧不能进运动控制。 self.get_logger().info( - "D435 位置还没到并且停稳,D405 这帧不下发", + "D405 输出已截断:D435 厂家移动还没结束或法兰还没停稳", throttle_duration_sec=1.0, ) return @@ -1407,9 +1414,12 @@ class ServoNode(TolerantNode): return geometric_jacobian(link_R, link_p, p_ee, RIZON_LINK_AXES) def _d435_oneshot_holds_the_arm(self) -> bool: - """D435 到点平移:action 短跳,等结果再重测。D405 主跟踪仍走原来的伺服。""" - if self.d435_move_only and self._hybrid and self._track_source == "overhead" and self._d435_settled: - # 位移测完了:守着,不许掉进 D405 的 _execute_twist。hold 只在到位那一拍发一次。 + """D435 粗定位只走厂家 MoveL。停稳后先钉住,等 D405 框心接上再放开。 + + 这段不许再用流式伺服去追 D435 那个点。D405 点被 ``_on_point`` 接上后 + 跟踪源变成腕部,这里返回 False,框心才开始带臂。 + """ + if self._hybrid and self._track_source == "overhead" and self._d435_settled: return True if not (self._hybrid and self._track_source == "overhead" and not self._d435_settled): return False @@ -1519,7 +1529,7 @@ class ServoNode(TolerantNode): """停或重新打开关节轨迹控制器。关节状态广播留着,硬件接口不下线。 用节点里的客户端,不走 ``ros2 service call``:那条命令自己就要一两秒, - 交回 D405 的预算会被它吃掉。 + 停稳锁定的预算会被它吃掉。 """ from controller_manager_msgs.srv import SwitchController @@ -1659,18 +1669,32 @@ class ServoNode(TolerantNode): if self._d435_vendor_failed or self._d435_vendor_busy: return True if not self._d435_vendor_done: - # busy 先于调用置位:另一条控制回调不能再开一次 MoveL,也不能提前交回 D405。 + if time.monotonic() < float(self._d435_retry_after): + return True + if int(self._d435_vendor_tries) >= int(D435_MOVEL_RETRY): + self._d435_vendor_failed = True + self.get_logger().error( + f"{D435_MOVEL_GAVE_UP} —— 一阶段中止,D405 也不带臂" + ) + return True + # busy 先于调用置位:另一条控制回调不能再开一次 MoveL,也不能提前锁定。 self._d435_vendor_busy = True ok = self._d435_vendor_movel() self._d435_vendor_busy = False if not ok: - self._d435_vendor_failed = True + self._d435_vendor_tries = int(self._d435_vendor_tries) + 1 + self._d435_retry_after = time.monotonic() + float(D435_MOVEL_RETRY_S) self.get_logger().error( - "厂家 MoveL 没走成 —— 停在原地,D405 的点继续不下发" + f"厂家 MoveL 第{self._d435_vendor_tries}次没走成," + f"{D435_MOVEL_RETRY_S:.1f}s 后再试" ) else: self._d435_vendor_done = True self._arrive.reset() + # MoveL 切回关节后 JTC 还握着出发前的悬停。不钉住的话 + # 硬件 3 s 窗口一过,臂会按悬停关节走回去(比交回 D405 的 10 cm 更远)。 + self._send_hold() + self._d435_pinned = True return True return True @@ -1729,7 +1753,7 @@ class ServoNode(TolerantNode): ): self._d435_pin_and_stop_hops( f"D435 上一跳几乎没走近,剩差还是 {dist*1000:.0f}mm " - f"—— 钉住不再补刀,等停稳后交回 D405" + f"—— 钉住不再补刀,等停稳后锁定进二阶段" ) return True if not at: @@ -1836,7 +1860,7 @@ class ServoNode(TolerantNode): return True def _tick_d435_arrive(self) -> None: - """走到 D435 那个点,并且法兰按一阶段的静止判据停稳,才允许下发 D405。""" + """厂家 MoveL 已结束:看法兰还在不在动。连续停稳后才打开 D405 框心。""" if not self._hybrid: return if ( @@ -1858,11 +1882,15 @@ class ServoNode(TolerantNode): return xy = np.asarray(T[:2, 3], dtype=float) dist = float(np.hypot(float(xy[0]) - goal[0], float(xy[1]) - goal[1])) - arrived = dist <= float(self.lock_still_move_m) or self._d435_give_up - settled = self._arrive.update( - xy, time.monotonic(), tracking_ok=arrived - ) + # 到位看法兰是否还在动,不看离视觉点还差几毫米。 + settled = self._arrive.update(xy, time.monotonic(), tracking_ok=True) if not settled: + self.get_logger().info( + f"D435 厂家 MoveL 已结束,法兰还在动或未满 " + f"{self.d435_handback_sec:.1f}s(离点 {dist*1000:.0f}mm)," + "D405 输出仍截断", + throttle_duration_sec=1.0, + ) return with self._lock: self._d435_settled = True @@ -1885,10 +1913,12 @@ class ServoNode(TolerantNode): self._lock_pub_t = now_m self._lock_pub.publish(Bool(data=True)) return + # 法兰已停稳:打开 D405。不在这里锁定;框心对准停稳后才精拍下臂。 self._publish_d435_commanding(False) self.get_logger().info( - f"已到 D435 位置(还差 {dist*1000:.0f}mm)并停稳 " - f"{self.d435_handback_sec:.1f}s —— 改回下发 D405 框心" + f"法兰已连续停稳 {self.d435_handback_sec:.1f}s" + f"(离 D435 点 {dist*1000:.0f}mm)" + " —— 打开 D405 输出,追工件框矩形中心;对准后才精拍下臂" ) def _tick_still_lock(self) -> None: diff --git a/src/arm_application/arm_application/site_io.py b/src/arm_application/arm_application/site_io.py index c16cb57..9243123 100644 --- a/src/arm_application/arm_application/site_io.py +++ b/src/arm_application/arm_application/site_io.py @@ -10,83 +10,13 @@ import yaml def ros2_root() -> Path: - """仓库根目录。先看环境变量,再从本文件向上找 ``config/site.yaml``。 - - 不能默认 ``~/ros2``:别人把仓库克隆到别的目录时,写死的家目录路径是空的, - 启动会报文件不存在。 - """ for key in ("SITE_ROS2_ROOT", "ROS2_WS"): v = os.environ.get(key, "").strip() if v: return Path(v).expanduser() - here = Path(__file__).resolve() - for parent in here.parents: - if (parent / "config" / "site.yaml").is_file() and (parent / "scripts" / "env.sh").is_file(): - return parent return Path.home() / "ros2" -_PATH_KEYS = frozenset({ - "named_poses_file", - "handeye_file", - "handeye_waypoints", - "handeye_workdir", - "handeye_calibrate", - "yolo_weights", - "hole_weights", - "weights", - "workpiece_weights", - "config_file", - "append_poses_file", -}) - - -def resolve_repo_path(value: str, root: Path | None = None) -> str: - """仓库内的相对路径展开成绝对路径。已经是绝对路径的原样保留。""" - text = str(value or "").strip() - if not text: - return "" - path = Path(text).expanduser() - if path.is_absolute(): - return str(path) - base = root or ros2_root() - return str((base / path).resolve()) - - -def _is_path_key(key: str) -> bool: - return ( - key in _PATH_KEYS - or key.startswith("handeye_file") - or key.startswith("handeye_waypoints") - or key.endswith("_weights") - or key.endswith("_file") - or key.endswith("_workdir") - ) - - -def absolutize_site_paths(data: Any, root: Path | None = None) -> Any: - """把 site 里的仓库相对路径展开。只动路径字段,不动别的字符串。""" - base = root or ros2_root() - if isinstance(data, dict): - out: dict[Any, Any] = {} - for key, val in data.items(): - name = str(key) - if isinstance(val, str) and _is_path_key(name): - out[key] = resolve_repo_path(val, base) - elif ( - name == "config" - and isinstance(val, str) - and (val.endswith((".yaml", ".yml", ".json")) or "/" in val) - ): - out[key] = resolve_repo_path(val, base) - else: - out[key] = absolutize_site_paths(val, base) - return out - if isinstance(data, list): - return [absolutize_site_paths(item, base) for item in data] - return data - - def site_path() -> Path: env = os.environ.get("SITE_CONFIG", "").strip() if env: @@ -94,90 +24,12 @@ def site_path() -> Path: return ros2_root() / "config" / "site.yaml" -def arm_short_id(sn: str) -> str: - """序列号末两位就是臂号(``Rizon4-063989`` → ``89``)。 - - 和 ``scripts/lib/export_site_env.arm_short_id`` 同一条规则。换一台臂 - 只改 ``site.yaml`` 的 SN,不要在界面里再写一张本机对照表。 - """ - tail = str(sn or "").strip().rsplit("-", 1)[-1] - return tail[-2:] if tail.isdigit() and len(tail) >= 2 else "" - - -def listed_arms(data: dict[str, Any] | None) -> list[dict[str, Any]]: - """``robots[]`` 里的真机臂。``sn: sim`` 不进控制。 - - 旋转爪只看夹爪型号里有没有 ``RGI``。右臂也可以是平行爪,不能靠臂号猜。 - """ - robots = ((data or {}).get("robot") or {}).get("robots") or [] - out: list[dict[str, Any]] = [] - seen: set[str] = set() - for robot in robots: - if not isinstance(robot, dict): - continue - sn = str(robot.get("sn") or "").strip() - if not sn or sn.lower() == "sim": - continue - arm_no = arm_short_id(sn) - if not arm_no: - continue - if arm_no in seen: - raise ValueError(f"两台臂的序列号末两位都是 {arm_no},--arm 分不清") - seen.add(arm_no) - side = str(robot.get("arm") or "").strip().lower() - grip = robot.get("gripper") if isinstance(robot.get("gripper"), dict) else {} - model = str(grip.get("model") or "") - shared = (data or {}).get("gripper") if isinstance((data or {}).get("gripper"), dict) else {} - stroke = grip.get("stroke_mm") - if stroke is None: - stroke = shared.get("stroke_mm") or 20.0 - out.append({ - "id": arm_no, - "label": str(robot.get("label") or "").strip() or f"臂{arm_no}", - "sn": sn, - "side": side if side in ("left", "right") else "", - "tactile": bool(robot.get("tactile")), - "camera": str((robot.get("camera") or {}).get("name") or "") if isinstance(robot.get("camera"), dict) else "", - "gripper": model, - "rotary": "RGI" in model.upper(), - "iface": str(robot.get("iface") or ""), - "pc_ip": str(robot.get("pc_ip") or ""), - "port": str(grip.get("port") or ""), - "stroke_mm": float(stroke), - }) - return out - - -def usb_expectations(data: dict[str, Any] | None) -> dict[str, int]: - """设备页上「应有几台」跟 ``site.yaml`` 走,不写死本机的 2+1+2。""" - cameras = (data or {}).get("cameras") or {} - d405 = 0 - d435 = 0 - if isinstance(cameras, dict): - for key, val in cameras.items(): - model = str((val or {}).get("model") or "") if isinstance(val, dict) else "" - token = f"{key} {model}".lower() - if "d405" in token: - d405 += 1 - elif "d435" in token: - d435 += 1 - tactile = (data or {}).get("tactile") or {} - orisys = 0 - if isinstance(tactile, dict): - orisys = sum(1 for key in ("left_video", "right_video") if tactile.get(key) is not None) - return {"d405": d405, "d435": d435, "orisys": orisys} - - def load_site_dict(path: str | Path | None = None) -> dict[str, Any]: p = Path(path).expanduser() if path else site_path() if not p.is_file(): return {} data = yaml.safe_load(p.read_text(encoding="utf-8")) or {} - if not isinstance(data, dict): - return {} - resolved = p.resolve() - root = resolved.parent.parent if resolved.parent.name == "config" else ros2_root() - return absolutize_site_paths(data, root) + return data if isinstance(data, dict) else {} def deep_merge(base: dict[str, Any], overlay: dict[str, Any]) -> dict[str, Any]: diff --git a/src/arm_application/arm_application/stage2_locate_node.py b/src/arm_application/arm_application/stage2_locate_node.py index 670bacb..a3575d2 100644 --- a/src/arm_application/arm_application/stage2_locate_node.py +++ b/src/arm_application/arm_application/stage2_locate_node.py @@ -69,6 +69,7 @@ from arm_application.overhead_oneshot import ( once_ff_tcp_xyz, oneshot_align_step_m, pixel_on_base_plane, + retract_goal_z, ) from arm_application.handeye_realsense import resolve_realsense_topics from arm_application.gauge_boot import perception_source_of @@ -249,7 +250,7 @@ class Stage2Locate(TolerantNode): # _coarse_target_reject / _bbox_on_border 的注释):粗居中的目的是把臂 # 挪到工件正上方,**挪错比不挪危险得多** —— 一挪错精定位就再也看不到 # k1/k2,而流程会把它当"孔检测失败",现场根本想不到是粗居中把臂带走的。 - self.coarse_max_offset_m = float(stage2.get("coarse_max_offset_m") or 0.12) + self.coarse_max_offset_m = float(stage2.get("coarse_max_offset_m") or 0.08) self.coarse_border_px = int(stage2.get("coarse_border_px") or 2) # 最后轻降到孔面上方 final_gap_mm(不接触)。启动参数 ≥0 覆盖 site。 self._final_gap_refused = "" @@ -901,10 +902,13 @@ class Stage2Locate(TolerantNode): min_dur: float = 1.6, hold_xy: bool = False, strict_xy: bool = False, + z_up_only: bool = False, ) -> Tuple[bool, np.ndarray, np.ndarray]: """闭环点到点:规划短 hop → 执行 → 重新看 TF → 重复,直到容差内。 hold_xy=True:只收 Z/姿态(悬停降锁高),不把下降中漂出的 XY 再拽回去。 + z_up_only=True:当前已经高于目标 Z 就停,不许为了追 5 mm 往下补 + (贴面 MoveL 后收臂 hop1 抬过头、hop2 回压进孔,2026-10-16)。 """ target = self._clamp_ws(target) last_dist: Optional[float] = None @@ -912,7 +916,14 @@ class Stage2Locate(TolerantNode): stall = 0 for hop in range(max_hops): p = Tee[:3, 3] - goal = np.array([p[0], p[1], target[2]]) if hold_xy else target + z_tgt = float(target[2]) + if z_up_only: + if float(p[2]) >= z_tgt - float(tol_m): + return True, q, Tee + z_tgt = retract_goal_z(float(p[2]), z_tgt) + goal = np.array([p[0], p[1], z_tgt]) if hold_xy else np.array( + [target[0], target[1], z_tgt], dtype=float + ) if hold_xy: goal = self._clamp_ws(goal) dp = goal - p @@ -1491,14 +1502,15 @@ class Stage2Locate(TolerantNode): 1. 目标点落在 ``workspace_xyz_*`` 的 XY 之外(含 5mm 余量)—— 一阶段刚锁 定时臂就在工件上方,工件**不该**在盒外; - 2. 目标点离当前夹爪轴线超过 ``coarse_max_offset_m``(缺省 0.12m)—— 粗居中 - 是"把臂挪到工件正上方",合理位移是厘米级;十几厘米只可能是坏读数。 + 2. 目标点离当前夹爪轴线超过 ``coarse_max_offset_m``(缺省 0.08m)—— 粗居中 + 是"把臂挪到工件正上方",合理位移是厘米级;十几厘米只可能是坏读数 + (2026-10-16 三次 110mm 回撤,0.12 放行了)。 注意:判误检后**原地不动**,由精定位(直接量 k1/k2)继续。不动的代价只是 "少挪一次",而挪错的代价是精定位彻底看不到孔 —— 所以这里必须选不动。 """ if max_offset_m is None: - max_offset_m = float(getattr(self, "coarse_max_offset_m", 0.12)) + max_offset_m = float(getattr(self, "coarse_max_offset_m", 0.08)) p = np.asarray(p_base, dtype=float).reshape(3) if not np.all(np.isfinite(p[:2])): return "目标点非有限值(深度/三角化失败)" @@ -2241,7 +2253,9 @@ class Stage2Locate(TolerantNode): ) fl0 = (float(Tee[0, 3]), float(Tee[1, 3]), float(Tee[2, 3])) ok = self._vendor_movel_tcp(movel_xyz, ONCE_FF_MOVEL_VEL_MPS) - q2, Tee2 = self._wait_state(2.0) + # MoveL 刚切回关节模式:JTC 还握着贴面前的悬停关节。等 2 s 再钉 + # 等于让 vmax=0.05 往悬停留 0.1 rad(2026-10-16 收臂实测多抬 13–20 mm)。 + q2, Tee2 = self._wait_state(0.3) if q2 is not None and Tee2 is not None: q, Tee = q2, Tee2 self._pin(q) @@ -3392,14 +3406,36 @@ class Stage2Locate(TolerantNode): ) # ---- 5) 锁定保持 ---- + # 贴面之后就停在这个法兰 Z。没有接触传感器,到位只认 final_gap_mm + # (规尖离面)。三阶段开局会抬回悬高,这里标明已经插到位,流水线不再上抬。 + if ( + self.descent_final_gap_m is not None + and self.final_descent_enable + and not self.hover_only + ): + final = dict(final) + final["insert_held"] = True + final["flange_z_m"] = round(float(Tee[2, 3]), 4) + final["tip_gap_mm"] = round(float(self.descent_final_gap_m) * 1000.0, 2) + final["commanded_gap_mm"] = round(float(self.final_gap_m) * 1000.0, 2) + final["insert_z_source"] = "final_gap_mm" self._state("hold") q_fin = self._q() self._pin(q_fin if q_fin is not None else q) - self.get_logger().info( - f"=== 二阶段定位完成: {self.target_hole} 残差 {final.get('residual_mm', -1)}mm," - f"规尖悬在孔面上方 ≈{gap_txt}mm(不接触)。" - f"结果: /stage2/result, /tmp/stage2_result.json ===" - ) + if final.get("insert_held"): + self.get_logger().info( + f"=== 二阶段定位完成: {self.target_hole} 残差 {final.get('residual_mm', -1)}mm," + f"法兰 Z={final.get('flange_z_m')}m,规尖离面 {gap_txt}mm" + f"(final_gap {self.final_gap_m * 1000:.1f}mm,无接触传感器)。" + "停在这一高度,不抬回悬高。" + f"结果: /stage2/result, /tmp/stage2_result.json ===" + ) + else: + self.get_logger().info( + f"=== 二阶段定位完成: {self.target_hole} 残差 {final.get('residual_mm', -1)}mm," + f"规尖悬在孔面上方 ≈{gap_txt}mm(不接触)。" + f"结果: /stage2/result, /tmp/stage2_result.json ===" + ) return 0, q_fin if q_fin is not None else q, Tee, final def _linger_hold(self, final: Dict[str, Any], seconds: Optional[float] = None) -> None: diff --git a/src/arm_application/arm_application/stage3_search_node.py b/src/arm_application/arm_application/stage3_search_node.py index e6e9bbf..acf1a50 100644 --- a/src/arm_application/arm_application/stage3_search_node.py +++ b/src/arm_application/arm_application/stage3_search_node.py @@ -526,7 +526,8 @@ class Stage3Search(Stage2Locate): tgt[2] += dz_m self._goto(q, Tee, tgt, verticalize=True, tol_m=0.001, tol_tilt=self.vert_tol_rad * 3.0, tag="retract", - v=0.03, max_hops=3, settle_s=0.2, min_dur=0.6) + v=0.03, max_hops=2, settle_s=0.2, min_dur=0.6, + hold_xy=True, z_up_only=True) except Exception: pass diff --git a/src/arm_application/config/perception.yaml b/src/arm_application/config/perception.yaml index c2411e9..5685bf9 100644 --- a/src/arm_application/config/perception.yaml +++ b/src/arm_application/config/perception.yaml @@ -12,7 +12,7 @@ perception_node: image_topic: "" camera_info_topic: "" depth_topic: "/d405/aligned_depth_to_color/image_raw" - weights: "models/yolo/gongjian0929.onnx" + weights: "/home/cc/ros2/models/yolo/gongjian0929.onnx" conf: 0.10 iou: 0.45 imgsz: 640 diff --git a/src/arm_application/launch/pipeline.launch.py b/src/arm_application/launch/pipeline.launch.py index fee00c6..ea18aff 100644 --- a/src/arm_application/launch/pipeline.launch.py +++ b/src/arm_application/launch/pipeline.launch.py @@ -159,7 +159,7 @@ def generate_launch_description() -> LaunchDescription: [ DeclareLaunchArgument("skip_stage1", default_value="false"), DeclareLaunchArgument("skip_stage2", default_value="false"), - DeclareLaunchArgument("skip_stage3", default_value="true"), + DeclareLaunchArgument("skip_stage3", default_value="false"), DeclareLaunchArgument("target_hole", default_value=""), DeclareLaunchArgument("insert_depth_mm", default_value=""), DeclareLaunchArgument("screw_pitch_mm", default_value=""), diff --git a/src/arm_application/test/test_overhead_oneshot.py b/src/arm_application/test/test_overhead_oneshot.py index 5d18dc6..443ba2b 100644 --- a/src/arm_application/test/test_overhead_oneshot.py +++ b/src/arm_application/test/test_overhead_oneshot.py @@ -40,6 +40,7 @@ from arm_application.overhead_oneshot import ( FROZEN_J_HOP_MAX_M, build_arc_xy_move, d405_commands_arm, + retract_goal_z, flange_down_w, hold_translation_vz, hybrid_cruise_vxy, @@ -149,7 +150,7 @@ class TestHandoff: assert "伺服没接住" in perc def test_d405_points_stay_off_the_arm_until_arrived_and_settled(self): - """用着 D435 那个点时,到位并且停稳之前,D405 的点不下发。推理不在这里关。""" + """D435 厂家移动没结束、法兰没停稳:D405 不下发。两件都满足才追框心。""" assert d405_commands_arm(using_d435=False, arrived=False, settled=False) is True assert d405_commands_arm(using_d435=True, arrived=False, settled=False) is False assert d405_commands_arm(using_d435=True, arrived=True, settled=False) is False @@ -169,7 +170,7 @@ class TestHandoff: assert "d435_commanding" in perc def test_after_d435_wrist_tracks_box_center_then_hole_without_depth(self): - """交回 D405 追框心;本分支单次精拍 k1/k2,孔坐标只交到工件面。""" + """交回 D405 追框矩形中心;框心对准后才精拍,孔坐标只交到工件面。""" perc = ( __import__("pathlib").Path(__file__).resolve().parents[1] / "arm_application" / "perception_node.py" @@ -344,7 +345,7 @@ class TestHandoff: assert is_control_stack_cmd("perception_node") is False def test_d435_move_only_never_hands_back_to_d405(self): - """只测位移:D405 点一律不下发,到位停稳也不交。默认路径仍会交回。""" + """只测位移:D405 点一律不下发。混合路径法兰停稳后才打开框心。""" assert d405_commands_arm( using_d435=False, arrived=True, settled=True, move_only=True ) is False @@ -370,7 +371,8 @@ class TestHandoff: run_all = (ws / "scripts" / "run_all.sh").read_text(encoding="utf-8") assert "move_only=self.d435_move_only" in servo assert "不交 D405、不进二阶段" in servo - assert "改回下发 D405" in servo + assert "打开 D405 输出,追工件框矩形中心" in servo + assert "锁定进二阶段,D405 流式不再带臂" not in servo assert "self.d435_move_only" in perc assert "or wrist_needs_handoff(" in perc assert "d435_move_only:={str(self.d435_move_only).lower()}" in pipe @@ -464,6 +466,21 @@ class TestHandoff: hold = src[src.index("def _d435_oneshot_holds_the_arm") : src.index("def _tick_d435_arrive")] assert 'self._track_source == "overhead"' in hold assert "not self._d435_settled" in hold + fn = src[ + src.index("def _d435_oneshot_holds_the_arm") : src.index( + "def _d435_pin_and_stop_hops" + ) + ] + assert "and self._d435_settled" in fn + assert "等 D405 框心接上" in fn + locked = src[ + src.index("def _d435_oneshot_holds_locked") : src.index( + "def _d435_oneshot_holds_locked_joint" + ) + ] + assert "self._d435_vendor_done = True" in locked + pin_at = locked.find("self._d435_vendor_done = True") + assert "_send_hold()" in locked[pin_at : pin_at + 500] assert "_publish_joint_schedule(" not in hold assert "oneshot_cartesian_dp(" not in hold assert "trap_joint_schedule(" not in hold @@ -872,7 +889,7 @@ class TestHoleBiasAxes: assert st2.get("hole_once_lock_xy_descend") is True assert st2.get("once_ff_look_per_m") == pytest.approx(ONCE_FF_LOOK_PER_M) servo = (site.get("servo") or {}) - assert servo.get("d435_handback_sec") == pytest.approx(0.5) + assert servo.get("d435_handback_sec") == pytest.approx(2.0) def test_oneshot_step_covers_half_hole_pair(self): """框心到 k1 约 29mm:必须整步走完,不许再截成 12mm。""" @@ -976,3 +993,85 @@ class TestHoleBiasAxes: assert rel is not None np.testing.assert_allclose(rel, [0.580, -0.460, 0.0245], atol=1e-9) assert once_ff_movel_from_tcp([0.580, -0.460, 0.096], [0.00129, 0.0], 0.0) is None + + +class TestRetractHold: + def test_retract_goal_z_never_goes_down(self): + """抬过头 = 成功。真机 hop1 +18 mm 后 hop2 不许用 −13 mm 往回压。""" + assert retract_goal_z(0.258, 0.258 + 0.005) == pytest.approx(0.263) + assert retract_goal_z(0.276, 0.258 + 0.005) == pytest.approx(0.276) + assert retract_goal_z(0.276, 0.263) >= 0.276 - 1e-12 + + def test_stage3_retract_is_z_up_only(self): + root = __import__("pathlib").Path(__file__).resolve().parents[1] + st3 = (root / "arm_application" / "stage3_search_node.py").read_text( + encoding="utf-8" + ) + blk = st3[st3.find("def _retract") : st3.find("def _resnap_align")] + assert "z_up_only=True" in blk + assert "hold_xy=True" in blk + st2 = (root / "arm_application" / "stage2_locate_node.py").read_text( + encoding="utf-8" + ) + assert "z_up_only: bool = False" in st2 + assert "retract_goal_z(" in st2 + + def test_vendor_movel_hold_seeds_joint_commands(self): + """MoveL 切回关节模式后必须用当前角覆盖 JTC 悬停指令,否则收臂会先弹。""" + root = __import__("pathlib").Path(__file__).resolve().parents[3] + cpp = ( + root / "src" / "flexiv_hardware" / "src" / "flexiv_hardware_interface.cpp" + ).read_text(encoding="utf-8") + hpp = ( + root + / "src" + / "flexiv_hardware" + / "include" + / "flexiv_hardware" + / "flexiv_hardware_interface.hpp" + ).read_text(encoding="utf-8") + assert "hold_current_joint_commands" in cpp + assert "hold_current_joint_commands" in hpp + assert "vendor_movel_hold_commands_" in cpp + assert "kArming" in hpp + assert "NRT_PRIMITIVE_EXECUTION" in cpp + assert "切到 NRT_PRIMITIVE_EXECUTION 超时" in cpp + assert "robot_->states().q" in cpp + assert "milliseconds(500)" in cpp + assert "nrt_gate_settle" in cpp + assert "vendor_movel_hold_q_" in cpp + assert "kHoldReleaseRad = 0.020" in cpp + assert "vendor_movel_hold_until_" not in cpp + settle = cpp[cpp.find("void nrt_gate_settle") : cpp.find("void request_nrt_settle")] + assert "gate.v_filt.assign" in settle + assert "gate.parked = true" in settle + assert "gate.last_cmd = q" in settle + servo = ( + __import__("pathlib").Path(__file__).resolve().parents[1] + / "arm_application" + / "servo_node.py" + ).read_text(encoding="utf-8") + assert "D435_MOVEL_GAVE_UP" in servo + assert "D435_MOVEL_RETRY" in servo + pipe = ( + __import__("pathlib").Path(__file__).resolve().parents[1] + / "arm_application" + / "pipeline_node.py" + ).read_text(encoding="utf-8") + assert "stage1_log_fatal(" in pipe + assert "finish_vendor_movel" in cpp + fin = cpp[cpp.find("void FlexivHardwareInterface::finish_vendor_movel") :] + fin = fin[: fin.find("bool FlexivHardwareInterface::poll_vendor_movel")] + assert "hold_current_joint_commands()" in fin + wr = cpp[cpp.find("FlexivHardwareInterface::write") :] + wr = wr[: wr.find("if (position_controller_running_")] + assert "vendor_movel_hold_commands_" in wr + assert "vendor_movel_hold_q_" in wr + assert "hw_states_joint_positions_" not in wr + send = cpp[cpp.find("static NrtSendGate nrt_gate;") :] + send = send[: send.find("if (nrt_should_send")] + assert "nrt_gate_settle(nrt_gate, target_pos)" in send + i_pin = cpp.find("const bool pin_arrival") + look = cpp[i_pin : cpp.find("send_pos[i] = target_pos[i]", i_pin)] + assert "pin_arrival" in look + assert "near_rest = pin_arrival" in look diff --git a/src/arm_application/test/test_overhead_track.py b/src/arm_application/test/test_overhead_track.py index be72bba..091b3f0 100644 --- a/src/arm_application/test/test_overhead_track.py +++ b/src/arm_application/test/test_overhead_track.py @@ -238,7 +238,7 @@ class TestRealSiteBranchDefault: assert s["handeye_mode"] == "eye_in_hand" assert s["handeye_file"].endswith("89_eye_in_hand.yaml") assert s["lock_still_sec"] == pytest.approx(3.0) - assert s["d435_handback_sec"] == pytest.approx(0.5) + assert s["d435_handback_sec"] == pytest.approx(2.0) assert s["wrist_align_sec"] == pytest.approx(0.5) assert s["lock_still_move_m"] == pytest.approx(0.015) # 负向:跟踪外参不能被换成顶视文件 diff --git a/src/arm_application/test/test_pipeline_stage1_diag.py b/src/arm_application/test/test_pipeline_stage1_diag.py index 720474c..624df10 100644 --- a/src/arm_application/test/test_pipeline_stage1_diag.py +++ b/src/arm_application/test/test_pipeline_stage1_diag.py @@ -11,13 +11,42 @@ ultralytics 早退了"被读成"算法不收敛",白查半天。这里把这 from __future__ import annotations +from pathlib import Path + import pytest -from arm_application.pipeline_node import init_z_refusal, stage1_timeout_reason, track_lock_z +from arm_application.overhead_oneshot import D435_MOVEL_GAVE_UP +from arm_application.pipeline_node import ( + hold_at_insert_z, + init_z_refusal, + stage1_log_fatal, + stage1_timeout_reason, + track_lock_z, +) -def test_zero_points_blames_perception_not_calibration(): - msg = stage1_timeout_reason(0, 150.0, "/tmp/pipeline_stage1.log") +def test_insert_hold_does_not_return_to_hover(): + """贴面后停住。三阶段开局会抬回悬高;没有传感器,不另设一个 Z。""" + assert hold_at_insert_z({"insert_held": True}, skip3=False) is True + assert hold_at_insert_z({}, skip3=False) is False + assert hold_at_insert_z(None, skip3=False) is False + assert hold_at_insert_z({}, skip3=True) is True + src = ( + Path(__file__).resolve().parents[1] / "arm_application" / "stage2_locate_node.py" + ).read_text(encoding="utf-8") + hold = src[src.find("# ---- 5) 锁定保持 ----") : src.find("def _linger_hold")] + assert 'final["insert_held"] = True' in hold + assert 'final["insert_z_source"] = "final_gap_mm"' in hold + pipe = ( + Path(__file__).resolve().parents[1] / "arm_application" / "pipeline_node.py" + ).read_text(encoding="utf-8") + run = pipe[pipe.find("def run") :] + assert "hold_at_insert_z(" in run + assert "不抬回悬高" in run + + +def test_zero_points_blames_perception_not_calibration(tmp_path: Path): + msg = stage1_timeout_reason(0, 150.0, str(tmp_path / "missing.log")) assert "一个视觉目标点都没收到" in msg # 必须逐后端给出对号入座的查法,否则人还是不知道下一步做什么 assert "yolo" in msg and "truth" in msg and "external" in msg @@ -27,8 +56,8 @@ def test_zero_points_blames_perception_not_calibration(): assert "不是标定" in msg -def test_points_seen_but_no_convergence_blames_geometry(): - msg = stage1_timeout_reason(123, 150.0, "/tmp/pipeline_stage1.log") +def test_points_seen_but_no_convergence_blames_geometry(tmp_path: Path): + msg = stage1_timeout_reason(123, 150.0, str(tmp_path / "missing.log")) assert "123" in msg assert "没收敛" in msg assert "手眼" in msg @@ -36,10 +65,11 @@ def test_points_seen_but_no_convergence_blames_geometry(): assert "ultralytics" not in msg -def test_timeout_seconds_and_log_path_are_in_the_message(): - msg = stage1_timeout_reason(0, 42.0, "/tmp/x.log") +def test_timeout_seconds_and_log_path_are_in_the_message(tmp_path: Path): + log = str(tmp_path / "x.log") + msg = stage1_timeout_reason(0, 42.0, log) assert "42s" in msg - assert "/tmp/x.log" in msg + assert log in msg def test_real_arm_locks_track_height_at_hover(): @@ -78,6 +108,20 @@ def test_movel_ipc_roundtrip(): assert parse_done("nope\n") is None +def test_movel_gave_up_in_log_is_fatal_not_a_vision_timeout(tmp_path: Path): + """2026-10-16 17:27:MoveL 控制模式失败后空等 150s 被读成粗定位卡住。""" + p = tmp_path / "stage1.log" + p.write_text(f"foo\n{D435_MOVEL_GAVE_UP} —— 一阶段中止\n", encoding="utf-8") + why = stage1_log_fatal(str(p)) + assert why + assert "控制模式" in why + assert "不是视野" in why + msg = stage1_timeout_reason(80, 150.0, str(p)) + assert "控制模式" in msg + assert "没收敛" not in msg + assert stage1_log_fatal(str(tmp_path / "nope.log")) == "" + + def test_missing_geometry_does_not_pretend_the_hover_is_clear(): """工具长或工件面缺失时必须抬升。缺数还锁在悬停高,等于没检查。""" assert track_lock_z(0.320, 0.0, 0.022) == pytest.approx(0.370) diff --git a/src/arm_application/test/test_stage2_coarse_guard.py b/src/arm_application/test/test_stage2_coarse_guard.py index d8f24c5..8f41e6f 100644 --- a/src/arm_application/test/test_stage2_coarse_guard.py +++ b/src/arm_application/test/test_stage2_coarse_guard.py @@ -229,9 +229,27 @@ def test_site_yaml_has_the_two_guard_keys(): assert "coarse_max_offset_m" in stage2, "site.yaml 没接 coarse_max_offset_m" assert "coarse_border_px" in stage2, "site.yaml 没接 coarse_border_px" off = float(stage2["coarse_max_offset_m"]) - # 必须小于真机那次出事的 211.5mm、又大于合理粗居中的厘米级位移 - assert 0.03 < off < BAD_ERR_M, ( - f"coarse_max_offset_m={off} 必须夹在'合理粗居中'与'那次 211.5mm'之间" + # 必须小于真机那次出事的 211.5mm、又大于合理粗居中的厘米级位移。 + # 还要挡住 2026-10-16 的 110mm 回撤(0.12 放行了)。 + assert 0.03 < off < 0.10, ( + f"coarse_max_offset_m={off} 必须夹在'合理粗居中'与'110mm 回撤'之间" + ) + + +def test_d435_handoff_110mm_reverse_is_rejected(): + """2026-10-16:D405 把法兰拉到 (0.569,-0.375),工件仍在 (0.512,-0.469)。 + + 110mm 必须挡住。旧上限 0.12 会放行 —— 这条是「改了阈值真能红」的负向。 + """ + gauge = np.array([0.569, -0.375]) + workpiece = np.array([0.512, -0.469, 0.320]) + err = float(np.hypot(workpiece[0] - gauge[0], workpiece[1] - gauge[1])) + assert 0.10 < err < 0.12, f"夹具前提变了:偏差是 {err*1000:.1f}mm 不是 ~110mm" + why = _reject(_Stub(max_offset_m=0.08), workpiece, gauge) + assert why is not None, "110mm 回撤漏过去了" + assert "上限" in why + assert _reject(_Stub(max_offset_m=0.12), workpiece, gauge) is None, ( + "旧上限 0.12 必须放行这条,否则负向基线失真" ) diff --git a/src/flexiv_ros2/flexiv_hardware/include/flexiv_hardware/flexiv_hardware_interface.hpp b/src/flexiv_ros2/flexiv_hardware/include/flexiv_hardware/flexiv_hardware_interface.hpp index adf1b7a..e8af21b 100644 --- a/src/flexiv_ros2/flexiv_hardware/include/flexiv_hardware/flexiv_hardware_interface.hpp +++ b/src/flexiv_ros2/flexiv_hardware/include/flexiv_hardware/flexiv_hardware_interface.hpp @@ -143,13 +143,26 @@ private: // D435 粗定位的 MoveL 走已经连上的这份 RDK。write() 里一步一步推进, // 不另开线程,也不在这段里往关节接口灌点。请求文件是 /tmp/flexiv_movel_req。 - enum class VendorMovelPhase { kIdle, kRunning }; + enum class VendorMovelPhase { kIdle, kArming, kRunning }; VendorMovelPhase vendor_movel_phase_ = VendorMovelPhase::kIdle; long vendor_movel_seq_ = -1; long vendor_movel_done_seq_ = -1; + double vendor_movel_x_ = 0.0; + double vendor_movel_y_ = 0.0; + double vendor_movel_z_ = 0.0; + double vendor_movel_vel_ = 0.0; + int vendor_movel_keep_z_ = 0; std::chrono::steady_clock::time_point vendor_movel_deadline_ {}; + std::chrono::steady_clock::time_point vendor_movel_arm_until_ {}; + // MoveL 切回关节模式后 JTC 还握着贴面前的悬停关节。钉住的是结束 + // 瞬间的关节角,直到 JTC 指令贴近它才放下。不许因“指令已被写成 + // 状态”或超时放行,也不许把 MoveL 的关节差当成下一拍的速度。 + bool vendor_movel_hold_commands_ = false; + std::chrono::steady_clock::time_point vendor_movel_hold_since_ {}; + std::vector vendor_movel_hold_q_; bool poll_vendor_movel(); void finish_vendor_movel(long seq, bool ok, const std::string& detail); + void hold_current_joint_commands(); }; } /* namespace flexiv_hardware */ diff --git a/src/flexiv_ros2/flexiv_hardware/src/flexiv_hardware_interface.cpp b/src/flexiv_ros2/flexiv_hardware/src/flexiv_hardware_interface.cpp index 7a648c7..d1e9545 100644 --- a/src/flexiv_ros2/flexiv_hardware/src/flexiv_hardware_interface.cpp +++ b/src/flexiv_ros2/flexiv_hardware/src/flexiv_hardware_interface.cpp @@ -191,6 +191,35 @@ bool nrt_should_send(NrtSendGate& gate, const std::vector& target, doubl return true; } +// MoveL 结束时 last_cmd 还是出发前的关节。下一拍若用 +// (到位角 - last_cmd) / dt 当速度,整段位移会变成一步速度, +// 前瞻再沿同一方向推(2026-10-19 09:25:vcmd=1.686 rad/s, +// 光心从 5mm 被推到 276mm 后急停)。锚到到位角并驻留,速度清零。 +struct NrtSettleRequest { + bool pending = false; + std::vector q_rdk; +}; + +NrtSettleRequest nrt_settle_request; + +void nrt_gate_settle(NrtSendGate& gate, const std::vector& q) +{ + gate.last_cmd = q; + gate.last_sent = q; + gate.prev_target = q; + gate.park_target = q; + gate.v_filt.assign(q.size(), 0.0); + gate.have_sent = true; + gate.parked = true; + gate.still_ticks = 0; +} + +void request_nrt_settle(const std::vector& q_rdk) +{ + nrt_settle_request.q_rdk = q_rdk; + nrt_settle_request.pending = true; +} + void fill_nrt_limits(std::vector& max_vel, std::vector& max_acc, double d_from_state, double cmd_speed) { @@ -741,6 +770,29 @@ hardware_interface::return_type FlexivHardwareInterface::read( return hardware_interface::return_type::OK; } +void FlexivHardwareInterface::hold_current_joint_commands() +{ + // 冻结 MoveL 结束瞬间的关节角。之后不许改去跟实时状态, + // 否则前瞻把臂推走时,钉住目标会跟着跑。 + const auto q = robot_->states().q; + const size_t ncmd = hw_commands_joint_positions_.size(); + vendor_movel_hold_q_.assign(ncmd, 0.0); + std::vector q_rdk(robot_->info().DoF, 0.0); + for (size_t rdk_idx = 0; rdk_idx < robot_->info().DoF && rdk_idx < q.size(); + ++rdk_idx) { + const size_t ros_idx = rdk_to_ros_map_[rdk_idx]; + q_rdk[rdk_idx] = q[rdk_idx]; + if (ros_idx < hw_states_joint_positions_.size() && ros_idx < ncmd) { + hw_states_joint_positions_[ros_idx] = q[rdk_idx]; + hw_commands_joint_positions_[ros_idx] = q[rdk_idx]; + vendor_movel_hold_q_[ros_idx] = q[rdk_idx]; + } + } + request_nrt_settle(q_rdk); + vendor_movel_hold_commands_ = true; + vendor_movel_hold_since_ = std::chrono::steady_clock::now(); +} + void FlexivHardwareInterface::finish_vendor_movel( long seq, bool ok, const std::string& detail) { @@ -756,12 +808,62 @@ void FlexivHardwareInterface::finish_vendor_movel( } vendor_movel_done_seq_ = seq; vendor_movel_phase_ = VendorMovelPhase::kIdle; + hold_current_joint_commands(); } bool FlexivHardwareInterface::poll_vendor_movel() { // 这一拍正在走 MoveL:只查 reachedTarget,不往关节接口灌点。 // read() 仍每拍更新状态,时间戳看门狗不会把这段误判成断连。 + if (vendor_movel_phase_ == VendorMovelPhase::kArming) { + // SwitchMode 返回了不等于已经进 primitive。2026-10-16 17:27 + // 同一拍 ExecutePrimitive 报 "not in an applicable control mode"。 + const auto now = std::chrono::steady_clock::now(); + const auto mode = robot_->mode(); + if (mode == flexiv::rdk::Mode::NRT_PRIMITIVE_EXECUTION) { + try { + const auto pose = robot_->states().tcp_pose; + double z = vendor_movel_z_; + if (vendor_movel_keep_z_ != 0) { + z = pose[2]; + } + const auto euler = movel_euler_deg(pose[3], pose[4], pose[5], pose[6]); + const flexiv::rdk::Coord target( + std::array {vendor_movel_x_, vendor_movel_y_, z}, + euler, + std::array {"WORLD", "WORLD_ORIGIN"}); + robot_->ExecutePrimitive( + "MoveL", + {{"target", target}, {"vel", vendor_movel_vel_}}, + true); + vendor_movel_phase_ = VendorMovelPhase::kRunning; + vendor_movel_deadline_ = now + std::chrono::seconds(90); + RCLCPP_INFO(getLogger(), + "厂家 MoveL 已下发 xyz=(%.3f, %.3f, %.3f) keep_z=%d vel=%.3f", + vendor_movel_x_, vendor_movel_y_, z, + vendor_movel_keep_z_, vendor_movel_vel_); + } catch (const std::exception& e) { + try { + robot_->SwitchMode(rdk_control_mode_); + } catch (const std::exception&) { + } + finish_vendor_movel(vendor_movel_seq_, false, e.what()); + } + return true; + } + if (now > vendor_movel_arm_until_) { + try { + robot_->SwitchMode(rdk_control_mode_); + } catch (const std::exception&) { + } + finish_vendor_movel( + vendor_movel_seq_, false, + "切到 NRT_PRIMITIVE_EXECUTION 超时,当前 mode=" + + std::to_string(static_cast(mode))); + return true; + } + return true; + } if (vendor_movel_phase_ == VendorMovelPhase::kRunning) { try { if (std::chrono::steady_clock::now() > vendor_movel_deadline_) { @@ -812,23 +914,22 @@ bool FlexivHardwareInterface::poll_vendor_movel() if (keep_z != 0) { z = pose[2]; } - const auto euler = movel_euler_deg(pose[3], pose[4], pose[5], pose[6]); if (robot_->fault()) { robot_->ClearFault(3); } - robot_->SwitchMode(flexiv::rdk::Mode::NRT_PRIMITIVE_EXECUTION); - const flexiv::rdk::Coord target( - std::array {x, y, z}, - euler, - std::array {"WORLD", "WORLD_ORIGIN"}); - robot_->ExecutePrimitive( - "MoveL", {{"target", target}, {"vel", vel}}, true); vendor_movel_seq_ = seq; - vendor_movel_phase_ = VendorMovelPhase::kRunning; - vendor_movel_deadline_ = std::chrono::steady_clock::now() + std::chrono::seconds(90); + vendor_movel_x_ = x; + vendor_movel_y_ = y; + vendor_movel_z_ = z; + vendor_movel_vel_ = vel; + vendor_movel_keep_z_ = keep_z; + robot_->SwitchMode(flexiv::rdk::Mode::NRT_PRIMITIVE_EXECUTION); + vendor_movel_phase_ = VendorMovelPhase::kArming; + vendor_movel_arm_until_ = std::chrono::steady_clock::now() + + std::chrono::seconds(2); RCLCPP_INFO(getLogger(), - "厂家 MoveL 已下发 xyz=(%.3f, %.3f, %.3f) keep_z=%d vel=%.3f", - x, y, z, keep_z, vel); + "厂家 MoveL 切 primitive 中 xyz=(%.3f, %.3f, %.3f) keep_z=%d 当前 mode=%d", + x, y, z, keep_z, static_cast(robot_->mode())); } catch (const std::exception& e) { try { robot_->SwitchMode(rdk_control_mode_); @@ -853,6 +954,41 @@ hardware_interface::return_type FlexivHardwareInterface::write( std::vector max_vel(robot_->info().DoF, kServoJointVelocity); std::vector max_acc(robot_->info().DoF, kServoJointAcceleration); + static rclcpp::Clock steady_clock(RCL_STEADY_TIME); + + if (vendor_movel_hold_commands_) { + const auto now_hold = std::chrono::steady_clock::now(); + double d_ext = 0.0; + bool cmd_nan = vendor_movel_hold_q_.size() + != hw_commands_joint_positions_.size(); + const size_t n = std::min( + hw_commands_joint_positions_.size(), vendor_movel_hold_q_.size()); + for (size_t i = 0; i < n; ++i) { + const double c = hw_commands_joint_positions_[i]; + if (c != c) { + cmd_nan = true; + continue; + } + // 比的是这一拍 JTC 写进来的指令和冻结角,不是我们自己覆盖后的值。 + d_ext = std::max(d_ext, std::fabs(c - vendor_movel_hold_q_[i])); + } + const bool min_hold = now_hold + >= vendor_movel_hold_since_ + std::chrono::milliseconds(500); + // 出发前的悬停和到位角差远大于 0.02 rad。超时放行会把那段再发出去。 + constexpr double kHoldReleaseRad = 0.020; + if (!cmd_nan && min_hold && d_ext < kHoldReleaseRad) { + vendor_movel_hold_commands_ = false; + } else { + for (size_t i = 0; i < n; ++i) { + hw_commands_joint_positions_[i] = vendor_movel_hold_q_[i]; + } + if (now_hold > vendor_movel_hold_since_ + std::chrono::seconds(3)) { + RCLCPP_WARN_THROTTLE(getLogger(), steady_clock, 2000, + "MoveL 后关节指令离到位角还差 %.3f rad,继续钉住到位角", + d_ext); + } + } + } bool is_pos_nan = false; bool is_vel_nan = false; @@ -869,8 +1005,6 @@ hardware_interface::return_type FlexivHardwareInterface::write( } } - static rclcpp::Clock steady_clock(RCL_STEADY_TIME); - if (position_controller_running_ && !is_pos_nan) { if (robot_->mode() != rdk_control_mode_) { RCLCPP_WARN_THROTTLE(getLogger(), steady_clock, 2000, @@ -924,6 +1058,12 @@ hardware_interface::return_type FlexivHardwareInterface::write( std::fabs(target_pos[rdk_idx] - hw_states_joint_positions_[ros_idx])); } static NrtSendGate nrt_gate; + // 钉住期间每拍把速度门闩锚回当前下发角。last_cmd 若还是 + // MoveL 之前的关节,这一拍的速度就是整段位移。 + if (vendor_movel_hold_commands_ || nrt_settle_request.pending) { + nrt_gate_settle(nrt_gate, target_pos); + nrt_settle_request.pending = false; + } RCLCPP_INFO_THROTTLE(getLogger(), steady_clock, 2000, "HW write running=%d nan=%d mode=%d want=%d op=%d busy=%d d_state=%.4f", static_cast(position_controller_running_), static_cast(is_pos_nan), @@ -954,8 +1094,10 @@ hardware_interface::return_type FlexivHardwareInterface::write( } constexpr double kLookahead = 0.14; std::vector send_pos = target_pos; - // Near rest: do NOT add lookahead (it sustains micro-oscillation) - const bool near_rest = (cmd_speed < 0.025 && d_from_state < 0.010); + // 钉住到位角时不许前瞻。近静止也不加,否则会自己维持微振。 + const bool pin_arrival = vendor_movel_hold_commands_; + const bool near_rest = pin_arrival + || (cmd_speed < 0.025 && d_from_state < 0.010); for (size_t i = 0; i < target_pos.size(); ++i) { if (near_rest) { send_pos[i] = target_pos[i];