目录
一、节点与节点管理器
二、通信方式
三、参数
四、文件系统
五、ROS命令行工具
六、创建工作空间与功能包
1)工作空间
2)创建功能包
七、发布者Publisher的代码实现
八、订阅者Subscriber的编程实现
九、自定义话题并使用
十、Client客户端的编程实现
1.创建功能包
2.配置CMakeLists.txt的编译规则
十一、Server的编程实现
十二、服务数据的定义与使用
1.定义服务数据
2.创建服务器代码
十三、参数的使用与编程方法
1.参数模型
2.创建功能包
3.参数命令行的使用
4.代码实现
十四、ROS中的坐标管理系统
1.机器人坐标变换例程
2.tf坐标系广播与监听的编程实现
十五、launch文件的使用方法
1.launch文件
2.核心语法
十六、常用可视化工具的使用
一、节点与节点管理器
二、通信方式
三、参数
全局共享的字典,里面存储的是整个全局所需要用到的参数,适合存储静态,非二进制类型。
四、文件系统
五、ROS命令行工具
- 启动ROS Master:
$ roscore
2.启动小海龟仿真器:
$ rosrun turtlesim turtlesim_node
3.启动海龟控制节点:
$ rosrun turtlesim turtle_teleop_key
注意:以上三个步骤分别需要在三个不同的终端中执行。
$ rqt_graph
这一代码用于显示系统计算图。
控制器将数据打包发给订阅了相关话题的节点,该节点接收到数据后会执行对应操作。
与节点相关:
rosnode //显示节点信息的指令。
rosnode list //列出节点
rosnode info 节点 //查看具体节点的信息
与话题相关:
rostopic //显示话题相关信息
rostopic list //列出话题
rostopic pub 话题/输入指令的数据结构 “具体数据” //发送话题的消息数据内容
rostopic pub -r 话题 //-r表示频率,代表一秒发送多少次
rosmsg show geometry_msgs/twist //查看这一指令的数据结构
与服务相关:
rosservice list //列出服务
rosservice call 服务名字 按tab键补全 //访问这一服务 //例如,访问/spawn,用于产生新的海龟,不过新海龟只能用
rostopic pub操作运动
rosservice call /spawn tab键补全
话题记录工具:
rosbag record -a -O cmd_record //话题记录。-a表示记录所有数据,-O表示把记录的数据压缩成压缩包
rosbag play cmd_record.bag //话题复现,即让仿真器再次完成之前记录数据代表的操作
六、创建工作空间与功能包
1)工作空间
存放工程开发相关文件的文件夹。
- src:代码空间
- build:编译空间
- devel:开发空间
- install:安装空间
//创建工作空间
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src cakin_init_workspace
//编译工作空间
cd ~/catkin_ws/ catkin_make catkin_make install
//设置环境变量
source devel/setup.bash
//检查环境变量
echo $ROS_PACKAGE_PATH
完成第九行代码后,你的catkin_ws文件夹(工作空间文件夹名字可以自己定)就会出现如下图样式
2)创建功能包
//创建功能包
cd ~/catkin_ws/src catkin_create_pkg test_pkg std_msgs rospy roscpp
catkin_create_pkg <package_name> [depend1] [depend2] [depend3] //<>里面的是船舰的功能包的名字,[]里面的是创建的功能包依赖ROS里面的哪些文件
//编译功能包
cd ~/catkin_ws catkin_make source ~/catkin_ws/devel/setup.bash
CMakelists中存写的是功能包代码编译的一些条件,协议等等。
package中存放的是功能包的相关信息。
任意两个功能包都会包含以上两个文件,只有拥有这两个文件,才代表这个文件夹是一个功能包。
注意:同一个工作空间(catkin_ws)中,不允许存在同名功能包,例如我们以上代码,在./src中创建了名叫test_pkg的功能包,则不能再次创建同名功能包。
七、发布者Publisher的代码实现
cd ~/catkin_ws/src
catkin_create_pkg learning_topic roscpp rospy std_msgs geometry_msgs turtlesim
创建发布者代码:
C++代码:
/*** publish turtle1/cmd_vel topic, the type of message is geometry_msgs::Twist*/#include <ros/ros.h>
#include <geometry_msgs/Twist.h>int main(int argc,char **argv)
{//ROS节点初始化 ros::init(argc,argv,"velocity_publisher");//创建节点句柄ros::NodeHandle n;//创建一个Publisher,发布名为turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10ros::Publisher turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel",10);//设置循环频率ros::Rate loop_rate(10);int count = 0;while(ros::ok()){//初始化geometry_msgs::Twist类型的消息,避免之前的数据造成的影响,设定基本参数geometry_msgs::Twist vel_msg;vel_msg.linear.x=0.5;vel_msg.angular.z=0.2;//发布消息turtle_vel_pub.publish(vel_msg);ROS_INFO("Publish turtle velocity command[%0.2f m/s, %0.2f rad/s]",vel_msg.linear.x,vel_msg.angular.z);//按照循环频率延时loop_rate.sleep();}return 0;
}
Python代码:(不需要像C++一样重编译成可执行文件,直接就可以执行,但需要勾选权限,详见第八节末尾注意)
#1 /usr/bin/env python
# -*- coding: utf-8 -*-
#该例程将发布turtle1/cmd_vel话题,消息类型geometry_msgs::Twistimport rospy
from geometry_msgs.msg import Twistdef velocity_publisher():#ROS节点初始化rospy.init_node('velocity_publisher',anonymous=True)#创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10turtle_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.5vel_msg.angular.z = 0.2#发布消息turtle_vel_pub.publish(vel_msg)rospy.loginfo("Publish 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
思路:
- 初始化ROS节点,使得发送的数据对这个节点起作用,例如,小车轮子接收到速度并以这个速度转动,轮子就是节点。
- 向ROS Master注册节点信息,包括发布的话题名和话题中的消息类型。
- 创建消息数据。
- 按照一定的频率循环发送。
配置发布者代码编译规则:
add_executable(velocity_publisher src/velocity_publisher.cpp) //将后一个.cpp文件编译成前面那个可执行文件 //转换成可执行文件后,才能执行文件相应操作,.cpp文件只是代码要想电脑执行则必须转换成可执行文件
target_link_libraries(velocity_publisher ${catkin_LIBRARIES}) //把可执行文件和ROS的一些库作链接
编译并运行发布者:
//回到catkin_ws的根目录下执行
cd ~/catkin_ws
catkin_make
source devel/setup.bash
roscore
rosrun turtlesim turtlesim_node
rosrun learning_topic velocity_publisher
对于第六行代码,可以在根目录下按住ctrl+h显示隐藏文件,打开.bashrc,在文件末尾加上绝对路径即可。
最终编译形成的可执行文件velocity_publisher存放在/catkin_ws/devel/lib/learning_topic目录下。
八、订阅者Subscriber的编程实现
实现步骤:
- 初始化ROS节点;
- 订阅需要的话题;
- 循环等待话题消息,接收到消息后进入回调函数;
- 在回调函数中完成消息处理。
C++代码:
/***该例程将订阅/turtle1/pose话题,消息类型turtlesim::Pose*/#include <ros/ros.h>#include "turtlesim/Pose.h"//接收到订阅的信息后,会进入信息回调函数//ConsPtr&是长指针类型,msg会指向收到的消息void poseCallback(const turtlesim::Pose::ConstPtr& msg){//将接收到的信息打印出来,类似与printfROS_INFO("Turtle pose: x:%0.6f, y:%0.6f",msg->x, msg->y); }int main(int argc,char **argv){//初始化ROS节点ros::init(argc, argv, "pose_subscriber");//创建节点句柄ros::NodeHandle n;//创建一个Subscriber,订阅名为/turtle1/pose的topic,注册回调函数poseCallback,打印相应接收到的信息ros::Subscriber pose_sub = n.subscribe("/turtle1/pose",10,poseCallback);//循环等待回调函数 ros::spin();return 0;}
Python代码:(不需要像C++一样重编译成可执行文件,直接就可以执行,但需要勾选权限,详见第八节末尾注意)
#!/user/bin/env python
# -*- coding: utf-8 -*-
#该例程将订阅/turtle1/pose话题,消息类型turtlesim::Poseimport 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', n=anonymous=True)#创建一个Subscriber,订阅名为/turtle1/pose的topic,注册回调函数poseCallback,打印相应接收到的信息rospy.Subscriber(/turtle1/pose", Pose, poseCallback)#循环等待回调函数 rospy.spin()#上面两个是定义函数,下面这个是用于执行主程序,类似C中的main函数
if __name__ == '__main__':pose_subscriber()
配置订阅者代码编译规则:
add_executable(pose_subscriber src/pose_subscriber.cpp)
//将后一个.cpp文件编译成前面那个可执行文件target_link_libraries(pose_subscriber ${catkin_LIBRARIES})
//把可执行文件和ROS的一些库作链接
后续操作同上发布者一样。
编译并运行订阅者:
//回到catkin_ws的根目录下执行
cd ~/catkin_wscatkin_makesource devel/setup.bashroscorerosrun turtlesim turtlesim_noderosrun learning_topic pose_subscriber
注意:若是Python作为可执行文件,则需要在文件属性的权限栏中勾选“允许作为程序可执行文件”。
九、自定义话题并使用
创建一个msg文件夹,存放msg文件。
在Person.msg文件中输入蓝色框框里面的内容。
在package。xml中输入以下内容:
添加Person.msg为编译的接口,当编译的时候会自动识别并针对文件作为编译的接口。
添加编译Person.msg时需要的ros库。
自定义话题的C++代码:
/*** publish /person_info topic, the type of message is learning_topic::Person*/#include <ros/ros.h>
#include "learning_topic/Person.h"//包含我们自己定义的消息类型learning_topic::Person的头文件int main(int argc,char **argv)
{//ROS节点初始化 ros::init(argc,argv,"person_publisher");//创建节点句柄ros::NodeHandle n;//创建一个Publisher,发布名为/person_info的topic,消息类型为learning_topic::Person,队列长度10ros::Publisher person_info_pub = n.advertise<learning_topic::Person>("/person_info",10);//设置循环频率ros::Rate loop_rate(1);int count = 0;while(ros::ok()){//初始化learning_topic::Person类型的消息,避免之前的数据造成的影响,设定基本参数learning_topic::Person person_msg;person_msg.name = "Aurora";person_msg.age = 18;person_msg.sex = learning_topic::Person::male;//调用在头文件中定义性别的宏 //发布消息person_info_pub.publish(person_msg);ROS_INFO("Publish Person Info: name: %s, age: %d, sex: %d",person_msg.name.c_str(),person_msg.age,person_msg.sex);//按照循环频率延时loop_rate.sleep();}return 0;
}
/***该例程将订阅/person_info话题,消息类型learning_topic::Person*/#include <ros/ros.h>#include "learning_topic/Person.h"//接收到订阅的信息后,会进入信息回调函数//ConsPtr&是长指针类型指向learning_topic::Person类型的数据,msg会指向收到的消息void poseCallback(const learning_topic::Person::ConstPtr& msg){//将接收到的信息打印出来,类似与printfROS_INFO("Subscribe Person Info: name:%s, age:%d, sex:%d",msg->name.c_str(), msg->age,msg->sex); //name.c_str()将name的字符串转换为C语言的风格}int main(int argc,char **argv){//初始化ROS节点,相当于此文件的执行文件,中断调用时,就是在调用并执行这个文件ros::init(argc, argv, "person_subscriber");//创建节点句柄ros::NodeHandle n;//创建一个Subscriber,订阅名为/turtle1/pose的topic,注册回调函数poseCallback,打印相应接收到的信息ros::Subscriber person_info_sub = n.subscribe("/person_info",10,poseCallback);//循环等待回调函数 ros::spin();return 0;}
配置CmakeLists.txt中的编译规则:
add_executable(person_publisher src/person_publisher.cpp)
target_link_libraries(person_publisher ${catkin_LIBRARIES})
add_dependencies(person_publisher ${PROJECT_NAME}_generate_messages_cpp)
//第三行代码是用于跟生成的头文件Person.h进行连接用的
add_executable(person_subscriber src/person_subscriber.cpp)
target_link_libraries(person_subscriber ${catkin_LIBRARIES})
add_dependencies(person_subscriber ${PROJECT_NAME}_generate_messages_cpp)
ROS里面的程序有运行对象这一选项,roscore帮助两个节点也就是运行程序找到对象就不在对两个节点其作用了。
在我看来,其实是两个程序相互联系后,基于共同topic和消息类型来执行程序并最终实现相应结果。
十、Client客户端的编程实现
ROS(Robot Operating System)客户端是指在ROS框架中,使用服务(Service)通信模式的节点,它发送请求给服务端(Server),并等待服务端处理请求后的响应。
相当于一个开关。
1.创建功能包
用户命令在指定位置显现出一直海归。
C++代码:
/***该例程将请求/spawn服务,服务数据类型turtlesim::Spawn*/#include <ros/ros.h>#include <turtlesim/Spawn.h>int main(int argc,char** argv){//初始化ROS节点ros::init(argc,argv,"turtle_spawn");//创建节点句柄ros::NodeHandle node;//发现/spawn服务后,创建一个服务客户端,连接名为/spawn的serviceros::service::waitForService("/spawn");ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("/spawn");//初始化turtlesim::Spawn的请求数据//这里相当于用户命令的封装turtlesim::Spawn srv;srv.request.x=2.0;srv.request.y=2.0;srv.request.name="turtle2";//请求服务调用ROS_INFO("Call service to spwan turtle[x:%0.6f, y:%0.6f, name:%s]",srv.request.x,srv.request.y,srv.request.name.c_str());add_turtle.call(srv);//显示服务调用结果ROS_INFO("Spwan turtle successfully [name:%s]",srv.request.name.c_str());return 0; };
Python代码:
#1 /usr/bin/env python
# -*- coding: utf-8 -*-
#该例程将请求/spawn服务,服务数据类型turtlesim::Spawnimport sys
import rospy
from turtlesim.srv import Spawndef turtle_spawn():#ROS节点初始化rospy.init_node('turtle_spawn')#发现/spawn服务后,创建一个服务客户端,连接名为/spawn的servicerospy.wait_for_service('/spawn') try:#客户端是add_turtle,服务连接名是/spawn,数据类型是Spawnadd_turtle = rospy.ServiceProxy('/spawn',Spawn)#请求服务调用,输入请求数据,直接将指定数据添加到客户端后面#调用程序让turtle2小海龟在所给数据指定位置处出现#add_turtle等待海龟是否产生成功,然后返回值给response,产生成功返回海龟名,不成功则执行except里面的代码response = add_turtle(2.0,2.0,0.0,"turtle2")return response.nameexcept rospy.ServiceException, e:print "Service call failed: %s"%eif __name__ == "__main__":#服务调用并显示调用结果print "Spawn turtle successfully [name:%s]" %(turtle_spawn())
实现一个客户端的流程:
- 初始化ROS节点;
- 创建一个Client实例;
- 发布服务请求数据;
- 等待Server处理之后的应答结果;
2.配置CMakeLists.txt的编译规则
- 设置需要编译的代码和生成可执行文件;
- 设置链接库;
add_executable(turtle_spawn src/turtle_spawn.cpp)
target_link_libraries(turtle_spawn ${catkin_LIBRARIES})//运行
roscore
rosrun turtlesim turtlesim_node
rosrun learning_service turtle_spawn
十一、Server的编程实现
发送具体海龟运动的指令。
C++代码:
/***该例程将执行/turtle_command服务,服务数据类型std_srvs/Trigger*/#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
#include <std_srvs/Trigger.h>ros::Publisher turtle_vel_pub;
bool pubCommand = false;//service回调函数,输入参数req,输出参数res
bool commandCallback(std_srvs::Trigger::Request &req,std_srvs::Trigger::Response &res)
{pubCommand = !pubCommand;//显示请求数据ROS_INFO("Publish turtle velocity command [%s]",pubCommand==true?"Yes":"No");//设置反馈数据res.success = true;res.message = "Change turtle command state!";return true;
}int main(int argc,char **argv)
{//ROS节点初始化ros::init(argc,argv,"turtle_command_server");//创建节点句柄ros::NodeHandle n;//创建一个名为/turtle_command的server,注册回调函数commandCallbackros::ServiceServer command_service = n.advertiseService("/turtle_command",commandCallback);//创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel",10);//循环等待回调函数ROS_INFO("Ready to receive turtle command.");//设置循环频率ros::Rate loop_rate(10);while(ros::ok()){//查看一次回调函数队列ros::spinOnce();//如果标志位为true,则发布速度命令if(pubCommand){geometry_msgs::Twist vel_msg;vel_msg.linear.x = 0.5;vel_msg.angular.z = 0.2;turtle_vel_pub.publish(vel_msg); } //按照循环频率延时 loop_rate.sleep(); }return 0;
}
展示数据类型:
十二、服务数据的定义与使用
1.定义服务数据
- 定义srv文件
三个横线之上是request数据,三个横线之下是response的数据。
- 在package.xml中添加功能依赖包
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
- 在CMakeLists.txt中添加编译选项
find_package(...... message_generation)add_service_files(FILES Person.srv)
//根据上一行所加的Person.srv文件生成对应头文件
generate_messages(DEPENDENCIES std_msgs)catkin_package(...... message_runtime)
- 编译生成语言相关文件
---上面的内容在PersonRequest里面,---下面的内容在PersonRespense里面,Person.h头文件包含这两个,所以在使用的时候,只用包含这一个就可以。
2.创建服务器代码
C++代码:
person_client.cpp
/**该例程将请求/show_person服务,服务数据类型learning_service::Person*/#include <ros/ros.h>#include "learning_service/Person.h"int main(int argc,char** argv){//初始化ROS节点ros::init(argc,argv,"person_client");//创建节点句柄ros::NodeHandle node;//发现/show_person服务后,创建一个服务客户端,连接名为/show_person的serviceros::service::waitForService("/show_person");ros::ServiceClient person_client = node.serviceClient<learning_service::Person>("/show_person");//初始化learning_service::Person的请求数据learning_service::Person srv;srv.request.name = "Aurora";srv.request.age = 18;srv.request.sex = learning_service::Person::Request::male;//请求服务调用,显示request发送的数据srvROS_INFO("Call service to show person[name:%s, age:%d, sex:%d]", srv.request.name.c_str(),srv.request.age,srv.request.sex);person_client.call(srv);//显示服务调用结果,即response这个返回值ROS_INFO("Show person result: %s",srv.response.result.c_str());return 0; }
person_server.cpp代码:
/**该例程将执行/show_person服务,服务数据类型learning_service::Person*/#include <ros/ros.h>
#include "learning_service/Person.h"//service回调函数,输入参数req,输出参数res
bool personCallback(learning_service::Person::Request &req,learning_service::Person::Response &res)
{//显示请求数据ROS_INFO("Person: name:%s, age:%d, sex:%d",req.name.c_str(),req.age,req.sex);//设置反馈函数res.result = "OK";return true;
} int main(int argc,char** argv)
{//ROS节点初始化ros::init(argc,argv,"person_server");//创建节点句柄ros::NodeHandle n;//创建一个名为/show_person的server,注册会调函数personCallback//数据传输的通道是/show_person或者说两个连接到/show_person节点相互通信ros::ServiceServer person_service = n.advertiseService("/show_person",personCallback);//循环等待回调函数ROS_INFO("Ready to show person information.");ros::spin();return 0;
}
实现步骤:
- 初始化ROS节点
- 创建Server实例
- 循环等待服务请求,进入回调函数
- 在回调函数中完成服务功能的处理,并反馈应答数据
(Person.srv定义的是一个数据类型)
CMakeLists.txt中添加
add_executable(person_server src/person_server.cpp)
target_link_libraries(person_server ${catkin_LIBRARIES})
add_dependencies(person_server ${PROJECT_NAME}_gencpp)add_executable(person_client src/person_client.cpp)
target_link_libraries(person_client ${catkin_LIBRARIES})
add_dependencies(person_client ${PROJECT_NAME}_gencpp)
//这通常用于确保在编译person_client之前,相关的代码生成步骤已经完成,生成了必要的源文件。
//可执行文件中肯定包含其它需要执行的文件,需要顺利执行可执行文件,这些源文件必不可少
配置规则:
- 设置要编译的代码和生成可执行的文件
- 设置链接库
- 添加依赖项
服务模型:
client:用户操控部分,用户端,用户下达命令或请求的地方。
server:响应用户命令和请求,并作出相应显示的部分。
注意:运行多个程序时都需要开启rosccore,这时roscore中可能就会包含多个同名参数,所以最好每次运行新程序时,先关闭roscore再打开。
十三、参数的使用与编程方法
1.参数模型
Parameter Server是一个全局字典(参数服务器),用于保存各个节点间的参数。
2.创建功能包
cd ~/catkin_ws/src
catkin_create_pkg learning_parameter roscpp rospy std_srvs
3.参数命令行的使用
参数存放在roscore中,所以要先打开roscore才能用rosparam查看当前在roscore中的参数。
dump和load都是默认存储或读取到当前终端所在路径。
4.代码实现
C++代码:
parameter_config.cpp
/**该例程设置/读取海龟例程中的参数 */#include <string>
#include <ros/ros.h>
#include <std_srvs/Empty.h>int main(int argc ,char** argv)
{int red,green,blue;//ROS节点化ros::init(argc,argv,"parameter_config");//创建节点句柄,用于与ROS通信ros::NodeHandle node;//读取背景颜色参数ros::param::get("/background_r",red);ros::param::get("/background_g",green);ros::param::get("/background_b",blue);ROS_INFO("Get Background Color[%d %d %d]",red,green,blue);//设置背景颜色参数ros::param::set("/background_r",255);ros::param::set("/background_g",255);ros::param::set("/background_b",255); ROS_INFO("Set Background Color[255 255 255]");//读取背景颜色参数ros::param::get("/background_r",red);ros::param::get("/background_g",green);ros::param::get("/background_b",blue); ROS_INFO("R-Get Background Color[%d %d %d]",red,green,blue); //调用回调函数,刷新背景颜色//这里把client和service的代码整合到一起//service等待/clear,接收到以后会执行相应程序//clear_background这个客户端发送/clear这个命令,这个命令后面所跟的数据类型是std_srvs::Empty,即数据是空的ros::service::waitForService("/clear");//ros::ServiceClient用于创建客户端,node.serviceClient用于告诉ROS创建了客户端ros::ServiceClient clear_background = node.serviceClient<std_srvs::Empty>("/clear");//定义一个空数据std_srvs::Empty srv;//clear_background客户端发出命令clear,再发送空数据给serviceclear_background.call(srv);sleep(1);return 0;
}
ROS中客户端发送命令后往往会跟着需要发送到的数据,这里不需要发送数据,所以定义数据类型为空。
流程:
- 初始化ROS节点
- get函数获取参数
- set函数设置参数
CMakeLists.txt中编译
add_executable(parameter_config src/parameter_config.cpp)
target_link_libraries(parameter_config ${catkin_LIBRARIES})
十四、ROS中的坐标管理系统
1.机器人坐标变换例程
sudo apt-get install ros-自己ROS的版本(例如我的是noetic)-turtle-tf
roslaunch turtle_tf turtle_tf_demo.launch
rosrun turtlesim turtle_teleop_key
rosrun tf view_frames
运行第一行代码时不知道自己的版本号,可以用下面代码查询:
rosversion -d
运行第二行代码可能会遇到以下问题:
这是缺少功能包,输入以下指令安装即可:
sudo apt install python-is-python3
正常显示结果:
运行第三行代码时可能会有以下报错:
直接输入:
sudo gedit /opt/ros/noetic/lib/tf/view_frames /opt/ros/noetic/lib/tf/view_frames是上面报错的路径。
等待这个文件打开,在88行如下图所示输入:
成功结果输出如下:
运行成功后在根目录下会有 以下两个文件:
注意:我这里是在根目录下运行的这行代码,所以默认存放到根目录下。
生成文件:
world是你这个海龟界面的基本坐标系,以左下角为原点。
turtle1:是第一只海龟的坐标。
turtel2:是第二只海龟的坐标。
打开三维坐标图:(先运行roscore)
rosrun rviz rviz -d `rospack find turtle_tf` /rviz/turtle_rviz.rviz
打不开先安装一下:
sudo apt-get install ros-noetic-rviz
2.tf坐标系广播与监听的编程实现
创建功能包
cd ~/catkin_ws/src
catkin_create_pkg learning_tf roscpp rospy tf turtlesim
如何实现一个tf广播器:
- 定义TF广播器
- 创建坐标变换值
- 发布坐标变换
C++代码:
/**该例程产生tf数据,并计算、发布turtle2的速度指令*/#include <ros/ros.h>
#include <tf/transform_broadcaster.h>
#include <turtlesim/Pose.h>std::string turtle_name;void poseCallback(const turtlesim::PoseConstPtr& msg)
{//创建tf的广播器static tf::TransformBroadcaster br;//初始化tf数据tf::Transform transform;transform.setOrigin(tf::Vector3(msg->x,msg->y,0.0));tf::Quaternion q;q.setRPY(0,0,msg->theta);transform.setRotation(q);//广播world与海龟坐标系之间的tf数据br.sendTransform(tf::StampedTransform(transform,ros::Time::now(),"world",turtle_name));
}int main(int argc,char** argv)
{//初始化ROS节点ros::init(argc,argv,"my_tf_broadcaster");//输入参数作为海龟的名字if(argc != 2){ROS_ERROR("need turtle name as argument");return -1; }turtle_name = argv[1];//订阅海龟的位置话题ros::NodeHandle node;ros::Subscriber sub = node.subscribe(turtle_name+"/pose",10,&poseCallback);//循环等待回调函数ros::spin();return 0;
};
如何实现一个TF监听器:
- 定义TF监听器
- 查找坐标变换
/***该例程监听tf数据,并计算、发布turtle2的速度指令*/#include <ros/ros.h>
#include <tf/transform_listener.h>
#include <geometry_msgs/Twist.h>
#include <turtlesim/Spawn.h>int main(int argc,char** argv)
{//初始化ROS节点ros::init(argc,argv,"my_tf_listener");//创建节点句柄ros::NodeHandle node;//请求产生turtle2ros::service::waitForService("/spawn");ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("/spawn");turtlesim::Spawn srv;add_turtle.call(srv);//创建发布turtle2速度控制指令的发布者ros::Publisher turtle_vel = node.advertise<geometry_msgs::Twist>("/turtle2/cmd_vel",10);//创建tf的监听器tf::TransformListener listener;ros::Rate rate(10.0);while(node.ok()){//获取turtle1与turtle2坐标系之间的tf数据tf::StampedTransform transform;try{listener.waitForTransform("/turtle2","/turtle1",ros::Time(0),ros::Duration(3.0));listener.lookupTransform("/turtle2","turtle1",ros::Time(0),transform);}catch (tf::TransformException &ex){ROS_ERROR("%s",ex.what());ros::Duration(1.0).sleep();continue; }//根据turtle1与turtle2坐标系之间的位置关系,发布turtle2的速度指令geometry_msgs::Twist vel_msg;vel_msg.angular.z=4.0*atan2(transform.getOrigin().y(),transform.getOrigin().x());vel_msg.linear.x=0.5*sqrt(pow(transform.getOrigin().x(),2)+pow(transform.getOrigin().y(),2)); turtle_vel.publish(vel_msg);rate.sleep(); }return 0;
};
配置CMakeLists.txt中的编译规则。
add_executable(turtle_tf_broadcaster src/turtle_tf_broadcaster.cpp)
target_link_libraries(turtle_tf_broadcaster ${catkin_LIBRARIES})add_executable(turtle_tf_listener src/turtle_tf_listener.cpp)
target_link_libraries(turtle_tf_listener ${catkin_LIBRARIES})
编译并运行:
这里用到了节点名重映射,因为一个ROS中的节点名是不能重复的,但是同一个程序要针对两只乌龟运行两次,那么这样就会导致出现对同一个节点名操作两次的结果,所以要用重映射分别来处理,而不是对一个来处理。
十五、launch文件的使用方法
1.launch文件
通过XML文件实现多节点的配置和启动(可自动启动ROS Master)
node代表一个ROS节点,而用一个launch就可以启动所有的节点,不需要单一的一个一个去分别启动。
2.核心语法
:launch文件中的根元素采用标签定义。
:表示配置结束。
:启动节点。
<node pkg="package-name" type="executable-name" name="node-name"/>
pkg:节点所在的功能包的名称
type:节点的可执行文件名称
name:节点运行时的名称
output、respawn、required、ns、args
如上图,对于同一个执行文件,在执行的时候需要针对两个不同对象执行同一个程序,这时候如果不更改节点名,它就会对同一个节点操作,那么最终结果也只有第一个对象改变。所以需要初始化两个节点名,这样虽然执行的程序是同一个,但是执行的对象有两个,互不冲突。
param:保存一个参数的名称和值。
rosparam:保存一个参数文件到ROS服务器里面。
arg:在launch内部定义一个变量,这个变量仅限于在launch内部使用。
通过上述最下面两行代码,就可以调用arg定义的局部变量。
重映射:利用remap把ROS中的计算图资源重命名。
注意:重映射后,原来的名字就没有了。
嵌套:可以将一个launch文件分成不同的文件,在同样一个文件中包含就可以了。
simple.launch文件
<launch><node pkg="learning_topic" type="person_subscriber" name="talker" output="screen"/><node pkg="learning_topic" type="person_publisher" name="listener" output="screen"/>
</launch>//output是输出到哪
//节点是可执行文件,节点名称是name里面的,虽然名字改变了,但是这个节点执行的还是同一个文件,完成相同操作
roslaunch learning_launch simple.launch
运行launch文件。
turtlesim_parameter_config.launch文件
<launch><param name="/turtle_number" value="2"/><node pkg="turtlesim" type="turtlesim_node" name="turtlesim_node">//这里缩进了,并且写到了node里面,这是在这个节点里面配置参数<param name="turtle_name1" value="Tom"/><param name="turtle_name2" value="Jerry"/><rosparam file="$(find learning_launch)/config/param.yaml" command="load"/>//$(find learning_launch)/config/param.yaml是找到learning_launch功能包,找到里面的param.yaml文件</node><node pkg="turtlesim" type="turtle_teleop_key" name="turtle_teleop_key" output="screen"/></launch>
param.yaml文件
A: 123
B: "hello"group:C: 456D: "hello"
注意:C和D之前不能用制表符tab,必须用空格键。否则会出错。
/turtlesim_node/turtle_name2的前缀代表这个是turtlesim_node这个节点的参数。
start_tf_demo_c++.launch文件
<launch><!-- Turtlesim Node--><node pkg="turtlesim" type="turtlesim_node" name="sim"/><node pkg="turtlesim" type="turtle_teleop_key" name="teleop" output="screen"/><node pkg="learning_tf" type="turtle_tf_broadcaster" args="/turtle1" name="turtle1_tf_broadcaster"/><node pkg="learning_tf" type="turtle_tf_broadcaster" args="/turtle2" name="turtle2_tf_broadcaster"/>//args是配置启动文件里面的参数<node pkg="learning_tf" type="turtle_tf_listener" name="listener"/></launch>
turtlesim_remap.launch文件
<launch><include file="$(find learning_launch)/launch/simple.launch"/><node pkg="turtlesim" type="turtlesim_node" name="turtlesim_node"><remap from="/turtle1/cmd_vel" to="/cmd_vel"/></node>
</launch>//<node></node>分别代表节点定义的开始和结束,如果要更改其它参数,则第一个《node》后面不用加/,在《/node》这里的/会自动完成定义
十六、常用可视化工具的使用
rqt_console:用于日志的接收。
(上面接收到的日志是因为海龟撞到墙而发出的)
rqt_plot:用于对于节点参数的读取。
(这里是对海龟速度的一个读取。)
rqt_image_view:摄像头图像的显示。
rqt:所有插件的一个集合,可以在这里面使用其它插件。
plugins里面是其它rqt插件。
Rviz:机器人开发的可视化平台。
Gazebo:一款功能强大的三维物理仿真平台。