ROS

作者: Jimmy_Tong | 来源:发表于2020-09-27 08:51 被阅读0次

    安装最新版cmake & cmake

    https://blog.csdn.net/u011291667/article/details/100022585
    安装/升级 cmake & cmake_gui 为最新版
    首先需要安装:

    sudo apt install qt4*
    sudo apt install openssl
    

    init_workspace需要在 根目录/src/ 下进行

    创建工作空间workspace & 功能包package

    1. 选定工作空间根文件夹
    2. 在根文件夹下创建src文件夹
    3. 在src文件夹下catkin_init_workspce
    4. 在工作空间根文件夹下catkin_make
    5. 在工作空间根文件夹下使能环境变量source devel/nsetup.bash
    6. 在src文件夹下catkin_create_pkg <功能包名称> [<依赖包名称> <...>]
    7. 在src文件夹中编写cpp文件。
    8. 修改CMakefile.txt
    9. 在工作根文件夹中catkin_make
    10. 使能环境变量
    11. 打开新的terminal设置环境变量后就可以运行设计好的功能包。

    几个名字说明

    1. 在命令行中调用的名字是CMakefile中add_executable()中出现的名字,而在rqt_graph中展示的名字是ros::init()中初始化的名字。

    创建用户定义的功能包

    1. 在功能包的文件夹目录中创建msg文件夹
    2. 在msg文件夹中创建<name>.msg文件
    3. 在<name>.msg内部定义数据类型和备选结构(其中数据类型的关键词都是在std_msgs中定义的)
    4. 在package.xml中添加依赖项(需要用的包在build_depend,执行时所需要的运行文件在exec_depend)
    5. 在CMakefile.txt中添加依赖项(需要用的包需要先查找find_package,执行时所需要的运行文件在exec_depend)
    6. CMakefile中需要注意为动态生成的文件添加依赖关系。

    一些小细节

    catkin_create_pkg <pkg_name> [<depend_pkg> ...]
    如果创建包<depend_pkg>中什么都不添加,创建出的package中仅有一个CMakefile.txt & package.xml。
    若添加额外依赖的功能包,生成的内容中才会包括

    roscore在每次运行一个新的程序之前需要重新启动一下以防止之前运行的程序参数干扰本次执行

    生成的头文件在devel下的include中。

    在ubuntu系统中双击F2可以直接对文件的全名进行修改。
    定义service时,注意引用两部分内容:

    1. 发送的topic内容
    2. std_srvs中的触发结构

    transformer(tf)学习

    tf 中的广播用于通知其他节点当前节点的位置
    tf 中的监听用于获得其他各个节点的位置并根据节点位置做出海龟的运动控制

    查看message结构体的数据类型

    rosmsg show geometry_msgs/Twist

    ROS的代码顺序

    注意ROS课程18中的这两行代码的顺序导致报错

    正确的代码&代用方式是:
    先初始化的时候可以先使用__name更改初始化功能包的名字,而先判断的话就一直报错

    ros::init(argc, argv, "transform_broadcaster");
    
    if(argc != 2){
        ROS_ERROR("need turtle name as an input argument");
        return -1;
    }
    //调用方式
    rosrun <pkg_name> <func_name> __name:=<changed_name> /turtle1
    

    但是如果运行的是下面的代码:

    if(argc != 2){
        ROS_ERROR("need turtle name as an input argument");
        return -1;
    }
    
    ros::init(argc, argv, "transform_broadcaster");
    
    //调用方式:
    rosrun <pkg_name> <func_name> __name:=<changed_name> /turtle1
    // 这种调用方式会出错,报错内容为ROS_ERROR  中定义的内容
    

    load.manifest(<pkg_name>)

    讲<pkg_name>复制到python的依赖路径中。

    rospy & roscpp 对应关系

    ROS roslib

    运行python脚本撰写的turtle_tf_broadcast.py中的第6行时候报错,syntax error near unexpected
    token 'learning_tf'

    https://answers.ros.org/question/115346/what-does-roslibload_manifest-do/

    ROS运行的python脚本头部必须添加下述两行代码

    #!/usr/bin/env python
    #-*- coding: utf-8 -*-
    

    否则python无法正确运行

    ROS19讲中的python执行方式

    代码中通过rospy.getparam来获得实际的参数。
    实际调用方式:
    修改内容:

    将ros.getparam('~turtle')修改为ros.getparam('/turtle')
    //新终端
    roscore
    //新终端
    rosrun turtlesim turtlesim_node
    //新终端
    rosparam set /turtle turtle1
    rosrun learning_tf turtle_tf_broadcaster.py __name:=turtle1_tf_broadcast
    //新终端
    rosparam set /turtle turtle2
    rosrun learning_tf turtle_tf_broadcaster.py __name:=turtle2_tf_broadcast
    //新终端
    rosrun learning_tf turtle_tf_listener.py
    //新终端
    rosrun turtlesim turtlesim_teleop_key
    

    注意:使用launch运行turtlesim时
    设定参数列表要更改为:

    ros.get_param('~turtle')
    

    因为launch下系统运行时定义的参数需使用~turtle来访问。

    // 对应于get_param('~turtle')的launch文件撰写
    <node pkg="learning_tf"  type="turtle_tf_broatcaster.py" name="broadcast1" output="screen">
    <param name="turtle" value="turtle1" >
    </node>
    
    <node pkg="learning_tf"  type="turtle_tf_broatcaster.py" name="broadcast2" output="screen">
    <param name="turtle" value="turtle2" >
    </node>
    
    <node pkg="learning_tf" type="turtle_tf_listener.py" name="listener" output="screen" />
    

    运行18讲的C代码和python代码可以直观的感受到python运行速度的慢。

    Launch

    ROS中减少我们频繁打开终端的启动方式:
    xml文件
    实现多个节点的配置&启动

    使用roslaunch文件

    launch文件可以自动启动ROS Master

    必选属性:

    标签说明 http://wiki.ros.org/roslaunch/XML

    pkg: 节点所在功能包的名字
    type: cpp文件编译生成的可执行文件名
    name:节点运行的名称
    output, respawn, required, ns, args

    <param name="output_frame" value="odom" />用来加载参数
    <rosparam file="params.yaml" command="load" ns="params" /> 从文件中load参数
    
    <arg name="arg_name" default="arg_value" />
    arg调用方式
    <param name="foo" value="$(arg arg_name)" />
    <node name="node" pkg="package" type="type" args="$(arg arg_name)" />
    
    <remap from="/turtlebot/cmd_vel" to="/cmd_vel" />
    <include file="$(dirname)/other.launch" />
    
    <launch>
      <node pkg="learning_topic" 
    </launch>
    

    上述内容中type就是CMakelist.txt中add_executable(<name> user_defined.cpp)中的<name>

    roslaunch按照古月居上面的教程讲解中

    output="screen" 指定了信息打印的输出位置为屏幕。

    既然launch文件多teminal操作问题?

    启动后所有的rosrun命令都在同一个terminal中实现,那么如果一个launch文件中调用了很多个需要命令行操作的rosrun功能包,那么如何实现同一个terminal中操作多个需要操作的命令呢?

    rqt是一个综合工具,可以作为调试机器人的上位机

    可视化工具&使用方法

    A. rqt_graph
    rqt_console来打开rqt操作台,默认收集系统的输出信息(显示日志),
    rqt_plot绘制数据曲线,可以订阅具体的某个数据,比如Topic中的xxx
    输入左斜线,自动匹配话题,显示海龟的pose位置,不断刷新数据。

    rqt_image_view查看摄像头图像。RGB color image

    rviz显示模型,点云,障碍物,图像(均已经预先定义好了)。也可以用户定义数据结构。

    • 使用xml进行数据传递
    • 工具栏

    Gazebo三维仿真平台
    本来没有数据,用来创建数据,仿真机器人&传感器&环境。
    可以去gazebo上下载离线模型下载到本地,免去打开时在线访问。

    ROS应用

    • moveit机械臂运动规划,碰撞检测,避障
    • 机器人仿真平台:gazebo + ROS + ros_control
    • slam:: wiki.ros.org/gmapping. wiki.ros.org/hector_slam
      运行别人的代码的问题:建图的精度不高,运行速度慢。

    机器人学:
    机器人基本理论

    1. CS223A - Introduction to Robot:https://see.stanford.edu/Course/CS223A
    2. Underactuated Robotics - Algorithms for Walking, Running, Swimming, Flying, and Manipulation:http://underactuated.csail.mit.edu/Spring2019/,中文翻译:https://blog.csdn.net/ZhangRelay/article/details/99677487
    3. Modern Robotics:http://hades.mech.northwestern.edu/index.php/Modern_Robotics,中文翻译:https://blog.csdn.net/ZhangRelay/article/details/88699314
    4. Robot Dynamics:https://rsl.ethz.ch/education-students/lectures/robotdynamics.html,中文翻译:https://blog.csdn.net/ZhangRelay/article/details/69382096
    5. 15-494/694 - Cognitive Robotics:http://www.cs.cmu.edu/afs/cs/academic/class/15494-s19/,中文翻译:https://blog.csdn.net/ZhangRelay/article/details/86736743
    6. Programming for Robotics - ROS:https://rsl.ethz.ch/education-students/lectures/ros.html,中文翻译:https://blog.csdn.net/ZhangRelay/article/details/79463689
      台湾 交通大学.png

    eth机器人系统课程:
    古月居


    课程说明.png
    资料链接.png
    书籍推荐.png
    机器人理论.png

    如果我们想要开源&解耦合为什么一定要采用ros,为什么不能独立开发一个我们的ROS?

    IMU输出线加速度&角加速度, ->积分得到速度 ->积分得到位置
    里程计:码盘->单位时间内的圈数->速度。

    robot pose
    EKF 使用卡尔曼滤波得到算法。
    机器人本地:TK1&本地的
    完成传感器数据接口。

    远程通过PC连接机器人

    处理器raspherry Pi -> 2. Odroid XU4 -> Nvidia Jetson TK1 -> Mini pc
    外部传感器:摄像头,

    摄像头驱动
    ros-kinertic-usb-cam
    roslaunch usb_cam usb_cam-test.launch
    rqt_image_view

    Kinect

    外部传感器:激光雷达rplidar
    ros-kinetic-rplidar-ros
    rosrun rplidar_ros rplidarNode

    机器人开发案例

    move_base 路径规划,用于导航的功能包。
    sudo apt-get install ros-kinetic-navigation
    自带
    可选
    针对所需机器人而专门设计的相关功能

    功能包的输入和输出。
    wiki会描述输入&输出,并不会描述功能包的各种细节,当所有功能都实现之后才需要关注功能包的实现。
    机器人的输入&输出,传感器数据,里程计信息,是否有IMU,base controller,机器人应当运行的速度。给你1m/s的速度来落实到电机上面。PWM波生成,从控制信号到电信号的转换。ros功能包用到机器人上面。接口问题。
    TurtleBot 资源最为丰富的机器人,上位机。相关书也是以这TurtleBot为例进行讲解的。

    ros中带有turtlebot3相关的功能包。

    应用:

    1. 云机器人,借助全局云完成数据存储&共享。

    ROS DSLAM安装及其debug

    OpenCV nonfree.hpp需要安装OpenCV-contrib

    cmake -DOPENCV_EXTRA_MODULES_PATH=<opencv_contrib>/modules  -DOPENCV_ENABLE_NONFREE=ON <opencv_main>
    

    上面两个<>指定的内容要更改为对应的路径
    安装opencv-contrib方法:
    https://blog.csdn.net/echoamor/article/details/83022352

    查看opencv版本

    时间.png

    sift已经被申请专利了,所以,在opencv3.4.3.16 版本后,这个功能就不能用了。

    采用gtsam-4.0.3进行cpython安装后可以使用。

    解决了OpenCV的问题之后catkin_make可以跑通,报错

    Client [/gen_tf] wants topic /visual_odometry/transform_relative to have datatype/md5sum [dslam_sp/TransformStamped_with_image/26e8057a7a61d2d334ba0ad6445993c3], but our version has [geometry_msgs/TransformStamped/b5764a33bfeb3588febc2682852579b0]. Dropping connection.
    

    issue cannot find octomap_ros

    sudo apt-get install ros-melodic-octomap ros-melodic-octomap-mapping ros-melodic-octomap-msgs ros-melodic-octomap-ros ros-melodic-octomap-rviz-plugins ros-melodic-octomap-server
    

    问题:安装g2o之后没有G2OConfig.cmake

    在对应功能包的CMakeLists.txt中添加
    SET(G2O_CMAKE_MODULES /home/jimmy/bin/Package/cmake_modules/)

    fatal error: Eigen/Core: No such file or directory

    当调用 eigen 库时,会报错:fatal error: Eigen/Core: No such file or directory
    这是因为 eigen 库默认安装在了 /usr/include/eigen3/Eigen 路径下,需使用下面命令映射到 /usr/include 路径下

    sudo ln -s /usr/include/eigen3/Eigen /usr/include/Eigen

    下一个任务time profiling。

    由于ROS并不是实时系统,因此测得的时间并不精确,解决方法:设计一个client-server的service,在灰调函数中设计定时改变,标记为1时开始计时,标记为0时停止计时并打印程序所采用的时间。

    PowerVR SDK使用

    Power VR SDK.png

    计时工具使用

    #include<ros/time.h>
    #include<ros/duration.h>
    
    ros::Time begin=ros::Time::now();
    //下面这5行不是实际可运行代码,而是函数的声明
    //写在这里用于说明函数的使用方式.
    // _sec表示以秒为单位的时间
    // _nsec表示以纳秒为单位的时间
    //_sec和_nsec时间之和为定义的时间
    //double t用来直接定义绝对时间。
    // duration数据类型=绝对时间之差
    ros::Time::Time(uint32_t _sec, uint32_t _nsec)
    ros::Time::Time(double t)
     
    ros::Duration::Duration(uint32_t _sec, uint32_t _nsec)
    ros::Duration::Duration(double t)
    
    //下面是实际的运行代码
    ros::Time at_some_time1(5, 2000_0000);
    ros::Time at_some_time2(5.2);
    ros::Duration one_hour(60*60, 0);
    //讲时间转换为以“秒”为单位
    double secs1=at_some_time1.toSec();
    double secs2=one_hour.toSec();
    
    ros::Duration dur = at_some_time2 - at_some_t
    </launch>ime1;
    ros::Time now_delay3s = ros::Time::now() + ros::Duration(3);
    
    ros::Duration d1=t2-t1;
    ros::Duration d2=d1-ros::Duration(0, 300);
    
    // 用于制定休眠的代码
    bool ros::Duration::sleep()
    ros::Duration(0.5).sleep() 休眠 0.5s
    
    // 定时触发回调函数
    ros::Timer timer1 = n.createTimer(ros::Duration(1), callback);
    
    

    cpp传入参数学习。

    #include<iostream>
    #include<unistd.h>
    
    using namespace std;
    int main(int argc, char** argv){
    
            int o;
            const char* optString = "a:s:d:";
            while((o=getopt(argc, argv, optString)) != -1){
                    switch(o){
                    case 'a':
                            cout << " I am in a" << optarg <<endl;
                            break;
                    case 's':
                            cout << " I am in b" << optarg <<endl;
                            break;
                    case 'd':
                            cout << " I am in c" <<+ optarg <<endl;
                            break;
                    default:
                            break;
                    }
            }
            return 0;
    }
    

    使用方式

    cmake_minimum_required(VERSION 2.8)
    project(test_cpp_version)
    
    set(CMAKE_BUILD_TYPE Release)
    set(CMAKE_CXX_FLAGS "-std=c++14 -O3")
    
    # OpenCV
    find_package(OpenCV REQUIRED)
    include_directories(${OpenCV_INCLUDE_DIRS})
    
    add_executable(testcpp_option test_option.cpp)
    target_link_libraries(testcpp_option ${OpenCV_LIBS})
    

    运行方式

    ./testcpp_option -a sd
    

    当同时制定了多个选项时,优先率先出现的选项

    c++ 析构函数:函数名称前面加上~

    CVBridge

    功能:由于ros内部信息发布publish时传递的图片和OpenCV中读取的图片格式不同,因此在ros内部发布或者OpenCV处理时的图片需要进行相应的转换。

    转换使用方式:
    OpenCV->ROS

    cv::Mat image = imread(<path2img>, -1)
    // -1 means read_image_unchanged
    sensor_msgs::ImagePtr ptr = cv_bridge::CvImage(std_msgs::Header(), "mono8", image).toImageMsg();
    ros::NodeHandle n;
    ros::Publisher pub = n.advertise<sensor_msgs::Image>("/type", fifo_depth);
    pub.publish(ptr);
    

    注:上述ROS类型指sensor_msgs/Image.msg

    // sensor_msgs/Image.msg数据结构
    Header header        # Header timestamp should be acquisition time of image
                         # Header frame_id should be optical frame of camera
                         # origin of frame should be optical center of camera
                         # +x should point to the right in the image
                         # +y should point down in the image
                         # +z should point into to plane of the image
                         # If the frame_id here and the frame_id of the CameraInfo
                         # message associated with the image conflict
                         # the behavior is undefined
    
    uint32 height         # image height, that is, number of rows
    uint32 width          # image width, that is, number of columns
    
    # The legal values for encoding are in file src/image_encodings.cpp
    # If you want to standardize a new string format, join
    # ros-users@lists.sourceforge.net and send an email proposing a new encoding.
    
    string encoding       # Encoding of pixels -- channel meaning, ordering, size
                          # taken from the list of strings in include/sensor_msgs/image_encodings.h
    
    uint8 is_bigendian    # is this data bigendian?
    uint32 step           # Full row length in bytes
    uint8[] data          # actual matrix data, size is (step * rows
    

    其中Header header内容如下:

    uint32 seq
    time stamp
    string frame_id
    

    其中seq是连续增长的ID
    其中stamp包括两个整数,stamp.sec & stamp.nsec

    http://wiki.ros.org/cv_bridge/Tutorials/UsingCvBridgeToConvertBetweenROSImagesAndOpenCVImages

    ROS->OpenCV
    主要有两种

    cv_bridge::CvImagePtr cv_ptr;
    sensor_msgs::ImagePtr dep_ptr = boost::make_shared<::sensor_msgs::Image>(msg->depth);
    // cout << "PRE1" << endl;
        
    cv_ptr=cv_bridge::toCvCopy(dep_ptr, dep_ptr->encoding);
    cv_ptr->image.copyTo(depth_curr);
    

    rso::init(argc, argv, <name>, init_option)

    option的选项:

    • NoSigintHandler: Don't install a SIGINT handler. You should install your own SIGINT handler in this case, to ensure that the node gets shutdown correctly when it exits.
    • AnonymousName: Anonymize the node name. Adds a random number to the end of your node's name, to make it unique.
    • NoRosout: Don't broadcast resconsole output to the /rosout topic.

    share_ptr是智能指针,不需要额外处理指针的释放。而且因为ros中所有的数据类型都采用的是share_ptr的指针定义,因此在对ros内部sensor_msgs/Image 进行数据分配时需要额外对输入数据进行boost::make_shared<>使其变为动态指针。

    visual_odometry_orb_node_orbh.cpp

    这个文件读取了keypoint & descriptor并且筛选K个top score的特征点。
    送入orb进行匹配。
    返回得到的R和t

    下一步,阅读ORB.hpp看其中的实现。

    形参 v.s. 实参

    1. 比如你定义一个函数void add(int a, int b),这里的a和b就是形参。
    2. 当你进行函数调用的时候,add(1, 2),这里的1和2就是实参。

    形式参数仅仅代表一个记号。
    e.g.

    // 函数定义时
    int add(int a, int b);
    

    实际参数

    // 函数调用时
    add(1, 2);
    

    几种函数传递类型:
    常量引用 vs 变量引用
    常量引用:

    int add(const int& a, const int& b){
    
    }
    

    一般调用函数的思想就是:将内存中已有的数据传入函数中进行处理得到返回数值。在函数中a &b就是用户传入的数值且在函数中不会创新新的变量&不会修改传入的变量数值。
    变量引用

    int add(int& a, int & b){
    }
    

    这部分函数传递时会传入定义的变量地址,在函数内部可以任意修改函数的数值
    应用:
    c++类定义时的构造函数定义:

    class Foo{
    
    private:
      int num;
    public:
      Foo(const int& num_value): num(num_value){}
      ~Foo(){std::cout << "Destruct Foo with Name =" << num<< std::endl;}
    }
    

    其中类Foo的构造函数Foo,传入参数就是常量引用类型,并将其赋值给内部私有变量num。

    boost类型指针使用:

    #include<boost/shared_ptr>;
    boost::shared_ptr<Foo> FooPtr;
    int main(int argc, char ** argv){
    FooPtr ptr1(new Foo(1)); // 创建一个Foo类的shared_ptr类型的智能指针。
    // new Foo(1) 创建了一个Foo类,初始化内部数值为1。
    // FooPtr创建了指向新创建Foo类的指针,名称为ptr1。
    ptr1
    FooPtr ptr2 = ptr1;   //创建新指针,与ptr1指向相同的对象(上次创建的new Foo(1)),从而将ptr内部的引用计数+1并且同时ptr2的
    引用计数也+1;
    
    assert(ptr2.usr_count == 2);
    
    ptr2.reset();  // 将ptr2的引用计数清空为NULL。同时ptr1内部的引用计数恢复为1,因为ptr2已经清空。
    assert(ptr2.get() == NULL);
    assert(ptr2.usr_count == 0);
    
    FooPtr ptr3(new Foo(2)); // 创建了新的Foo类,初始化值为2,并且创建新的指向这个Foo类的指针ptr3.
    ptr3.reset(new Foo(3));    // 首先清空ptr3原来的引用,并释放ptr3对于Foo(2)类的指向&&同时创建了新的Foo(3),将ptr3的指针转移到Foo(3)上。
    }
    
    

    循环引用问题:
    Parent&Child分别定义两个指针,而且两者相互引用。

    class Parent;
    class Child;
    class Parent{
    public:
    ~Parent(){std::cout << "call deconstruction function" << std::endl;
    public:
    boost::shared_ptr<Child> child_ptr;
    }
    
    int main(int argc, char** argv){
    
    }
    
    class Child{
    public:
      ~Child(){
        std::cout << "调用child的析构函数\n";
      }
    public:
      ParentPtr parent;
    };
    
    int main(int argc, char** argv){
      ParentPtr father(new Parent());
      ChildPtr son(new Child());
      father->child = son;
      son->parent = father;
    }
    

    采用上述类定义下,若指针之间存在相互引用则在程序结束后指针无法析构。

    而如果内部采用weak_ptr指针,则整个系统可以正常析构。

    class Parent;
    class Child;
    class Parent{
    public:
      ~Parent(){std::cout << "call deconstruction function" << std::endl;
    public:
      boost::weak_ptr<Child> child_ptr;
    }
    
    class Child{
    public:
     ~Child(){std::cout << "deconstruct the child" << std::endl;
    public:
      boost::weak_ptr<Parent> parent_ptr;
    

    修改使其成为weak_ptr后便可以正常进行析构。
    但weak_ptr没有对资源的操作权(weak ptr获得的是资源的观察权)所以无法使用son->parent对parent进行操作。
    boost考虑到了这一点,因此额外增加一个操作使weak_ptr 的son也可以对parent进行操作:
    方法为:

    std::cout << son->parent.lock().use_count() << std::endl;
    

    注意:

    1. weak_ptr获得的是对象的观察权,其不会引起被指向对象引用计数的增减。
    2. weak_ptr可以作为容器的元素。比如std::vector<boost::weak_ptr<Node> > children;

    shared_ptr与容器

    1. boost::shared_ptr<std::vector<> >
    2. std::vector< boost::shared_ptr<> >

    技巧,使用boost封装fileptr自动实现内存管理

    void FileClose(FILE* file){
      int result = fclose(file);
      printf("invoke fclose result = %d\n", result)
    }
    
    FilePtr FileOpen(char const* path, char const* mode){
      FilePtr fptr(fopen(path, mode), fclose); // fclose就是f的删除器,因为对于全局或者静态函数而言,函数名称就是他的首地址。
      return fptr;
    }
    
    int main(int argc, char** argv){
      FilePtr ptr = FileOpen("memory.log", "r");
      FilePtr ptr2 = ptr;
      char data[50];
      FileRead(ptr, data, 50);
      data[50] = "\0";
       printf("%d", data[50]);
       return 0;
    }
    //在程序执行结束后会自动调用fclose从而不必手动管理fclose。
    
    
    

    技巧:使用c++桥接模式来封装现有的C函数,隐藏实现细节。

    在c++的.h文件中声明如下类:
    class FIleSharedPtr
    {
    private:
    class impl;
    boost::shared_ptr<impl> pimp;

    public:
    FileSharedPtr(char const * name, char const* mode);

    指针定义

    • 从右向左读
    char ** p1; 
    //    pointer to    pointer to    char 
    const char **p2;
    //    pointer to    pointer to const char 
    char * const * p3;
    //    pointer to const pointer to    char 
    const char * const * p4;
    //    pointer to const pointer to const char 
    char ** const p5;
    // const pointer to    pointer to    char 
    const char ** const p6;
    // const pointer to    pointer to const char 
    char * const * const p7;
    // const pointer to const pointer to    char 
    const char * const * const p8;
    // const pointer to const pointer to const char
    
    • 等效
    const int *p;    //same as below  const (int) * p
    int const *q;    // (int) const *p
    

    boost 技巧3
    用c++设计模式来封装现有的c++函数,隐藏实现细节。

    // 头文件
    
    class FileSharedPtr
    {
    private:
      class impl;
      boost::shared_ptr<impl> pimpl;
      
    public:
       FIleSharedPtr(char const* name, char const* mode);
       void read(void * data, size_t size);
    };
    
    // cpp 文件
    class FIleSharedPtr::impl
    {
    private:
      impl(impl const &){} 
      impl& operator = (impl const &){}
      FILE* f;
    
    public:
      impl(char const* name, char const* mode){ f= fopen(name, mode);}
      ~impl()
      {
        int result = fclose(f);
        printf("invoke FIleSharedPtr::impl析构函数result");
      }
      void read(void* data, size_t size){ fread(data, 1, size, f)}
    }
    
    FileSharedPtr::FileSharedPtr(const char* name, const char * mode): pimpl (new FIleSharedPtr::impl(name, mode)){}
    
    void FileSharedPtr::Read(void* data, size_tr size){pimpl->read(data, size);}
    
    void Test_CPP_File_Ptr(){
      FileSharedPtr ptr("memory.log", "r");
      FileSharedPtr ptr2 = ptr;
      char data[100];
      ptr.Read(data, 100);
      printf("%s\n", data);
    }
    

    rospython如果报类似如下的错误

    import-im6.q16: not authorized `rospy' @ error/constitute.c/WriteImage/1037.
    import-im6.q16: not authorized `time' @ error/constitute.c/WriteImage/1037.
    /home/jimmy/work/ROS_work/test/src/time_profiling/scripts/time_profiling_node.py: line 12: syntax error near unexpected token `'rgbd_to_pointcloud''
    /home/jimmy/work/ROS_work/test/src/time_profiling/scripts/time_profiling_node.py: line 12: `    rospy.init_node('rgbd_to_pointcloud')'
    

    请在python开头加上如下内容:

    #!/usr/bin/env python
    #-*- coding: utf-8 -*-
    

    python阅读文本内容:
    文件打开模式 描述
    r 以只读模式打开文件,并将文件指针指向文件头;如果文件不存在会报错
    w 以只写模式打开文件,并将文件指针指向文件头;如果文件存在则将其内容清空,如果文件不存在则创建
    a 以只追加可写模式打开文件,并将文件指针指向文件尾部;如果文件不存在则创建
    r+ 在r的基础上增加了可写功能
    w+ 在w的基础上增加了可读功能
    a+ 在a的基础上增加了可读功能
    b 读写二进制文件(默认是t,表示文本),需要与上面几种模式搭配使用,如ab,wb, ab, ab+(POSIX系统,包括Linux都会忽略该字符)

    1. 查看rgb2point_cloud的c++ profiling。
    2. 查找xzl写的octomap的时间print函数。

    rgb2point_cloud的python实现在秒级别

    Gazebo

    按照下面的方式解决gazebo打开黑屏的问题。
    https://blog.csdn.net/weixin_41536025/article/details/85783092

    rviz中无法显示gazebo仿真环境下的机器人

    在terminal中运行

    roslaunch rrt_exploration_tutorials single_simulated_house.launch 
    

    报错Timed out waiting for transform from robot_1/base_link to robot_1/map to bec

    余老板说ros-kinetic-kuboki包在melodic中没有直接的对应包。因此需要源码编译。
    按照此教程操作:
    https://www.ncnynl.com/archives/201903/2884.html

    操作中遇到cmake版本较低(不知道我安装了什么东西改了原先cmake-3.18.2的版本为3.3.2)

    解决方法,找到cmake-3.18.2源码编译安装的目录将目标/bin文件夹添加到路径中即可。

    cmake报有找不到efl的包

    pkg_check_modules(BFL REQUIRED orocos-bfl)
    安装ros-melodic-efl解决。

    sudo apt install ros-melodic-bfl
    

    按照上面的教程运行时报下列错误:

    [ WARN] [1598951602.664929798]: Kobuki : device does not (yet) available, is the usb connected?.
    [ WARN] [1598951602.915293883]: Kobuki : no data stream, is kobuki turned on?
    [WARN] [1598951602.983775]: Battery : unable to check laptop battery info [/sys/class/power_supply/BAT0/charge_full_design || /sys/class/power_supply/BAT0/energy_full_design does not exist]
    

    似乎仍然没有安装上kobuki。

    要求按照下面的代码安装melodic版本的rocon

    mkdir ~/rocon
    cd ~/rocon
    wstool init -j5 src https://raw.github.com/robotics-in-concert/rocon/indigo/rocon.rosinstall
    source /opt/ros/indigo/setup.bash
    rosdep install --from-paths src -i -y
    catkin_make
    

    但是这个url 404了,所以上github上直接找rocon项目对应的rocon.rosinstall源码然后查看对应的raw文件。

    rosdep install

    报错
    首先,ros wiki上说明这个功能包是用于安装catkin workspace中缺失依赖的http://wiki.ros.org/rosdep
    而且上面那个代码似乎无法安装依赖,直接运行下面这一行代码使其可以运行。

    rosdep install --from-paths src --ignore-src -r -y
    

    最终运行的代码为:
    因为我的个人PC已经安装ros,所以没有运行激活ros的环境变量代码。

    mkdir ~/Package/rocon
    cd ~/Package/rocon
    wstool init -j5 src https://raw.githubusercontent.com/robotics-in-concert/rocon/devel/rocon.rosinstall
    rosdep install --from-paths src --ignore-src -r -y
    catkin_make
    
    sudo apt install ros-melodic-rocon-*
    

    这个方法安装过的内容似乎无法被所需函数使用。

    https://blog.csdn.net/zhuquan945/article/details/79694599

    编译安装kobuki时候遇到问题:找不到gzmath.hh。两种办法:

    1. 重新编译安装gazebo9:现在正在下载gazebo9,通过cmakelist中的版本号能够判断具体的型号、
    2. 直接只安装gazebo/math的数据库:这条路可能行不通。
    3. 是否应该安装更高版本的gazebo?

    kobuki能够下载的版本和gazebo接口bug

    因为kobuki的gazebo接口是采用旧版本gazebo而非新版本的gazebo,新版本去掉了原有的math而改用了ignition::math,而且还有physics和sensor两个大类中存在很多函数区别,因此整体有很多依赖bug。下面是修改的一些bug。

    /home/jimmy/Package/kobuki_wstool/src/kobuki_desktop/kobuki_gazebo_plugins/src/gazebo_ros_kobuki_updates.cpp:52:50: error: ‘class gazebo::physics::Joint’ has no member named ‘GetAngle’; did you mean ‘GetForce’?
       joint_state_.position[RIGHT] = joints_[RIGHT]->GetAngle(0).Radian();
    // GetAngle(0).Radian() -> Position(0)
    
    /home/jimmy/Package/kobuki_wstool/src/kobuki_desktop/kobuki_gazebo_plugins/src/gazebo_ros_kobuki_updates.cpp:92:24: error: ‘using element_type = class gazebo::sensors::ImuSensor {aka class gazebo::sensors::ImuSensor}’ has no member named ‘GetAngularVelocity’; did you mean ‘AngularVelocity’?
       vel_angular_ = imu_->GetAngularVelocity();
    
    /home/jimmy/Package/kobuki_wstool/src/kobuki_desktop/kobuki_gazebo_plugins/src/gazebo_ros_kobuki_loads.cpp: In member function ‘bool gazebo::GazeboRosKobuki::prepareIMU()’:
    /home/jimmy/Package/kobuki_wstool/src/kobuki_desktop/kobuki_gazebo_plugins/src/gazebo_ros_kobuki_loads.cpp:298:41: error: ‘class gazebo::physics::World’ has no member named ‘GetName’; did you mean ‘Name’?
                 sensors::get_sensor(world_->GetName()+"::"+node_name_+"::base_footprint::"+imu_name));
    
    // GetName() -> Name()
    
    In file included from /home/jimmy/Package/kobuki_wstool/src/kobuki_desktop/kobuki_gazebo_plugins/src/gazebo_ros_kobuki_updates.cpp:36:0:
    /home/jimmy/Package/kobuki_wstool/src/kobuki_desktop/kobuki_gazebo_plugins/include/kobuki_gazebo_plugins/gazebo_ros_kobuki.h:233:3: error: ‘math’ does not name a type; did you mean ‘tanh’?
       math::Vector3 vel_angular_;
    // math::Vector3 -> ignition::math::Vector3 
    // #include<gazebo/math/gzmath.hh>  -> #include<ignition/math/Vector3.hh>
    如果有需要就在gazebo的repo里面搜math,找到对应的头文件include。
    
    /home/jimmy/Package/kobuki_wstool/src/kobuki_desktop/kobuki_gazebo_plugins/src/gazebo_ros_kobuki_updates.cpp:92:24: error: ‘using element_type = class gazebo::sensors::ImuSensor {aka class gazebo::sensors::ImuSensor}’ has no member named ‘GetAngularVelocity’; did you mean ‘AngularVelocity’?
       vel_angular_ = imu_->GetAngularVelocity();
                            ^~~~~~~~~~~~~~~~~~
    // GetAngularVelocity() -> AngularVelocity();
    

    除此之外还有很多其他由于版本不兼容而导致的bug,所以不进行kobuki的debug
    新问题:
    gazebo没有发出odom库,需要安装的方式为:
    分别下载以下两个repo到两个独立的catkin_workspace中分别编译

    在安装下面两个功能包之前需要先初始化rosdep,并且更新(如果机器之前已经初始化过则无需再次初始化)

    rosdep init
    rosdep update
    

    若上述rosdep init失败,则运行下面代码替换rosdep init

    sudo mkdir -p /etc/ros/rosdep/source.list.d
    cd /etc/ros/rosdep/sources.list.d
    sudo gedit 20-default.list
    # copy the following contents into 20-default.list
    #################
    # os-specific listings first
    yaml https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/osx-homebrew.yaml osx
     
    # generic
    yaml https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/base.yaml
    yaml https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/python.yaml
    yaml https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/ruby.yaml
    gbpdistro https://raw.githubusercontent.com/ros/rosdistro/master/releases/fuerte.yaml fuerte
    #################
    

    下面安装下面的kobuki包
    第一个repo
    kobuki_desktop

    mkdir kobuki_desktop_ws && cd kobuki_desktop_ws
    mkdir src && cd src
    git clone https://github.com/yujinrobot/kobuki_desktop
    cd ../
    rosdep install --from-paths src --ignore-src -r -y
    

    第二个repo

    mkdir kobuki_msgs_ws && cd kobuki_msgs_ws
    mkdir src && cd src
    git clone https://github.com/yujinrobot/kobuki_msgs
    cd ../
    rosdep install --from-paths src --ignore-src -r -y
    ```择日哦通过rosdep安装可以自己安装相应的依赖关系。
    
    
    
    到此为止
    
    ------
    
    回到rrt debug增加一个 /  的问题debug
    

    [ERROR] [1599011298.911594610, 2434.510000000]: TF Exception that should never happen for sensor frame: /robot_1/base_laser_link, cloud frame: robot_1/base_laser_link, Invalid argument "/robot_1/base_laser_link" passed to lookupTransform argument source_frame in tf2 frame_ids cannot start with a '/' like:

    
    
    在debug的同时需要了解系统是如何工作的,方法:
    学习tf tree
    

    rosrun tf view_frames

    可以看到frame之间的关系,生成pdf(tf tree)
    morphology
    

    rosrun rqt_tf_tree rqt_tf_tree

    也可以展示tf tree并且可以刷新看。
    当tf停止发送时,rqt显示的tf tree是最后一次运行时的tf tree
    通过下面的参数n来制定显示多少个tf关系。
    

    rostopic echo -n1 /tf

    
    直接查看turle1 到turtle2的tf
    

    rosrun tf view_frames
    rosrun tf tf_echo turtle1 turtle2

    最后一种可视化方式,采用rviz
    

    rosrun rviz rviz

    在其中add 直接添加tf就可以显示frame之间的相互关系
    
    tf中的publisher就是broadcaster
    tf中的subscriber就是listener
    
    
    

    rossrv show <service_name>
    rosservice info <service_name>
    rosservice call <service_name>

    
    查看ros环境变量
    

    env | grep ROS_
    unset <path_name>

    // set path using source devel/setup.bash

    roscd <package_name> // change the directory to the root path of the package

    使用相同的topic的ros模块会产生多个robot pub相同的topic,导致无法区分topic产生方到底是哪一个。
    解决方法:采用namespace
    
    

    rosrun <pkg_name> <scripts_name> __ns:=robot1

    
    
    rosbag可以记录机器人的移动位置
    

    //start a new terminal
    rosbag -a

    // after record the path of the robot
    rosbag play xxx.bag

    
    rqt_plot <topic_name>可以用于绘制某个topic的名字
    
    

    rosrun rqt_console rqt_ console

    load debug info
    
    rospack list 列出所有功能包的名字
    roscd后面的功能包名字就是package.xml中的定义的<name>
    
    gazebo通过.world文件来创建仿真环境
    
    
    rostest 用于测
    costmap 是障碍物的inflation
    单机代码学习&理解地图中所有信息。
    有问题主要看wiki
    rrt_exploration的边界设置
    测试的时候centroid record[]等等都是空的是什么意思?
    地图Map的topic的含义。
    
    1. 单机:rviz上点,小车走,
    2. 探索速度,走的路径更短,达到多少覆盖率时的探索时间短。
    3. 启动修改
    
    3. 多机新指标:一致性?
    - 一致性?自己定义:尽可能使两个车探索时走同一个路径。强行。(新问题需要定义:定义新问题然后提出解决方案)
    - 摄像头数据
    
    
    ?? rospack
    
    视觉里程计-VO测试
    
    因为没有多机地图融合所以没有 global merge map,所以收不到地图。
    
    # launch 文件中定义的param在参数服务器中的名称
    

    <node pkg="rrt_exploration" type="local_rrt_detector" name="robot2_rrt_detector" output="screen">
    <param name="eta" value="$(arg eta)"/>
    <param name="map_topic" value="robot_2/map"/>
    <param name="robot_frame" value="robot_2/base_link"/>
    </node>

    定义出的param实际名称为:
    

    robot2_rrt_detector/robot_frame

    
    
    # 自定义msg列表中在c++和python中使用的不同
    

    An array of pointstamped

    geometry_msgs/PointStamped[] pointstamped

    这个msg在c++中是一个vector
    而在python中是一个list
    
    
    懵逼内容:filter.py中 24行中args[1]是global_frame到哪个机器人的tf
    得到了tf之后经过24行后所有的坐标点规范到了哪个机器人坐标系下。
    归根结底:
    1. 搞懂transformPoint的含义
    2. 弄清waitTransform的含义。
    
    核心目标:每个机器人sendgoal时得到的是自己坐标系下的坐标。
    
    # 对于transform的理解
    1. 重点要搞清楚是哪个坐标向哪个坐标的坐标变换(由谁指向谁)
    2. 坐标系的朝向,平移正方向:x朝前,y朝左,z朝上,选择正方向是右手握成“你真棒”,拇指指向z轴正方向,四指指向为z旋转正方向。
    3. 
    

    StampedTransform (const tf::Transform &input, const ros::Time &timestamp, const std::string &frame_id, const std::string &child_frame_id)

    上述函数理解为:
    1. 目前tf树中已经有frame_id定义的frame,我想定义一个新的坐标系(叫做child_frame_id)。从frame_id -> child_frame_id的坐标变换(transform)为input。
     
    

    tf::TransformBroadcaster br;
    br.sendTransform(tf::StrampedTransform(transform, ros::Time::now(), "world", turtle_name))

    下面的sendTransform即将定义好的新坐标系child_frame_id发布,即插入tf树中。
    胡春旭写的ROS实践一书中,从frame_id -> child_frame_id的坐标变换(transform)写成了“child_frame_id相对于frame_id的坐标变换,字符记为T_{frameId_childFrameId}"
    
    以乌龟车追踪为例理解transform的相对性。
    每个车都是一个frame,都是一个坐标系。
    

    tf::StampedTransform transform;
    tf::TransformListener listener;
    listener.waitForTransform("/turtle2", "/turtle1", ros::Time(0), ros::Duration(3.0));
    // listener接收tf树中在ros::Time(0)时刻由"/turtle1" -> "/turtle2"的坐标变换,接收后缓存3s。
    listener.lookupTransform("/turtle2", "/turtle1", ros::Time(0), transform);
    // listener查找由"/turtle1" -> "/turtle2"的坐标变换,并将该变换写入transform中。
    //(We want the transform from frame /turtle1 to frame /turtle2.)。

    调用的函数接口
    

    waitForTransform(const std::string &traget_frame, const std::string & source_frame, const ros::Time &time, const ros::Duration &timeout);
    //等待tf 树中出现由source_frame指向target_frame的tf

    最后,乌龟车turtle2追踪turtle1应该获得turtle2 - turtle1的坐标变换,但是transform获得的是turtle1 ->  turtle2的变换,这为什么仍然能够正常work呢?因为turtle2 - turtle1移动是通过调整角度 & 向调整角度后的方向移动x、y的平方和的距离来实现的。由于turtle2 & turtle1之间坐标角度变换只涉及xOy平面的变换,其实turtle1->turtle2变换的角度就是turtle2->turtle1变换的角度(手动画张图就能够理解)。而x,y只是用来平方和距离的,所以正负都不重要,所以该函数能够实现功能。
    
    附录:
    1. 其中atan2和atan的功能是不同的。
    
    atan2(y, x)是4象限反正切,它的取值不仅取决于正切值y/x,还取决于点 (x, y) 落入哪个象限:
    
    当点(x, y) 落入第一象限时,atan2(y, x)的范围是 0 ~ pi/2;
    当点(x, y) 落入第二象限时,atan2(y, x)的范围是 pi/2 ~ pi;
    当点(x, y) 落入第三象限时,atan2(y, x)的范围是 -pi~-pi/2;
    当点(x, y) 落入第四象限时,atan2(y, x)的范围是 -pi/2~0.
    而 atan(y/x) 仅仅根据正切值为y/x求出对应的角度 (可以看作仅仅是2象限反正切):
    
    当 y/x > 0 时,atan(y/x)取值范围是 0 ~ pi/2;
    当 y/x < 0 时,atan(y/x)取值范围是 -pi/2~0.
    
    2. ros::Time(0)
    You can also see we specified a time equal to 0. For tf, time 0 means "the latest available" transform in the buffer.
    说白了就是本地缓存中最新的tf。
    
    那这个最新的tf是不是现在的呢?
    No。
    
    那现在是的是什么?怎么调用?
    Now, change this line to get the transform at the current time, "now()":
    
    
    ros教程
    [http://wiki.ros.org/ROS/Tutorials](http://wiki.ros.org/ROS/Tutorials)
    actionlib
    [http://wiki.ros.org/ROS/Tutorials](http://wiki.ros.org/ROS/Tutorials)
    
    
    namespace或frame增加/为绝对路径,不加/为相对路径。
    
    
    如何查看frame_id中有没有斜杠?
    Solution: rostopic echo <topic_name> > /tmp/log之后可以直接看/tmp/log开始的头文件中的frame_id中有没有
    
    # 地图合并出问题
    能够rosrun启动的节点无法通过roslaunch 启动。
    
    通过修改机器人的movebase中读取的参数数据能够修改机器人运行时的costmap inflation 大小。
    
    ----
    
    
    # 
    先修改move_base的参数使其存储局部地图 或者 直接采用全局地图进行导航。
    此bug解决之后搞人工势场。
    [http://wiki.ros.org/move_slow_and_clear](http://wiki.ros.org/move_slow_and_clear)
    move_base_params_robot1.yaml中的move_slow_and_clear;
    
    报错:
    

    [ INFO] [1600327283.341323192]: No matching device found.... waiting for devices. Reason: std::__cxx11::string astra_wrapper::AstraDriver::resolveDeviceURI(const string&) @ /home/xtark/ros_ws/src/third_packages/ros_astra_camera/src/astra_driver.cpp @ 972 : Invalid device number 1, there are 0 devices connected.
    [ INFO] [1600327283.822143753]: Recovery behavior will clear layer 'obstacle_layer'

    
    通过直接看costmap的数值发现,探索过的地方存在的障碍物被自动清理为0了。
    
    从static map更改为rolling window之后全局的global costmap中的障碍物不再消失,但是所有的障碍物会累加使精度降低。
    
    
    move_base实际不发送costmap,costmap是通过costmap_2d_ros启动发送,在costmap中创建LayeredCostmap发送对应消息。LayeredCostmap初始化中有rolling_window参数,而没有static_map参数。
    
    全部与costmap相关的初始化,修改都在costmap_2d_ros.cpp中完成。
    costmap_2d_ros中定义了
    
    改掉了动态清除障碍物的问题之后出现了障碍物检测飘的情况。
    ![current_issue.png](https://img.haomeiwen.com/i15871661/8e68a350582116c3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    
    ## launch文件中group标签的作用
    group标签含有ns(namespace)参数,指定参数后,其中全部的topic/service都会增加ns的前缀。
    而frame_id前面不会增加ns作为前缀,若想在frame_id前面增加前缀,有以下方案:
    1. 手动修改所有的frame_id字符串,有的写在程序中,有的写在参数配置文件中。
    2. 在launch中写一个arg文件,并在所有的程序.py/cpp文件中增加读取arg并定义。
    
    
    比如我自己写的turtle_tracking.launch文件,若不增加namespace的rqt_graph如下所示
    ![未增加namespace前缀.png](https://img.haomeiwen.com/i15871661/2ca3ac637e451a40.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    
    ![增加了namespace前缀.png](https://img.haomeiwen.com/i15871661/4450f06833314725.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    由此可见,turtle1/pose topic采用参数传递的方式定义,如下所示:
    
    

    in launch file

    <group ns="ns1">
    <node pkg="learning_tf" type="turtle_tf_broadcaster.py" name="broadcast1" output="screen">
    <param name="turtle" type="string" value="$(arg ns)/turtle1" />
    </node>
    </group>

    in py file

    turtlename = rospy.get_param('~turtle')     
    rospy.Subscriber('/%s/pose' % turtlename,
                 turtlesim.msg.Pose,
                 handle_turtle_pose,
                 turtlename)
    
    '/%s/pose' % turtlename没有在前面加上ns的前缀,因此无法正常工作。
    
    另外,请注意:
    

    turtlename = rospy.get_param('~turtle')

    采用~<参数名称>读取的参数就是实际launch文件中指定的数值,因此
    
    若publish内部定义的topic名称前面没有/,代表相对路径,则实际发布的topic名称前会增加如下:
    

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

    则实际发布的topic会将相对路径名称的topic前增加ns,而frameid就只能手动指定。
    
    最终版代码如下,修改group标签的ns和arg标签namespace的value可以直接修改全局的topic和frame id。
    
    

    ###############################
    ############# wrapper launch file
    ###############################
    <launch>

    <group ns="nsw">
    <include file="/home/jimmy/work/ROS_work/test/src/learning_launch/launch/turtle_tracking.launch" >
    <arg name="namespace" value="nsw" />
    </include>
    </group >
    </launch>

    ###############################
    ############# launch file
    ###############################
    <launch>
    <arg name="namespace" default="" />
    <node pkg="turtlesim" type="turtlesim_node" name="node" />
    <node pkg="turtlesim" type="turtle_teleop_key" name="teleop_key" output="screen" />

    <node pkg="learning_tf" type="turtle_tf_broadcaster.py" name="broadcast1" output="screen">
      <param name="turtle" type="string" value="turtle1" />
      <param name="namespace" type="string" value="$(arg namespace)" />
    </node>
    
    <node pkg="learning_tf" type="turtle_tf_broadcaster.py" name="broadcast2" output="screen">
      <param name="turtle" type="string" value="turtle2" />
      <param name="namespace" type="string" value="$(arg namespace)" />
    </node>
    
    <node pkg="learning_tf" type="turtle_tf_listener.py" name="listener" 
    output="screen" >
      <param name="namespace" type="string" value="$(arg namespace)" /> 
    </node>
    

    </launch>
    #################################
    ############# turtle_tf_broadcaster.py
    #################################

    !/usr/bin/env python

    -- coding: utf-8 --

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

    import argparse #引入模块

    import tf
    import turtlesim.msg

    ###################

    added by jianming

    import time
    from time_profiling_node_prevar import TIME_PROFILING
    from time_profiling_node_prevar import TIME_PROFILING_PATH
    ###################

    def handle_turtle_pose(msg, framename):
    br = tf.TransformBroadcaster()
    br.sendTransform((msg.x, msg.y, 0),
    tf.transformations.quaternion_from_euler(0, 0, msg.theta),
    rospy.Time.now(),
    framename,
    "world")
    if TIME_PROFILING:
    ofile = open(TIME_PROFILING_PATH + "turtle_tf_broadcaster.txt", 'a')
    end = time.time()
    ofile.write('time_profiling_node time: %s Seconds'%(end-start))
    ofile.write('\n')

    if name == 'main':
    if TIME_PROFILING:
    start =time.time()
    rospy.init_node('turtle_tf_broadcaster')

    parser = argparse.ArgumentParser()

    parser.add_argument("turtle_name", type=str, help="input turtle's name")

    turtlename = parser.parse_args().turtle_name

    namespace  = rospy.get_param('~namespace') 
    turtlename = rospy.get_param('~turtle')     
    rospy.Subscriber('%s/pose' % (turtlename),
                 turtlesim.msg.Pose,
                 handle_turtle_pose,
                 namespace +"/"+ turtlename)
    
    for i in range(10):
        rospy.loginfo("%s\n", turtlename)
    
    rospy.spin()
    

    #################################
    ############# turtle_tf_listener.py
    #################################

    !/usr/bin/env python

    -- coding: utf-8 --

    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')
    namespace = rospy.get_param('~namespace')
    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)
    while not rospy.is_shutdown():
        try:
            (trans, rot) = listener.lookupTransform(namespace+'/turtle2', namespace+'/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()
    
    
    为了找到为什么之前更改的车存在bug,而xtark原厂的程序直接连通cartographer不出现bug,逐步比对之前余老板/天翔哥定义的文件的内容和原有之间的区分。
    1. 余老板杀出了EKF融合定位节点。xtark_bringup_robot1.launch中没有
    
    其中所有的topic均使用相对路径,即名称前不加"/",而所有的frame_id均使用arg传入的参数作为前缀。
    
    ## 修改xtark内部launch文件使其适配robot1前缀的修改流程:
    1. 新建xtark_mapping_cartographer_robot<num>_jianming.launch  (<num>替换为robot号码)
    2. 将xtark_mapping_cartographer_robot1_jianming.launch 内容复制到1新建文件中,修改第二行ns为robot<num>,修改第三行robot1为指定编号。
    3. xtark_bringup_robot1_jianming.launch文件。其内部包含一下模块
    - xtark_ros_wrapper_node.py:修改:base_footprint, odom, base_imu_link三个frame_id前面增加了robot1. 另外yaml文件中需要手动修改base_frame。
    - robot_pose_ekf.cpp:修改:odom, base_footprint两个frame_id前面增加了robot1.
    - odom_ekf_jianming.py:修改child_frame_id该frame前面增加robot1.
    - rplidarNode.cpp 由于该车的雷达型号为XAS直接找到对应的调用文件复制到此处。修改laser frame前面增加robot1
    - scan_to_scan_filter_chain的参数配置文件中需要修改yaml文件中的box_frame
    - 静态tf树发布为两个frame_id增加robot1前缀。
    - 由于找不到laser所以发布laser和base_footprint之间的tf关系。前面添加robot1名称。
    4. xtark_camera_robot1_jianming.launch文件。里面根据不同的分辨率启动了不同分辨率的launch文件,无frameid需要修改。
    5. cartographer_node文件,需要修改xtark_robot1_jianming.lua内部的frameid,包括:map_frame, tracking_frame, published_frame & odom_frame.
    6. teb_move_base_omni_jianming.launch 文件内需要修改6个yaml文件,不仅要修改frameid还需要修改topic。
    - teb_local_planner_params_robot1.yaml:  map_frame: robot1/odom
    - global_costmap_params_robot1.yaml:    global_frame: robot1/map,   robot_base_frame: robot1/base_footprint
    
    - local_costmap_params_robot1.yaml (   global_frame: robot1/odom)(   robot_base_frame: robot1/base_footprint)
    注意:一下的topic不能修改,因为把movebase放在group内其会自动增加相应的索引。
    - costmap_common_params_robot1.yaml: (  laser_scan_sensor: topic: robot1/scan )(  laser_scan_sensor_2: topic: robot1/scan )(  sonar_scan_sensor: topic: robot1/sonar_cloudpoint ) (static_layer:  "robot1/map")
    
    其中9,10,11,17,18,30,40,56行均手动加入了$(arg namespace)前缀:即手动为下列frame增加了namespace前缀 laser。另有7行xtark_params.yaml需要修改内部的frame名称(8行)
    7. cartographer2discrete.cpp:内部topic定义采用相对路径,内部无frameid,因此无需额外修改。
    8. cartographer_occupancy_grid_node:内部topic定义采用相对路径,内部无frameid,因此无需额外修改。
    
    
    ## 如何将robot1更改为robot2?
    1. xtark_mapping_cartographer_robot2jianming.launch中namespace/robotname取值都修改为robot2。
    2. 参数修改:
    - cartographer的参数修改:xtark_robot1_jianming.lua----所有frame前缀都加入robot1
    - teb_move_base_onmi_jianming.launch中include的6个参数文件中的robot1全部更改为robot2.
    - 创建"$(find xtark_ros_wrapper)/config/xtark_params_robot<num>_jianming.yaml"中并在frame_id前增加robot<num>
    - 创建"$(find xtark_ros_wrapper)/config/xtark_laserfilter_robot1_jianming.yaml"中并在frame_id前增加robot<num>
    - 创建xtark_ros_wrapper/script/odom_ekf_jianming.py,将backup中的内容复制到该文件中并增加**可执行权限**。
    
    
    下面的操作,逐个修改全部的frameid&topic.
    Solution:   在xtark_bringup.launch文件中
    

    <node pkg="xtark_ros_wrapper" type="odom_ekf.py" name="odom_ekf_node" args="-c base_footprint" output="screen">

    默认参数选择-c base_footprint。
    
    
    另外一个warning
    
    

    Map update loop missed its desired rate of 5.0000Hz... the loop actually took 0.3952 seconds

    网上搜索到的一个解释:
    

    This means that your map cannot be updated as fast as you've parameterized it. You can choose to use a smaller or lower resolution map or provide more computational power.

    使用低精度地图或者增加更高的算力。
    最简单的方法就是降低地图的更新频率。实际地图为0.39s那就 2 Hz每秒。
    另外方法:释放算力。
    
    与树莓派相比,树莓派是A72四核心,jetson nano b01是a57四核心。 The Cortex-A72 is a direct successor to the Cortex-A57 - taking the predecessor as a baseline in order to iterate and improve it.
    
    ![A57_vs_A72.png](https://img.haomeiwen.com/i15871661/ab0ffddfb6288a2a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    
    直接将cartographer和move_base直接连接在图片中会额外多出很多数值为0的点。
    ![image.png](https://img.haomeiwen.com/i15871661/184e4487ce8f76fa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    对应的costmap数据会错误。
    
    发现laser scan偶尔会错位找不到tf。
    ![laser_scan正常.png](https://img.haomeiwen.com/i15871661/314ce9e4d96afb1f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    ![laser_scan错位.png](https://img.haomeiwen.com/i15871661/027f89b7a8a77040.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    如果解决laser_scan错位?
    
    已经测试:使用MECX1没有出现该问题。
    使用MECX1的雷达&MECX2的move_base出现了该问题。
    使用MECX2的雷达&MECX1的move_base没有出现该问题,因此认为MECX2的move_base有问题,目前正在重新安装MECX2的move_base模块。
    
    其中
    
    

    sudo apt-get purge ros-*
    sudo rm -rf /etc/ros
    gedit ~/.bashrc

    找到:带有melodic的那一行删除,保存,然后:
    

    source ~/.bashrc

    
    按照ros官网上下载key并且安装完整桌面版本。
    
    

    sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
    sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
    curl -sSL 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0xC1CF6E31E6BADE8868B172B4F42ED6FBAB17C654' | sudo apt-key add -

    安装之后启动在安装以下模块:
    

    sudo apt install ros-melodic-uvc-camera
    sudo apt install ros-melodic-bfl
    sudo apt install ros-melodic-navigation
    sudo apt-get install ros-melodic-web-video-server
    sudo apt-get install ros-melodic-teb-local-planner
    rosdep install teb_local_planner
    sudo apt-get install ros-melodic-teb-local-planner-tutorials
    sudo apt install ros-melodic-uuid-msgs
    sudo apt install ros-melodic-depthimage*
    sudo apt install ros-melodic-unique-id*

    
    [robot_pose_ekf](https://github.com/ros-planning/robot_pose_ekf)
    安装
    

    cd catkin_ws/src git clone https://github.com/ros-planning/robot_pose_ekf.git
    cd .. catkin_make

    source devel/setup.bash rosdep install robot_pose_ekf
    roscd robot_pose_ekf rosmake

    
    
    ![image.png](https://img.haomeiwen.com/i15871661/3e6ccce89f7a6346.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    
    ![rplidar_data_shift.png](https://img.haomeiwen.com/i15871661/25a625b63a362144.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    ![image.png](https://img.haomeiwen.com/i15871661/ba7c70b1d906a687.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    # 从本身增加robot1 namespace需要修改以下文件:
    
    - /home/xtark/ros_ws/src/xtark_nav/launch/xtark_mapping_cartographer_robot1_jianming.launch
    
    - /home/xtark/ros_ws/src/xtark_ros_wrapper/config/xtark_params_robot1_jianming.yaml
    
    -  /home/xtark/ros_ws/src/xtark_ros_wrapper/launch/xtark_bringup_robot1_jianming.launch
    还有其他的,此处不列举。
    
    
    ![image.png](https://img.haomeiwen.com/i15871661/d417fc1967ab662f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    
    rqt_console可以查看warn的报错节点。
    
    hypre-param 就是至yaml文件中说明的内容。
    
    

    相关文章

      网友评论

          本文标题:ROS

          本文链接:https://www.haomeiwen.com/subject/hutyjktx.html