ROS发展史

2007年诞生于丝塔芙STAIR项目Morgan Quigley,2008年Willow Garage接手,2010年ROS1.0发布,2011年Turtle Bot发布,2012年第一届ROScon,2013年OSRF接管,2014年ROS Indigo发布,2016年ROS Kinetic发布,2017年ROS2.0 Ardent发布,2018年ROS Melodic发布

微软将ROS引入Windows10,支持基于硬件加速的Windows机器学习、Azure Conitive服务、Azure IoT云服务、Visual Studio等

AWS RoboMaker扩展了ROS功能,可以轻松实现大规模开发、测试和部署智能机器人应用程序,可以连接到云服务

ROS是什么:通信机制+开发工具+应用功能+生态系统

提高机器人研发中的软件复用率

ROS中的通信机制:松耦合分布式通信

ROS中的开发工具:TF坐标变换、QT工具箱、命令行&编译器、Rviz、Gazebo

ROS中的应用功能:Navigation、SLAM、Movelt!

ROS中的生态系统:

  • 发行版(Distribution):ROS发行版包括一系列带有版本号、可以直接安装的功能包
  • 软件源(Repository):ROS依赖于共享网络上的开源代码,不同的组织机构可以开发或者共享自己的机器人软件
  • ROS wiki:记录ROS信息文档的主要论坛
  • 邮件列表(Mailing list):交流ROS更新的主要渠道,同时也可以交流ROS开发的各种疑问
  • ROS Answers:咨询ROS相关问题的网站
  • 博客(Blog):发布ROS社区中的新闻、图片、视频(http://www.ros.org/news)

ROS核心概念

通信机制:

通信机制

节点与节点管理器:

  • 节点(Node):执行单元
    • 执行具体任务的进行、独立运行的可执行文件
    • 不同节点可使用不同的编程语言,各分布式运行在不同的主机
    • 节点在系统中的名称必须是唯一的
  • 节点管理器(ROS Master):控制中心
    • 为节点提供命名和注册服务
    • 跟踪和记录话题/服务通信,辅助节点相互查找、建立连接
    • 提供参数服务器,节点使用此服务器存储和检索运行时的参数
  • 话题(Topic):异步通信机制
    • 节点间用来传输数据的重要总线
    • 使用发布/订阅模型,数据由发布者传输到订阅者,同一个话题的订阅者或发布者可以不唯一
  • 消息(Message):话题数据
    • 具有一定的类型和数据结构,包括ROS提供的标准类型和用户自定义类型
    • 使用编程语言无关的.msg文件定义,编译过程中生成对应的代码文件
  • 服务(Service):同步通信机制
    • 使用客户端/服务器(C/S)模型,客户端发送请求数据,服务器完成处理后返回应答数据
    • 使用编程语言无关的.srv文件定义请求和应答数据结构,编译过程中生成对应的代码文件
话题 服务
同步性 异步 同步
通信模型 发布/订阅 服务器/客户端
底层协议 ROSTCP/ROSUDP ROSTCP/ROSUDP
反馈机制
缓冲区
实时性
节点关系 多对多 一对多(一个Server)
适用场景 数据传输 逻辑处理
  • 参数(Parameter):全局共享字典
    • 可通过网络访问的共享、多变量字典
    • 节点使用此服务器来存储和检索运行时的参数
    • 适合存储静态、非二进制的配置参数,不适合存储动态配置的数据

文件系统:

  • 功能包(Package):ROS软件中的基本单元,包含节点源码、配置文件、数据定义等
  • 功能包清单(Package manifest):记录功能包的基本信息,包括作者信息、许可信息、依赖选项、编译标志等
  • 元功能包(Meta Packages):组织多个用于同一目的功能包

ROS命令行工具的使用

常用命令:

  • roscore:启动ros服务

  • rosrun:运行在某个功能包中的指令

  • rqt_graph:显示系统计算图的工具

  • rosnode:

    • rosnode list:列出系统中所有节点
    • rosnode info …:查看节点信息(Publication发布那些话题、Subscription订阅那些话题、Services服务)
  • rostopic:

    • rostopic list:话题指令列表

    • rostopic pub -r 10 /turtle1/cmd_vel geometry_msgs/Twist “linear:

      x: 1.0​

      y: 0.0

      z: 0.0

      angular:

      x: 0.0

      y: 0.0

      z: 0.0”

  • rosmsg show geometry_msgs/Twist:查看数据结构

  • rosservice

    • roservice list:查看服务内容

    • roservice call /spawn “x: 2.0

      y: 0.0

      theta: 0.0

      name: ‘turtle2’”:创建一个新的海龟

  • rosbag

    • rosbag record -a -O cmd_record:记录一下数据并保存为cmd_record压缩文件
    • rosbag play cmd_record.bag:复现cmd_record压缩文件所记录的话题

创建工作空间与功能包

工作空间:工作空间(workspace)是一个存放工程开发相关文件的文件夹

  • src:代码空间(Source Space)
  • build:编译空间(Build Space)
  • devel:开发空间(Development Space)
  • install:安装空间(Install Space)

创建工作空间:

  • 创建工作空间:

    mkdir -p ~/catkin_ws/src
    cd ~/catkin_ws/src
    catkin_init_workspace
  • 编译工作空间:

    cd ~/catkin_ws/
    catkin_make
  • 设置环境变量:

    source devel/setup.bash
  • 检查环境变量:

    echo $ROS_PACKAGE_PATH

创建功能包:catkin_create_pkg [depend1] [depend2] [depend3]

# 创建功能包
cd ~/catkin_ws/src
catkin_create_pkg test_pkg std_msgs rospy roscpp

# 编译功能包
cd ~/catkin_ws
catkin_make
source ~/catkin_ws/devel/setup.bash

同一工作空间下,不允许存在同名功能包

不同工作空间下,允许存在同名功能包

  • 发布者(Publishers):发布者是ROS中的一个节点,负责将特定类型的数据发布到 ROS 话题(Topics)。它可以发布传感器数据、状态信息、命令等。其他节点可以订阅这些话题以接收发布的数据。提供数据以供其他节点使用,例如传感器数据、状态信息等
  • 订阅者(Subscribers):订阅者是ROS中的节点,用于接收和订阅发布者发布到话题上的数据。它能够订阅一个或多个话题,以获取相关的信息进行处理或响应。接收发布者发布的数据,用于执行特定的任务、处理数据或者做出相应的动作
  • 客户端(Clients):客户端是ROS服务的请求者。它能向服务服务器发送请求,并等待并接收来自服务的响应。通常用于执行特定的任务或获取特定的信息。发送请求,希望服务器执行特定任务或获取信息
  • 服务器(Servers):服务器是提供ROS服务的节点。它等待来自客户端的请求,并对这些请求做出响应。当接收到客户端请求时,服务器执行相应的任务并返回结果给客户端。接收客户端的请求,执行请求的任务并返回结果给客户端

发布者Publisher的编程实现

话题模型:

话题模型

实现一个发布者:

  • 初始化ROS节点
  • 向ROS Master注册节点信息,包括发布的话题名和话题中的消息类型
  • 创建消息数据
  • 按照一定频率循环发布消息

小海龟实验中的Publisher:

// C++
// 文件执行步骤
// 1. 将文件添加到Cmakelist.txt中, learning中的Cmakelist.txt
// 2. 进行编译catkin_make, 返回项目目录catkin_ws文件夹中执行
// 3. 添加环境变量source setup.bash, 在catkin_ws下的devel文件夹中执行
// 4. 执行命令rosrun learning_topic velocity_publisher
// 该例程将发布turtle1/cmd_vel话题, 消息类型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的资源
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 = 10;
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
# 文件执行步骤
# 1. 在learning_topic文件夹中创建scripts文件夹用于存放.py文件
# 2. 给.py文件添加执行权限
# 3. 执行命令rosrun learning_topic scripts/velocity_publisher.py
# 该例程将发布turtle1/cmd_vel话题, 消息类型geometry_msgs::Twist

# 注意使用rosrun时是使用sh解释执行, 我们需要添加下面这句话类执行解释环境
#!/usr/bin/env python

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

订阅者Subscriber的编程实现

实现一个订阅者:

  • 初始化ROS节点
  • 订阅需要的话题
  • 循环等待话题消息,接收到消息后进入回调函数
  • 在回调函数中完成消息处理
// C++

#include <ros/ros.h>
#include "turtlesim/Pose.h"

// 接收到订阅的消息后,会进入消息回调函数
void poseCallback(const turtlesim::Pose::ConstPtr& msg)
{
// 将接收到的消息打印出来
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

# 注意使用rosrun时是使用sh解释执行, 我们需要添加下面这句话类执行解释环境
#!/usr/bin/env python

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()

话题消息的定义与使用

话题模型:

自定义话题消息:

string name
uint8 sex
uint8 age

uint8 unknown = 0
uint8 male = 1
uint8 female = 2
  • 定义.msg文件

  • 在package.xml中添加功能包依赖

    message_generation

    messsage_runtime

  • 在CMakeList.txt添加编译选项

    find_package(…… message_generation)

    add_message_files(FILES Person.msg)

    generate_messages(DEPENDENCISE std_msgs)

    catkin_package(…… message_runtime)

  • 编译生成语言相关文件

// C++
// person_publisher.cpp

#include <ros/ros.h>
#include "learning_topic/Person.h"

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 = "Tom";
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;
}
// C++
// person_subscriber.cpp

#include <ros/ros.h>
#include "learning_topic/Person.h"

// 接收到订阅的消息后,会进入消息回调函数
void personInfoCallback(const learning_topic::Person::ConstPtr& msg)
{
// 将接收到的消息打印出来
ROS_INFO("Subcribe Person Info: name:%s age:%d sex:%d",
msg->name.c_str(), msg->age, msg->sex);
}

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

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

// 创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallback
ros::Subscriber person_info_sub = n.subscribe("/person_info", 10, personInfoCallback);

// 循环等待回调函数
ros::spin();

return 0;
}
# Python
# person_publisher.py
#!/usr/bin/env python

import rospy
from learning_topic.msg import Person

def velocity_publisher():
# ROS节点初始化
rospy.init_node('person_publisher', anonymous=True)

# 创建一个Publisher, 发布名为/person_info的topic, 消息类型为learing_topic::Person
person_info_pub = rospy.Publisher('/person_info', Person, queue_size=10)

# 设置循环频率
rate = rospy.Rate(10)

while not rospy.is_shutdown():
# 初始胡learning_topic::Person类型的消息
person_msg = Person()
person_msg.name = "Tom"
person_msg.age = 18
person_msg.sex = Person.male

# 发布消息
person_info_pub.publish(person_msg)
rospy.loginfo("Publish person message[%s, %d, %d]", person_msg.name,person_msg.age, person_msg.sex)

# 按照循环频率延时
rate.sleep()

if __name__ == '__main__':
try:
velocity_publisher()
except rospy.ROSInterruptException:
pass
# Python
# person_subscriber.py

#!/usr/bin/env python

import rospy
from learning_topic.msg import Person

def personInfoCallback(msg):
rospy.loginfo("Subcribe Person Info: name:%s age:%d sex:%d",
msg.name, msg.age, msg.sex)

def person_subscriber():
# ROS节点初始化
rospy.init_node('person_subscriber', anonymous=True)

# 创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallback
rospy.Subscriber("/person_info", Person, personInfoCallback)

# 循环等待回调函数
rospy.spin()

if __name__ == '__main__':
person_subscriber()

客户端Client的编程实现

服务模型:

服务模型

实现一个客户端:

  • 初始化ROS节点
  • 创建一个Client实例
  • 发布服务请求数据
  • 等待Server处理之后的应答结果
// C++

#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.response.name.c_str());

return 0;
}
# Python

#!/usr/bin/env python

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 = rospy.ServiceProxy('/spawn', Spawn)

# 请求服务调用 ,输入请求数据
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 "Spwan turtle successfully [name:%s]" %(turtle_spawn())

服务端Server的编程实现

实现一个服务器:

  • 初始化ROS节点
  • 创建Server实例
  • 循环等待服务请求,进入回调函数
  • 在回调函数中完成服务功能的处理,并反馈应答数据
// C++

#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;
}
# Python

#!/usr/bin/env python

import rospy
import thread,time
from geometry_msgs.msg import Twist
from std_srvs.srv import Trigger, TriggerResponse

pubCommand = False;
turtle_vel_pub = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size=10)

def command_thread():
while True:
if pubCommand:
vel_msg = Twist()
vel_msg.linear.x = 0.5
vel_msg.angular.z = 0.2
turtle_vel_pub.publish(vel_msg)

time.sleep(0.1)

def commandCallback(req):
global pubCommand
pubCommand = bool(1-pubCommand)

# 显示请求数据
rospy.loginfo("Publish turtle velocity command![%d]", pubCommand)

# 反馈数据
return TriggerResponse(1, "Change turtle command state!")

def turtle_command_server():
# ROS节点初始化
rospy.init_node('turtle_command_server')

# 创建一个名为/turtle_command的server,注册回调函数commandCallback
s = rospy.Service('/turtle_command', Trigger, commandCallback)

# 循环等待回调函数
print "Ready to receive turtle command."

thread.start_new_thread(command_thread, ())
rospy.spin()

if __name__ == "__main__":
turtle_command_server()

服务数据的定义与使用

自定义服务数据:

string name
uint8 age
uint8 sex

uint8 unknown = 0
uint8 male = 1
uint8 female = 2
---
string result
  • 定义.srv文件

  • 在package.xml中添加功能包依赖

    message_generation

    message_runtime

  • 在CMakeList.txt添加编译选项

    find_package(…… Message_generation)

    add_service_files(FILES Person.srv)

    generate_messages(DEPENDENCIES std_msgs)

    catkin_package(…… message_runtime)

  • 编译生成语言相关文件

// C++
// Client

#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;

// 发现/spawn服务后,创建一个服务客户端,连接名为/spawn的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 = "Tom";
srv.request.age = 20;
srv.request.sex = learning_service::Person::Request::male;

// 请求服务调用
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);

// 显示服务调用结果
ROS_INFO("Show person result : %s", srv.response.result.c_str());

return 0;
};
// C++
// Server

#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
ros::ServiceServer person_service = n.advertiseService("/show_person", personCallback);

// 循环等待回调函数
ROS_INFO("Ready to show person informtion.");
ros::spin();

return 0;
}
# Python
# Client

#!/usr/bin/env python

import sys
import rospy
from learning_service.srv import Person, PersonRequest

def person_client():
# ROS节点初始化
rospy.init_node('person_client')

# 发现/spawn服务后,创建一个服务客户端,连接名为/spawn的service
rospy.wait_for_service('/show_person')
try:
person_client = rospy.ServiceProxy('/show_person', Person)

# 请求服务调用,输入请求数据
response = person_client("Tom", 20, PersonRequest.male)
return response.result
except rospy.ServiceException, e:
print "Service call failed: %s"%e

if __name__ == "__main__":
#服务调用并显示调用结果
print "Show person result : %s" %(person_client())

# Python
# Server

#!/usr/bin/env python

import rospy
from learning_service.srv import Person, PersonResponse

def personCallback(req):
# 显示请求数据
rospy.loginfo("Person: name:%s age:%d sex:%d", req.name, req.age, req.sex)

# 反馈数据
return PersonResponse("OK")

def person_server():
# ROS节点初始化
rospy.init_node('person_server')

# 创建一个名为/show_person的server,注册回调函数personCallback
s = rospy.Service('/show_person', Person, personCallback)

# 循环等待回调函数
print "Ready to show person informtion."
rospy.spin()

if __name__ == "__main__":
person_server()

参数的使用与编程方法

参数模型:

创建功能包:

cd ~/catkin_ws/src
catkin_create_pkg learning_parameter roscpp rospy std_srvs

参数命令行使用:rosparam

  • 列出当前所有参数:rosparam list
  • 显示某个参数数值:rosparam get param_key
  • 设置某个参数值:rosparam set param_key param_value
  • 保存参数到文件:rosparam dump file_name
  • 从文件读取参数:rosparam load file_name
  • 删除参数:rosparam delete param_key

YAML参数文件:

background_b: 255
background_g: 86
background_r: 69
rosdistro: 'noetic'
roslaunch:
uris: {host_daymac__53059: 'http://daymac:53059'}
rosversion: '1.15.14'
run_id: 7f4d7d80-7926-11ee-97c1-663d7a150c7e

编程方法实现:

  • 初始化ROS节点
  • get函数获取参数
  • set函数设置参数

配置代码编译规则:

  • 设置需要编译的代码和生成的可执行文件
  • 设置链接库
// C++

#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::NodeHandle node;

// 读取背景颜色参数
ros::param::get("/turtlesim/background_r", red);
ros::param::get("/turtlesim/background_g", green);
ros::param::get("/turtlesim/background_b", blue);

ROS_INFO("Get Backgroud Color[%d, %d, %d]", red, green, blue);

// 设置背景颜色参数
ros::param::set("/turtlesim/background_r", 255);
ros::param::set("/turtlesim/background_g", 255);
ros::param::set("/turtlesim/background_b", 255);

ROS_INFO("Set Backgroud Color[255, 255, 255]");

// 读取背景颜色参数
ros::param::get("/turtlesim/background_r", red);
ros::param::get("/turtlesim/background_g", green);
ros::param::get("/turtlesim/background_b", blue);

ROS_INFO("Re-get Backgroud Color[%d, %d, %d]", red, green, blue);

// 调用服务,刷新背景颜色
ros::service::waitForService("/clear");
ros::ServiceClient clear_background = node.serviceClient<std_srvs::Empty>("/clear");
std_srvs::Empty srv;
clear_background.call(srv);

sleep(1);

return 0;
}
# Python

#!/usr/bin/env python

import sys
import rospy
from std_srvs.srv import Empty

def parameter_config():
# ROS节点初始化
rospy.init_node('parameter_config', anonymous=True)

# 读取背景颜色参数
red = rospy.get_param('/turtlesim/background_r')
green = rospy.get_param('/turtlesim/background_g')
blue = rospy.get_param('/turtlesim/background_b')

rospy.loginfo("Get Backgroud Color[%d, %d, %d]", red, green, blue)

# 设置背景颜色参数
rospy.set_param("/turtlesim/background_r", 255);
rospy.set_param("/turtlesim/background_g", 255);
rospy.set_param("/turtlesim/background_b", 255);

rospy.loginfo("Set Backgroud Color[255, 255, 255]");

# 读取背景颜色参数
red = rospy.get_param('/turtlesim/background_r')
green = rospy.get_param('/turtlesim/background_g')
blue = rospy.get_param('/turtlesim/background_b')

rospy.loginfo("Get Backgroud Color[%d, %d, %d]", red, green, blue)

# 发现/spawn服务后,创建一个服务客户端,连接名为/spawn的service
rospy.wait_for_service('/clear')
try:
clear_background = rospy.ServiceProxy('/clear', Empty)

# 请求服务调用,输入请求数据
response = clear_background()
return response
except rospy.ServiceException, e:
print "Service call failed: %s"%e

if __name__ == "__main__":
parameter_config()

ROS中的坐标管理系统

机器人中的坐标变化:

  • TF功能包能干什么?
    • 五秒钟之前,机器人头部坐标系相对于全局坐标系的关系是什么样的
    • 机器人抓取的物体相对于机器人中心坐标系的位置在哪里
    • 机器人中心坐标系相对于全局坐标系的位置在哪里
  • TF坐标变化如何实现
    • 广播TF变换
    • 监听TF变换
roslaunch turtle_tf turtle_tf_demo.launch
rosrun turtlesim turtle_teleop_key
rosrun tf view_frames

输出到当前路径下PDF文件,记录当前ros环境的TF坐标关系:rosrun tf view_frames

不断输出两个TF坐标系关系:rosrun tf tf_echo turtle1 turtle2

  • Translation:平移数据
  • Rotation:旋转数据(四元数、弧度、角度)

坐标系之间的可视化工具:rosrun rviz rviz -d`rospack find turtle_tf`/rviz/turtle_rviz.rviz

TF坐标系广播与监听的编程实现

创建功能包:

cd ~/catkin_ws/src
catkin_create_pkg learning_tf roscpp rospy tf turtlesim

实现一个TF广播器:

  • 定义TF广播器(TransformBroadcaster)
  • 创建坐标变换值
  • 发布坐标变换(sendTransform)
// C++

#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数据, 姿态相对于world的平移及旋转
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;
};
# Python

#!/usr/bin/env python

import roslib
roslib.load_manifest('learning_tf')
import rospy

import tf
import turtlesim.msg

def handle_turtle_pose(msg, turtlename):
br = tf.TransformBroadcaster()
br.sendTransform((msg.x, msg.y, 0),
tf.transformations.quaternion_from_euler(0, 0, msg.theta),
rospy.Time.now(),
turtlename,
"world")

if __name__ == '__main__':
rospy.init_node('turtle_tf_broadcaster')
turtlename = rospy.get_param('~turtle')
rospy.Subscriber('/%s/pose' % turtlename,
turtlesim.msg.Pose,
handle_turtle_pose,
turtlename)
rospy.spin()

实现一个TF监听器:

-

// C++

#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;
};
# Python

#!/usr/bin/env python

import roslib
roslib.load_manifest('learning_tf')
import rospy
import math
import tf
import geometry_msgs.msg
import turtlesim.srv

if __name__ == '__main__':
rospy.init_node('turtle_tf_listener')

listener = tf.TransformListener()

rospy.wait_for_service('spawn')
spawner = rospy.ServiceProxy('spawn', turtlesim.srv.Spawn)
spawner(4, 2, 0, 'turtle2')

turtle_vel = rospy.Publisher('turtle2/cmd_vel', geometry_msgs.msg.Twist,queue_size=1)

rate = rospy.Rate(10.0)
while not rospy.is_shutdown():
try:
(trans,rot) = listener.lookupTransform('/turtle2', '/turtle1', rospy.Time(0))
except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):
continue

angular = 4 * math.atan2(trans[1], trans[0])
linear = 0.5 * math.sqrt(trans[0] ** 2 + trans[1] ** 2)
cmd = geometry_msgs.msg.Twist()
cmd.linear.x = linear
cmd.angular.z = angular
turtle_vel.publish(cmd)

rate.sleep()

配置CMakeLists.txt中的编译规则:

  • 设置需要编译的代码和生成的可执行文件
  • 设置链接库

launch启动文件的使用方法

Launch文件:通过XML文件实现多节点的配置和启动(可自动启动ROS Master)

launch文件语法:

<launch>
<node pkg="turtlesim" name="sim1" type="turtlesim_node"/>
<node pkg="turtlesim" name="sim2" type="turtlesim_node"/>
</launch>

<launch>:launch文件中的根元素采用<launch>来定义

<node>:启动节点<node pkg="package-name" name="executable-name" type="node-name"/>

  • pkg:节点所在的功能包名称
  • type:节点的可执行文件名称
  • name:节点运行时的名称
  • output:控制节点是否需要打印日志信息到终端
  • respawn:控制节点是否需要重启
  • required:节点是否必须启动
  • ns:给节点命名空间,避免冲突
  • args:给节点输入参数

<param>/<rosparam>:设置ROS系统运行中的参数,存储在参数服务器中<param name="output_frame" value="odom"/>

  • name:参数名
  • value:参数值
  • 加载参数文件中的多个参数:<rosparam file="params.yaml" command="load" ns="params"/>

<arg>:launch文件内部的局部变量,仅限于launch文件使用<arg name="arg-name" default="arg-value"/>

  • name:参数名
  • value:参数值
  • 调用:<param name="foo" value="${arg arg-name}"/><node name="node" pkg="package" type="type" args="${arg arg-name}"/>

<remap>:重映射ROS计算图资源的命名<remap from="/turtlebot/cmd_vel" to="/cmd_vel"/>

  • from:原命名
  • to:映射之后的命名

<include>:包含其他launch文件,类似C语言中的头文件包含<include file="$(dirname)/other.launch"/>

  • file:包含的其他launch文件路径

更多标签可参见:http://wiki.ros.org/roslaunch/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>
<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>
<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" />

<node pkg="learning_tf" type="turtle_tf_listener" name="listener" />
</launch>
<launch>

<param name="/turtle_number" value="2"/>

<node pkg="turtlesim" type="turtlesim_node" name="turtlesim_node">
<param name="turtle_name1" value="Tom"/>
<param name="turtle_name2" value="Jerry"/>

<rosparam file="$(find learning_launch)/config/param.yaml" command="load"/>
</node>

<node pkg="turtlesim" type="turtle_teleop_key" name="turtle_teleop_key" output="screen"/>

</launch>

常用可视化工具的使用

Qt工具箱:

  • 日志输出工具:rqt_console
  • 计算图可视化工具:rqt_graph
  • 数据绘图工具:rqt_plot
  • 图像渲染工具:rqt_image_view

Rviz:机器人开发过程中的数据可视化界面

  • 机器人模型
  • 坐标
  • 运动规划
  • 导航
  • 点云
  • 图像

Rviz是一款三维可视化工具,可以很好的兼容基于ROS软件框架的机器人平台

  • 在rviz中,可以使用可扩展标记语言XML对机器人、周围物体等任何实物进行尺寸、质量、位置、材质、关节等属性的描述,并且在界面中呈现出来
  • 同时,rviz还可以通过图像化的方式,实时显示机器人传感器的信息、机器人的运动状态、周围环境的变化等信息
  • 总而言之,rviz通过机器人模型参数,机器人发布的传感器信息等数据,为用户进行所有可监测信息的图形化显示。用户和开发者也可以在rviz的控制下,通过按钮、滑动条、数值等方式控制机器人的行为

Gazebo:是一款功能强大的三维物理仿真平台

  • 具备强大的物理引擎
  • 高质量的图形渲染
  • 方便的编程与图形接口
  • 开源免费

其典型应用场景包括:

  • 测试机器人算法
  • 机器人的设计
  • 现实情景下的回溯测试