目录
一、节点与节点管理器
二、通信方式
三、参数
全局共享的字典,里面存储的是整个全局所需要用到的参数,适合存储静态,非二进制类型。
四、文件系统
五、ROS命令行工具
- 启动ROS Master:
bash
$ roscore
2.启动小海龟仿真器:
bash
$ rosrun turtlesim turtlesim_node
3.启动海龟控制节点:
bash
$ rosrun turtlesim turtle_teleop_key
注意:以上三个步骤分别需要在三个不同的终端中执行。
bash
$ rqt_graph
这一代码用于显示系统计算图。
控制器将数据打包发给订阅了相关话题的节点,该节点接收到数据后会执行对应操作。
与节点相关:
bash
rosnode //显示节点信息的指令。
rosnode list //列出节点
rosnode info 节点 //查看具体节点的信息
与话题相关:
bash
rostopic //显示话题相关信息
rostopic list //列出话题
rostopic pub 话题/输入指令的数据结构 "具体数据" //发送话题的消息数据内容
rostopic pub -r 话题 //-r表示频率,代表一秒发送多少次
rosmsg show geometry_msgs/twist //查看这一指令的数据结构
与服务相关:
bash
rosservice list //列出服务
rosservice call 服务名字 按tab键补全 //访问这一服务 //例如,访问/spawn,用于产生新的海龟,不过新海龟只能用
rostopic pub操作运动
rosservice call /spawn tab键补全
话题记录工具:
bash
rosbag record -a -O cmd_record //话题记录。-a表示记录所有数据,-O表示把记录的数据压缩成压缩包
rosbag play cmd_record.bag //话题复现,即让仿真器再次完成之前记录数据代表的操作
六、创建工作空间与功能包
1)工作空间
存放工程开发相关文件的文件夹。
- src:代码空间
- build:编译空间
- devel:开发空间
- install:安装空间
bash
//创建工作空间
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)创建功能包
bash
//创建功能包
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的代码实现
bash
cd ~/catkin_ws/src
catkin_create_pkg learning_topic roscpp rospy std_msgs geometry_msgs turtlesim
创建发布者代码:
C++代码:
cpp
/**
* 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,队列长度10
ros::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++一样重编译成可执行文件,直接就可以执行,但需要勾选权限,详见第八节末尾注意)
python
#1 /usr/bin/env python
# -*- coding: utf-8 -*-
#该例程将发布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("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注册节点信息,包括发布的话题名和话题中的消息类型。
- 创建消息数据。
- 按照一定的频率循环发送。
配置发布者代码编译规则:
bash
add_executable(velocity_publisher src/velocity_publisher.cpp) //将后一个.cpp文件编译成前面那个可执行文件 //转换成可执行文件后,才能执行文件相应操作,.cpp文件只是代码要想电脑执行则必须转换成可执行文件
target_link_libraries(velocity_publisher ${catkin_LIBRARIES}) //把可执行文件和ROS的一些库作链接
编译并运行发布者:
bash
//回到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++代码:
cpp
/**
*该例程将订阅/turtle1/pose话题,消息类型turtlesim::Pose
*/
#include <ros/ros.h>
#include "turtlesim/Pose.h"
//接收到订阅的信息后,会进入信息回调函数
//ConsPtr&是长指针类型,msg会指向收到的消息
void poseCallback(const turtlesim::Pose::ConstPtr& msg)
{
//将接收到的信息打印出来,类似与printf
ROS_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++一样重编译成可执行文件,直接就可以执行,但需要勾选权限,详见第八节末尾注意)
python
#!/user/bin/env python
# -*- coding: utf-8 -*-
#该例程将订阅/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', n=anonymous=True)
#创建一个Subscriber,订阅名为/turtle1/pose的topic,注册回调函数poseCallback,打印相应接收到的信息
rospy.Subscriber(/turtle1/pose", Pose, poseCallback)
#循环等待回调函数
rospy.spin()
#上面两个是定义函数,下面这个是用于执行主程序,类似C中的main函数
if __name__ == '__main__':
pose_subscriber()
配置订阅者代码编译规则:
bash
add_executable(pose_subscriber src/pose_subscriber.cpp)
//将后一个.cpp文件编译成前面那个可执行文件
target_link_libraries(pose_subscriber ${catkin_LIBRARIES})
//把可执行文件和ROS的一些库作链接
后续操作同上发布者一样。
编译并运行订阅者:
bash
//回到catkin_ws的根目录下执行
cd ~/catkin_ws
catkin_make
source devel/setup.bash
roscore
rosrun turtlesim turtlesim_node
rosrun learning_topic pose_subscriber
注意:若是Python作为可执行文件,则需要在文件属性的权限栏中勾选"允许作为程序可执行文件"。
九、自定义话题并使用
创建一个msg文件夹,存放msg文件。
在Person.msg文件中输入蓝色框框里面的内容。
在package。xml中输入以下内容:
添加Person.msg为编译的接口,当编译的时候会自动识别并针对文件作为编译的接口。
添加编译Person.msg时需要的ros库。
自定义话题的C++代码:
cpp
/**
* 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,队列长度10
ros::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;
}
cpp
/**
*该例程将订阅/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)
{
//将接收到的信息打印出来,类似与printf
ROS_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中的编译规则:
bash
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++代码:
cpp
/**
*该例程将请求/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的service
ros::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代码:
python
#1 /usr/bin/env python
# -*- coding: utf-8 -*-
#该例程将请求/spawn服务,服务数据类型turtlesim::Spawn
import sys
import rospy
from turtlesim.srv import Spawn
def turtle_spawn():
#ROS节点初始化
rospy.init_node('turtle_spawn')
#发现/spawn服务后,创建一个服务客户端,连接名为/spawn的service
rospy.wait_for_service('/spawn')
try:
#客户端是add_turtle,服务连接名是/spawn,数据类型是Spawn
add_turtle = rospy.ServiceProxy('/spawn',Spawn)
#请求服务调用,输入请求数据,直接将指定数据添加到客户端后面
#调用程序让turtle2小海龟在所给数据指定位置处出现
#add_turtle等待海龟是否产生成功,然后返回值给response,产生成功返回海龟名,不成功则执行except里面的代码
response = add_turtle(2.0,2.0,0.0,"turtle2")
return response.name
except rospy.ServiceException, e:
print "Service call failed: %s"%e
if __name__ == "__main__":
#服务调用并显示调用结果
print "Spawn turtle successfully [name:%s]" %(turtle_spawn())
实现一个客户端的流程:
- 初始化ROS节点;
- 创建一个Client实例;
- 发布服务请求数据;
- 等待Server处理之后的应答结果;
2.配置CMakeLists.txt的编译规则
- 设置需要编译的代码和生成可执行文件;
- 设置链接库;
bash
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++代码:
cpp
/**
*该例程将执行/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,注册回调函数commandCallback
ros::ServiceServer command_service = n.advertiseService("/turtle_command",commandCallback);
//创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10
turtle_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中添加功能依赖包
bash
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
- 在CMakeLists.txt中添加编译选项
bash
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
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的service
ros::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发送的数据srv
ROS_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代码:
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中添加
bash
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.创建功能包
bash
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
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,再发送空数据给service
clear_background.call(srv);
sleep(1);
return 0;
}
ROS中客户端发送命令后往往会跟着需要发送到的数据,这里不需要发送数据,所以定义数据类型为空。
流程:
- 初始化ROS节点
- get函数获取参数
- set函数设置参数
CMakeLists.txt中编译
bash
add_executable(parameter_config src/parameter_config.cpp)
target_link_libraries(parameter_config ${catkin_LIBRARIES})
十四、ROS中的坐标管理系统
1.机器人坐标变换例程
bash
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
运行第一行代码时不知道自己的版本号,可以用下面代码查询:
bash
rosversion -d
运行第二行代码可能会遇到以下问题:
这是缺少功能包,输入以下指令安装即可:
bash
sudo apt install python-is-python3
正常显示结果:
运行第三行代码时可能会有以下报错:
直接输入:
bash
sudo gedit /opt/ros/noetic/lib/tf/view_frames /opt/ros/noetic/lib/tf/view_frames是上面报错的路径。
等待这个文件打开,在88行如下图所示输入:
成功结果输出如下:
运行成功后在根目录下会有 以下两个文件:
注意:我这里是在根目录下运行的这行代码,所以默认存放到根目录下。
生成文件:
world是你这个海龟界面的基本坐标系,以左下角为原点。
turtle1:是第一只海龟的坐标。
turtel2:是第二只海龟的坐标。
打开三维坐标图:(先运行roscore)
bash
rosrun rviz rviz -d `rospack find turtle_tf` /rviz/turtle_rviz.rviz
打不开先安装一下:
bash
sudo apt-get install ros-noetic-rviz
2.tf坐标系广播与监听的编程实现
创建功能包
bash
cd ~/catkin_ws/src
catkin_create_pkg learning_tf roscpp rospy tf turtlesim
如何实现一个tf广播器:
- 定义TF广播器
- 创建坐标变换值
- 发布坐标变换
C++代码:
cpp
/*
*该例程产生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监听器
- 查找坐标变换
cpp
/**
*该例程监听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;
//请求产生turtle2
ros::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中的编译规则。
bash
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文件中的根元素采用标签定义。
:表示配置结束。
:启动节点。
bash
<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文件
XML
<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文件
XML
<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文件
XML
A: 123
B: "hello"
group:
C: 456
D: "hello"
注意:C和D之前不能用制表符tab,必须用空格键。否则会出错。
/turtlesim_node/turtle_name2的前缀代表这个是turtlesim_node这个节点的参数。
start_tf_demo_c++.launch文件
XML
<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文件
XML
<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:一款功能强大的三维物理仿真平台。