解决方案--针对于moveit订阅连续路点规划执行过程中出现的错误

在使用moveit控制UR5机械臂进行连续路点规划时遇到超出关节限制的错误,导致无法规划和执行。通过调整参数、更换逆运动学规划包、理解MoveGroupInterface的工作原理以及修改关节限制,逐步解决了问题。具体措施包括:关闭虚拟关节运行、更改初始位置、使用KDL替代IK-Fast、理解和正确使用AsyncSpinner以及调整关节限制。

最近想要用点云来识别物体位姿,然后订阅发布的位姿并利用moveit控制UR5机械臂的移动,但由于最近系统进行了重装,整个环境的搭建都重新开始,于是先复现了一下之前用AprilTag输出空间位姿,然后订阅位姿利用moveit控制UR5机械臂的移动。目前可以实现连续的路点移动,但是会出现超出某一个关节限制的警告,从而导致无法规划,执行的错误。

但是在复现过程控制UR5机械臂在gazebo仿真时相继出现了以下两个错误:
第一个主要问题
,发生在

rosrun apriltags2_node apriltags2_client

在moveit启动节点下:
[ WARN] [1567497398.552827574]: Fail: ABORTED: No motion plan found. No execution attempted.

roslaunch ur5_moveit_config ur5_moveit_planning_execution.launch sim:=true

启动节点下
[ERROR] [1567673055.288638354]: RRTConnect: Unable to sample any valid states for goal tree

自己反复修改了几个方面后解决了,具体原因还不清楚
有以下几个修改思路:
1,首先要关闭虚拟关节运行
在demo.launch文件中参数fake_execution的值改为false

在这里插入代码片<!--此段代码来自moveit配置文件demo.launch-->
<!-- Run the main MoveIt executable without trajectory execution (we do not have controllers configured by default) -->
  <include file="$(find aubo_i5_moveit_config)/launch/move_group.launch">
    <arg name="allow_trajectory_execution" value="true"/>
    <arg name="fake_execution" value="false"/> //请看这个参数
    <arg name="info" value="true"/>
    <arg name="debug" value="$(arg debug)"/>
  </include>

2,在rviz下拖动改变当前机械臂的位置,换一下初始位置
如下:
在这里插入图片描述
3,如果用gazebo模拟我的controllers.yaml变成下面这样:
在/home/harry/catkin_ws/src/universal_robot/ur5_moveit_config/config/controllers.yaml

controller_list:
#  - name: ""
#    action_ns: follow_joint_trajectory
#    type: FollowJointTrajectory
#    joints:
#      - shoulder_pan_joint
#      - shoulder_lift_joint
#      - elbow_joint
#      - wrist_1_joint
#      - wrist_2_joint
#      - wrist_3_joint

#-------------for gazebo UR5--------------------------
lyx@lyx-virtual-machine:~/Codroid_ROS2_WS/src/codroid_msgs/src$ python3 2.py [INFO] [1761226713.090949430] [s360_robot_full_control]: === S3 60机器人控制节点启动 === [INFO] [1761226713.206587598] [s360_robot_full_control]: ✅ MoveIt2接口初始化完成 [INFO] [1761226713.207614623] [s360_robot_full_control]: 📡 Socket服务器监听: 192.168.101.111:8888 [INFO] [1761226713.208277726] [s360_robot_full_control]: ✅ 系统初始化完成: 192.168.101.111:8888 [INFO] [1761226713.209199884] [s360_robot_full_control]: 🚀 节点进入运行状态,等待指令... [INFO] [1761226718.977626025] [s360_robot_full_control]: 📞 新客户端连接: ('192.168.101.112', 58502) [INFO] [1761226718.994709784] [s360_robot_full_control]: 📥 接收指令: 类型=p2, 参数=[[0.4, 0.1, 0.2], [1.0, 0.0, 0.0, 0.0]] [FATAL] [1761226719.002192131] [s360_robot_full_control]: ❌ 节点运行异常: Failed to get number of ready entities for action client: wait set index for result client is out of bounds, at ./src/rcl_action/action_client.c:635 Traceback (most recent call last): File "/home/lyx/Codroid_ROS2_WS/src/codroid_msgs/src/2.py", line 491, in main executor.spin() File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 294, in spin self.spin_once() File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 806, in spin_once self._spin_once_impl(timeout_sec) File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 787, in _spin_once_impl handler, entity, node = self.wait_for_ready_callbacks( File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 723, in wait_for_ready_callbacks return next(self._cb_iter) File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 642, in _wait_for_ready_callbacks if wt in waitables and wt.is_ready(wait_set): File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/action/client.py", line 231, in is_ready ready_entities = self._client_handle.is_ready(wait_set) rclpy._rclpy_pybind11.RCLError: Failed to get number of ready entities for action client: wait set index for result client is out of bounds, at ./src/rcl_action/action_client.c:635 [INFO] [1761226719.002788258] [s360_robot_full_control]: 🛑 节点开始关闭,释放资源中... [INFO] [1761226719.003273958] [s360_robot_full_control]: ⏳ 等待Socket线程退出... [WARN] [1761226719.009362504] [s360_robot_full_control]: Joint states are not available yet! [INFO] [1761226719.010012613] [s360_robot_full_control]: Joint states are available now [INFO] [1761226719.072622565] [s360_robot_full_control]: 📋 收到规划轨迹: 37个点 [INFO] [1761226719.077731579] [s360_robot_full_control]: ⏳ 开始运动完成检测... [INFO] [1761226719.078853332] [s360_robot_full_control]: 📤 指令执行结果: None(客户端:('192.168.101.112', 58502)) Exception in thread Thread-2 (handle_client): Traceback (most recent call last): File "/usr/lib/python3.10/threading.py", line 1016, in _bootstrap_inner self.run() File "/usr/lib/python3.10/threading.py", line 953, in run self._target(*self._args, **self._kwargs) File "/home/lyx/Codroid_ROS2_WS/src/codroid_msgs/src/2.py", line 453, in handle_client self.get_logger().info(f"📞 客户端连接已断开: {client_socket.getpeername()}") OSError: [Errno 9] Bad file descriptor [INFO] [1761226719.981364921] [s360_robot_full_control]: 📡 Socket服务器已关闭 [INFO] [1761226719.982142669] [s360_robot_full_control]: 🗑️ 销毁MoveIt2资源... [INFO] [1761226719.988285992] [s360_robot_full_control]: ✅ 所有资源已释放 [INFO] [1761226720.018866697] [s360_robot_full_control]: ✅ 节点已完全关闭 #!/usr/bin/env python3 """ S3 60机器人控制节点 - 运动完成确认修复版 基于关节角度比较确认运动完成,到达目标后等待0.5s返回 修复:解决关节数据顺序问题、多线程资源竞争、节点关闭资源泄漏问题 """ import socket import json import time import traceback from threading import Thread, Lock import rclpy from rclpy.node import Node from rclpy.executors import MultiThreadedExecutor from sensor_msgs.msg import JointState from moveit_msgs.msg import DisplayTrajectory from pymoveit2 import MoveIt2 from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint # -------------------------- 配置参数 -------------------------- # 请根据你的机器人硬件配置调整以下参数 JOINT_NAMES = ["Joint1", "Joint2", "Joint3", "Joint4", "Joint5", "Joint6"] BASE_LINK_NAME = "base_link" END_EFFECTOR_NAME = "link6" MOVE_GROUP_ARM = "arm" PLANNER_ID = "RRTConnectkConfigDefault" SERVER_IP = "192.168.101.111" # 机器人控制节点IP SERVER_PORT = 8888 # 通信端口 MAX_VELOCITY = 0.5 # 最大速度(单位:rad/s) MAX_ACCELERATION = 0.5 # 最大加速度(单位:rad/s²) JOINT_TOLERANCE = 0.03 # 关节角度容差(单位:rad) MAX_POINTS = 5 # 轨迹最大采样点数 MOTION_TIMEOUT = 30.0 # 运动超时时间(单位:s) class S360RobotControlNode(Node): def __init__(self): super().__init__("s360_robot_full_control") self.get_logger().info("=== S3 60机器人控制节点启动 ===") # 状态管理(新增:is_shutting_down用于关闭状态标记) self.is_shutting_down = False self.joint_state_received = False # 关节状态是否已接收 self.current_joint_positions = {name: 0.0 for name in JOINT_NAMES} # 关节位置字典 # 线程锁(新增:解决多线程资源竞争) self.joints_lock = Lock() # 关节数据锁 self.trajectory_lock = Lock() # 轨迹数据锁 self.moveit_lock = Lock() # MoveIt2操作锁(核心修复) # 轨迹与运动相关变量 self.planned_trajectory = None # -------------------------- 子系统初始化 -------------------------- # 1. 轨迹发布器(控制机器人运动) self.trajectory_pub = self.create_publisher(JointTrajectory, 'RobotMove', 10) # 2. 轨迹监控器(接收MoveIt2规划结果) self.trajectory_sub = self.create_subscription( DisplayTrajectory, '/display_planned_path', self.trajectory_callback, 10 ) # 3. 关节状态订阅器(获取机器人当前关节位置) self.joint_state_sub = self.create_subscription( JointState, 'joint_states', self.joint_state_callback, 10 ) # 4. MoveIt2初始化(运动规划核心) self.moveit2 = None self.init_moveit2() # 5. Socket服务器(接收外部控制指令) self.socket_thread = Thread( target=self.start_socket_server, daemon=True ) self.socket_thread.start() self.get_logger().info(f"✅ 系统初始化完成: {SERVER_IP}:{SERVER_PORT}") # ======================= 核心初始化方法 ======================= def init_moveit2(self): """初始化MoveIt2接口,建立与运动规划器的连接""" try: self.moveit2 = MoveIt2( node=self, joint_names=JOINT_NAMES, base_link_name=BASE_LINK_NAME, end_effector_name=END_EFFECTOR_NAME, group_name=MOVE_GROUP_ARM ) self.moveit2.planner_id = PLANNER_ID self.moveit2.max_velocity = MAX_VELOCITY self.moveit2.max_acceleration = MAX_ACCELERATION self.get_logger().info("✅ MoveIt2接口初始化完成") except Exception as e: self.get_logger().error(f"❌ MoveIt2初始化失败: {e}") self.moveit2 = None # ======================= 数据回调方法 ======================= def joint_state_callback(self, msg): """关节状态回调:更新当前关节位置(字典存储,解决顺序问题)""" try: # 建立“关节名-位置”映射,避免顺序错乱 joint_mapping = {name: position for name, position in zip(msg.name, msg.position)} with self.joints_lock: # 只更新已知关节的位置,过滤无效数据 for name in JOINT_NAMES: if name in joint_mapping: self.current_joint_positions[name] = joint_mapping[name] self.joint_state_received = True # 标记关节数据已就绪 except Exception as e: self.get_logger().error(f"❌ 关节状态处理异常: {e}\n{traceback.format_exc()}") def trajectory_callback(self, msg): """轨迹回调:接收MoveIt2规划的轨迹点""" try: if msg.trajectory and msg.trajectory[0].joint_trajectory.points: with self.trajectory_lock: self.planned_trajectory = msg.trajectory[0].joint_trajectory.points self.get_logger().info(f"📋 收到规划轨迹: {len(self.planned_trajectory)}个点") except Exception as e: self.get_logger().error(f"❌ 轨迹处理异常: {e}") # ======================= 数据工具方法 ======================= def get_current_joints_in_standard_order(self): """获取标准顺序(JOINT_NAMES)的当前关节位置列表""" with self.joints_lock: return [self.current_joint_positions[name] for name in JOINT_NAMES] def wait_for_planned_trajectory(self, timeout=5.0): """等待MoveIt2规划轨迹生成,超时返回None""" start_time = time.time() while rclpy.ok() and not self.is_shutting_down: with self.trajectory_lock: if self.planned_trajectory: return self.planned_trajectory if time.time() - start_time > timeout: self.get_logger().warning("⚠️ 等待轨迹超时") return None time.sleep(0.1) return None # ======================= 轨迹发布方法 ======================= def publish_joint_trajectory(self, target_joints): """发布单目标点轨迹(用于简单关节运动)""" try: traj_msg = JointTrajectory() traj_msg.joint_names = JOINT_NAMES point = JointTrajectoryPoint() point.positions = target_joints point.time_from_start = rclpy.duration.Duration(seconds=2.0).to_msg() traj_msg.points.append(point) self.trajectory_pub.publish(traj_msg) return True except Exception as e: self.get_logger().error(f"❌ 关节轨迹发布失败: {e}") return False def publish_planned_trajectory(self, trajectory_points): """发布MoveIt2规划的多段轨迹(采样后发布,避免点数过多)""" try: if not trajectory_points: self.get_logger().warning("⚠️ 无轨迹点可发布") return False traj_msg = JointTrajectory() traj_msg.joint_names = JOINT_NAMES # 采样轨迹点(最多MAX_POINTS个,平衡平滑度与实时性) if len(trajectory_points) > MAX_POINTS: step = max(1, len(trajectory_points) // MAX_POINTS) trajectory_points = [trajectory_points[i] for i in range(0, len(trajectory_points), step)][:MAX_POINTS] # 分配时间戳,确保运动平滑 total_time = 5.0 # 总运动时长(可根据需求调整) for i, point in enumerate(trajectory_points): traj_point = JointTrajectoryPoint() traj_point.positions = point.positions traj_point.time_from_start = rclpy.duration.Duration( seconds=total_time * (i / (len(trajectory_points) - 1)) ).to_msg() traj_msg.points.append(traj_point) self.trajectory_pub.publish(traj_msg) return True except Exception as e: self.get_logger().error(f"❌ 规划轨迹发布失败: {e}") return False def publish_smooth_trajectory(self, start_joints, target_joints, num_points=5): """发布平滑过渡轨迹(从起点到目标点的线性插值)""" try: traj_msg = JointTrajectory() traj_msg.joint_names = JOINT_NAMES num_points = min(num_points, MAX_POINTS) # 限制最大点数 total_time = 5.0 # 总运动时长 # 线性插值生成中间点 for i in range(num_points): ratio = i / (num_points - 1) if num_points > 1 else 1.0 point = JointTrajectoryPoint() point.positions = [ start + ratio * (target - start) for start, target in zip(start_joints, target_joints) ] point.time_from_start = rclpy.duration.Duration(seconds=total_time * ratio).to_msg() traj_msg.points.append(point) self.trajectory_pub.publish(traj_msg) return True except Exception as e: self.get_logger().error(f"❌ 平滑轨迹发布失败: {e}") return False # ======================= 运动控制核心方法 ======================= def execute_joint_motion(self, target_joints): """执行关节空间运动:根据目标关节角度控制机器人""" # 修复1:关闭状态下拒绝执行 if self.is_shutting_down: self.get_logger().error("❌ 节点正在关闭,拒绝关节运动指令") return False # 修复2:检查关节数据是否就绪 if not self.joint_state_received: self.get_logger().error("❌ 未收到关节状态数据,无法执行关节运动") return False # 基础参数校验 if not self.moveit2 or len(target_joints) != len(JOINT_NAMES): self.get_logger().error("❌ 关节运动参数无效(MoveIt2未就绪或关节数不匹配)") return False try: # 获取当前关节位置,用于生成平滑轨迹 current_joints = self.get_current_joints_in_standard_order() self.publish_smooth_trajectory(current_joints, target_joints) # 修复3:MoveIt2操作加锁,避免多线程竞争 with self.moveit_lock: self.moveit2.move_to_configuration(target_joints) # 等待运动完成(基于关节角度容差判断) return self.wait_for_motion_completion(target_joints, timeout=MOTION_TIMEOUT) except Exception as e: self.get_logger().error(f"❌ 关节运动异常: {e}\n{traceback.format_exc()}") return False def execute_pose_motion(self, target_pose): """执行位姿空间运动:根据目标位姿(位置+姿态)控制机器人""" # 修复1:关闭状态下拒绝执行 if self.is_shutting_down: self.get_logger().error("❌ 节点正在关闭,拒绝位姿运动指令") return False # 修复2:检查关节数据是否就绪 if not self.joint_state_received: self.get_logger().error("❌ 未收到关节状态数据,无法执行位姿运动") return False # 基础参数校验(target_pose格式:[[x,y,z], [x,y,z,w]]) if not self.moveit2 or len(target_pose) != 2 or len(target_pose[0]) != 3 or len(target_pose[1]) != 4: self.get_logger().error("❌ 位姿运动参数无效(格式错误MoveIt2未就绪)") return False try: target_pos, target_quat = target_pose # 拆分位置和姿态 # 修复3:MoveIt2操作加锁,避免多线程竞争 with self.moveit_lock: # 1. 计算逆运动学(位姿→关节角度) ik_result = self.moveit2.compute_ik(target_pos, target_quat) if not ik_result: self.get_logger().error("❌ 逆运动学求解失败(目标位姿不可达)") return False # 2. 执行位姿运动 self.moveit2.move_to_pose(position=target_pos, quat_xyzw=target_quat, cartesian=False) # 3. 提取目标关节角度(用于运动完成判断) target_joints = self._extract_joint_positions_from_state(ik_result) if not target_joints: self.get_logger().error("❌ 无法提取目标关节角度,无法判断运动完成") return False # 4. 发布轨迹(优先用规划轨迹,其次用平滑轨迹) planned_trajectory = self.wait_for_planned_trajectory(timeout=5.0) if planned_trajectory: self.publish_planned_trajectory(planned_trajectory) else: current_joints = self.get_current_joints_in_standard_order() self.publish_smooth_trajectory(current_joints, target_joints) # 5. 等待运动完成 return self.wait_for_motion_completion(target_joints, timeout=MOTION_TIMEOUT) except Exception as e: self.get_logger().error(f"❌ 位姿运动异常: {e}\n{traceback.format_exc()}") return False def _extract_joint_positions_from_state(self, joint_state): """从JointState消息中提取标准顺序的关节位置""" if not joint_state or not hasattr(joint_state, 'position'): self.get_logger().warning("⚠️ 无效的JointState消息,无法提取关节位置") return None try: position_map = {name: pos for name, pos in zip(joint_state.name, joint_state.position)} # 按JOINT_NAMES顺序提取,确保与目标顺序一致 return [position_map[name] for name in JOINT_NAMES if name in position_map] except Exception as e: self.get_logger().error(f"❌ 关节提取失败: {e}") return None # ======================= 运动完成判断方法 ======================= def wait_for_motion_completion(self, target_joints, timeout=30.0): """基于关节角度容差判断运动是否完成(稳定0.5s后确认)""" start_time = time.time() target_reached_time = None # 首次到达目标的时间 last_log_time = start_time # 上次打印日志的时间 stable_count = 0 # 稳定计数器(0.1s/次,5次=0.5s) self.get_logger().info("⏳ 开始运动完成检测...") while rclpy.ok() and not self.is_shutting_down: # 1. 检查超时 if time.time() - start_time > timeout: current_joints = self.get_current_joints_in_standard_order() errors = [abs(c - t) for c, t in zip(current_joints, target_joints)] self.get_logger().error( f"⚠️ 运动超时! 最大误差: {max(errors):.4f}rad\n" f"目标关节: {[round(t, 3) for t in target_joints]}\n" f"当前关节: {[round(c, 3) for c in current_joints]}" ) return False # 2. 获取当前关节位置 current_joints = self.get_current_joints_in_standard_order() # 3. 检查是否到达目标(所有关节在容差内) if self._joints_reached_target(current_joints, target_joints): if target_reached_time is None: target_reached_time = time.time() self.get_logger().info("🎯 首次到达目标位置,开始稳定检测...") stable_count += 1 # 稳定0.5s(5个检测周期)后确认完成 if stable_count >= 5: stable_time = time.time() - target_reached_time self.get_logger().info(f"✅ 运动完成! 稳定时间: {stable_time:.2f}s") return True else: # 未到达目标,重置稳定计数器 target_reached_time = None stable_count = 0 # 4. 每秒打印一次运动状态(避免日志刷屏) current_time = time.time() if current_time - last_log_time >= 1.0: last_log_time = current_time elapsed = current_time - start_time errors = [abs(c - t) for c, t in zip(current_joints, target_joints)] self.get_logger().info( f"⏱️ 运动中... 已耗时: {elapsed:.1f}s | " f"最大误差: {max(errors):.4f}rad | " f"稳定计数: {stable_count}/5" ) time.sleep(0.1) # 100ms检测周期,平衡实时性与资源占用 def _joints_reached_target(self, current_joints, target_joints): """检查所有关节是否在容差范围内到达目标位置""" return all(abs(c - t) <= JOINT_TOLERANCE for c, t in zip(current_joints, target_joints)) # ======================= 网络通信方法 ======================= def start_socket_server(self): """启动Socket服务器,接收外部控制指令(如p1/p2/p3)""" server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 允许端口复用 try: server_socket.bind((SERVER_IP, SERVER_PORT)) server_socket.listen(5) # 最大同时连接数 server_socket.settimeout(1.0) # 超时避免阻塞关闭流程 self.get_logger().info(f"📡 Socket服务器监听: {SERVER_IP}:{SERVER_PORT}") while rclpy.ok() and not self.is_shutting_down: try: # 接收客户端连接(非阻塞,超时后循环检查关闭状态) client_socket, addr = server_socket.accept() self.get_logger().info(f"📞 新客户端连接: {addr}") # 启动线程处理客户端请求(避免阻塞其他连接) Thread(target=self.handle_client, args=(client_socket,), daemon=True).start() except socket.timeout: continue # 超时无连接,继续循环检查关闭状态 except Exception as e: self.get_logger().error(f"❌ Socket连接异常: {e}") except Exception as e: self.get_logger().fatal(f"❌ Socket服务器启动失败: {e}") finally: server_socket.close() self.get_logger().info("📡 Socket服务器已关闭") def handle_client(self, client_socket): """处理单个客户端的控制指令(指令格式:cmd_type:json_params)""" try: # 接收指令(最大1024字节,避免数据包过大) data = client_socket.recv(1024).decode().strip() if not data: self.get_logger().warning("⚠️ 客户端发送空指令,断开连接") return # 解析指令格式(如"p2:[[0.4,0.1,0.2],[1.0,0.0,0.0,0.0]]") if ':' not in data: self.get_logger().error(f"❌ 无效指令格式: {data}(需符合'cmd:params')") client_socket.sendall("False".encode()) return cmd_type, cmd_json = data.split(':', 1) cmd_params = json.loads(cmd_json) self.get_logger().info(f"📥 接收指令: 类型={cmd_type}, 参数={cmd_params}") # 执行对应指令 success = False if cmd_type == "p1": # p1指令:关节空间运动(参数:[joint1, joint2, ..., joint6]) success = self.execute_joint_motion(cmd_params) elif cmd_type in ["p2", "p3"]: # p2/p3指令:位姿空间运动(参数:[[x,y,z], [x,y,z,w]]) success = self.execute_pose_motion(cmd_params) else: self.get_logger().error(f"❌ 未知指令类型: {cmd_type}(支持p1/p2/p3)") # 向客户端返回执行结果(True/False) client_socket.sendall(str(success).encode()) self.get_logger().info(f"📤 指令执行结果: {success}(客户端:{client_socket.getpeername()})") except json.JSONDecodeError: self.get_logger().error(f"❌ JSON参数解析失败: {cmd_json}") client_socket.sendall("False".encode()) except Exception as e: self.get_logger().error(f"❌ 客户端处理异常: {e}\n{traceback.format_exc()}") client_socket.sendall("False".encode()) finally: client_socket.close() self.get_logger().info(f"📞 客户端连接已断开: {client_socket.getpeername()}") # ======================= 节点关闭方法 ======================= def shutdown(self): """优化关闭流程:确保线程退出、资源释放(核心修复)""" self.is_shutting_down = True self.get_logger().info("🛑 节点开始关闭,释放资源中...") # 1. 等待Socket线程退出(避免通信中强制关闭) if hasattr(self, 'socket_thread') and self.socket_thread.is_alive(): self.get_logger().info("⏳ 等待Socket线程退出...") self.socket_thread.join(timeout=2.0) # 最多等待2秒 if self.socket_thread.is_alive(): self.get_logger().warning("⚠️ Socket线程未正常退出,强制终止") # 2. 销毁MoveIt2资源(避免资源泄漏导致的"Destroyable"错误) if hasattr(self, 'moveit2') and self.moveit2 is not None: self.get_logger().info("🗑️ 销毁MoveIt2资源...") # 置空MoveIt2实例,触发垃圾回收 self.moveit2 = None # 3. 关闭发布器与订阅器(ROS2节点标准操作) self.destroy_publisher(self.trajectory_pub) self.destroy_subscription(self.trajectory_sub) self.destroy_subscription(self.joint_state_sub) self.get_logger().info("✅ 所有资源已释放") def main(): """节点主函数:初始化ROS2、启动执行器""" rclpy.init() node = S360RobotControlNode() # 使用多线程执行器,支持同时处理回调与Socket通信 executor = MultiThreadedExecutor(num_threads=4) executor.add_node(node) try: node.get_logger().info("🚀 节点进入运行状态,等待指令...") executor.spin() except KeyboardInterrupt: # 捕获Ctrl+C中断信号 node.get_logger().info("🛑 收到键盘中断信号(Ctrl+C)") except Exception as e: # 捕获其他未知异常 node.get_logger().fatal(f"❌ 节点运行异常: {e}\n{traceback.format_exc()}") finally: # 确保关闭流程执行 node.shutdown() node.destroy_node() rclpy.shutdown() node.get_logger().info("✅ 节点已完全关闭") if __name__ == "__main__": main() 怎么解决,给我完整的解决代码
最新发布
10-24
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值