背景:自动驾驶知识比较缺乏,所以需要一个基本学习
环境:Ubuntu16.04 ROS-kinetic vscode
前言:本文的编程部分,假设ROS-kinetic,vscode等相关的依赖或者运行环境都已经配置成功而展开的
概要:围绕ROS的基本操作以及使用展开,重点推荐node编写
1.1什么叫ROS?
–ROS:The Robot Operating System
1.2发展历程
–2007年,起源于Stanford AI实验室,为了支持STAIR机器人而建立的交换庭(switchyard)项目。后又与Willow Garage公司的个人机器人项目(Personal Robot Program)之间合作;
–2008年,主要由Willow Garage来进行推动。随着PR2的不可思议的表现,ROS得到越来越多的关注;
–2010年,Willow Garage正式发布ROS1.0;
–2013年,Open Source Robotic Foundation接手维护;
–2016年,正式发布ROS2.0
1.3为啥使用ROS?
–点对点设计(比如一个结点读取相机数据,传送给相应的处理程序。)
–优点:可以分散实时计算的压力
–分布式设计(节点可以放在多个设备上面)
–多语言(官方支持C++、python)
–轻量级
–免费且开源
–社区完善
1.4 ROS正确认识:
–不是传统意义上的一种操作系统,而是一种系统软件框架,该框架使用了流行的面向服务(SOA)的软件技术,通过网络协议将节点间数据通信解耦(意味着,可以使用不同的语言来实现不同功能,但是他们之间如果满足协议就可以通信)。这样就能够轻松地集成不同语言不同功能的代码;
–ROS不是一种编程语言;
–ROS不仅是一个函数库,除包含客户端(Client Libraries)外,还包含一个中心服务器(Central Server)、一系列命令行工具、图形化界面工具以及编译环境;
–ROS不是集成开发环境。
节点(node):比较小的,实现特定功能的小程序
节点管理工具:就是负责管理节点的工具
参数服务器:简单点理解就是负责管理参数的工具,如读取yawl文件并保存在里面(个人当前理解)
消息(message):节点和节点之间通信的数据
主题(topic):可以通过此来获取想要的消息
服务(serve):节点暴露出来的东西
消息记录包:保存运行过程中产生的数据消息,方便后面查阅
1)消息一种publish/subscribe的方式传递
2)节点可以在给定的主题中发布/订阅消息
3)一个节点可以订阅/发布多个不同的主题
4)允许多个节点订阅/发布同一个主题
5)订阅节点和发布节点并不知道相互之间的存在
例子:
ROS master类似一个中心管理系统,管理一切在这里面注册的节点;而且,每次有且只有一个ros master
2.2 Cmake
–ROS 的编译系统
–ROS对CMake进行了拓展
–适合ROS进行大型项目,多包,多节点的场景下进行批量编译
Cmake是预编译,make是编译
2.3 Packages& catkin workspaces
packages
–packages是ROS系统中最底层最基本的组织,里面保存着各种文件库、工具、可执行文件等
Catkin workspaces
–包的顶层工作目录,一个catkin workspace 包含一个工程下面多个ros package
Package.xml http://wiki.ros.org/catkin/package.xml
–每个包的描述文件,都需要放置在包的根目录下,对包的名字/版本/作者/依赖关系进行说明。其中,最重要的是,依赖depend。
CMakeList.txt http://wiki.ros.org/catkin/CMakeList.txt
–定义一个包的编译指令
–cmake不会找package.xml文件.依据cmakelist.txt文件编译需要清晰指出头文件和库文件的指向
常用的关键词:
–catkin_package(CATKIN_DEPENDS roscpp) 声明依赖本包同时需要的其他ros包
–find_package(catkin REQUIRED COMPONENTS …) 声明编译本包所需要的其他ros包
–add_executable 声明编译本包生成的可执行文件
–target_link_libraries 链接可执行文件和依赖库
2.4 ROS node
–一个节点是ROS程序包中的一个可执行文件
–ROS节点可以使用ROS客户库与其他节点通信
–节点可以发布或者接受一个话题
–节点可以提供或使用某项服务
常用命令
–rosnode list 查看当前注册到ros master的所有节点
–rosnode info 查看某个节点的具体信息
2.5 ROS topic
–节点之间通过一个ROS topic来相互通信的
–通过publicer声明所发布topic名称
–通过subscriber声明所需要监听的topic名称
常用命令
–rostopic list 查看当前注册到ros master 的所有topic列表
–rostopic echo 把当前topic输出到控制台,方便调试和查看
2.6 ROS param
–rosparam命令允许我们在ROS参数服务器上存储和复制数据。参数服务器可以存储整数、浮点数、布尔值、字典和列表值
–一个小型的KV库
常用命令
–rosparam set 设置参数
–rosparam get 获取参数
–rosparam list 查看当前服务器上的参数
2.7 Listener&Talker
Publisher 发布数据的一方(Talker)
Subscriber 订阅数据的一方(Listener)
Message 数据类型
2.8 catkin_make生成的build&devel文件夹
build:存放编译的缓存文件
devel:存放编译完成的文件
对应devel文件,需要将其setup.bash到.bashrc文件中,也就是设置环境变量,让系统能够找到该工作空间。当然,更新了.bashrc文件后,需要执行一次 source ~/.bashrc 命令,或者bash 一下终端窗口,目的是让系统重新读取一次 .bashrc 文件。
基本流程,首先创建一个工作空间workplace,然后根据实际需要创建相应的包package,编写相应的需求文件,如源文件;根据编译运行需要,补充CMakeLists.txt、package.xml相应说明,如添加依赖,查找相关包,运行所需要的包,消息类型等等。
mkdir -p xxx_ws //创建工作空间
catkin_make //编译工作空间
//打开.bashrc 设置xxx_ws工作空间的环境变量
catkin_create_pkg xxxx(包) xx(依赖) //创建包
上面如何创建工作空间,创建包,编译以及设计环境变量等相关操作,可以参考本人其他blog或者百度其他人的介绍,挺多的,不展开介绍了。
3.1编写一个talker的node
在工作空间的src/目录下,
第1步,创建一个talker的包study:$ catkin_create_pkg study roscpp
第2步,打开vcode(或者其他ide),study/src,创建源文件study_node.cpp,代码如下:
#include"ros/ros.h"
#include"std_msgs/String.h"
#include
//编写一个node并发布出来
int main(int argc,char **argv){
ros::init(argc,argv,"study_talker");//定义node的属性
ros::NodeHandle n;//ros提供的一个类,可以实例化publisher,进行发布数据
ros::Publisher study_pub=n.advertise<std_msgs::String>("/study_topic",10);
//定义node要发布的topic的名称,以及发布的有效率(表示10为有效的,如果很久都没有缓存进去,那么早期发布的就会被释放掉)
ros::Rate loop_rate(10);//每秒发布10次
int count=0;//记录发布的次数
while(ros::ok()){
//ros::ok的作用是,只要ros master正常运行,如果没有中断操作,都会继续运行的
std_msgs::String msg;//定义要发布到ros里面的变量名称,或者实例化一个对象来保存要发布的消息
std::stringstream ss;//定义c++变量来保存输出的变量
ss<<"hello study world!"<<count;//输出的字符串以及次数
count++;//次数自加
msg.data=ss.str();//发布的字符串封装到ros变量
study_pub.publish(msg);//把消息通过ros上面 定义的publisher发布出去
loop_rate.sleep();//暂停0.1秒
}
}
第3步,设置CMakeLists.txt&package.xml
CMakeLists.txt:
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
)//告诉系统编译本包时,需要找到这两个包
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES study
CATKIN_DEPENDS roscpp std_msgs
# DEPENDS system_lib
)//声明依赖本包同时需要里面这两个ros包
add_executable(${PROJECT_NAME}_node src/study_node.cpp)//编译本包生成的可执行文件
add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})//链接可执行文件和依赖库
//一般情况而已,CMakeLists.txt是创建包同时,系统自动生成的,然后,我们需要的工作,一般情况就是把上面基本地方去掉#号就行了(目的告诉系统,关于该包,在哪,依赖是啥)
package.xml:
<build_depend>roscpp</build_depend>
<build_depend>std_msgs</build_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>std_msgs</exec_depend>
//主要修改类似样式,当然,本例子比较简单,系统生成的,不需要做其他修改,但是,如果节点添加新的依赖,需要在这些地方添加相应的包
第4步,编译
$ catkin_make //注意在要做工作空间的一级目录下使用该指令
第5步,验证
终端1运行
$ roscore//运行一个ROS master
终端2运行
$ rosrun study study_node //包名+节点名
终端3运行
$ rostopic list
/study_topic //表示定义的topic正常发布
/rosout
/rosout_agg
终端4,查看
$ rostopic echo /study_topic
data: "hello study world!2360"//表示发布成功
---
data: "hello study world!2361"
这时,从零开始创建的talker节点node,成功创建完成
3.2编写一个listener的node
基本流程和3.1差不多,这里主要区别就是源代码,以及定义listener节点node时候,一定要匹配talker发布的topic名称才行。
在工作空间的src/目录下,
第1步,创建一个listener的包study_listen:
$ catkin_create_pkg study_listen roscpp
第2步,打开vcode(或者其他ide),study_listen/src,创建源文件study_listen_node.cpp,代码如下:
#include"ros/ros.h"
#include"std_msgs/String.h"
//创建一个listener的node
void studyCallback(const std_msgs::String::ConstPtr& msg){
//回调函数一定是要求是无返回类型
ROS_INFO("I can see you again,%s",msg->data.c_str());
}
int main(int argc,char ** argv){
ros::init(argc,argv,"study_listener");//初始化这么一个node
ros::NodeHandle n;//命名空间
ros::Subscriber sub=n.subscribe("study_topic",10,studyCallback);
//表示聆听study_topic这个主题,每次听到就会启动回调函数,这里的10也表示一个缓冲数量,多了,前面的会被是放掉
//这里一定要注意,聆听的topic一定要和发布的topic的名称对应上,否则,是没办法接收的
ros::spin();
return 0;
}
第3步,设置CMakeLists.txt&package.xml
CMakeLists.txt:
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
)//告诉系统编译本包时,需要找到这两个包
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES study
CATKIN_DEPENDS roscpp std_msgs
# DEPENDS system_lib
)//声明依赖本包同时需要里面这两个ros包
add_executable(${PROJECT_NAME}_node src/study_listen_node.cpp)//编译本包生成的可执行文件
add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})//链接可执行文件和依赖库
//一般情况而已,CMakeLists.txt是创建包同时,系统自动生成的,然后,我们需要的工作,一般情况就是把上面基本地方去掉#号就行了(目的告诉系统,关于该包,在哪,依赖是啥)
package.xml:
<build_depend>roscpp</build_depend>
<build_depend>std_msgs</build_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>std_msgs</exec_depend>
//主要修改类似样式,当然,本例子比较简单,系统生成的,不需要做其他修改,但是,如果节点添加新的依赖,需要在这些地方添加相应的包
第4步,编译
$ catkin_make //注意在要做工作空间的一级目录下使用该指令
第5步,验证
终端1运行
$ roscore//运行一个ROS master
终端2运行
$ rosrun study study_node //包名+节点名;由于测试listener的节点,所以需要启动一个talker的node才行
终端3,查看
$ rostopic echo /study_topic
data: "hello study world!432"
---
data: "hello study world!433" //表示发布成功
终端4运行
$ rosrun study study_listen_node //包名+节点名
[ INFO] [1606043574.419247936]: I can see you again,hello study world!178 //表示监听成功
这时,从零开始创建的listener节点node,成功创建完成
3.3自定义消息类型并发布演示
这里主要就是定义一个消息类型是重点,然后,在上面创建的talker以及listener包里面头文件包含,然后在CMakeLists.txt&package.xml两文件中,添加新的头文件类型即可使用新定义的消息类型了。
第一步,创建一个包:
$ catkin_create_pkg study_msgs std_msgs roscpp
第二步,在包创建一个msg文件夹(注意文件夹名称一定是这样子的,否则,在cmakelists要修改很多)
第三步,在文件夹中新建文件,StudyMsg.msg
(注意命名方式以及后缀)
string detail
int32 id
注意,自定义的消息类型,记得一定不能写其他说明性内容进去,否则发生编译错误
第四步,设置CMakeLists.txt&package.xml
CMakeLists.txt:
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs message_generation
)
## Generate messages in the 'msg' folder//这里可以看出,系统只会在msg文件夹中查找,对应第二步
add_message_files(
FILES
StudyMsg.msg
)
## Generate added messages and services with any dependencies listed here
generate_messages(
DEPENDENCIES
std_msgs
)
package.xml:
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
第五步,编译,catkin_make
第六步,查看
$ cd include/study_msgs
$ ls
StudyMsg.h //表示该头文件生成成功
这时候,自定义消息类型生成成功,并且,可以被其他包通过头文件#include"study_msgs/StudyMsg.h"
方式,就可以正常使用。
下面,以发布一个talker节点来展示,并且发布的topic名称为/study_topic_new
当然,CMakeLists.txt&package.xml要做相应的补充,
CMakeLists.txt:
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs study_msgs
)
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES study
CATKIN_DEPENDS roscpp std_msgs study_msgs
# DEPENDS system_lib
)
//两者都是添加新消息类型study_msgs
package.xml:
<build_depend>study_msgs</build_depend>
<exec_depend>study_msgs</exec_depend>
//添加新消息类型study_msgs
此时,study_node.cpp为:
#include"ros/ros.h"
#include"std_msgs/String.h"
#include
#include"study_msgs/StudyMsg.h"//使用自定义消息类型
int main(int argc,char **argv){
ros::init(argc,argv,"study_talker");//node's attribute name
ros::NodeHandle n;
ros::Publisher study_pub=n.advertise<std_msgs::String>("/study_topic",10);//define publisher's topic
ros::Publisher study_pub_new=n.advertise<study_msgs::StudyMsg>("/study_topic_new",10);//新定义消息发布的主题名称定义
ros::Publisher study_pub_param=n.advertise<study_msgs::StudyMsg>("/params_topic",10);
ros::Rate loop_rate(10);//10 per/s
int count=0;
while(ros::ok()){
std_msgs::String msg;
std::stringstream ss;
//std::stringstream ss_new;
ss<<"hello study world!"<<count;
count++;
msg.data=ss.str();
study_pub.publish(msg);
//使用自定义消息类型发布消息
study_msgs::StudyMsg studyMsg;
studyMsg.id=count;//发布内容的id
studyMsg.detail="hello study world! new";//自定义消息类型发布的内容datail
study_pub_new.publish(studyMsg);
loop_rate.sleep();//pause 0.1s
}
}
终端查看:
$ rostopic echo /study_topic_new
---
detail: "hello study world! new"
id: 29975
---
至此,自定义的数据类型并成功给其他包使用演示结束。那么,自定义数据类型的存在意义又是什么呢?因为,实际机器人开发过程中,仅是基本数据类型是无法满足实际需要的,所以需要根据自定义的来方便开发。
3.4参数中心rosparam的使用演示
开始展示之前,一定要记得在study_listen包的CMakeLists.txt&package.xml做好跟上面的talker包study一样准备,保证可以使用自定义消息类型。
那么,
study_node.cpp:
#include"ros/ros.h"
#include"std_msgs/String.h"
#include
#include"study_msgs/StudyMsg.h"
int main(int argc,char **argv){
ros::init(argc,argv,"study_talker");//node's attribute name
ros::NodeHandle n;
ros::Publisher study_pub=n.advertise<std_msgs::String>("/study_topic",10);//define publisher's topic
ros::Publisher study_pub_new=n.advertise<study_msgs::StudyMsg>("/study_topic_new",10);//测试新定义消息类型
ros::Publisher study_pub_param=n.advertise<study_msgs::StudyMsg>("/params_topic",10);//测试参数中心
ros::Rate loop_rate(10);//10 per/s
int count=0;
while(ros::ok()){
std_msgs::String msg;
std::stringstream ss;
//std::stringstream ss_new;
ss<<"hello study world!"<<count;
count++;
msg.data=ss.str();
study_pub.publish(msg);
//发布新定义消息类型
study_msgs::StudyMsg studyMsg;
studyMsg.id=count;
studyMsg.detail="hello study world! new";
study_pub_new.publish(studyMsg);
//测试参数中心
study_msgs::StudyMsg studyMsg_param;
std::string param_string;
n.param<std::string>("myparam",param_string,"cc");//通过该myparam可以重新定义发布的消息,否则 会一直使用用的是默认值 cc
studyMsg_param.detail=param_string;
studyMsg_param.id=count;
study_pub_param.publish(studyMsg_param);
loop_rate.sleep();//pause 0.1s
}
}
study_listen_node.cpp:
#include"ros/ros.h"
#include"std_msgs/String.h"
#include"study_msgs/StudyMsg.h"
void studyCallback(const std_msgs::String::ConstPtr& msg){
ROS_INFO("I can see you again,%s ",msg->data.c_str());
}//测试是监听study_topic成功与否
void studyCallback_new(const study_msgs::StudyMsg::ConstPtr& msg){
ROS_INFO("I think I cant forget you,%s,%d",msg->detail.c_str(),msg->id);
}//测试监听自定义消息成功与否
void studyCallback_param(const study_msgs::StudyMsg::ConstPtr& msg){
ROS_INFO("I think I cant forget you,%s,%d",msg->detail.c_str(),msg->id);
}//测试rosparam参数中心是否工作成功
int main(int argc,char ** argv){
ros::init(argc,argv,"study_listener");
ros::NodeHandle n;
ros::Subscriber sub=n.subscribe("/study_topic",10,studyCallback);
ros::Subscriber sub_new=n.subscribe("study_topic_new",10,studyCallback_new);
ros::Subscriber sub_param=n.subscribe("/params_topic",10,studyCallback_param);
ros::spin();
return 0;
}
修改了上面的两个文件,接下来就是编译整个工作空间并成功,接着
终端1:
$ roscore //启动ros master
终端2:
$ rosrun study study_node //启动talker节点
终端3:
$ rosrun study_listen study_listen_node //启动listener节点
[ INFO] [1606047990.464340880]: I can see you again,hello study world!99
[ INFO] [1606047990.464436530]: I think I cant forget you,hello study world! new,100
[ INFO] [1606047990.476383810]: I think I cant forget you,cc,100
这时候我们发现,上面我们定义的回调函数,都成功调用了(表示上面的talker/listener/自定义消息 类型都是编写并运行正常的)
当我们在
终端4启动参数中心rosparam设置参数:
$ rosparam set /myparam "yqy"
终端3的显示调整为:
[ INFO] [1606048163.371016634]: I can see you again,hello study world!368
[ INFO] [1606048163.371113447]: I think I cant forget you,hello study world! new,369
[ INFO] [1606048163.394940978]: I think I cant forget you,yqy,369
也就是将周期第三个,cc调整为yqy了。
本章小结:创建一个包,需要定义好CmakeLists.txt 、package.xml两个文件,主要就是告诉系统,我这个包编译和运行时依赖是什么;talker和listener的编写也是基本套路一样的,重新发布的主题以及消息类型以及内容就发布ok了;listener的回调函数,注意是无返回类型的;重新自定义一个消息类型,注意文件夹命名以及文件后缀名称。基本是这些。
编译成功后,第一次运行某一个包里面的node时候,可能出现Tab不出来的情况,那么,我们应该直接进入该包,然后使用 rosrun 该包 该包某node 的方式来启动;下次启动时候,就不需要如此了。
3.5 roslaunch
–批量启动一系列节点
–载入rosparam参数
– //可以理解“节点昵称”就是//“节点名”的别称
–
–使用launch
这一章基本实操一遍,关键点,在于docker的安装,这个自行百度ubuntu16.04+docker+install,很多 的;然后,注意点有,工作空间的数据关联,查询容器的id,进入目标容器,退出目标容器操作,编译前环境变量的设置,基本就是这些。
4.1什么是Docker
–docker基于容器技术的轻量级虚拟化解决方案
–docker是容器引擎,把Linux的cgroup、namespace等容器底层技术进行封装抽象,为用户提供了创建和管理容器的便捷界面(包含命令行和API)
–Docker是一个开源项目,诞生于2013年初,基于Google公式推出的Go语言是实现
4.2 Docker可以做什么
–隔离系统环境(docker类似虚拟机,但比虚拟机更加轻量级,一个系统可以装上千个docker,但是不能装上千个虚拟机;docker占用资源相对很低,虚拟机占用资源相对很高)
–极简的安装和部署方式
–让复杂系统安装配置成为历史
4.3 Docker优点
1、传统程序分发
–源码分发:需要用户自己编译,解决依赖问题
–解决依赖时,对系统又侵入性,可能解决了这个依赖,其他的程序又不能运行,python2&python3等case案例
–可执行文件分发:多平台多次编译,跨平台受限
2、docker分发
–要求用户安装docker即可
–环境,依赖均独立,不影响系统原有库
使用docker:
前:DEV/Code->Build-》Tar/RPM/Deb/Jar/War/zip/Exe
后:DEV/Code->Dockerfile Build->image Repository
4.4 Docker概念
1)Docker Images(类似镜像)
Docker images 是Docker container 最基本的模板。Image通过容器使系统和应用易于安装,Docker image是用来运行的容器。http://hub.docker.com/找到许多images(多种操作系统和软件已经被安装好的docker)
2)Docker container(以类做近似对比的话,image是class,container是image类的一个实例)
Docker容器(docker container)是一个image,在运行docker image上读取和写入。Docker是一个联合的文件系统作为容器后台,容器任何变化,都将保存在一个基本的image新的层上。我们安装应用程序的层就是容器。每个主机上运行的容器都是独立的,因此,提供了一个安全的应用平台。
3)Docker Registry
Docker registry 是为Docker images提供的库。它提供了公共和私有库。公共docker库被叫做Docker Hub。这里我们能够上传push和pull自己的images。
4.5 Docker安装使用
–安装docker客户端
https://yq.aliyun.com/articles/110806?spm=5176.8351553.0.053b11991Bpafu4
–从docker hub找到ROS docker
–Docker pull 该镜像
4.6 使用Docker的案例:
安装docker:
终端运行:
sudo docker pull ros:kinetic
Sudo docker images //显示对应版本的大小等等信息则表示安装本地成功
将工作空间移动到docker里面编译运行:
cd ~/catkin_ws
sudo docker run –itd –v$(pwd):/data ros:kinetic //启动一个ros:kinetic版本的image创造一个container
//-itd 表示一直可以在后台运行
//-v 表示将数据集和虚拟机的文件系统关联起来
//pwd 表示当前目录
// /data 表示的是虚拟机目录
Sudo docker ps –a //查看当前的docker运行的镜像,获取container的ID
//进入docker镜像,编译程序
Sudo docker exec –it container的ID /bin/bash//下次进入我们自己的docker也是通过该语句的
//exec 表示使用container执行一行命令
//-it 表示执行命令时,需要获取一个terminal
// /bin/bash表示执行的命令
//同时,执行命令时,要获得镜像里面一(excel的cel?)
//进入了一个docker目录下
// /data/src 指的是原工作空间数据
//然后,docker目录下,新建一个同名的工作空间 mkdir catkin_ws
//把原工作空间的内容拷贝到新建的工作空间中
cp –r /data/src ./catkin_ws
//进入工作空间中
cd ./catkin_ws
//在编译之前需要配置环境变量
echo ‘opt/ros/kinetic/setup.bash’ >> /root/.bashrc
//可以通过指令 cat /root/.bashrc 查看环境变量是否添加成功
//发现上面添加的语句出现在表框中
//这时候,使用指令
source /root/.bashrc
//开始编译,
catkin_make
//下面是个人使用docker编译的结果
[ 63%] Built target study_msgs_generate_messages
[ 81%] Building CXX object study/CMakeFiles/study_node.dir/src/study_node.cpp.o
[ 81%] Building CXX object study_listen/CMakeFiles/study_listen_node.dir/src/study_listen_node.cpp.o
[ 90%] Linking CXX executable /test_ws/devel/lib/study/study_node
[ 90%] Built target study_node
[100%] Linking CXX executable /test_ws/devel/lib/study_listen/study_listen_node
[100%] Built target study_listen_node
//发现,之前的包、node,topic都生成成功了
//如果想要退出docker目录,使用指令
exit
4.7 把自己封装的Docker镜像(image)打包导出操作
//还是在最原始的工作空间(不是docker上)下,首先查看刚才container的ID
Sudo docker ps –a
//查看了id
Sudo docker commit container的ID myimage(自定义镜像名字)
Sudo docker images 查看自己打包的镜像(myimage)
//这时候,可以使用该镜像进行安装,并可以使用其中的所有功能程序了
感觉,这一章总算系统把以前的ROS知识梳理一遍,以前虽然知道这么搞,但是,原理以及系统性还是很缺乏的。个人感觉,这一节讲到的虽然很基本,但是很全并且很具有系统性,对ros的认识也加深了一步。希望可以对朋友有帮助哈。
############################
不积硅步,无以至千里
B站大学
部分图片版权归原作者所有
致谢原作者