ROS Action Server中'move_turtle'无'xOdom'属性问题求助
里程计数据订阅Action Server报错与数据追加实现
问题描述
开发订阅里程计(Odometry)数据的Action Server时,里程计订阅回调能正常获取并打印位姿数据,但在Action的execute回调中使用这些数据时触发以下错误:
ERROR] [1672904624.503538, 1251.206000]: Exception in your execute callback: 'move_turtle' object has no attribute 'xOdom' Traceback (most recent call last): File "/home/simulations/public_sim_ws/src/all/actionlib/actionlib/src/actionlib/simple_action_server.py", line 289, in executeLoop self.execute_callback(goal) File "/home/user/catkin_ws/src/real_robot/scripts/action_server.py", line 51, in callback print(self.xOdom) AttributeError: 'move_turtle' object has no attribute 'xOdom'
需求:理解错误含义,实现每次传感器读数时将xOdom数据追加到dist_x变量中。
原代码如下:
#! /usr/bin/env python from math import degrees, sqrt import rospy import actionlib import numpy as np from real_robot.msg import OdomRecordAction, OdomRecordResult, OdomRecordFeedback from nav_msgs.msg import Odometry from geometry_msgs.msg import Point32 from tf.transformations import euler_from_quaternion odom_record_data=Point32() class move_turtle(object): # create messages that are used to publish feedback and results _result = OdomRecordResult() _feedback = OdomRecordFeedback() def __init__(self): # creates the action server self._as = actionlib.SimpleActionServer("record_odom", OdomRecordAction, self.callback, False) self._as.start() self.ctrl_c = False self.rate = rospy.Rate(1) self.subodom = rospy.Subscriber('/odom', Odometry, self.call_record_odom) def call_record_odom(self,data): self.xOdom = data.pose.pose.position.x self.yOdom = data.pose.pose.position.y self.thOdom = data.pose.pose.orientation.z def callback(self,goal): rospy.loginfo("executing odometry record") i=0 distance = 0 dist_x = np.array((1, ), dtype = float) dist_y = np.array((1, ), dtype = float) orient_z = np.array((1, ), dtype = float) lap=100 flag=False while distance<=lap: #calc traveled distance print(self.xOdom) dist_x= np.append(dist_x,self.xOdom) dist_y = np.append(dist_y,self.yOdom) orient_z = np.append(orient_z,self.thOdom) distance += sqrt( pow(dist_x[i] - dist_x[i-1], 2 ) + ( dist_y[i] - dist_y[i-1] )**2 ) print("travelled distance:", distance) print(dist_x) # check that preempt (cancelation) has not been requested by the action client if self._as.is_preempt_requested(): rospy.loginfo('The goal has been cancelled/preempted') # the following line, sets the client in preempted state (goal cancelled) self._as.set_preempted() # build and publish the feedback message self._feedback.current_total = distance self._as.publish_feedback(self._feedback) # the sequence is computed at 1 Hz frequency self.rate.sleep() flag=True i+=i if flag is True: self._result.list_of_odoms.x = dist_x self._result.list_of_odoms.y = dist_y self._result.list_of_odoms.z = orient_z self._as.set_succeeded(self._result.list_of_odoms) rospy.loginfo("Returning odometry values") if __name__ == '__main__': rospy.init_node('Odom_record_node') move_turtle() rospy.spin()
错误含义解析
AttributeError: 'move_turtle' object has no attribute 'xOdom' 表示:move_turtle类的实例对象在执行Action回调时,尚未创建xOdom属性。原因是里程计订阅回调call_record_odom还未被触发(没有收到任何里程计数据),但Action的callback已经开始尝试访问self.xOdom。
问题根源与修复方案
1. 类属性未提前初始化
在__init__方法中提前初始化xOdom、yOdom、thOdom,避免未收到里程计数据时访问不存在的属性:
def __init__(self): # ... 原有代码 ... self.subodom = rospy.Subscriber('/odom', Odometry, self.call_record_odom) # 初始化里程计属性 self.xOdom = 0.0 self.yOdom = 0.0 self.thOdom = 0.0
2. 循环变量递增错误
原代码中i += i会导致i始终为0(初始值0,0+0=0),数组索引访问会出现逻辑错误,应改为i += 1。
3. 初始数组不合理
原代码中dist_x等数组初始值为[1.],会干扰实际里程数据的记录和距离计算,应初始化为空数组,在首次获取数据后再填充:
dist_x = np.array([], dtype=float) dist_y = np.array([], dtype=float) orient_z = np.array([], dtype=float)
4. 数据追加逻辑优化
如果需要每次传感器读数时自动追加数据,可以将dist_x等数组设为类属性,在里程计回调中直接追加;同时在Action启动时重置数组,避免多次执行Action时数据累积:
修改后的完整代码:
#! /usr/bin/env python from math import sqrt import rospy import actionlib import numpy as np from real_robot.msg import OdomRecordAction, OdomRecordResult, OdomRecordFeedback from nav_msgs.msg import Odometry from geometry_msgs.msg import Point32 from tf.transformations import euler_from_quaternion odom_record_data=Point32() class move_turtle(object): # create messages that are used to publish feedback and results _result = OdomRecordResult() _feedback = OdomRecordFeedback() def __init__(self): # creates the action server self._as = actionlib.SimpleActionServer("record_odom", OdomRecordAction, self.callback, False) self._as.start() self.ctrl_c = False self.rate = rospy.Rate(1) self.subodom = rospy.Subscriber('/odom', Odometry, self.call_record_odom) # 初始化里程计属性与记录数组 self.xOdom = 0.0 self.yOdom = 0.0 self.thOdom = 0.0 self.dist_x = np.array([], dtype=float) self.dist_y = np.array([], dtype=float) self.orient_z = np.array([], dtype=float) def call_record_odom(self,data): # 每次收到里程计数据时更新属性并追加到记录数组 self.xOdom = data.pose.pose.position.x self.yOdom = data.pose.pose.position.y self.thOdom = data.pose.pose.orientation.z self.dist_x = np.append(self.dist_x, self.xOdom) self.dist_y = np.append(self.dist_y, self.yOdom) self.orient_z = np.append(self.orient_z, self.thOdom) def callback(self,goal): rospy.loginfo("executing odometry record") distance = 0.0 lap=100 # 启动Action时重置记录数组,避免累积旧数据 self.dist_x = np.array([], dtype=float) self.dist_y = np.array([], dtype=float) self.orient_z = np.array([], dtype=float) # 等待首次里程计数据 while len(self.dist_x) == 0 and not rospy.is_shutdown(): rospy.loginfo("Waiting for first odom data...") self.rate.sleep() flag=False while distance<=lap and not rospy.is_shutdown(): # 计算累计行驶距离(至少有两个数据点才计算) if len(self.dist_x) >=2: last_idx = len(self.dist_x)-1 distance += sqrt( pow(self.dist_x[last_idx] - self.dist_x[last_idx-1], 2 ) + pow(self.dist_y[last_idx] - self.dist_y[last_idx-1], 2 ) ) print("travelled distance:", distance) print("recorded x data:", self.dist_x) # 检查是否被抢占 if self._as.is_preempt_requested(): rospy.loginfo('The goal has been cancelled/preempted') self._as.set_preempted() return # 发布反馈 self._feedback.current_total = distance self._as.publish_feedback(self._feedback) self.rate.sleep() flag=True if flag is True: self._result.list_of_odoms.x = self.dist_x self._result.list_of_odoms.y = self.dist_y self._result.list_of_odoms.z = self.orient_z self._as.set_succeeded(self._result) rospy.loginfo("Returning odometry values") if __name__ == '__main__': rospy.init_node('Odom_record_node') move_turtle() rospy.spin()
关键修改说明
- 类初始化时提前创建里程计属性和记录数组,避免属性不存在的错误。
- 里程计回调中直接追加数据,满足“每次传感器读数时追加”的需求。
- Action启动时重置记录数组,避免多次执行Action时数据混淆。
- 增加首次数据等待逻辑,确保计算距离时有有效数据。
- 修复循环索引错误,改用数组长度动态计算索引,避免手动维护索引的失误。
内容的提问来源于stack exchange,提问作者mikel lasa
相关产品推荐
相关产品推荐

