2 ROS编程基础

tech2022-08-03  144

2 ROS编程基础

2.1 创建项目的工作空间2.2 添加功能包2.3 通信编程2.3.1话题编程2.3.2 Python编写话题2.3.3 自定义话题消息 2.4 服务编程2.5 动作编程2.6 launch启动文件2.7 录制和回放操作

2.1 创建项目的工作空间

Step1----- 在磁盘新建文件夹,作为工作空间。举例在home文件夹下新建了一个文件夹catkin_ws作为工作空间的根目录。 Step2----- 在catkin_ws工作空间下新建src文件夹。 Step3----- 用cd命令进入到src目录下,输入命令catkin_init_workspace对项目进行初始化。执行成功后src目录下回出现CamkeLists.txt文件。 Step4----- 进入到工作空间根目录catkin_ws下,输入命令catkin_make进行编译初始化,执行成功后catkin_ws目录会多出两个文件夹devel和build。 Step5-----在根目录catkin_ws下,输入gedit ~/.bashrc,利用gedit编辑器对bashrc编辑,主要是把source ~/catkin_ws/devel/setup.bash添加进去(仿造ROS的源环境变量,只是换了路径),关闭文档,执行source ~/.bashrc来设置工作空间的环境变量。执行成功后,可以新建一个终端,输入echo $ROS_PACKAGE_PATH查看环境变量是否添加进去。

2.2 添加功能包

Step1----- 用cd进入到src目录下来创建功能包,其命令为catkin_create_pkg 包名 依赖项1 依赖项2…,先为了执行通信例子,创建listening and talk功能包,即catkin_creat_pkg l_t std_msgs rospy roscpp Step2----- 编译功能包,进入到工作空间根目录下,执行编译命令catkin_make来编译功能包。

2.3 通信编程

2.3.1话题编程

Step1----- 创建talker和listener。注意,talker和listener是放在功能包的src目录里面的(即l_t的src目录下),代码可以是C++或者python等。

//catkin_ws/src/l_t/src/talker.cpp /** * 该例程将发布talker(chatter)话题,消息类型String */ #include <sstream> #include "ros/ros.h" #include "std_msgs/String.h" int main(int argc, char **argv) { // ROS节点初始化 ros::init(argc, argv, "talker"); // 创建节点句柄 ros::NodeHandle n; // 创建一个Publisher,发布名为chatter的topic,消息类型为std_msgs::String ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000); // 设置循环的频率 ros::Rate loop_rate(10); int count = 0; while (ros::ok()) { // 初始化std_msgs::String类型的消息 std_msgs::String msg; std::stringstream ss; ss << "hello world " << count; msg.data = ss.str(); // 发布消息 ROS_INFO("%s", msg.data.c_str()); chatter_pub.publish(msg); // 循环等待回调函数 ros::spinOnce(); // 按照循环频率延时 loop_rate.sleep(); ++count; } return 0; } // catkin_ws/src/l_t/src/listener.cpp /** * 该例程将订阅listener(subscriber)话题,消息类型String */ #include "ros/ros.h" #include "std_msgs/String.h" // 接收到订阅的消息后,会进入消息回调函数 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, "listener"); // 创建节点句柄 ros::NodeHandle n; // 创建一个Subscriber,订阅名为chatter的topic,注册回调函数chatterCallback ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback); // 循环等待回调函数 ros::spin(); return 0; }

Step2----- 设置编译选项。打开功能包里的CmakeLists.txt文件,对listener和talker话题进行添加环境变量,对于talker添加代码add_executable(talker src/talker.cpp)和target_link_libraries(talker ${catkin_LIBRARIES})。对于listener添加代码add_executable(listener src/listener.cpp)和target_link_libraries(listener ${catkin_LIBRARIES})。其中add_executable(生成的话题/通过什么文件来生成,可以多个文件生成一个可执行的话题)。target_link_libraries(生成的话题 ${生成话题所需要的库})。

// catkin_ws/src/l_t/package.xml add_executable(talker src/talker.cpp) target_link_libraries(talker ${catkin_LIBRARIES})add_executable(listener src/listener.cpp) target_link_libraries(listener ${catkin_LIBRARIES})

Step3------ 编译工作空间,在工作空间的根目录catkin_ws下执行catkin_make来进行编译成可执行文件,对于python文件它本身就是个可执行文件,可以不需要编译。 Step4----- 运行。首先打开一个终端输入roscore代开ROS,再打开新终端输入rosrun l_t talker即可打开talker话题,最后新打开一个终端输入rosrun l_t listener,演示结果为talker发布的指令listener全部都能同步接收到。

2.3.2 Python编写话题

Step1----- 在catkin_ws/src路径下执行catkin_create_pkg pytest rospy std_msgs Step2----- 创建好pytest功能包后,里面会有src文件夹,但是py文件作为可执行文件,一般存放在scripts文件夹,因此要在catkin_ws/src/pytest路径下新建一个scripts文件夹存放py文件 Step3----- 编写talker.py文件和listener.py文件,此listener与wiki有改动

// catkin_ws/src/pytest/scripts/talker.py #!/usr/bin/env python import rospy from std_msgs.msg import String def talker(): #话题名,rostopic list查看 pub = rospy.Publisher('chatter', String, queue_size=10) #节点名,rosnode list 查看 rospy.init_node('talker') rate = rospy.Rate(1) # 1hz i = 1 while not rospy.is_shutdown(): hello_str = "number: %d" % i rospy.loginfo(hello_str) #在listener.py里的data即是hello_str所有的内容 pub.publish(hello_str) i += 1 rate.sleep() if __name__ == '__main__': try: talker() except rospy.ROSInterruptException: pass // catkin_ws/src/pytest/scripts/listener.py #!/usr/bin/env python import rospy from std_msgs.msg import String #data即chatter带来的所有内容() def callback(data): rospy.loginfo("publisher is " + rospy.get_caller_id() + ' I subscribe %s', data.data) def listener(): #节点名 rospy.init_node('listener') #这里的chatter是在talker.py定义的publish的话题名,区分节点名和话题名 rospy.Subscriber('chatter', String, callback) # spin() simply keeps python from exiting until this node is stopped rospy.spin() if __name__ == '__main__': listener()

Step4----- 在catkin_ws/路径下编译文件 Step5----- 打开roscore,然后新建终端输入rosrun pytest talker.py等即可运行py文件

2.3.3 自定义话题消息

Step1----- 定义话题文件msg。在功能包的目录下,创建新目录msg,在msg目录里,创建话题文件.msg。如在l_t/msg创建了Person.msg话题文件。

// catkin_ws/src/l_t/msg/Person.msg string name uint8 sex uint8 age uint unknown = 0 uint8 male = 1 uint8 female = 2

Step2----- 在功能包的package.xml里添加话题的依赖。如在l_t文件夹的package.xml里对,新建的Person.msg文件添加<exec_depend>message_runtime</exec_depend>和<build_depend>message_generation</build_depend> Step3---- 在功能包的CmakeLists.txt添加编译选项。首先在CMakeLists.txt把话题配置进去,即在find_package(catkin REQUIRED COMPONENTS …+包),如

// catkin_ws/src/l_t/CMakeLists.txt find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation )

接着找到catkin_package(…+包),如

// catkin_ws/src/l_t/CMakeLists.txt catkin_package( # INCLUDE_DIRS include # LIBRARIES l_c CATKIN_DEPENDS roscpp rospy std_msgs message_runtime # DEPENDS system_lib )

最后添加配置命令,add_message_files(FILES Person.msg)和generate_messages(DEPENDENCIES std_msgs)。 Step4----- 到工作空间根目录下,执行catkin_make进行编译。 Step5----- 编译成功后,可以新建终端,输入命令runmsg show Person(这个Person是话题文件名),查看话题。

2.4 服务编程

Step1----- 创建服务程序。在功能包的目录下,创建srv文件用来存放服务程序,即在l_t/sev里来存放服务程序文件,服务程序后缀名必须为.srv。本例中服务程序表示两数之和,前俩行的a和b表示服务端的请求数据,—号表示分割,c表示服务端的应答数据,此时的a,b和c只是指代请求端(request)和响应端的数据类型(respond),没有任何操作功能。最后保存为AddTwoInts.srv。

// catkin_ws/src/l_t/srv/AddTwoInt.srv int64 a int64 b --- int64 c

Step2----- 在功能包的package.xml文件中添加依赖项。注意,sev文件在package.xml文件夹所需要的添加的功能包依赖项和msg文件所需要的的依赖项是一样的,都是<exec_depend>message_runtime</exec_depend>和<build_depend>message_generation</build_depend>。 Step3---- 在功能包的CMakeLists.txt文件添加编译选项。首先在find_package(catkin REQUIRED COMPONENTS …+包),如

// catkin_ws/src/l_t/CMakeLists.txt find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation )

接着找到catkin_package(…+包),如

//catkin_ws/src/l_t/CMakeLists.txt catkin_package( # INCLUDE_DIRS include # LIBRARIES l_c CATKIN_DEPENDS roscpp rospy std_msgs message_runtime # DEPENDS system_lib )

注意:前两步和msg文件所要的内容是一模一样的,区别在于最后一此添加的选项不同,sev文件最后添加配置命令为add_service_files(FILES AddTwoInts.srv)。

// catkin_ws/src/l_t/CMakeLists.txt add_service_files(FILES AddTwoInts.srv)

Step4----- 到工作空间根目录下,执行catkin_make进行编译。 Step5----- 创建服务端程序。这一步就是真正利用请求端与应答端定义的数据来编程服务所需的功能。在功能包的src目录下,创建服务程序server.cpp。

// catkin_ws/src/l_t/src/server.cpp /** * AddTwoInts Server */ #include "ros/ros.h" #include "l_c/AddTwoInts.h" // service回调函数,输入参数req,输出参数res bool add(l_c::AddTwoInts::Request &req, l_c::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节点初始化 ros::init(argc, argv, "add_two_ints_server"); // 创建节点句柄 ros::NodeHandle n; // 创建一个名为add_two_ints的server,注册回调函数add() ros::ServiceServer service = n.advertiseService("add_two_ints", add); // 循环等待回调函数 ROS_INFO("Ready to add two ints."); ros::spin(); return 0; }

Step6----- 创建客户端程序。在src目录下,创建客户端程序client.cpp。

// catkin_ws/src/l_t/src/client.cpp /** * AddTwoInts Client */ #include <cstdlib> #include "ros/ros.h" #include "l_c/AddTwoInts.h" int main(int argc, char **argv) { // ROS节点初始化 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; // 创建一个client,请求add_two_int service,service消息类型是l_c::AddTwoInts ros::ServiceClient client = n.serviceClient<l_c::AddTwoInts>("add_two_ints"); // 创建l_c::AddTwoInts类型的service消息 l_c::AddTwoInts srv; srv.request.a = atoll(argv[1]); srv.request.b = atoll(argv[2]); // 发布service请求,等待加法运算的应答结果 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; }

Step7----- 配置服务端和客户端代码的编译选项。在功能包的目录下,打开其CMakeLists.txt文件。

// catkin_ws/src/l_t/CMakeLists.txt add_executable(server src/server.cpp) target_link_libraries(server ${catkin_LIBRARIES}) add_dependencies(server ${PROJECT_NAME}_gencpp) add_executable(client src/client.cpp) target_link_libraries(client ${catkin_LIBRARIES}) add_dependencies(client ${PROJECT_NAME}_gencpp)

Step7----- 在工作空间的根目录下进行编译操作。 Step8----- 运行服务编程。首先打开一个终端输入roscore,打开ROS,再新建一个终端输入rosrun l_t server运行服务程序。最后新建终端输入rosrun l_t client 8 9即运行客户端并执行8和9的加法操作。

2.5 动作编程

Step1----- 定义动作文件。在功能包的目录下,创建新目录action,在action目录里,创建动作文件.action。如在l_t/action创建了DoDishes.action文件。其中的内容分为三个部分,第一部分goal对应目标数据,即客户端发布数据。第二部分result定义结果,服务端应答给客户端的结果。第三部分是反馈部分,服务端反馈一些信息给客户端。

// catkin_ws/src/l_t/action/DoDishes.action # Define the goal uint32 dishwasher_id # Specify which dishwasher we want to use --- # Define the result uint32 total_dishes_cleaned --- # Define a feedback message float32 percent_complete

Step2----- 在功能包的package.xml里添加依赖。如在l_t文件夹的package.xml里对,新建的DoDishes.action文件添加依赖。

// catkin_ws/src/l_t/package.xml var foo = 'bar'; // An highlighted block <build_depend>actionlib</build_depend> <build_depend>actionlib_msgs</build_depend> <exec_depend>actionlib</exec_depend> <exec_depend>actionlib_msgs</exec_depend>

Step3---- 在功能包的CmakeLists.txt添加编译选项。首先在find_package(catkin REQUIRED COMPONENTS …+包),如

//catkin_ws/src/l_t/CMakeLists.txt find_package(catkin REQUIRED COMPONENTS … actionlib_msgs actionlib )

最后添加配置命令,add_action_files(DIRECTORY action FILES DoDishes.action)和generate_messages(DEPENDENCIES actionlib_msgs)

// catkin_ws/src/l_t/CMakeLists.txt add_action_files(DIRECTORY action FILES DoDishes.action) generate_messages(DEPENDENCIES std_msgs actionlib_msgs) 注:相同的命令行所添加的配置可以放在一起。

Step4----- 到工作空间根目录下,执行catkin_make进行编译。 Step5----- 实现动作服务器。动作的服务器包括服务端程序和客户端程序,首先在工作空间的根目录的src文件夹里创建服务程序DoDishes_server.cpp。

// catkin_ws/src/l_t/src/DoDishes_server.cpp #include <ros/ros.h> #include <actionlib/server/simple_action_server.h> #include "l_c/DoDishesAction.h" typedef actionlib::SimpleActionServer<l_c::DoDishesAction> Server; // 收到action的goal后调用该回调函数 void execute(const l_c::DoDishesGoalConstPtr& goal, Server* as) { ros::Rate r(1); l_c::DoDishesFeedback feedback; ROS_INFO("Dishwasher %d is working.", goal->dishwasher_id); // 假设洗盘子的进度,并且按照1hz的频率发布进度feedback for(int i=1; i<=10; i++) { feedback.percent_complete = i * 10; as->publishFeedback(feedback); r.sleep(); } // 当action完成后,向客户端返回结果 ROS_INFO("Dishwasher %d finish working.", goal->dishwasher_id); as->setSucceeded(); } int main(int argc, char** argv) { ros::init(argc, argv, "do_dishes_server"); ros::NodeHandle n; // 定义一个服务器 Server server(n, "do_dishes", boost::bind(&execute, _1, &server), false); // 服务器开始运行 server.start(); ros::spin(); return 0; }

接下来在src目录下,创建客户端程序client.cpp。

// catkin_ws/src/l_t/src/DoDishes_client.cpp #include <actionlib/client/simple_action_client.h> #include "l_c/DoDishesAction.h" typedef actionlib::SimpleActionClient<l_c::DoDishesAction> Client; // 当action完成后会调用该回调函数一次 void doneCb(const actionlib::SimpleClientGoalState& state, const l_c::DoDishesResultConstPtr& result) { ROS_INFO("Yay! The dishes are now clean"); ros::shutdown(); } // 当action激活后会调用该回调函数一次 void activeCb() { ROS_INFO("Goal just went active"); } // 收到feedback后调用该回调函数 void feedbackCb(const l_c::DoDishesFeedbackConstPtr& feedback) { ROS_INFO(" percent_complete : %f ", feedback->percent_complete); } int main(int argc, char** argv) { ros::init(argc, argv, "do_dishes_client"); // 定义一个客户端 Client client("do_dishes", true); // 等待服务器端 ROS_INFO("Waiting for action server to start."); client.waitForServer(); ROS_INFO("Action server started, sending goal."); // 创建一个action的goal l_c::DoDishesGoal goal; goal.dishwasher_id = 1; // 发送action的goal给服务器端,并且设置回调函数 client.sendGoal(goal, &doneCb, &activeCb, &feedbackCb); ros::spin(); return 0; }

Step7----- 配置动作的服务端和客户端代码的编译选项。在功能包的目录下,打开其CMakeLists.txt文件。

// catkin_ws/src/l_t/CMakeLists.txt add_executable(DoDishes_client src/DoDishes_client.cpp) target_link_libraries( DoDishes_client ${catkin_LIBRARIES}) add_dependencies(DoDishes_client ${${PROJECT_NAME}_EXPORTED_TARGETS}) add_executable(DoDishes_server src/DoDishes_server.cpp) target_link_libraries(DoDishes_server ${catkin_LIBRARIES}) add_dependencies(DoDishes_server ${${PROJECT_NAME}_EXPORTED_TARGETS})

Step7----- 在工作空间的根目录下进行编译操作。 Step8----- 运行服务编程。首先打开一个终端输入roscore,打开ROS,再新建一个终端输入rosrun l_t DoDishes_server运行服务程序。最后新建终端输入rosrun l_t DoDishes_client即运行客户端并执行动作操作。

2.6 launch启动文件

launch文件能够在ROS中一次启动多个节点。参照1.3.1的计算图启动海龟的launch文件,分析launch文件的参数。 为了方便设置和修改, launch文件支持参数设置的功能,类似于编程语言中的变量声明。关于参数设置的标签元素有两个: 和,一个代表parameter,另-一个代表argument。

2.7 录制和回放操作

以录制控制小乌龟运动为例,先运行键盘控制乌龟的两个端口命令,再打开一个新的终端窗口,在终端中执行以下命令: mkdir ~/bagfiles cd ~/bagfiles rosbag record -a 在这里先建立一个用于录制的临时目录,然后在该目录下运行rosbag record命令,并附加-a选项,该选项表示将当前发布的所有话题数据都录制保存到一个bag文件中。然后回到turtle_teleop节点所在的终端窗口并控制turtle随处移动10秒钟左右。 在运行rosbag record命令的窗口中按Ctrl-C退出该命令。现在检查看~/bagfiles目录中的内容,应该会看到一个以年份、日期和时间命名并以.bag作为后缀的文件。这个就是bag文件,它包含rosbag record运行期间所有节点发布的话题。 录制好的bag文件可以使用rosbag info检查看它的内容,使用rosbag play命令回放出来。首先在turtle_teleop_key节点运行时所在的终端窗口中按Ctrl+C退出该节点。让turtlesim节点继续运行。在终端中bag文件所在目录下运行以下命令: rosbag play 可以通过-s参数选项让rosbag play命令等待一段时间跳过bag文件初始部分后再真正开始回放。也可以用参数-r选项,来改变消息发布速率。如果执行:rosbag play -r 2 ,这时的轨迹应该是相当于当之前以两倍的速度通过按键发布控制命令时产生的轨迹。 注:一般情况下录制文件不选用-a参数,先通过rostopic list列出所有话题,查看所需要的功能是哪个话题发布的,仅仅录制这几个话题即可。命令为rosbag record -O test /turtle1/cmd_vel /turtle1/pose。其中-O参数告诉rosbag record将数据记录保存到名为test-日期.bag的文件中,同时后面的话题参数告诉rosbag record只能录制这两个指定的话题。

最新回复(0)