ros2乌龟跟随仿真作业

我看我室友写了一个ros2的海龟跟随实验作业,是c++版本的,我学习了一下,借鉴了他的以及网上的一些教程,也写了一个python版本的海龟跟随功能包,但是我写代码的能力还是偏弱,最后代码相对比较冗长。

  1. 首先打开一个终端,在终端输入代码
ros2 run turtlesim turtlesim_node

可以正常运行turtlesim模拟环境。

  1. 创建工作空间,并进入
mkdir -p hw_ws/src
cd hw_ws
  1. 创建功能包,填入包名,类型选择ament_python,然后配好依赖
ros2 pkg create hw1 --build-type ament_python --dependencies rclpy tf2 tf2_ros tf2_geometry_msgs std_msgs os glob geometry_msgs turtlesim
  1. 编译文件夹并source一下,让系统能识别到这个功能包
colcon build --packages-select hw1
source install/setup.bash
  1. 下面编写功能包中的节点文件

乌龟的生成文件,命名乌龟为turtle2,因为启动turtlesim节点的时候就已经会产生一个turtle1乌龟了,文件名为turtle_spawner.py

import rclpy
from rclpy.node import Node
from turtlesim.srv import Spawn

class TurtleSpawner(Node):
    def __init__(self):
        super().__init__('turtle_spawner')
        self.spawn_turtles()

    def spawn_turtles(self):
        # 创建服务客户端
        client = self.create_client(Spawn, 'spawn')
        while not client.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('服务未准备好,正在等待...')

        # 第二个乌龟
        request2 = Spawn.Request()
        request2.x = 1.0
        request2.y = 1.0
        request2.theta = 0.0
        request2.name = 'turtle2'

        future2 = client.call_async(request2)

        rclpy.spin_until_future_complete(self, future2)

def main(args=None):
    rclpy.init(args=args)
    turtle_spawner = TurtleSpawner()
    turtle_spawner.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

订阅乌龟位置信息并发布,在终端上进行显示,文件名为turtle_position_publisher.py

import rclpy
from rclpy.node import Node
from turtlesim.msg import Pose
from std_msgs.msg import String

class TurtlePositionPublisher(Node):
    def __init__(self):
        super().__init__('turtle_position_publisher')
        
        # 创建订阅者,订阅乌龟的位置信息
        self.create_subscription(Pose, '/turtle1/pose', self.pose_callback, 10)

        # 创建发布者,发布位置信息
        self.position_publisher = self.create_publisher(String, '/turtle1/coordinates', 10)

    def pose_callback(self, msg):
        # 构建位置信息字符串
        position_info = f'Turtle1 Position -> x: {msg.x}, y: {msg.y}, theta: {msg.theta}'
        
        # 发布位置信息
        self.position_publisher.publish(String(data=position_info))
        self.get_logger().info(position_info)

def main(args=None):
    rclpy.init(args=args)
    node = TurtlePositionPublisher()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

将乌龟的坐标信息转变成坐标变换并订阅发布,在跟踪节点中方便订阅并跟随,文件名为turtle_transform_publisher.py

import rclpy
from rclpy.node import Node
from geometry_msgs.msg import TransformStamped
import tf_transformations
from tf2_ros import TransformBroadcaster
from turtlesim.msg import Pose

class TurtleTFBroadcaster(Node):

    def __init__(self, name):
        super().__init__(name)
        self.declare_parameter('turtlename', 'turtle')
        self.turtlename = self.get_parameter('turtlename').get_parameter_value().string_value
        self.tf_broadcaster = TransformBroadcaster(self)
        self.subscription = self.create_subscription(
            Pose,
            f'/{self.turtlename}/pose',
            self.turtle_pose_callback, 1)

    def turtle_pose_callback(self, msg):
        transform = TransformStamped()
        transform.header.stamp = self.get_clock().now().to_msg()
        transform.header.frame_id = 'world'
        transform.child_frame_id = self.turtlename
        transform.transform.translation.x = msg.x
        transform.transform.translation.y = msg.y
        transform.transform.translation.z = 0.0
        q = tf_transformations.quaternion_from_euler(0, 0, msg.theta)
        transform.transform.rotation.x = q[0]
        transform.transform.rotation.y = q[1]
        transform.transform.rotation.z = q[2]
        transform.transform.rotation.w = q[3]
        self.tf_broadcaster.sendTransform(transform)

def main(args=None):
    rclpy.init(args=args)
    node = TurtleTFBroadcaster("turtle_transform_publisher")
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

turtle2跟踪turtle1的跟随代码,这里用的是我网上看的比较多的tf跟随控制,就是把距离和角度差乘以一个系数就发布成控制指令,但是会让turtle2快一阵慢一阵的,放到真实情况实现不了,后面还有用PID和模糊控制方法控制的。
这里给turtle2发布速度指令,文件名为turtle_follower.py

import math
import rclpy
from rclpy.node import Node
import tf_transformations
from tf2_ros import TransformException
from tf2_ros.buffer import Buffer
from tf2_ros.transform_listener import TransformListener
from geometry_msgs.msg import Twist
from turtlesim.srv import Spawn

class TurtleFollowing(Node):

    def __init__(self, name):
        super().__init__(name)
        self.declare_parameter('source_frame', 'turtle1')
        self.source_frame = self.get_parameter('source_frame').get_parameter_value().string_value
        self.tf_buffer = Buffer()
        self.tf_listener = TransformListener(self.tf_buffer, self)
        self.spawner = self.create_client(Spawn, 'spawn')
        self.turtle_spawning_service_ready = False
        self.turtle_spawned = False
        self.publisher = self.create_publisher(Twist, 'turtle2/cmd_vel', 1)
        self.timer = self.create_timer(1.0, self.on_timer)

    def on_timer(self):
        from_frame_rel = self.source_frame
        to_frame_rel = 'turtle2'

        if self.turtle_spawning_service_ready:
            if self.turtle_spawned:
                try:
                    now = rclpy.time.Time()
                    trans = self.tf_buffer.lookup_transform(
                        to_frame_rel,
                        from_frame_rel,
                        now)
                except TransformException as ex:
                    self.get_logger().info(
                        f'Could not transform {to_frame_rel} to {from_frame_rel}: {ex}')
                    return

                msg = Twist()
                scale_rotation_rate = 1.0
                msg.angular.z = scale_rotation_rate * math.atan2(
                    trans.transform.translation.y,
                    trans.transform.translation.x)

                scale_forward_speed = 0.5
                msg.linear.x = scale_forward_speed * math.sqrt(
                    trans.transform.translation.x ** 2 +
                    trans.transform.translation.y ** 2)

                self.publisher.publish(msg)
            else:
                if self.result.done():
                    self.get_logger().info(
                        f'Successfully spawned {self.result.result().name}')
                    self.turtle_spawned = True
                else:
                    self.get_logger().info('Spawn is not finished')
        else:
            if self.spawner.service_is_ready():
                request = Spawn.Request()
                request.name = 'turtle2'
                request.x = float(4)
                request.y = float(2)
                request.theta = float(0)

                self.result = self.spawner.call_async(request)
                self.turtle_spawning_service_ready = True
            else:
                self.get_logger().info('Service is not ready')

def main(args=None):
    rclpy.init(args=args)
    node = TurtleFollowing("turtle_follower")
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

在src文件夹中新建一个launch文件夹,再编写launch文件,以此启动多个节点。文件名为follow.launch.py

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
     
        Node(
            package='turtlesim',
            executable='turtlesim_node',
            name='turtlesim',
            output='screen'
        ),
        Node(
            package='hw1',
            executable='turtle_spawner',
            name = 'turtle2_spawn'
        ),

        Node(
            package='hw1',
            executable='turtle_transform_publisher',
            name='publisher1',
            parameters=[
                {'turtlename': 'turtle1'}
            ]
        ),
        DeclareLaunchArgument(
            'target_frame', default_value='turtle1',
            description='Target frame name.'
        ),
        Node(
            package='hw1',
            executable='turtle_transform_publisher',
            name='publisher2',
            parameters=[
                {'turtlename': 'turtle2'}
            ]
        ),
        Node(
            package='hw1',
            executable='turtle_follower',
            name='follower1',
            parameters=[
                {'target_frame': LaunchConfiguration('target_frame')}
            ]
        ), 
    ])

setup.py文件进行修改,让系统识别节点名称对应的节点文件

from setuptools import find_packages, setup
import os
from glob import glob

package_name = 'hw1'

setup(
    name=package_name,
    version='0.0.0',
    packages=find_packages(exclude=['test']),
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*.launch.py'))),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='cf',
    maintainer_email='cf@todo.todo',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
             'turtle_position_publisher = hw1.turtle_position_publisher:main',
             'turtle_follower = hw1.turtle_follower:main',
             'turtle_transform_publisher = hw1.turtle_transform_publisher:main',
             'turtle_spawner = hw1.turtle_spawner:main',           
        ],
    },
)

检查一遍package.xml文件中的依赖是否缺少

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>hw1</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="cf@todo.todo">cf</maintainer>
  <license>TODO: License declaration</license>

  <depend>rclpy</depend>
  <depend>tf2</depend>
  <depend>tf2_ros</depend>
  <depend>tf2_geometry_msgs</depend>
  <depend>std_msgs</depend>
  <depend>geometry_msgs</depend>
  <depend>turtlesim</depend>
  <depend>pynput</depend>

  <test_depend>ament_copyright</test_depend>
  <test_depend>ament_flake8</test_depend>
  <test_depend>ament_pep257</test_depend>
  <test_depend>python3-pytest</test_depend>

  <export>
    <build_type>ament_python</build_type>
  </export>
</package>
  1. 一切就绪就可以对功能包进行编译运行了,打开一个终端输入
cd hw_ws #进入工作空间
colcon build --packages-select hw1 #编译功能包
source install/setup.bash #source一下
  1. 运行launch文件
ros2 launch hw1 follow.launch.py 

然后就能看见turtle2跟踪到turtle1

在这里插入图片描述

这时候我们再打开一个终端,输入

ros2 run turtlesim turtle_teleop_key 

就可以控制小乌龟turtle1的运动,使用方向键盘操纵即可,可以看见turtle2依然在跟踪turtle1

  1. PID控制实现

修改turtle_follower.py文件即可,代码如下,这里由于系统本身能够在稳态时保持较小的误差,不需要积分环节来消除稳态误差,所以没有加入ki。

import math
import rclpy
from rclpy.node import Node
import tf_transformations
from tf2_ros import TransformException
from tf2_ros.buffer import Buffer
from tf2_ros.transform_listener import TransformListener
from geometry_msgs.msg import Twist
from turtlesim.srv import Spawn

class TurtleFollowing(Node):

    def __init__(self, name):
        super().__init__(name)
        self.declare_parameter('source_frame', 'turtle1')
        self.source_frame = self.get_parameter('source_frame').get_parameter_value().string_value
        self.tf_buffer = Buffer()
        self.tf_listener = TransformListener(self.tf_buffer, self)
        self.spawner = self.create_client(Spawn, 'spawn')
        self.turtle_spawning_service_ready = False
        self.turtle_spawned = False
        self.publisher = self.create_publisher(Twist, 'turtle2/cmd_vel', 1)
        self.timer = self.create_timer(1.0, self.on_timer)
        self.kp_linear = 0.5
        self.kd_linear = 0.1
        self.kp_angular = 1.0
        self.kd_angular = 0.1
        self.prev_distance = 0.0
        self.prev_angle = 0.0

    def on_timer(self):
        from_frame_rel = self.source_frame
        to_frame_rel = 'turtle2'

        if self.turtle_spawning_service_ready:
            if self.turtle_spawned:
                try:
                    now = rclpy.time.Time()
                    trans = self.tf_buffer.lookup_transform(
                        to_frame_rel,
                        from_frame_rel,
                        now)
                except TransformException as ex:
                    self.get_logger().info(
                        f'Could not transform {to_frame_rel} to {from_frame_rel}: {ex}')
                    return

                msg = Twist()
                distance = math.sqrt(trans.transform.translation.x ** 2 +
                                     trans.transform.translation.y ** 2)
                angle = math.atan2(trans.transform.translation.y, trans.transform.translation.x)

                linear_speed = self.kp_linear * distance + self.kd_linear * (distance - self.prev_distance)
                angular_speed = self.kp_angular * angle + self.kd_angular * (angle - self.prev_angle)

                msg.linear.x = max(0.0, linear_speed)
                msg.angular.z = angular_speed

                self.publisher.publish(msg)
                self.prev_distance = distance
                self.prev_angle = angle
            else:
                if self.result.done():
                    self.get_logger().info(
                        f'Successfully spawned {self.result.result().name}')
                    self.turtle_spawned = True
                else:
                    self.get_logger().info('Spawn is not finished')
        else:
            if self.spawner.service_is_ready():
                request = Spawn.Request()
                request.name = 'turtle2'
                request.x = float(4)
                request.y = float(2)
                request.theta = float(0)

                self.result = self.spawner.call_async(request)
                self.turtle_spawning_service_ready = True
            else:
                self.get_logger().info('Service is not ready')

def main(args=None):
    rclpy.init(args=args)
    node = TurtleFollowing("turtle_follower")
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

结果如下:
在这里插入图片描述

  1. 模糊控制实现

将修改turtle_follower.py文件即可,代码如下

import math
import rclpy
from rclpy.node import Node
import tf_transformations
from tf2_ros import TransformException
from tf2_ros.buffer import Buffer
from tf2_ros.transform_listener import TransformListener
from geometry_msgs.msg import Twist
from turtlesim.srv import Spawn

class FuzzyController:
    def __init__(self):
        pass

    def fuzzy_control(self, distance, angle):
        linear_speed = 0.0
        angular_speed = 0.0

        if distance < 0.5:
            linear_speed = 0.0
        elif distance < 1.0:
            linear_speed = 0.3
        elif distance < 2.0:
            linear_speed = 0.5
        else:
            linear_speed = 1.0

        if angle < -0.3:
            angular_speed = -1.0
        elif angle < 0.0:
            angular_speed = -0.5
        elif angle > 0.3:
            angular_speed = 1.0
        elif angle > 0.0:
            angular_speed = 0.5
        else:
            angular_speed = 0.0

        return linear_speed, angular_speed

class TurtleFollowing(Node):

    def __init__(self, name):
        super().__init__(name)
        self.declare_parameter('source_frame', 'turtle1')
        self.source_frame = self.get_parameter('source_frame').get_parameter_value().string_value
        self.tf_buffer = Buffer()
        self.tf_listener = TransformListener(self.tf_buffer, self)
        self.spawner = self.create_client(Spawn, 'spawn')
        self.turtle_spawning_service_ready = False
        self.turtle_spawned = False
        self.publisher = self.create_publisher(Twist, 'turtle2/cmd_vel', 1)
        self.timer = self.create_timer(0.1, self.on_timer)  # Increase frequency
        self.fuzzy_controller = FuzzyController()

    def on_timer(self):
        from_frame_rel = self.source_frame
        to_frame_rel = 'turtle2'

        if self.turtle_spawning_service_ready:
            if self.turtle_spawned:
                try:
                    now = rclpy.time.Time()
                    trans = self.tf_buffer.lookup_transform(
                        to_frame_rel,
                        from_frame_rel,
                        now)
                except TransformException as ex:
                    self.get_logger().info(
                        f'Could not transform {to_frame_rel} to {from_frame_rel}: {ex}')
                    return

                msg = Twist()
                distance = math.sqrt(trans.transform.translation.x ** 2 +
                                     trans.transform.translation.y ** 2)
                angle = math.atan2(trans.transform.translation.y, trans.transform.translation.x)

                linear_speed, angular_speed = self.fuzzy_controller.fuzzy_control(distance, angle)

                msg.linear.x = max(0.0, linear_speed)
                msg.angular.z = angular_speed

                self.publisher.publish(msg)
            else:
                if self.result.done():
                    self.get_logger().info(
                        f'Successfully spawned {self.result.result().name}')
                    self.turtle_spawned = True
                else:
                    self.get_logger().info('Spawn is not finished')
        else:
            if self.spawner.service_is_ready():
                request = Spawn.Request()
                request.name = 'turtle2'
                request.x = float(4)
                request.y = float(2)
                request.theta = float(0)

                self.result = self.spawner.call_async(request)
                self.turtle_spawning_service_ready = True
            else:
                self.get_logger().info('Service is not ready')

def main(args=None):
    rclpy.init(args=args)
    node = TurtleFollowing("turtle_follower")
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

结果如下:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值