3、创建第一个节点(Python)
创建你的第一个节点(Python)
本教程将介绍创建ROS2包和用Python编写ROS2节点的步骤。有关C++示例,请单击此处 here。这些步骤类似于ROS 2 Tutorial,但侧重于与TurtleBot 4的交互。有关源代码,请单击此处here。
您可以在TurtleBot 4的Raspberry Pi或您的电脑上遵循本教程。
创建一个TB4的文件夹
如果您还没有工作空间,请打开一个终端并在您选择的目录中创建一个:
mkdir ~/turtlebot4_ws/src -p
用python创建包和节点
您需要创建一个ROS 2包来保存您的文件。在本教程中,我们将创建一个名为
turtlebot4_python_tutorials
的包,其中包含一个称为turtlebot4_first_python_node
的节点。source /opt/ros/humble/setup.bash cd ~/turtlebot4_ws/src ros2 pkg create --build-type ament_python --node-name turtlebot4_first_python_node turtlebot4_python_tutorials
这将创建一个 turtlebot4_python_tutorials
文件夹,并用一个基本的“Hello World”节点以及ROS 2 python包所需的setup和package.xml文件填充它
使用python编写您的节点
下一步是开始编码。在本教程中,我们的目标是使用Create®3界面按钮1更改Create®3光环的颜色。在您最喜欢的文本编辑器中打开“Hello World”.py
文件,该文件位于~/turtlebot4_ws/src/turtlebot4_python_tutorials/turtlebot4_python_tutorials/turtlebot4_first_python_node.py
。
添加您的依赖项
对于本教程,我们需要使用 rclpy
和 irobot_create_msgs
包。rclpy
包允许我们创建ROS2节点,并使我们能够完全访问Python中的所有基本ROS2功能。 irobot_create_msgs
包使我们能够访问create®3用于读取按钮按压和控制灯环的自定义消息。
在package.xml中,在 <buildtool_depend>ament_cmake</buildtool_depend>
下添加以下行:
<depend>rclpy</depend> <depend>irobot_create_msgs</depend>
在 .py
文件中,导入以下包:
from irobot_create_msgs.msg import InterfaceButtons, LightringLeds import rclpy from rclpy.node import Node from rclpy.qos import qos_profile_sensor_data
创建一个类
既然已经设置了依赖项,我们就可以创建一个从rclpy.Node
类继承的类。我们将这个类称为 TurtleBot4FirstNode
。
class TurtleBot4FirstNode(Node): def __init__(self): super().__init__('turtlebot4_first_python_node')
请注意,我们的类调用 super()
构造函数,并将节点的名称turtlebot4_first_python_node
传递给它。
我们现在可以在 main
函数中创建我们的节点并旋转它。由于我们的节点是空的,所以节点会被创建,但它不会做任何事情。
def main(args=None): rclpy.init(args=args) node = TurtleBot4FirstNode() rclpy.spin(node) node.destroy_node() rclpy.shutdown()
订阅Create®3界面按钮
我们的下一步是订阅Create®3界面按钮主题以接收按钮按压。
我们需要为订阅创建一个 rclpy.Subscription
以及一个回调函数。每次我们收到关于界面按钮主题的消息时,都会调用回调函数。
class TurtleBot4FirstNode(Node): lights_on_ = False def __init__(self): super().__init__('turtlebot4_first_python_node') # Subscribe to the /interface_buttons topic self.interface_buttons_subscriber = self.create_subscription( InterfaceButtons, '/interface_buttons', self.interface_buttons_callback, qos_profile_sensor_data) # Interface buttons subscription callback def interface_buttons_callback(self, create3_buttons_msg: InterfaceButtons):
请注意, interface_buttons_subscriber
使用 InterfaceButtons消息类型,服务质量为 qos_profile_sensor_data
。这些参数必须与主题匹配,否则订阅将失败。如果您不确定主题使用的消息类型或QoS,可以使用ROS 2 CLI查找此信息。
调用 ros2 topic info /<topic> --verbose
以获取完整的详细信息。
Test Create® 3 按钮 1
现在我们已经订阅了,让我们通过每次按下按钮1时打印一条消息来测试我们的节点。
编辑 interface_buttons_callback
函数如下所示:
# Interface buttons subscription callback def interface_buttons_callback(self, create3_buttons_msg: InterfaceButtons): # Button 1 is pressed if create3_buttons_msg.button_1.is_pressed: self.get_logger().info('Button 1 Pressed!')
现在,每当我们收到关于 /interface_buttons
主题的消息时,我们都会检查按钮1是否被按下,如果是,则节点将打印一条消息。
To test this out, we will need to build our package using colcon
:为了测试这一点,我们需要使用 colcon
构建我们的包:
cd ~/turtlebot4_ws colcon build --symlink-install --packages-select turtlebot4_python_tutorials source install/local_setup.bash
--symlink-install
允许我们安装到Python脚本的符号链接,而不是脚本的副本。这意味着我们对脚本所做的任何更改都将应用于已安装的脚本,因此我们不需要在每次更改后重新构建包。
--packages-select
标志允许您输入要构建的任意数量的包,以防您不想在工作区中构建所有包。
现在,尝试运行节点:
ros2 run turtlebot4_python_tutorials turtlebot4_first_python_node
当你运行它时,除非你按下TurtleBot 4上的按钮1,否则什么都不会发生。
按下按钮,您应该会在终端中看到以下消息:
[INFO] [1652384338.145094927] [turtlebot4_first_python_node]: Button 1 Pressed!
像这样打印消息是调试代码的好方法。
创建光环发布者
现在我们可以接收按钮按下,让我们创建一个lightring发布器。
class TurtleBot4FirstNode(Node): def __init__(self): super().__init__('turtlebot4_first_python_node') # Subscribe to the /interface_buttons topic self.interface_buttons_subscriber = self.create_subscription( InterfaceButtons, '/interface_buttons', self.interface_buttons_callback, qos_profile_sensor_data) # Create a publisher for the /cmd_lightring topic self.lightring_publisher = self.create_publisher( LightringLeds, '/cmd_lightring', qos_profile_sensor_data)
Lightring发布者使用LightringLeds消息类型。
接下来,让我们创建一个函数来填充 LightringLeds
消息并发布它。
在您的 interface_buttons_callback
函数下面添加以下代码:
def button_1_function(self): # Create a ROS 2 message lightring_msg = LightringLeds() # Stamp the message with the current time lightring_msg.header.stamp = self.get_clock().now().to_msg() # Override system lights lightring_msg.override_system = True # LED 0 lightring_msg.leds[0].red = 255 lightring_msg.leds[0].blue = 0 lightring_msg.leds[0].green = 0 # LED 1 lightring_msg.leds[1].red = 0 lightring_msg.leds[1].blue = 255 lightring_msg.leds[1].green = 0 # LED 2 lightring_msg.leds[2].red = 0 lightring_msg.leds[2].blue = 0 lightring_msg.leds[2].green = 255 # LED 3 lightring_msg.leds[3].red = 255 lightring_msg.leds[3].blue = 255 lightring_msg.leds[3].green = 0 # LED 4 lightring_msg.leds[4].red = 255 lightring_msg.leds[4].blue = 0 lightring_msg.leds[4].green = 255 # LED 5 lightring_msg.leds[5].red = 0 lightring_msg.leds[5].blue = 255 lightring_msg.leds[5].green = 255 # Publish the message self.lightring_publisher.publish(lightring_msg)
此函数创建 LightringLeds
消息并填充参数。
我们首先在消息上标记当前时间:
lightring_msg.header.stamp = self.get_clock().now().to_msg()
然后,我们将 override_system
参数设置为 True
,以便我们的命令覆盖Create®3发送到光环的任何命令。
lightring_msg.override_system = True
接下来,我们用我们想要的任何颜色填充 leds
阵列中的6个LED。
# LED 0 lightring_msg.leds[0].red = 255 lightring_msg.leds[0].blue = 0 lightring_msg.leds[0].green = 0 # LED 1 lightring_msg.leds[1].red = 0 lightring_msg.leds[1].blue = 255 lightring_msg.leds[1].green = 0 # LED 2 lightring_msg.leds[2].red = 0 lightring_msg.leds[2].blue = 0 lightring_msg.leds[2].green = 255 # LED 3 lightring_msg.leds[3].red = 255 lightring_msg.leds[3].blue = 255 lightring_msg.leds[3].green = 0 # LED 4 lightring_msg.leds[4].red = 255 lightring_msg.leds[4].blue = 0 lightring_msg.leds[4].green = 255 # LED 5 lightring_msg.leds[5].red = 0 lightring_msg.leds[5].blue = 255 lightring_msg.leds[5].green = 255
每个RGB值可以设置在0和255之间。您可以查找任何颜色的RGB值并在此处进行设置。
最后,我们发布消息。
self.lightring_publisher.publish(lightring_msg)
按下按钮即可发布照明环命令
现在我们可以将我们的界面按钮订阅连接到我们的lightring发行商。只需在 interface_buttons_callback
中调用button_1_function
即可。
# Interface buttons subscription callback def interface_buttons_callback(self, create3_buttons_msg: InterfaceButtons): # Button 1 is pressed if create3_buttons_msg.button_1.is_pressed: self.get_logger().info('Button 1 Pressed!') self.button_1_function()
通过像以前一样运行节点来测试这一点。
按下按钮1,指示灯应如下所示:
切换照明环
你会注意到,一旦你设置了发光二极管,它们将永远保持原样。让我们每次按下按钮时都切换灯的打开或关闭。
添加布尔值以跟踪灯光状态:
class TurtleBot4FirstNode(Node): lights_on_ = False def __init__(self):
并修改 button_1_function
以切换灯光:
# Perform a function when Button 1 is pressed def button_1_function(self): # Create a ROS 2 message lightring_msg = LightringLeds() # Stamp the message with the current time lightring_msg.header.stamp = self.get_clock().now().to_msg() # Lights are currently off if not self.lights_on_: # Override system lights lightring_msg.override_system = True # LED 0 lightring_msg.leds[0].red = 255 lightring_msg.leds[0].blue = 0 lightring_msg.leds[0].green = 0 # LED 1 lightring_msg.leds[1].red = 0 lightring_msg.leds[1].blue = 255 lightring_msg.leds[1].green = 0 # LED 2 lightring_msg.leds[2].red = 0 lightring_msg.leds[2].blue = 0 lightring_msg.leds[2].green = 255 # LED 3 lightring_msg.leds[3].red = 255 lightring_msg.leds[3].blue = 255 lightring_msg.leds[3].green = 0 # LED 4 lightring_msg.leds[4].red = 255 lightring_msg.leds[4].blue = 0 lightring_msg.leds[4].green = 255 # LED 5 lightring_msg.leds[5].red = 0 lightring_msg.leds[5].blue = 255 lightring_msg.leds[5].green = 255 # Lights are currently on else: # Disable system override. The system will take back control of the lightring. lightring_msg.override_system = False # Publish the message self.lightring_publisher.publish(lightring_msg) # Toggle the lights on status self.lights_on_ = not self.lights_on_
现在,如果我们再次按下按钮1,Create®3将重新控制光环。
您的第一个Python节点
您已经完成了第一个Python节点的编写!最后的 .py
文件应该如下所示:
from irobot_create_msgs.msg import InterfaceButtons, LightringLeds import rclpy from rclpy.node import Node from rclpy.qos import qos_profile_sensor_data class TurtleBot4FirstNode(Node): lights_on_ = False def __init__(self): super().__init__('turtlebot4_first_python_node') # Subscribe to the /interface_buttons topic self.interface_buttons_subscriber = self.create_subscription( InterfaceButtons, '/interface_buttons', self.interface_buttons_callback, qos_profile_sensor_data) # Create a publisher for the /cmd_lightring topic self.lightring_publisher = self.create_publisher( LightringLeds, '/cmd_lightring', qos_profile_sensor_data) # Interface buttons subscription callback def interface_buttons_callback(self, create3_buttons_msg: InterfaceButtons): # Button 1 is pressed if create3_buttons_msg.button_1.is_pressed: self.get_logger().info('Button 1 Pressed!') self.button_1_function() # Perform a function when Button 1 is pressed def button_1_function(self): # Create a ROS 2 message lightring_msg = LightringLeds() # Stamp the message with the current time lightring_msg.header.stamp = self.get_clock().now().to_msg() # Lights are currently off if not self.lights_on_: # Override system lights lightring_msg.override_system = True # LED 0 lightring_msg.leds[0].red = 255 lightring_msg.leds[0].blue = 0 lightring_msg.leds[0].green = 0 # LED 1 lightring_msg.leds[1].red = 0 lightring_msg.leds[1].blue = 255 lightring_msg.leds[1].green = 0 # LED 2 lightring_msg.leds[2].red = 0 lightring_msg.leds[2].blue = 0 lightring_msg.leds[2].green = 255 # LED 3 lightring_msg.leds[3].red = 255 lightring_msg.leds[3].blue = 255 lightring_msg.leds[3].green = 0 # LED 4 lightring_msg.leds[4].red = 255 lightring_msg.leds[4].blue = 0 lightring_msg.leds[4].green = 255 # LED 5 lightring_msg.leds[5].red = 0 lightring_msg.leds[5].blue = 255 lightring_msg.leds[5].green = 255 # Lights are currently on else: # Disable system override. The system will take back control of the lightring. lightring_msg.override_system = False # Publish the message self.lightring_publisher.publish(lightring_msg) # Toggle the lights on status self.lights_on_ = not self.lights_on_ def main(args=None): rclpy.init(args=args) node = TurtleBot4FirstNode() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()