ROS学习记录

ROS学习记录 来自远方 2024-01-15 15:45:30 347

文章目录

第一步-创建工作空间

工作空间是一个存放工程开发相关文件的文件夹,主要包含了源代码空间src,编译空间build,开发空间devel,安装空间install。
在src目录中,包含了整个工程的编译列表文件(CMakeLists.txt),以及各个包package的编译列表文件和包的相关参数信息package.xml。
创建工作空间的相关命令为

mkdir -p ~/catkin_ws/src          #创建在根目录下的工作空间的源代码空间路径 ~/catkin_ws/src,-p是指当已经存在时也不进行报错
cd ~/catkin_ws/src                  #进入源代码空间
catkin_init_workspace              #初始化源代码空间的命令
cd ~/catkin_ws                      #进入工作空间
catkin_make                       #编译整个工作空间

在成功编译完之后,工作空间中会自动生成build和devel两个目录及相关文件(如果你编译失败了,可以根据编译结果的提示修改CMakeLists.txt的编译内容)。在devel的目录中已经生成了几个setup.*sh的环境变量设置脚本。为了使这些环境变量可以生效,需要使用以下命令

source ~/catkin_ws/devel/setup.bash

执行完命令之后可以用以下命令来检查是否生效,若路径中包含了刚才创建的工作空间路径即表明工作空间创建完成。

echo $ROS_PACKAGE_PATH

另外,source 命令只会在当前的终端生效,在下一次打开终端之后就需要重新执行source命令。可以将sourch命令添加到终端的配置文件中。我们可以在根目录中查看相关文件

cd ~/            #进入根目录
ls -a             #查看所有文件包括隐藏文件,可以看到其中有一个.bashrc的文件,该文件就是终端的配置文件
gedit .bashrc    #编辑这个文件

在文件下面添加一行命令

source ~/catkin_ws/devel/setup.bash

就可以不用在每次打开终端时重复输入source命令。

注:也可以直接文件重定向命令 echo “source ~/catkin_ws/devel/setup.bash”>> ~/.bashrc

第二步-创建功能包

ros中的功能包的组成格式为

package_name/
——CMakeLists.txt #功能包的编译信息文件
——package.xml #对于功能包属性的描述信息文件
——/include #头文件目录
——/launch #启动文件目录
——/msg #消息文件目录
——/src #源代码目录
——/srv #服务文件目录
注:功能包不可以嵌套其他的功能包,多个功能包之间必须平行的放置在代码空间中

创建功能的命令:

catkin_create_pkg <package_name> [depend1] [depend2] [depend3]

注: 创建功能包的命令要到工作区目录下的src文件夹中进行执行。
catkin_create_pkg:创建功能包的命令
package_name:功能包的名字
dependx:创建的功能所依赖的其他功能包,
在ROS操作系统中,存在两个性质的包路径,一个是系统工作区的包路径,一个是用户工作空间的包路径,可以用以下命令行来获取路径信息。

echo $ROS_PACKAGE_PATH

若想要删除自己创建的功能包时,只需要到工作区中删除该文件夹,然后再工作空间的根目录进行一次catkin_make即可。
若想要删除系统工作取的功能包时,则需要用以下命令来实现。

sudo apt-get purge <package_name>   #删除功能包
sudo apt-get autoremove                #删除功能包的依赖包
第三步-话题订阅和发布

首先,在功能包路径中的src文件夹中创建c++源文件。
源文件主要包含以下内容:

#include "ros/ros.h"
//引用的头文件及其他需要的头文件
int main(int argc,char **argv)
{
    ros::init(argc,argv,"NODE_NAME");
    //创建节点,将argc和argv参数传入,并且节点名称不可重复
    ros::NodeHandle n;
    //创建节点句柄,用于对节点的相关操作

    ros::Publisher pub = n.advertise<std_msgs::String>("TOPIC_NAME",1000);
    //创建一个话题,<std_msgs:String>是对话题内容的定义,即话题包含的是字符串信息

    ros::Subscriber sub = n.subscribe("TOPIC_NAME",1000,${SUBCRIBE_CALLBACK_FUNCTION});
    //创建一个订阅话题的subscriber    

    ros::Rate loop_rate(10);
    //设置循环的频率,单位为hz

    while(ros::ok())
    {
        std_msgs::String msg;
        std::stringstream ss;
        ss<<"hello world";
        msg.data = ss.str();
        //将字符串流写入消息中
        pub.publish(msg);
        //发布消息
        ros::spinOnce();
        //对订阅消息的回调处理
        loop_rate.sleep();
        //周期循环
    }
    return 0;
}

编写完源文件后,需要设置编译选项文件,将功能包目录下的CMakeLists.txt打开,补充写入以下编译命令。

include_directories(include ${catkin_INCLUDE_DIRS})                       #添加引用路径
add_executable(FILE_NAME src/*.cpp)                                        #添加编译对象信息,FILE_NAME是编译生成的可执行文件名称
target_link_libraries(FILE_NAME ${catkin_LIBRARIES})                    #添加链接库信息
add_dependencies(FILE_NAME ${PROJECT_NAME}_generate_messages_cpp)        #添加依赖库信息

保存后回到catkin_ws目录下,执行catkin_make命令。
编译成功后,执行该节点。

roscore #在一个终端中输入
rosrun PACKAGE_NAME NODE_NAME #在新的终端下打开
第四步-自定义消息类型

首先在功能包的目录下创建msg的文件夹,并在文件夹中创建消息文件 MSG_NAME.msg,并且定义消息中包含的信息,例如:

uint8 data1 
float32 data2
uint32 data3

然后在到功能包的CMakeLists.txt和package.xml中添加相关编译及依赖选项。
在package.xml中,需要补充的是

<build_depend>message_generation</build_depend>        #这里编译的依赖包
<execute_depend>message_runtime</execute_depend>    #这里是运行时的依赖包

在CMakeLists.txt中,需要补充的是

#寻找依赖的元功能包
find_package(catkin REQUIRED COMPONENTS
    ……
    roscpp
    rospy
    std_msgs
    message_generation
    ……
)
#添加好catkin编译需要的依赖
catkin_package(
    ……
    CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
    ……
)
#添加自定义的消息文件
add_message_files(
    FILES
    MSG_NAME.msg
)
#这里面是用到的消息的依赖
generate_messages(
    DEPENDENCIES
    std_msgs
)

设置完成后回到catkin_ws目录下进行catkin_make进行编译即可完成自定义消息的创建。

第五步-自定义消息下的话题发布与订阅

整合前几步中,以一个实际的案例来描述设计一个自定义消息的话题发布订阅的实现过程。

1、首先在工作区 ~/catkin_ws/src 中使用功能包创建命令 catkin_create_pkg 创建一个名为 my_tutorials 的功能包。

cd ~/catkin_ws/src                                                                #进入目录
catkin_create_pkg my_tutorials roscpp rospy message_generation message_runtime  #创建功能包
cd ~/catkin_ws                                                                    #回到工作区根目录
catkin_make                                                                        #编译工作区
rospack list                                                                    #查看功能包列表中是否包含新创建的功能包

2、在功能包目录下创建msg文件夹,并且创建自定义消息文件my_msg1.msg,编写消息文件内容。

cd ~/catkin_ws/src/my_tutorials    #进入功能包的文件夹目录
mkdir -p msg                       #创建msg目录
gedit my_msg1.msg                    #创建消息文件

以下是msg中的内容,保存后退出

uint8 data1
uint32 data2
float32 data3
string data4

3、回到功能包路径下,接下来需要修改的是功能包的CMakeLists.txt和package.xml补充编译选项信息和包依赖信息。

cd ~/catkin_ws/src/my_tutorials   #打开功能包路径
gedit package.xml                  #编辑包信息文件

要保证package.xml文件下包含以下依赖包信息:

  <build_depend>roscpp</build_depend>
  <build_depend>rospy</build_depend>
  <build_depend>message_generation</build_depend>

  <build_export_depend>roscpp</build_export_depend>
  <build_export_depend>rospy</build_export_depend>
  <build_export_depend>message_generation</build_export_depend>

  <exec_depend>message_runtime</exec_depend>
  <exec_depend>roscpp</exec_depend>
  <exec_depend>rospy</exec_depend>
  <exec_depend>message_generation</exec_depend>

在创建时我们已经添加了这几个依赖包,因此就无需再次手动添加。
在CMakeList.txt文件中添加对消息文件my_msg1.msg的编译选项:

find_package(catkin REQUIRED COMPONENTS
  message_runtime
  roscpp
  rospy
  message_generation
)
#添加依赖包信息

add_message_files(
 FILES
 my_msg1.msg
)
#添加需要编译的消息文件

catkin_package(
  ……
  CATKIN_DEPENDS message_runtime roscpp rospy message_generation
  ……
)
#catkin编译时所依赖的包(似乎不加也可以编译通过)

generate_messages(
  DEPENDENCIES
  std_msgs  # Or other packages containing msgs
)
#由于我们的消息文件中的 uint8 uint32 等类型是ros系统工作区中 std_msgs包所包含的,因此消息生成也需要添加对于std_msgs包的依赖信息。

回到工作区目录下进行编译

cd ~/catkin_ws/
catkin_make

4、创建一个话题发布节点。
进入功能包目录下的src目录创建节点源文件(节点代码,可以是cpp也可以是python)

cd ~/catkin_ws/src/my_tutorials/src            #进入到源代码目录
gedit talker.cpp                            #创建话题发布节点

键入以下代码并保存

#include "ros/ros.h"
//调用ros的头文件
#include "my_tutorials/my_msg1.h"
//调用自定义消息的头文件
#include "std_msgs/String.h"
//调用std_msgs头文件
#include <sstream>

int main(int argc,char **argv)
{


    ros::init(argc,argv,"talker");
    //创建话题发布节点

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

    ros::Publisher pub = n.advertise<my_tutorials::my_msg1>("my_topic1",1000);
    //创建一个名为my_topic1的话题,其中 <my_tutorials::my_msg1>重定向了话题的消息类型
    //1000定义话题的队列长度

    ros::Rate loop_rate(10);
    //设置循环频率

    my_tutorials::my_msg1  msg;     
    //定义自定义消息变量

    int count = 0;

    while(ros::ok())
    {
        std::stringstream ss;    
        count++;        
        ss << "string count " << count;
        //重定向字符串流ss
        msg.data1+=1;
        msg.data2+=10;
        msg.data3+=0.1f;
        msg.data4 = ss.str();
        ROS_INFO("topic message subcribe %d",count);
        //打印消息调试
        pub .publish(msg);
        //发布信息
        ros::spinOnce();
        //订阅信息回调函数
        loop_rate.sleep();
        //休眠
    }
    return 0;
}

接下来要对编写CMakeList.txt中的编译选项,在该文件下,有一块区域是专门定义编译内容的:

###########
## Build ##
###########

## Specify additional locations of header files
## Your package locations should be listed before other locations
include_directories(
# include
  ${catkin_INCLUDE_DIRS}
)

add_executable(talker_node src/talker.cpp)
add_dependencies(talker_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(talker_node
  ${catkin_LIBRARIES}
)

## Declare a C++ library
# add_library(${PROJECT_NAME}
#   src/${PROJECT_NAME}/my_tutorials.cpp
# )

## Add cmake target dependencies of the library
## as an example, code may need to be generated before libraries
## either from message generation or dynamic reconfigure
# add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## Declare a C++ executable
## With catkin_make all packages are built within a single CMake context
## The recommended prefix ensures that target names across packages don't collide
# add_executable(${PROJECT_NAME}_node src/my_tutorials_node.cpp)

## Rename C++ executable without prefix
## The above recommended prefix causes long target names, the following renames the
## target back to the shorter version for ease of user use
## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node"
# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "")

## Add cmake target dependencies of the executable
## same as for the library above
# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## Specify libraries to link a library or executable target against
# target_link_libraries(${PROJECT_NAME}_node
#   ${catkin_LIBRARIES}
# )

其中,只需要根据文本中的提示信息,添加对talker.cpp的编译信息即可:

add_executable(talker_node src/talker.cpp)
add_dependencies(talker_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(talker_node
  ${catkin_LIBRARIES}
)
#其中talker_node是对talker.cpp编译的结果,并且需要将add_executable放置到第一行,否则将会出现找不到add_dependencies对象target的报错。

注: 其中的talker_node可以是自定义的,也可以通过宏字符${PROJECT_NAME}来统一工程文件命名。

编写完成后回到工作区进行编译:

cd ~/catkin_ws
catkin_make

5、测试话题发布节点是否正常运行
通过rostopic echo的功能来测试话题发布节点是否正常运行

#在终端1中
roscore #打开ros内核
#在终端2中
rosrun my_tutorials talker_node #运行话题发布节点
#在终端3中
rostopic list  #查看是否存在
#如果可以看到my_topic1则说明节点正常运行。
#可以通过rostopic echo my_topic1 来订阅话题消息

以上是对话题发布节点的测试图

6、编写话题订阅节点并运行
同样的方式编写listener.cpp,同样在功能包的src目录下创建:

#include "ros/ros.h"
#include "std_msgs/String.h"
#include "my_tutorials/my_msg1.h"

//注意callback的传参需要根据消息类型进行变换
void pub_callback(const my_tutorials::my_msg1::ConstPtr& msg)
{
    ROS_INFO("data1:%d",msg->data1);
    ROS_INFO("data2:%d",msg->data2);
    ROS_INFO("data3:%f",msg->data3);
    ROS_INFO("data4:%s",msg->data4.c_str());
}

int main(int argc,char **argv)
{
    ros::init(argc,argv,"listener");
    //创建订阅任务节点
    ros::NodeHandle n;
    //创建节点句柄
    ros::Subscriber sub = n.subscribe("my_topic1",1000,pub_callback);
    //创建一个订阅my_topic1话题的subscriber
    ros::spin();
    //订阅信息回调函数     
    return 0;
}

以同样的方式编写CMakeList.txt:

add_executable(listener_node src/listener.cpp)
add_dependencies(listener_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(listener_node
  ${catkin_LIBRARIES}
)

运行订阅节点和发布节点:

roscore 
rosrun my_tutorials talker.cpp
rosrun my_tutorials listener.cpp

注:三个命令需要在三个终端中打开。

至此,便完成了自定义消息的话题发布订阅过程。

第六步-建立ROS节点的服务

服务是节点之间进行同步通信的一种方式,本质是由client发送请求,由server进行响应反馈的过程。其通信过程的载体类似于话题消息,是以服务消息来进行信息传递过程。服务消息包含两个部分,其中一部分是请求数据域,另一部分是响应数据域。
对于服务和话题两种通信方式的异同,个人理解是可以把话题通信当成是一种原料供应商和买家的关系,话题的发布者定位为原料供应商,向那些订购的买家进行持续性的货物(信息)流动。而服务通信相当于将server理解为加工厂商,提供了一个接口(client)向外,其他用户利用这一接口(client)自行满足需求,类似于自助服务平台,server为服务的内容,client为服务的接口平台。
总结来说,话题通信提供某种信息(话题)的持续供给(节点之间);服务通信提供接口完成某项定向服务(服务仅限定于所在节点,client则面向用户)。二者存在一定共性,区别在于提供的资源性质不同。

1、在功能包路径下添加srv文件夹,用于添加服务消息的文件;

cd ~/catkin_ws/src/my_tutorials   #进入功能包路径
mkdir srv                           #创建服务文件夹
cd srv                              #进入该目录
gedit my_server_msg1.srv          #创建并编辑服务消息文件

服务消息文件的格式和话题消息一致,只是需要用- - - 将两个部分的内容隔开:

int64 a
int64 b
---
int64 sum
#先以将两个整数相加的服务为例

在功能包目录下的CMakeLists.txt添加对于服务消息文件的编译选项。

## Generate services in the 'srv' folder
add_service_files(
  FILES
  my_server_msg1.srv
)
#添加服务消息文件
find_package(catkin REQUIRED COMPONENTS
  message_runtime
  roscpp
  rospy
  message_generation
)
#服务消息的编译依然依赖的是message_generation的功能包

可以回到工作区目录下编译catkin_make检查是否正确。

2、创建服务中的server源代码文件。
进入功能包的src目录,创建server.cpp

cd ~/catkin_ws/src/my_tutorials/src   #进入源代码目录
gedit server.cpp                      #创建并编辑代码

键入以下代码~仅供参考

#include "ros/ros.h"
#include "my_tutorials/my_server_msg1.h"


//服务回调
bool add_callback(my_tutorials::my_server_msg1::Request &req,
                  my_tutorials::my_server_msg1::Response &res)
{
    ROS_INFO("Get Server Request a = %d b = %d",req.a,req.b);
    res.sum = req.a + req.b;
    ROS_INFO("The Result is %d",res.sum);
    return true;
}

int main(int argc,char **argv)
{
    ros::init(argc,argv,"server");
    //创建节点
    ros::NodeHandle n;
    //创建节点句柄
    ros::ServiceServer server = n.advertiseService("AddTwoInts",add_callback);
    ROS_INFO("SERVER IS READY -- ADD TWO INTS");
    ros::spin();
    return 0;
}

保存后需要增加编译选项,打开功能包目录下的CMakeLists.txt:

add_executable(server_node src/server.cpp)
add_dependencies(server_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(server_node
  ${catkin_LIBRARIES}
)
#这里是默认上面的内容已经添加了所需要的依赖选项。

回到工作区路径下编译检查是否正确。
正确后运行roscore以及server节点观察终端反馈信息。

roscore
rosrun my_tutorials server_node


3、编写client部分源码并运行。

#include "ros/ros.h"
#include "my_tutorials/my_server_msg1.h"
#include <cstdlib>

int main(int argc,char **argv)
{
    ros::init(argc,argv,"client");
    //创建节点
    ros::NodeHandle n;
    //创建节点句柄

    if(argc != 3)
    {
        //命令的输入参数等于 argc-1,因此当需要输入两个数字参数时,argc的指应该为3
        ROS_INFO("ERROR INPUT -- THE right usage is \"rosrun $PACK_NAME $NODE_NAME X Y\",X And Y is the number ");
        return 1;
    }
    ros::ServiceClient client = n.serviceClient<my_tutorials::my_server_msg1>("AddTwoInts");
    //创建client
    my_tutorials::my_server_msg1 msg;
    msg.request.a = atoll(argv[1]);
    msg.request.b = atoll(argv[2]);
    //创建服务消息变量
    if(client.call(msg))
    {
        //发送请求
        ROS_INFO("SUM:%d",msg.response.sum);
        return 0;
    }
    else
    {
        //请求失败
        ROS_INFO("ERROR -- FAILED TO CALL THE SERVER");
        return 1;
    }
    return 0;
}

修改编译选项

add_executable(client_node src/client.cpp)
add_dependencies(client_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(client_node
  ${catkin_LIBRARIES}
)
#这里是默认上面的内容已经添加了所需要的依赖选项。

回到工作区路径下编译后运行

cd ~/catkin_ws
catkin_make
roscore
rosrun my_tutorials server_node
rosrun my_tutorials client_node

结果如图:

这个案例看起来过于简单,但事实上service的通信机制提供了一个很良好的人机交互接口,使开发过程有了能够手动干预的能力。

第七步-编写启动文件

当系统运行时需要启动多个节点,打开多个终端输入命令启动节点的方式十分低效且麻烦。启动文件的出现可以完美的解决这个问题。
启动文件的格式是xml,与之前经常编写的package.xml的规则是一致的,文件的格式可以是 .xml,也可以是 .launch。

1、启动文件的标识符 launch

<launch>
……
</launch>
<!-- 省略号指启动文件的具体内容,此处是启动文件的注释格式 -->

2、启动节点的标签 < node >

<node pkg=${PACKAGE_NAME} name=${NODE_NAME} type=${NODE_TYPE} ns=${NAMESPACE} />
<!-- 如果该节点启动不需要附加更多的参数或其他命令,则可以这样实现节点启动  -->
<!-- 其中  PACKAGE_NAME:节点所在包的名称;NODE_NAME:节点的名称;NODE_TYPE:节点的类型,即编译文件中生成的可执行文件名; -->
<!-- NAMESPACE:命名空间,用于区分多个命名空间的节点,在不同的命名空间中可以存在名称相同的节点 -->


<node pkg=${PACKAGE_NAME} name=${NODE_NAME} type=${NODE_TYPE} ns=${NAMESPACE} >
    <remap from=${OLD_NAME} to=${NEW_NAME}\>
    ……
</node>
<!-- 如果节点包含多个标签命令,则需要分段描述,注意第一行的“>”左边是没有“/”的 -->

其还包含了各种参数,包括:

output = "screen"                          #将节点的输出打印的终端屏幕,默认是输出为日志文档
respawn = "true"                        #复位属性,该节点停止时自动重启,默认为false
required = "true"                        #必要节点,该节点停止时整个启动文件中的所有节点均会被终止
ns = "${NAMESPACE}"                        #命名空间
args = "${arg ${ARGUMENT_NAME}}"        #参数名称对应的参数值

3、分组启动标签< group >
若系统运行节点可以根据不同性质分类到多个组别,则可以如以下操作

<group ns=${GROUP1_NS}>
    <node pkg=${PACKAGE_NAME} name=${NODE1_NAME} type=${NODE_TYPE} ns=${NAMESPACE} />
    <node pkg=${PACKAGE_NAME} name=${NODE2_NAME} type=${NODE_TYPE} ns=${NAMESPACE} />
    <node pkg=${PACKAGE_NAME} name=${NODE3_NAME} type=${NODE_TYPE} ns=${NAMESPACE} />
    ……
</group>

<group ns=${GROUP2_NS}>
    <node pkg=${PACKAGE_NAME} name=${NODE4_NAME} type=${NODE_TYPE} ns=${NAMESPACE} />
    <node pkg=${PACKAGE_NAME} name=${NODE5_NAME} type=${NODE_TYPE} ns=${NAMESPACE} />
    <node pkg=${PACKAGE_NAME} name=${NODE6_NAME} type=${NODE_TYPE} ns=${NAMESPACE} />
    ……
</group>

4、系统参数标签 < param >
parameter是指ROS系统运行中的参数,存储在参数服务器中。在启动文件中通过加载到parameter;
其使用方法为:

<param name=${PARAM_NAME} value=${PARAM_VALUE}\>

也可以利用rosparam命令加载yaml文件来直接将需要的所有参数加载到系统parameter中:

<rosparam file=${YAML_FILE_PATH} command="load" ns=${NAMESPACE} />
<!-- YAML_FILE_PATH:参数文件.YAML所在的路径,command需要设置为“load”,也可以设置命名空间 -->

5、启动文件参数 < arg >
parm和arg都代表参数的意思,但是二者作用的对象和作用范围并一样,arg是对于启动文件内参数的定义,并不会影响节点内部的运作。在启动文件内可以通过以下方法定义参数:

<arg name=${ARGUMENT_NAME} default=${ARGUMENT_VALUE} />

6、命名重映射机制 < remap >
当调用多个包的多个节点时,可能由于节点中包含的某些话题或者其他内容名称不一致,导致无法正常工作,这些问题都可以在启动文件中通过remap的机制来解决。具体以后面的实际案例进行讲解。其实现方式为:

<remap from=${OLD_NAME} to=${NEW_NAME} \>
<!-- remap一般是在节点运行命令中出现的,用于将节点内的话题或者其他功能名称重新映射 -->

7、嵌套复用 < include >
可以在一个启动文件中去引用其他的启动文件,可以完成该启动文件中的内容,实现嵌套功能。该命令实现为:

<include file = "${LAUNCH_FILE_PATH}" \>
第八步-实战启动官方小乌龟例程

后面先以官方教程小乌龟为例记录启动文件的一些常用细节。
在官方的教程中,小乌龟的功能包的启动文件是这么写的。

<launch>
  <group ns="turtlesim1">
    <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
  </group>
<!-- 这里创建一个名为“turtlesim1”的命名空间,并且在此空间内创建了一个小乌龟节点 -->
  <group ns="turtlesim2">
    <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
  </group>
<!-- 这里创建一个名为“turtlesim2”的命名空间,并且在此空间内创建了一个小乌龟节点,尽管该节点名称和前一个节点名称一样,但是由于处在不同的 命名空间,因此不会冲突  -->
  <node pkg="turtlesim" name="mimic" type="mimic">
    <remap from="input" to="turtlesim1/turtle1"/>
    <remap from="output" to="turtlesim2/turtle1"/>
  </node>
<!-- 这里启用一个镜像节点,并且重新映射了“input”和“output”两个命名 -->
</launch>

对于镜像节点中的input和output具体包含什么内容,可以直接打开,在调用命令查看其节点信息。

roscore
rosrun turtlesim mimic
rostopic list -v

从话题的列表可以看出,input中包含了乌龟的姿态话题,output中包含了乌龟的运动控制指令。而在启动文件中,它将input映射为turtlesim1/turtle1,也就是将话题 input/pose 重新映射为 turtlesim1/turtle1/pose,将话题 output/cmd_vel 重新映射为 turtlesim2/turtle1/cmd_vel。这是为何?
我们将刚才的mimic节点和roscore通过ctrl-C 杀掉,重新输入命令:

roscore
rosrun turtlesim turtlesim_node __ns:="turtlesim1" __name:="sim"
#这里就是和启动文件中的启动命令本质一样
rostopic list -v
#查看一下此时的话题列表信息

对比没有修改命名空间和节点名称的小乌龟节点,其话题信息为:

显然pose和cmd_vel的名称发生了变化。结合启动文件中的重映射的目的,是为了将mimic节点中的input/pose话题重新映射到turtlesim1/turtle1/pose上,将 output/cmd_vel 重新映射到 turtlesim2/turtle1/cmd_vel。这样就可以实现,将乌龟1的位姿信息转化成对乌龟2的运动控制信息,让乌龟2跟随乌龟1的位姿。

<launch>
    <node pkg="turtlesim" name="turtle1" type="turtlesim_node">
        <param name="background_r" value="0" type="int"/>    
        <param name="background_g" value="255" type="int"/>
        <param name="background_b" value="0" type="int"/>
    </node>

    <node pkg="turtlesim" name="turtle2" type="turtlesim_node" >
        <param name="background_r" value="0" type="int"/>    
        <param name="background_g" value="0" type="int"/>
        <param name="background_b" value="255" type="int"/>    
    </node>
    <node pkg="turtlesim" name="turtle_key" type="turtle_teleop_key">

    </node>
</launch>

试着解读一下这个启动文件的内容及其作用。

第九步-在ROS中使用python语言编写节点

在前面的内容中用的都是c++的语言来编写源代码,对于使用c++的节点代码编写和编译选项都相对于比较熟悉了。但是在很多实际的项目中,使用python进行程序编写的开发成本和开发效率更高,尽管它的运行效率相对于c++较低,但瑕不掩瑜,同时借此机会来记录一下开发python的基本流程。
用python开发ros节点代码门槛比较低,但是当工程内容庞大工程结构复杂之后,就会需要掌握更高阶的开发技巧。网上有很多帖子我也看过很多,有很多值得参考的地方。作为python的超级菜鸟,我懂新手在入门这一部分时的痛点是什么,此处仅记录开发中所需要的最低限度的技巧和相关知识,不深入研究python的运作机制(主要是我也不太懂)。

1、python简单脚本文件在ROS中的执行
python的文件中如果包含了main字段的话,其就可以在终端中输入命令来运行该脚本

python *.py

在ROS中,python的执行脚本一般是放在包路径下的 scripts文件下的,而其他用户的自定义python模块则是定义在src下的。当然我不认为这是固定不变的,可以根据自己的需求和习惯修改。

roscd mytutorials   #进入功能包
mkdir scripts         #创建脚本文件的目录
gedit hello.py        #创建脚本文件

简单的编写一下脚本文件的代码

import rospy

if __name__ == '__main__':
    rospy.init_node("hello_python")
    rate = rospy.Rate(1)
    while not rospy.is_shutdown():
        rospy.loginfo("hello:%s",rospy.get_time())
        rate.sleep()

这是一个单独的简单脚本文件,没有调用自定义的python模块。这个尝试运行这个节点脚本。

rosrun my_tutorials hello.py
#运行该节点脚本
[rosrun] Couldn't find executable named hello.py below /home/lzx/catkin_ws/src/my_tutorials
[rosrun] Found the following, but they're either not files,
[rosrun] or not executable:
[rosrun]   /home/lzx/catkin_ws/src/my_tutorials/scripts/hello.py

发现无法正常运行,这是因为没有给这个脚本文件执行的权限,可以在终端中输入该命令

sudo chmod a+x catkin_ws/src/my_tutorials/scripts/hello.py

再次运行该节点脚本可以看到

上述记录了在ROS中运行一个简单的python节点所需要的最基本的流程。在包内创建目录 /scripts,创建脚本文件,赋予可执行权限。但是在一个实际的项目工程中,python代码是庞大复杂的,因此需要掌握好catkin_make提供的开发python节点的工具。

2、ROS中编写Python工程
一般的python工程中都会包含python的模块,在功能包的/src目录下创建module的文件夹。

cd ~/catkin_make/src/my_tutorials/src
mkdir my_tutorials
gedit __init__.py            #创建__init__.py用于创建模块
gedit sayhello.py            #创建一个简单的模块代码文件

该代码文件内容为:

# coding:utf-8
import rospy
def say_hello():
    rospy.loginfo("你好中国,我喜欢冰淇淋,我最爱冰淇淋")

这个时候需要在功能包的根目录添加一个setup.py用于描述python工程的路径以及可执行文件的路径等信息。

roscd my_tutorials
gedit setup.py            #创建一个简单的模块代码文件

向setup.py写入:

from distutils.core import setup

setup(
    version = '0.0.0',                        #这里的版本号需要和package.xml中的版本号对应
    scripts = ['scripts/hello.py'],            #可执行脚本的文件路径
    packages = ['my_tutorials'],            #描述我们的模块的名称
    package_dir = {'':'src'}                #描述我们的模块所在的位置
)

打开CMakeLists.txt,加入以下内容:

catkin_python_setup()

回到工作区路径编译。

catkin_make —pkg my_tutorials

此时再次运行该节点。

roscore
rosrun my_tutorials hello.py

结果如图所示:

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 点赞 收藏 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
来自远方
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区