ROS具有一套自己的工作流程,其文件体系依托在工作空间中,开发的流程如下图所示。
如果是 python 代码则不需要配置编译规则。
工作空间
工作空间(Workspace):存放工程开发相关文件的文件夹。类似一个IDE(例如Pycharm)新建一个工程,就是一个工作空间。包含4个文件夹:
- src:代码空间(Source Space):放置功能包代码
- build:编译空间(Build Space):编译过程中产生的中间文件,不用过多关注
- devel:开发空间(Development Space):放置编译生成的可执行文件、库、脚本
- install:安装空间(Install Space):存放可执行文件,与上面有一定重复
创建工作空间
#创建工作空间指令
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_init_workspace
我们先创建一个src文件夹,然后进入该文件夹
第3行catkin_init_workspace
意为将当前文件夹变为工作空间,使其拥有工作空间的属性
注:“~/”意为当前用户名目录;“-p”意为递归创建目录,即直接创建多级目录。
src文件夹即代码空间,不能自行用别的名称代替
#编译空的工作空间
cd ~/catkin_ws/
catkin_make #将src里的源码进行了编译
#生成了build和devel两个新文件夹,devel存放了编译完成的内容
#设置环境变量
source devel/setup.bash
#如果要生成install文件夹
catkin_make install
为了不每次打开终端都设置环境变量(即都执行一遍 $ source devel/setup.bash
),可以把这句话添加到系统环境变量.bashrc
中。
- 功能包是放置ROS源码的最小单元。
1, package文件夹为功能包,也就是我们开发ROS工程主要编辑的地方
2, package文件夹下package.xml文件主要是描述整个功能包的属性。
3, package文件夹下CMakeLists.txt文件主要作用是描述整个功能包的编译规则,依赖库等等。
4, package文件夹下的src文件夹为功能包源文件所在。
5, package文件夹下的include文件夹为功能包源文件的头文件所在。
6, package文件夹下的script文件夹为python脚本和bash脚本所在,即无需编译即可运行的文件。
7, package文件夹下的launch文件夹为launch文件所在
8, package文件夹下的msg文件夹为msg文件所在
9, package文件夹下的srv文件夹为srv文件所在
9, package文件夹下的action文件夹为action文件所在
- 上面我们创建了一个空的工作空间,src文件夹里面没写东西,现在我们创建一个自己的功能包。 注意同一工作空间下,不允许存在同名功能包;不同工作空间下,允许存在同名功能包。
指令格式:catkin_create_pkg <package_name> [depend1] [depend2] [depend3]
<package_name>
为包名[depend]
为依赖,即指明编译的时候需要ROS中的其他功能包,如需要调用python、C++库,就要指明rospy、roscpp。cd ~/catkin_ws/src # 创建一个名叫learning_communication的功能包 # 这个功能包实现的接口有python接口,C++接口,标准定义的消息与服务接口 catkin_create_pkg learning_communication rospy roscpp std_msgs std_srvs # 编译功能包 cd ~/catkin_ws catkin_make source ~/catkin_ws/devel/setup.bash
Topic 通信机制
发布者Publisher
- 初始化ROS节点
- 向ROS Master注册节点信息,包括发布的话题名和话题中的消息类型
- 创建消息数据
- 按照一定的频率循环发布消息
这个cpp文件创建在src文件夹下,有了源码之后需要在CMakeLists.txt中添加编译规则:/** * 该例程将发布chatter话题,消息类型String */ // 字符串的流的数据转换 #include <sstream> // 包含ros头文件,一般都会写 #include "ros/ros.h" // 后面会用到ROS中string的消息类型 #include "std_msgs/String.h" int main(int argc, char **argv) { // ROS节点初始化,声明节点名,这个名字不能和其他节点重名 ros::init(argc, argv, "string_publisher"); // 创建节点句柄,完成ROS核心资源的管理。 // 在节点初始化和关闭一节中,使用句柄管理节点的内部引用,使启动和关闭一个节点变得简单。 ros::NodeHandle n; // 创建一个Publisher,发布名为chatter的topic,消息类型为std_msgs::String。 // 在ros::Publisher类下实例化一个chatter_pub对象。 // 1000表示缓冲区大小,超过1000会把时间戳最老的删除掉。 // advertise( ) 返回一个 Publisher 对象。 通过调用对象的 publish( )函数可以在这个topic上发布 message。 ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000); // 设置循环的频率 // loop_rate()是ros::Rate类可定制频率的函数,接受一个int型参数,通过调用主函数末尾的上来sleep()函数,将该参数置为发布频率。 ros::Rate loop_rate(10); int count = 0; // ros::ok()会设置一个SIGINT监听,函数返回True,仅当以下四种情况发生时,该函数返回False: // 1. SIGINT被触发(Ctrl+c) // 2.被另一同名节点踢出 // 3.函数ros::shutdown()被程序另一部分调用 // 4.程序所有句柄被销毁 while (ros::ok()) { // 初始化std_msgs::String类型的消息 std_msgs::String msg; // 初始化字符串流 std::stringstream ss; // 把信息放到字节流里 ss << "hello world " << count; // msg中的data是存储信息的,直接赋值 msg.data = ss.str(); // 发布消息 // 输出一个字符串变量,ROS_INFO其实就是printf // c_str():将C++的string转化为C的字符串数组,c_str()生成一个const char *指针,指向字符串的首地址。 ROS_INFO("%s", msg.data.c_str()); // 通过调用对象的 publish( )函数发布数据 chatter_pub.publish(msg); // 按照循环频率延时 loop_rate.sleep(); ++count; } return 0; }
回到工作空间目录,执行编译.# 设置需要编译的代码和生成的可执行文件 # 让string_publish.cpp这个cpp文件编译生成string_publish add_executable(string_publisher src/string_publisher.cpp) # 设置string_publisher与库catkin_LIBRARIES的连接 target_link_libraries(string_publisher ${catkin_LIBRARIES}) # 后面的Subscriber也是如此 add_executable(string_subscriber src/string_subscriber.cpp) target_link_libraries(string_subscriber ${catkin_LIBRARIES})
$ cd ~/catkin_ws $ catkin_make # 配置系统环境变量后这步可以省略 $ source devel/setup.bash $ roscore $ rosrun learning_communication string_publisher $ rosrun learning_communication string_subscriber
- python的源代码,放在功能包文件夹下的scripts目录下的。
# 该例程将发布turtle1/cmd_vel话题,消息类型geometry_msgs::Twist import rospy from geometry_msgs.msg import Twist def velocity_publisher(): # ROS节点初始化 rospy.init_node('velocity_publisher', anonymous=True) # 创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10 turtle_vel_pub = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size=10) #设置循环的频率 rate = rospy.Rate(10) while not rospy.is_shutdown(): # 初始化geometry_msgs::Twist类型的消息 vel_msg = Twist() vel_msg.linear.x = 0.5 vel_msg.angular.z = 0.2 # 发布消息 turtle_vel_pub.publish(vel_msg) rospy.loginfo("Publsh turtle velocity command[%0.2f m/s, %0.2f rad/s]", vel_msg.linear.x, vel_msg.angular.z) # 按照循环频率延时 rate.sleep() if __name__ == '__main__': try: velocity_publisher() except rospy.ROSInterruptException: pass
需要打开py文件的可执行权限。
订阅者Subscriber
- 初始化ROS节点
- 订阅需要的话题
- 循环等待话题消息,接收到消息后进入回调函数
- 在回调函数中完成消息处理
配置编译规则和环境变量,进行编译。/** * 该例程将订阅chatter话题,消息类型String */ // ros头文件 #include "ros/ros.h" // 后面会用到ROS中string的消息类型 #include "std_msgs/String.h" // 接收到订阅的消息后,会进入消息回调函数 // 定义一个指针常量的引用msg void chatterCallback(const std_msgs::String::ConstPtr& msg) { // 将接收到的消息打印出来 ROS_INFO("I heard: [%s]", msg->data.c_str()); } int main(int argc, char **argv) { // 初始化ROS节点 ros::init(argc, argv, "string_subscriber"); // 创建节点句柄 ros::NodeHandle n; // 创建一个Subscriber,订阅名为chatter的topic,注册回调函数chatterCallback ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback); // 循环等待回调函数 // spin本身的循环条件就是ros::ok() ros::spin(); return 0; }
- python版本
# 该例程将订阅/turtle1/pose话题,消息类型turtlesim::Pose
import rospy
from turtlesim.msg import Pose
def poseCallback(msg):
rospy.loginfo("Turtle pose: x:%0.6f, y:%0.6f", msg.x, msg.y)
def pose_subscriber():
# ROS节点初始化
rospy.init_node('pose_subscriber', anonymous=True)
# 创建一个Subscriber,订阅名为/turtle1/pose的topic,注册回调函数poseCallback
rospy.Subscriber("/turtle1/pose", Pose, poseCallback)
# 循环等待回调函数
rospy.spin()
if __name__ == '__main__':
pose_subscriber()
消息
我们通过自定义msg文件来自定义话题消息。在功能包根目录下新建文件夹msg,并touch Person.msg
,文件内容
string name
uint8 age
uint8 sex
uint8 unknown = 0
uint8 male = 1
uint8 female = 2
在package.xml中添加功能包依赖:
<build_depend>message_generation</build_depend> <exec_depend>message_runtime</exec_depend>
build_depend为编译依赖,这里依赖的是一个会动态产生message的功能包
exer_depend为执行依赖,这里依赖的是一个动态runtime运行的功能包
在CMakeLists.txt中添加编译选项:
find_package(…… message_generation)
- 添加:
add_message_files(FILES PersonMsg.msg)
和generate_messages(DEPENDENCIES std_msgs)
(前一句话是编译生成头文件,后一句话是调用了 ros 的标准数据类型) catkin_package(…… message_runtime)
回到根目录,编译catkin_make
,编译完成后,我们可以在devel/include/learning_topic/ 下找到这个C++的头文件
Service通信机制
详细步骤懒得摘录了,跟Topic机制有一定的类似。直接见ROS学习笔记 03 ROS通信编程 | 孙健耕的博客