You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 21:35:18