ROS简介

参考教程:cn - ROS Wiki

2024-04-01-112959.png

ros下编译一个hello world程序

创建工作空间,在终端上创建一个工程文件夹名为hello_ws,并在src目录下创建一个ros包

1
2
3
4
5
mkdir -p hello_ws/src
cd hello_ws
catkin_make
cd /src
catkin_create_pkg hello_world roscpp std_msgs

roscpp和std_msgs是hello_world的依赖项。

进入hello_world包的目录,并创建一个名为hello.cpp`的C++源文件:

1
2
cd ~/hello_ws/src/hello_world/src
vi hello.cpp

编辑hello.cpp文件,并添加以下内容:

1
2
3
4
5
6
7
8
9
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <stdio.h>

int main(int argc, char **argv)
{
printf("hello world\n");
return 0;
}

现在,将这包中的源文件添加到hello_world包中的CMakeLists.txt中:

1
2
3
add_executable(hello_node src/hello.cpp)

target_link_libraries(hello_node ${catkin_LIBRARIES})

最后,编译ROS包:

1
2
3
cd ~/hello_ws
catkin_make #编译
source devel/setup.bash #构建环境

在另一个终端中启动ROS核心:

1
roscore #主节点

在之前的终端中运行发布者节点:

1
rosrun hello_world hello_node

现在,就可以在ros环境下打印出hello world。

以下是一个工作空间的正常目录

屏幕截图 2024-04-01 154108.png

话题和服务的区别

屏幕截图 2024-04-01 152042.png

服务

在ROS中,服务(Service)用于实现节点之间的请求-响应通信模式。服务允许一个节点向另一个节点发送请求,并等待该节点的响应。服务通常用于执行一些计算密集型或耗时的任务,或者获取节点的状态信息。

在ROS中创建并使用服务(Service)

创建服务文件:

在ROS软件包中创建srv目录,并在其中创建服务文件。服务文件使用.srv扩展名。例如,创建一个名为MyService.srv的服务文件。

1
2
mkdir srv
vi MyService.srv

定义服务消息结构

  1. 打开您服务文件MyService.srv,并在其中定义服务消息结构。服务消息结构由请求和响应组成。请求和响应。这两部分用一条---线隔开,例如:
1
2
3
4
int64 a
int64 b
---
int64 sum

编辑**CMakeLists.txt**文件

  1. 在您的ROS软件包目录中,打开CMakeLists.txt文件,并确保它包含了正确的服务生成器指令。将以下行添加到CMakeLists.txt文件中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
find_package(catkin REQUIRED COMPONENTS
rospy
std_msgs
message_generation
)

add_service_files(
FILES
MyService.srv
)

generate_messages(
DEPENDENCIES
std_msgs
)

构建您的软件包

  1. 返回到您的工作空间目录,并运行catkin_make来构建您的软件包。这将编译并生成您的服务代码。
1
2
cd ~/catkin_ws
catkin_make

使用服务

  1. 在您的ROS节点中,您可以使用rospyroscpp等库来编写服务的客户端和服务器端。首先,确保您的节点依赖于生成的消息包。然后,在您的节点代码中导入所需的服务消息,并编写客户端和服务器端的逻辑。

服务器端

创建一个ROS节点来处理服务请求,

  • 客户端

在另一个节点中,创建一个服务客户端来发送请求,并处理响应。

ros下创建服务端和客户端

以下为写两个数相加的客服端和服务端代码

创建一个软件包

1
catkin_create_pkg  beginner_tutorials std_msgs rospy roscpp

同理在软件包下面创建srv文件夹,里面写srv的文件,命名AddTwoInts.srv

1
2
3
4
int64 a
int64 b
---
int64 sum

在src文件夹下编写服务器端文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 服务端代码 server.cpp

#include "ros/ros.h"
#include "std_msgs/String.h"
#include "beginner_tutorials/AddTwoInts.h"

bool add(beginner_tutorials::AddTwoInts::Request &req,
beginner_tutorials::AddTwoInts::Response &res)
{
res.sum = req.a + req.b;
ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
ROS_INFO("sending back response: [%ld]", (long int)res.sum);
return true;
}

int main(int argc, char **argv)
{
ros::init(argc, argv, "add_two_ints_server");
ros::NodeHandle n;

ros::ServiceServer service = n.advertiseService("add_two_ints", add);
ROS_INFO("Ready to add two ints.");
ros::spin();

return 0;
}

客服端文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 客户端代码 client.cpp

#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"

int main(int argc, char **argv)
{
ros::init(argc, argv, "add_two_ints_client");
if (argc != 3)
{
ROS_INFO("usage: add_two_ints_client X Y");
return 1;
}

ros::NodeHandle n;
ros::ServiceClient client = n.serviceClient<beginner_tutorials::AddTwoInts>("add_two_ints");
beginner_tutorials::AddTwoInts srv;
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
if (client.call(srv))
{
ROS_INFO("Sum: %ld", (long int)srv.response.sum);
}
else
{
ROS_ERROR("Failed to call service add_two_ints");
return 1;
}

return 0;
}

之后修改CMakeLists.txt文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
cmake_minimum_required(VERSION 2.8.3)
project(beginner_tutorials)

find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)

add_service_files(
FILES
AddTwoInts.srv
)

generate_messages(
DEPENDENCIES
std_msgs
)

catkin_package(
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
)

include_directories(
${catkin_INCLUDE_DIRS}
)

add_executable(add_two_ints_server src/server.cpp)
target_link_libraries(add_two_ints_server ${catkin_LIBRARIES})
add_dependencies(add_two_ints_server ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

add_executable(add_two_ints_client src/client.cpp)
target_link_libraries(add_two_ints_client ${catkin_LIBRARIES})
add_dependencies(add_two_ints_client ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

话题

ros下如何创建消息

在刚在hello_world的软件包下,创建一个名为msg的文件夹,用于存放自定义消息文件。

1
mkdir msg

创建一个名为MyMessage.msg的文件,并在其中定义消息结构。例如:

1
2
string data
int32 value

在软件包目录中,打开CMakeLists.txt文件将以下行添加到CMakeLists.txt文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
message_generation
)

add_message_files(
FILES
MyMessage.msg
)

generate_messages(
DEPENDENCIES
std_msgs
)

之后退出编译后,生成的.h文件会出现在devel/include目录下。

ROS中订阅节点和发布节点

ros节点间进行通信的主要机制是发送和接收消息,不同的消息分门别类的组织起来,组织结构被成为话题,节点可以将消息发布到特定的话题,也可以订阅话题从而获取接收消息。

节点是执行某些动作的进程,节点发布消息,并由其他节点接收消息,这些发布的消息,在ros中称为话题。

以下是如何使用C++编写一个ROS程序,其中包括一个发布者节点和一个订阅者节点,用于发送和接收”Hello, World!”消息。

首先,确保您已经创建了一个ROS工作空间(如果没有,请先创建)。

创建一个名为catkin_ws的工程文件夹以及一个hello_worldROS包:

1
2
3
mkdir -p catkin_ws/src
cd ~/catkin_ws/src
catkin_create_pkg hello_world roscpp std_msgs

进入hello_world包下的src目录,并创建一个名为hello_publisher.cpp的C++源文件:

1
2
cd ~/catkin_ws/src/hello_world/src
vi hello_publisher.cpp

编辑hello_publisher.cpp文件,并添加以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include "ros/ros.h"
#include "std_msgs/String.h"

int main(int argc, char **argv)
{
// 初始化ROS节点
ros::init(argc, argv, "hello_publisher");

// 创建节点句柄
ros::NodeHandle nh;

// 创建一个Publisher,发布名为 "hello_topic" 的String消息
ros::Publisher pub = nh.advertise<std_msgs::String>("hello_topic", 10);

// 设置发布频率为1Hz
ros::Rate rate(1);

while (ros::ok())
{
// 创建一个String类型的消息
std_msgs::String msg;
msg.data = "Hello, World!";

// 发布消息
pub.publish(msg);

// 打印发布的消息
ROS_INFO("Publishing: %s", msg.data.c_str());

// 按照指定的频率休眠
rate.sleep();
}

return 0;
}

创建一个名为hello_subscriber.cpp的C++源文件:

1
vi hello_subscriber.cpp

编辑hello_subscriber.cpp文件,并添加以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include "ros/ros.h"
#include "std_msgs/String.h"

// 回调函数,用于处理收到的消息
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("Received: [%s]", msg->data.c_str());
}

int main(int argc, char **argv)
{
// 初始化ROS节点
ros::init(argc, argv, "hello_subscriber");

// 创建节点句柄
ros::NodeHandle nh;

// 创建一个Subscriber,订阅名为 "hello_topic" 的String消息,当有消息到来时,调用回调函数chatterCallback
ros::Subscriber sub = nh.subscribe("hello_topic", 10, chatterCallback);

// 进入事件循环
ros::spin();

return 0;
}

将这两个源文件添加包中的CMakeLists.txt中:

1
2
3
4
5
add_executable(hello_publisher src/hello_publisher.cpp)
add_executable(hello_subscriber src/hello_subscriber.cpp)

target_link_libraries(hello_publisher ${catkin_LIBRARIES})
target_link_libraries(hello_subscriber ${catkin_LIBRARIES})

最后,编译ROS包:

1
2
cd ~/catkin_ws
catkin_make

在一个终端中启动ROS核心:

1
roscore

在另一个终端中运行发布者节点:

1
2
source devel/setup.bash
rosrun hello_world_cpp hello_publisher

在第三个终端中运行订阅者节点:

1
2
source devel/setup.bash
rosrun hello_world_cpp hello_subscriber

现在,您应该能够在订阅者节点的终端中看到打印出的消息:”Received: Hello, World!”。这样就完成了一个简单的ROS节点的创建和通信。

123.png

ROS下多个节点执行

假如我的文件构造结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
workspace/
|-- src/
| |-- package1/
| | |-- CMakeLists.txt
| | |-- package.xml
| | |-- src/
| | |-- node1.cpp
| | |-- node2.cpp
| |-- package2/
| |-- CMakeLists.txt
| |-- package.xml
| |-- src/
| |-- node3.cpp
| |-- node4.cpp
|-- launch/
| |-- all_nodes.launch

将多个包的多个节点放入到all_nodes.launch文件中。

1
2
3
4
5
6
7
8
9
<launch>
<!-- 启动 package1 中的节点 -->
<node pkg="package1" type="node1" name="node1" output="screen"/>
<node pkg="package1" type="node2" name="node2" output="screen"/>

<!-- 启动 package2 中的节点 -->
<node pkg="package2" type="node3" name="node3" output="screen"/>
<node pkg="package2" type="node4" name="node4" output="screen"/>
</launch>

之后在工作空间的目录下进行编译后执行。

1
2
3
source devel/setup.bash
catkin_make
roslaunch launch/my_launch_file.launch