【Autolabor初级教程】ROS机器人入门
背景
概念
作用
定义 action 文件
创建工作空间
$ mkdir -p demo06_ws/src
$ cd demo06_ws
$ catkin_make
$ code .
右键 src 创建功能包 demo6_ws,并导入依赖: roscpp rospy std_msgs actionlib actionlib_msgs
功能包下新建 action 目录,然后新建 AddInts.action 文件
# 目标值
int32 num
---
# 最终结果
int32 result
---
# 连续反馈
float64 progress_bar
编辑配置文件 CMakeLists.txt
find_package(catkin REQUIRED COMPONENTS
actionlib
actionlib_msgs
roscpp
rospy
std_msgs
)
## Generate actions in the 'action' folder
add_action_files(
FILES
AddInts.action
)
## Generate added messages and services with any dependencies listed here
generate_messages(
DEPENDENCIES
actionlib_msgs
std_msgs
)
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES demo01_action
CATKIN_DEPENDS actionlib actionlib_msgs roscpp rospy std_msgs
# DEPENDS system_lib
)
需求
实现流程
1. vscode 配置
{
"configurations": [
{
"browse": {
"databaseFilename": "",
"limitSymbolsToIncludedHeaders": true
},
"includePath": [
"/opt/ros/noetic/include/**",
"/usr/include/**",
"/home/yxd/ros_test/demo06_ws/devel/include/**" // 配置 head 文件的路径
],
"name": "ROS",
"intelliSenseMode": "gcc-x64",
"compilerPath": "/usr/bin/gcc",
"cStandard": "c11",
"cppStandard": "c++17"
}
],
"version": 4
}
2. 服务端实现
// action01_server.cpp
#include "ros/ros.h"
#include "actionlib/server/simple_action_server.h"
#include "demo01_action/AddIntsAction.h"
typedef actionlib::SimpleActionServer<demo01_action::AddIntsAction> Server;
// 请求处理(a.解析提交的目标值;b.产生连续反馈;c.最终结果响应) ---回调函数
void cb(const demo01_action::AddIntsGoalConstPtr &goal, Server* server){
// a.解析提交的目标值
int num = goal->num;
ROS_INFO("目标值:%d", num);
// b.产生连续反馈
int result = 0;
ros::Rate rate(10); // 通过频率设置休眠时间
for (int i = 1; i <= num; i++) {
// 累加
result += i;
// 产生连续反馈,组织连续数据并发布
demo01_action::AddIntsFeedback feedback;
feedback.progress_bar = i / (double)num;
server->publishFeedback(feedback);
rate.sleep();
}
// c.最终结果响应
demo01_action::AddIntsResult r;
r.result = result;
server->setSucceeded(r);
ROS_INFO("最终结果:%d", r.result);
}
int main(int argc, char *argv[]) {
setlocale(LC_ALL, "");
ROS_INFO("action服务端实现");
// 2.初始化ROS节点;
ros::init(argc, argv, "AddInts_server");
// 3.创建NodeHandle;
ros::NodeHandle nh;
// 4.创建action服务对象;
/*SimpleActionServer(ros::NodeHandle n,
std::string name,
boost::function execute_callback,
bool auto_start)
参数 1:NodeHandle
参数 2:话题名称
参数 3:回调函数
参数 4:是否自动启动
*/
// actionlib::SimpleActionServer server(....);
Server server(nh, "addInts", boost::bind(&cb, _1, &server), false);
server.start(); // 若上行代码自动启动设置为 false,则需手动调用 start 函数启动服务
// 5.处理请求,产生反馈与响应;
// 6.spin().
ros::spin();
return 0;
}
3. 客户端实现
// action02_client.cpp
#include "ros/ros.h"
#include "actionlib/client/simple_action_client.h"
#include "demo01_action/AddIntsAction.h"
typedef actionlib::SimpleActionClient<demo01_action::AddIntsAction> Client;
// 处理最终结果
void done_cb(const actionlib::SimpleClientGoalState &state, const demo01_action::AddIntsResultConstPtr &result) {
if (state.state_ == state.SUCCEEDED)
{
ROS_INFO("最终结果:%d",result->result);
} else {
ROS_INFO("任务失败!");
}
}
// 服务已经激活
void active_cb() {
ROS_INFO("服务已经被激活....");
}
// 处理连续反馈
void feedback_cb(const demo01_action::AddIntsFeedbackConstPtr &feedback){
ROS_INFO("当前进度:%.2f",feedback->progress_bar);
}
int main(int argc, char *argv[]) {
setlocale(LC_ALL,"");
// 2.初始化ROS节点;
ros::init(argc,argv,"AddInts_client");
// 3.创建NodeHandle;
ros::NodeHandle nh;
// 4.创建action客户端对象;
// SimpleActionClient(ros::NodeHandle & n, const std::string & name, bool spin_thread = true)
// actionlib::SimpleActionClient client(nh,"addInts");
Client client(nh,"addInts",true);
//等待服务启动
client.waitForServer();
// 5.发送目标,处理反馈以及最终结果;
/*
void sendGoal(const demo01_action::AddIntsGoal &goal,
boost::function done_cb,
boost::function active_cb,
boost::function feedback_cb)
*/
demo01_action::AddIntsGoal goal;
goal.num = 10;
client.sendGoal(goal,&done_cb,&active_cb,&feedback_cb);
// 6.spin().
ros::spin();
return 0;
}
4. 编辑配置文件
add_executable(action01_server src/action01_server.cpp)
add_executable(action02_client src/action02_client.cpp)
add_dependencies(action01_server ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
add_dependencies(action02_client ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(action01_server
${catkin_LIBRARIES}
)
target_link_libraries(action02_client
${catkin_LIBRARIES}
)
5. 执行并查看结果
$ roscore
# 服务端启动
$ source devel/setup.bash
$ rosrun demo01_action action01_server
[ INFO] [1697618181.712584129]: action服务端实现
[ INFO] [1697618204.874229874]: 目标值:10
[ INFO] [1697618205.877111637]: 最终结果:55
# 客户端启动
$ source devel/setup.bash
$ rosrun demo01_action action02_client
[ INFO] [1697618204.874318234]: 服务已经被激活....
[ INFO] [1697618204.876057963]: 当前进度:0.10
[ INFO] [1697618204.979878251]: 当前进度:0.20
[ INFO] [1697618205.088223356]: 当前进度:0.30
[ INFO] [1697618205.177005844]: 当前进度:0.40
[ INFO] [1697618205.276733595]: 当前进度:0.50
[ INFO] [1697618205.384083330]: 当前进度:0.60
[ INFO] [1697618205.480721833]: 当前进度:0.70
[ INFO] [1697618205.578354465]: 当前进度:0.80
[ INFO] [1697618205.677074995]: 当前进度:0.90
[ INFO] [1697618205.776672717]: 当前进度:1.00
[ INFO] [1697618205.877300477]: 最终结果:55
参数服务器的数据被修改时,如果节点不重新访问,那么就不能获取修改后的数据
一些特殊场景下,要求做到参数动态获取,也即参数一旦修改,能够通知节点参数已经修改并读取修改后的数据
在 ROS 中针对这种场景已经给出的解决方案:dynamic reconfigure 动态配置参数
需求
1. 新建功能包
2. 新建 cfg 文件夹并添加 dr.cfg 文件
#! /usr/bin/env python
# 1.导包
from dynamic_reconfigure.parameter_generator_catkin import *
PACKAGE = "demo02_dr"
# 2.创建生成器
gen = ParameterGenerator()
# 3.向生成器添加若干参数
#add(name, paramtype, level, description, default=None, min=None, max=None, edit_method="")
gen.add("int_param", int_t, 0, "int", 50, 0, 100)
gen.add("double_param", double_t, 0, "double", 1.57, 0, 3.14)
gen.add("string_param", str_t, 0, "string", "hello world ")
gen.add("bool_param", bool_t, 0, "bool", True)
many_enum = gen.enum([gen.const("small", int_t, 0, "a small size"),
gen.const("mediun", int_t, 1, "a medium size"),
gen.const("big", int_t, 2, "a big size")
], "a car size set")
gen.add("list_param", int_t, 0, "list", 0, 0, 2, edit_method = many_enum)
# 4.生成中间文件并退出
exit(gen.generate(PACKAGE, "dr_node", "dr"))
3. 配置 CMakeLists.txt
generate_dynamic_reconfigure_options(
cfg/dr.cfg
)
4. 编译
1. 服务器代码实现
// demo01_dr_server
#include "ros/ros.h"
#include "dynamic_reconfigure/server.h"
#include "demo02_dr/drConfig.h"
void cb(demo02_dr::drConfig &config, uint32_t level){
ROS_INFO("动态参数解析数据 : %d, %.2f, %d, %s, %d",
config.int_param,
config.double_param,
config.bool_param,
config.string_param.c_str(),
config.list_param
);
}
int main(int argc, char *argv[]) {
setlocale(LC_ALL,"");
// 2.初始化 ros 节点
ros::init(argc,argv,"dr");
// 3.创建服务器对象
dynamic_reconfigure::Server<demo02_dr::drConfig> server;
// 4.创建回调对象(使用回调函数,打印修改后的参数)
dynamic_reconfigure::Server<demo02_dr::drConfig>::CallbackType cbType;
cbType = boost::bind(&cb, _1, _2);
// 5.服务器对象调用回调对象
server.setCallback(cbType);
// 6.spin()
ros::spin();
return 0;
}
2. 编辑配置文件
add_executable(demo01_dr_server src/demo01_dr_server.cpp)
...
add_dependencies(demo01_dr_server ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
...
target_link_libraries(demo01_dr_server
${catkin_LIBRARIES}
)
3. 执行
$ roscore
$ rosrun demo02_dr demo01_dr_server
$ rosrun rqt_reconfigure rqt_reconfigure
pluginlib:插件库,所谓插件就是可插拔的组件
概念
作用
需求
1. 准备工作
{
"configurations": [
{
"browse": {
"databaseFilename": "",
"limitSymbolsToIncludedHeaders": true
},
"includePath": [
"/opt/ros/noetic/include/**",
"/usr/include/**",
"/home/yxd/ros_test/demo07_ws/devel/include/**" //配置 head 文件的路径
],
"name": "ROS",
"intelliSenseMode": "gcc-x64",
"compilerPath": "/usr/bin/gcc",
"cStandard": "c11",
"cppStandard": "c++17"
}
],
"version": 4
}
2. 创建基类
// polygon_base.h
#ifndef POLYGON_BASE_H_
#define POLYGON_BASE_H_
namespace polygon_base {
class RegularPolygon {
public:
virtual void initialize(double side_length) = 0;
virtual double area() = 0;
virtual ~RegularPolygon(){}
protected:
RegularPolygon(){}
};
};
#endif
3. 创建插件(类)
#ifndef POLYGON_PLUGINS_H_
#define POLYGON_PLUGINS_H_
#include
#include
// 创建了正方形与三角形两个派生类继承基类
namespace polygon_plugins {
class Triangle : public polygon_base::RegularPolygon {
public:
Triangle(){}
void initialize(double side_length) {
side_length_ = side_length;
}
double area() {
return 0.5 * side_length_ * getHeight();
}
double getHeight() {
return sqrt((side_length_ * side_length_) - ((side_length_ / 2) * (side_length_ / 2)));
}
private:
double side_length_;
};
class Square : public polygon_base::RegularPolygon {
public:
Square(){}
void initialize(double side_length) {
side_length_ = side_length;
}
double area() {
return side_length_ * side_length_;
}
private:
double side_length_;
};
};
#endif
4. 注册插件(类)
#include // pluginlib 宏,可以注册插件类
#include
#include
// 参数 1:派生类 参数 2:基类
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Triangle, polygon_base::RegularPolygon)
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Square, polygon_base::RegularPolygon)
5. 构建插件库
include_directories(
include
${catkin_INCLUDE_DIRS}
)
add_library(polygon_plugins
src/polygon_plugins.cpp
)
至此,可调用 catkin_make 编译,编译完成后,在工作空间 /devel/lib 目录下,会生成相关的 .so 动态链接库文件
<library path = "lib/libpolygon_plugins">
<class type = "polygon_plugins::Triangle" base_class_type = "polygon_base::RegularPolygon">
<description>This is a triangle plugin.description>
class>
<class type = "polygon_plugins::Square" base_class_type = "polygon_base::RegularPolygon">
<description>This is a square plugin.description>
class>
library>
<export>
<demo03_plugin plugin = "${prefix}/polygon_plugins.xml" />
export>
编译后,可以调用 rospack plugins --attrib=plugin demo03_plugin 命令查看配置是否正常,如无异常,会返回 6.1 中编写的 polygon_plugins.xml 文件的完整路径,这意味着插件已经正确的集成到了 ROS 工具链
// polygon_loader.cpp
#include // 类加载器相关的头文件
#include
int main(int argc, char** argv) {
// 创建类加载器
// 参数 1:基类功能包名称 参数 2:基类全限定名称
pluginlib::ClassLoader<polygon_base::RegularPolygon> poly_loader("xxx", "polygon_base::RegularPolygon");
try {
// 创建插件类实例
// 参数:插件类全限定名称
boost::shared_ptr<polygon_base::RegularPolygon> triangle = poly_loader.createInstance("polygon_plugins::Triangle");
triangle->initialize(10.0);
boost::shared_ptr<polygon_base::RegularPolygon> square = poly_loader.createInstance("polygon_plugins::Square");
square->initialize(10.0);
ROS_INFO("Triangle area: %.2f", triangle->area());
ROS_INFO("Square area: %.2f", square->area());
} catch(pluginlib::PluginlibException& ex) {
ROS_ERROR("The plugin failed to load for some reason. Error: %s", ex.what());
}
return 0;
}
add_executable(polygon_loader src/polygon_loader.cpp)
target_link_libraries(polygon_loader
${catkin_LIBRARIES}
)
$ source ./devel/setup.bash
$ rosrun demo03_plugin polygon_loader
[ INFO] [WallTime: 1279658450.869089666]: Triangle area: 43.30
[ INFO] [WallTime: 1279658450.869138007]: Square area: 100.00
ROS 通信是基于 Node (节点) 的,Node 使用方便、易于扩展,可以满足 ROS 中大多数应用场景,但是也存在一些局限性,由于一个 Node 启动之后独占一根进程,不同 Node 之间数据交互其实是不同进程之间的数据交互,当传输类似于图片、点云的大容量数据时,会出现延时与阻塞的情况
概念
作用
1. 案例简介
2. nodelet 基本使用语法
nodelet load pkg/Type manager - Launch a nodelet of type pkg/Type on manager manager
nodelet standalone pkg/Type - Launch a nodelet of type pkg/Type in a standalone node
nodelet unload name manager - Unload a nodelet a nodelet by name from manager
nodelet manager - Launch a nodelet manager node
3. 内置案例调用
<launch>
<node pkg = "nodelet" type = "nodelet" name = "mymanager" args = "manager" output = "screen" />
<node pkg = "nodelet" type = "nodelet" name = "n1" args = "load nodelet_tutorial_math/Plus mymanager" output = "screen">
<param name = "value" value = "100" />
node>
<node pkg = "nodelet" type = "nodelet" name = "n2" args = "load nodelet_tutorial_math/Plus mymanager" output = "screen">
<param name = "value" value = "-50" />
<remap from = "/n2/in" to = "/n1/out" />
node>
launch>
$ source ./devel/setup.bash
$ rosrun demo04_nodelet test01_nodelet.launch
4. 执行
$ rostopic pub -r 10 /n1/in std_msgs/Float64 "data: 10.0"
$ rostopic echo /n2/out
data: 60.0
---
...
nodelet 本质也是插件,实现流程与插件实现流程类似,并且不需要自定义接口,也不需要使用类加载器加载插件类
需求
1. 准备
2. 创建插件类并注册插件
#include "nodelet/nodelet.h"
#include "pluginlib/class_list_macros.h"
#include "ros/ros.h"
#include "std_msgs/Float64.h"
namespace nodelet_demo_ns {
class MyPlus: public nodelet::Nodelet {
public:
MyPlus(){
value = 0.0;
}
void onInit(){
// 获取 NodeHandle
ros::NodeHandle& nh = getPrivateNodeHandle();
// 从参数服务器获取参数
nh.getParam("value",value);
// 创建发布与订阅对象
pub = nh.advertise<std_msgs::Float64>("out",100);
sub = nh.subscribe<std_msgs::Float64>("in",100,&MyPlus::doCb,this);
}
// 回调函数
void doCb(const std_msgs::Float64::ConstPtr& p){
double num = p->data;
// 数据处理
double result = num + value;
std_msgs::Float64 r;
r.data = result;
// 发布
pub.publish(r);
}
private:
ros::Publisher pub;
ros::Subscriber sub;
double value;
};
}
PLUGINLIB_EXPORT_CLASS(nodelet_demo_ns::MyPlus, nodelet::Nodelet)
3. 构建插件库
add_library(mynodeletlib
src/myplus.cpp
)
target_link_libraries(mynodeletlib
${catkin_LIBRARIES}
)
编译后,会在工作空间 /devel/lib/ 生成文件:libmynodeletlib.so
4. 使插件可用于 ROS 工具链
4.1 配置 xml
<library path = "lib/libmynodeletlib">
<class name = "demo04_nodelet/MyPlus" type = "nodelet_demo_ns::MyPlus" base_class_type = "nodelet::Nodelet" >
<description>hellodescription>
class>
library>
4.2 导出插件
<export>
<nodelet plugin = "${prefix}/my_plus.xml" />
export>
5. 执行
<launch>
<node pkg = "nodelet" type = "nodelet" name = "my" args = "manager" output="screen" />
<node pkg = "nodelet" type = "nodelet" name = "p1" args = "load demo04_nodelet/MyPlus my" output = "screen">
<param name = "value" value = "100" />
<remap from = "/p1/out" to = "con" />
node>
<node pkg = "nodelet" type = "nodelet" name = "p2" args = "load demo04_nodelet/MyPlus my" output = "screen">
<param name = "value" value = "-50" />
<remap from = "/p2/in" to = "con" />
node>
launch>
$ rostopic pub -r 10 /p1/in std_msgs/Float64 "data: 10.0"
$ rostopic echo /p2/out
data: 60.0
---
...