ROS_General - RicoJia/notes GitHub Wiki

  1. Neato Robot: Camp Peavy, can get a docking station: https://www.servomagazine.com/magazine/article/neato-ros-robot-navigation https://github.com/cpeavy2/botvac_node [email protected]

  2. Good read

========================================================================

Installation

========================================================================

  1. sudo apt-get update, apt-get install ros-noetic-desktop-full 问题: 网络可能卡顿
    • rosdep update 报错: 网络,重来
    • sudo rosdep init: Python 组件缺失 sudo apt-get install python-rosdep python-wstool

========================================================================

Create and Build Packages

======================================================================== 0. Good read on Basics

  1. Create Packages

    1. before catkin build, catkin make, source /opt/ros/noetic/setup.bash
    2. catkin_cretae_pkg pkg_name dependencies (like rospy, roscpp).
      • catkin_create_pkg beginner_tutorials std_msgs rospy roscpp for dependent packages
    3. catkin_make --only-pkg-with-deps <target_package>
  2. ros package Basic Structure

    1. src(one workspace) -> multiple packages(no nested packages) -> each package has one package.xml and Cmakelist.txt.
      • ROS only thinks of a directory with package.xml and CMakelists.txt as a package. So you can group different packages however way you want, and they can be repos.
    2. you have devel, src, build. devel is where libraries, executables go. build is where cmake and make are called to configure packages.
    3. Dependency check:
      1. rospack depends1 pkg_name (first order dependencies)
      2. rospack depends pkg_name(all dependencies)
  3. Build

    1. run catkin_make
      • In python, Cmakelist is only for installing dependencies. So your code works if you don't have everything installed. However, in C++, you compile first.
    2. source ~/catkin_ws/devel/setup.bash

CMakelists.txt

  1. After you move your pakcage to another place, or simply change its name, you need to catkin_make it. CMakelist wont match

Misc

  1. Workspace

    1. where you run a project, with multiple packages. source devel/setup.bash - overlay current workspace on top of other, adding the current package path to the $ROS_PACKAGE_PATH
    2. check current workspace: $echo $ROS_PACKAGE_PATH needs to be current_workspace:root workspace
    3. Workspace overlaying: using dependencies from multiple workspaces. Useful when making a newer version of a package. Just source setup.bash
  2. Basic Structure:

    • build: CMake is invoked with cached info.
    • devel: built targets.
    • catkin clean cleans the entire build and devel space.
    • src
      • config
      • include/package_name
      • launch
      • src
      • test (unit/ROS tests)
      • package.xml (MUST INCLUDE IN catkin-compliant packages)
      • CMakeLists.txt - to be put into CMake Build System
      • package_name_msgs
        • action
        • msg
        • srv

========================================================================

Nodes

========================================================================

Basics

  1. shut down ros with signal rospy.signal_shutdown("Action Ser ver not Available!")

  2. To make your python file to be ros file,

    1. no .py, cuz in linux suffix doesnt matter
    2. shbang #!/usr/bin/env python
    3. chmod 775 node
  3. robot_state_publisher: transforms joint states to TF2, given "robot_description"

========================================================================

Publisher, Publisher, Topic

========================================================================

Subscriber

  1. get info from array: use iterator.
    for (auto i = msg.landmarks.begin(); i!= msg.landmarks.end(); ++i){
        nuslam::Landmark landmark = *i;
        std::cout<<landmark.range;
    }

Publisher

  1. Message examples
    • geometry_msgs.msg.PoseStamped. example
    • There's still no geometry_msgs::Quaternion(1,2,3,4); yet.

Topic

  1. Command line tools

    rotopic - hz
    rostopic list -v: verbose list	 
    rostopic pub /TOPIC MESSAGE_TYPE '{field_name:[1,2,34]}'    #publishing an array
  2. rqt_plot:

    1. source setup.bash; rosrun rqt_plot rqt_plot
    2. then, you add exact msg (name/subfields)to it. you can check the name by rostopic echo
    3. if the topic you specified does not exist when you launch, then it will not show that topic and you will have to manually select it.
  3. use latched topic (last message will be sent to any new subscriber), as in:

    pub = rospy.Publisher('topic_name', std_msgs.msg.String, queue_size=10, latch = False). 

Tips and Errors

  1. Ros_publish and subscriber should be in the same call back function. 1. code is simpler 2.single thread.

  2. boost::bind and passing extra arguments into a callback function. (boost::bind "binds" arguments to functions. )

    • example
      bool update_grid_map_data(std::vector<int>& data, prm::update_grid_map_data::Request& req, prm::update_grid_map_data::Response& res) {
          return true;
      }
      std::vector<int> grid_map_data = grid_map.get_data(); 
      ros::ServiceServer srv = nh.advertiseService<prm::update_grid_map_data::Request, rm::update_grid_map_data::Response> ("update_grid_map_data", boost::bind( &update_grid_map_data, grid_map_data, _1, _2));
      boost::bind(&omnid_group_planning::Omnid_Group_Planner::subCB, this,_1));
      //_1, _2 are placeholders; 
      // you need to declare type <prm::update_grid_map_data::Request, prm::update_grid_map_data::Response>
    • TODO: can we use std::bind as well?

========================================================================

ROS Messages

========================================================================

  1. Motivation: it's a schema, like proto! So it generates code in python/c++ for msgs. it's in msg folder

    • supports simple data types. int, float... but also array/vector.
  2. Command Line Operations

    rosmsg show -- check msg composition

Create a Message

  1. msg file

    #MyMessageType.msg
    
    Header header;
    SomeExtraInfo info
    IntMultiArray theUnderlyingData
    • Special type: header, contains time stamp and coords
  2. add to package.xml so code can be generated.

    <buildtool_depend>catkin</buildtool_depend>
    <build_depend>message_generation</build_depend>
    <exec_depend>message_runtime</exec_depend>
    <depend>geometry_msgs</depend>
    • You need build_depend only for messages in another package
  3. add to Cmakelist.txt:

    # catkin combines all your projects into one, and without this your code may run fine if a previous proj has it. But it will break!!!
    find_package{message_generation}		
    
    # export runtime dependency	
    catkin_package(CATKIN_DEPENDS message_runtime
    
    add_message_files(
     FILES
     Your_MSG_File.msg	#			  #FILES is just CMake syntax.
    ))
    
    generate_messages(		#make sure this func is called
     DEPENDENCIES
     std_msgs
    )
  4. Code

    1. std_msgs: contains all the basic message types, int, float, Float64MultiArray, etc. even has std_empty.
    2. Native conversion b/w arr and vector. ```cpp int32[] arr = std::vector; std::vector = int32[] arr;
      
      
  5. catkin_make install

    • Now c++ code is generated here, ~/catkin_ws/devel/include/beginner_tutorials/, python in ~/catkin_ws/devel/lib/python2.7/dist-packages/beginner_tutorials/msg
    • catkin_make install: create install, and a binary version in it. Good for distribution.

Common Errors and Tips for Messages

  1. Vector3 does not use []. you should do msg.property

  2. latched msg will be saved and sent to any subscriber that connects in the future.

========================================================================

ROS Services

========================================================================

Calling a Service from Code

  1. check what goes into a message
    rostopic pub topic_name, then hit double tab  
  2. In Python Create a message
    duration_msg = Duration()       #Duration is the comes from std_msgs Duration
    duration_msg.data.secs = sleep_time #data is the field
    duration_msg.data.nsecs = 0           
  3. Python Call Service
    	#serviceproxy returns an object that you can call. 
    self._tele = rospy.ServiceProxy(self._mark + "/teleport_absolute",TeleportAbsolute)
    
    	# the serivce name is "mark/teleport_absolute", and TeleportAbsolute is the rosservice type. TeleportAbsolute.srv is in the roaming_turtle folder	
    self.teleport1(x = init_pos[0],y=init_pos[1],theta = init_heading)

Creating a Service In Code

  1. Make your own service
    1. create srv folder,
      1. Create .msg
        geometry_msgs/Twist twist
        ---
        TurtleVel tvel      #unit8 off is used for boolean values
      2. Create .srv, composed of request and response types.
        int64 A
        int64 B
        ---
        int64 sum
    2. In main file:
      from turtle_control.srv import VelTranslate, VelTranslateResponse
      self._srv = rospy.Service("vel_translate", VelTranslate, self.from_twist)   #callback function
      
      ...
      def from_twist(self, req): 
          if req.twist.linear.y != 0.0 
              return None
          else:`
              return VelTranslateResponse(TurtleVel(linear = req.twist.linear.x, angular = req.twist.angular.z))
      # You don't have to set every single value, cuz there're default values
      • cpp
        #include Service_Name, Service_NameResponse
    3. in your Cmakelist file,
      find_package(catkin REQUIRED COMPONENTS
      		  roscpp
      		  rospy
      		  std_msgs
      		  message_generation
      		)
      # Note that message_generation is also needed here!
      
      add_service_files(
         FILES
         VelTranslate.srv
      )
      
      # make sure your srv is compiled before linking: 
      add_dependencies(executable ${project_EXPORTED_TARGETS})
    4. package.xml
      	  <build_depend>message_generation</build_depend>
      	  <build_export_depend>message_runtime</build_export_depend>
      	  <exec_depend>message_runtime</exec_depend>

Common Errors for Service

  1. if you intend to return nothing, you should return std_srvs.Empty. Returning none or not returning will trigger error.
  2. You also need the full ros msg stuff - service is built on top of msg
    # Python
    from std_srvs.srv import Empty.srv
    
    #in Cmakelist
    find_package(catkin REQUIRED COMPONENTS geome try_msgs message_generation std_srvs)
    generate_messages(DEPENDENCIES geometry_msgs std_msgs std_srvs)
    #在add_message_files、add_service_files宏之后必须加上这句话,用于生成srv msg头文件/module,生成的文件位于devel/include中
                    
    catkin_package(CATKIN_DEPENDS geometry_msgs message_runtime std_msgs std_srvs) # catkin宏命令,用于配置ROS的package配置文件和CMake文件
    
    # package.xml: 
    <depend>std_msgs</depend>

========================================================================

ROS Action

========================================================================

  1. Purpose: long services can be stopped. link
    • actionlib API is used for implementing an action server, or use an action client that depends on actionlib)
  2. Structure: action server + action client, with goal, feedback, and result
    • goal: move_base, go to that destination! Laser_scanner: tilt to that angle!
    • feedback: we're at this location sir.
    • result: turtle has moved to this location! laser scanner is tilted AND THIS IS THE LASER scanned file!

Create a ROS Action

  1. build ./action/DoDishes.action

    • similar to service, but 6 messages are generated! one on the client, one on the server.
    # Define the goal
    uint32 dishwasher_id  # Specify which dishwasher we want to use
    ---
    # Define the result
    uint32 total_dishes_cleaned
    ---
    # Define a feedback message
    float32 percent_complete
  2. CMakelist

    find_package(catkin REQUIRED genmsg actionlib_msgs actionlib)
    add_action_files(DIRECTORY action FILES DoDishes.action)
    generate_messages(DEPENDENCIES actionlib_msgs)
  3. package.xml

    <build_depend>actionlib</build_depend>
    <build_depend>actionlib_msgs</build_depend>
    <exec_depend>actionlib</exec_depend>
    <exec_depend>actionlib_msgs</exec_depend>
  4. Python example simple client:

    import roslib
    roslib.load_manifest('my_pkg_name')
    import rospy
    import actionlib
    
    from chores.msg import DoDishesAction, DoDishesGoal
    
    if __name__ == '__main__':
        rospy.init_node('do_dishes_client')
        client = actionlib.SimpleActionClient('do_dishes', DoDishesAction)
        client.wait_for_server()
    
        goal = DoDishesGoal()
        # Fill in the goal here
        client.send_goal(goal)
        client.wait_for_result(rospy.Duration.from_sec(5.0))
  5. Launch

    roslaunch pkg_name node_name arg:= something 
    #or if arg is private (so in launch file it's ~, here it's _)
    roslaunch pkg_name node_name _arg:= something 

========================================================================

Launch Files

========================================================================

Xml Basics

  1. Xml file is indentation-insensitive

  2. Syntax: Don't forget to include / at the end!

    <node name = "roaming_turtle" pkg="turtlesim" type="turtlesim_node"/>. 
  3. namspace: like directories. they can be nested.

    1. it's relative naming - names will be prepended with namespace

      node1 resolves to /workspace1/node1
      topic1 resolves to /node1/topic1
    2. global names: with /

    3. private name: specific to only one node.

      ~topic1 resolves to /namespace1/node1/topic1
    4. Always use relative names in your code, so you can have two nodes with the same code, but different parameters.

  4. launch file should have <?xml version="1.0"?> so vim will recognize this

Launch File Fields

  1. Launch file roslaunch rjje_arm rjje_arm.launch

  2. You need to source the workspace before using $find$ in launch files

    • $(find pkg): useful for finding a package, but again you need to source it!
  3. <include>: include another launch file

  4. <group>: group nodes together, so you can apply the same settings on them.

    1. Use: assign name space to a group of nodes
      <group ns="threes">
  5. <machine>: specifies a remote machine, which nodes will run upon

    1. in command line,
      export ROS_MASTER_URI=http://host_name:11311/
    2. Specify Name
      <machine name="local_from_turtlebot" address="localhost" user="student" env-loader="/home/student/install/env.sh"/>
  6. inside<node>

    1. The order in which nodes start is non-deterministic.
      • All launch files are launched before the nodes are launched in the launch file.
      • so these params are launched before the node is launched.
    2. use different namespaces if two nodes have the same name.
      <group ns="turtlesim1">
        <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
      </group>
    3. if the node dies, the whole roslaunch is terminated.
    4. <node pkg="usb_cam" type="usb_cam_node" name="cam1" required="true" output="screen">
      • executable file name is type,
      • name is the base name (relative name) of the node. 这样你这个topic 里所有的topic, service,等 都叫/cam1/something
      • output=screenerror message will be sent to screen. otherwise, output = "log", error message will be sent to log. This is important if you want to output msgs to screen.
  7. remap:

    • change the name of your node, etc. from command line
      $ rosrun turtlesim turtlesim_node __name:=my_turtle
    • remap private name even inside
      <node name="ekf_and_odometer" type="ekf_and_odometer" pkg="nuslam >
               <remap from="~set_pose" to="/Odometer/set_pose"/>
      </node>
  8. <env>: set environement variables for nodes.

    • $(env ENVIRONMENT_VARIABLE)$ Expands the environment variable

Pass Args

  1. <arg>: specify arguments, which can be used in command line.$(arg myarg): expands myarguement

    1. set up an argument and its doc. In lauchfile,
      <arg name="up" default="true" doc="Launch the count up node if true"/>       
    2. Appplication: conditionals
      //example 1
      <group if="$(arg foo)">
          ... 
      </group>
      <param name="foo" value="bar" unless="$(arg foo)" />  
      
      // example 2
      <node pkg="counter" type="count_down" name="up" if="$(arg up)" />
    3. Application: argument for next included arg:
      // example 3 - arg
      <include file="$(find darknet_ros)/launch/darknet_ros.launch" >
          <arg name="image" value="/cam1/image_raw"/>
      </include>
  2. use : set individual parameters on the parameter server.

    1. in launchfile, inside the corresponding node section,
      <param name="~val" value="$(eval 2+2)"  type='double|bool|str|int'/>
      or <param name="landmark_mode" value="$(arg robot)"/>
    2. in code
      nh2.getParam("val", val);
    3. rosrun pkg node _arg_name should have _at the front, otherwise it is not recognized by ros.
      // in cpp
      nh2.getParam("arg_name", arg_name)
    4. Param Server
      # get param
      rosparam get /use_sim_time
  3. command line -> launch file -> node: (TODO: untested, used to pass args into nodes. )

    • launch file
      <arg name="use_default_grid" default='1'/>
      <node name=....>
         <param name="use_default_grid" value="$(arg use_default_grid)"/>
      </node>
    • cpp
      nh2.getParam("use_default_grid",use_default_grid); 
  4. evaluate args: $(eval <expression>) runs python code and expands the result.

    $(eval arg('somearg') + str(arg('another_arg'))"

Roslaunch Tricks and Errors

  1. tricks 0. Check doc of the arguments passed to the launch file bash $roslaunch me495_image usb_cam.launch --ros-args
    1. Check nodes being launched
      $roslaunch me495_image libuvc_cam.launch --node       
    2. Check arguments passed to a node
      $roslaunch me495_image usb_cam.launch --args = /cam1
    3. Check if there are oterh launch files included
      $roslaunch me495_image usb_cam.launch --files
    4. Find which launch file starts this node:
      $roslaunch me495_image usb_cam.launch --find /cam1

========================================================================

MultiThreading

========================================================================

Basic Concepts

  1. What do ros::spin() and ros::spinOnce() do?

    • Messages will come to a buffer first and wait to be processed by these two functions. ros::spin() will enter a loop, wait and execute callbacks. So messages are processed on ONE thread
    • You can do timer callbacks+ ros::spinOnce at the same time.
    • ros has a single-threaded model, but behind the scenes it might use multiple threads to do network management, scheduling etc. ros::spinOnce will read all available callbacks on the queue.
  2. For callbacks on Multithreads

    • ros::MultiThreadedSpinner is a blocking spinner (so blocks a callback from being run on multiple threads)?
    • start right away
    • ros::MultiThreadedSpinner(num_thread), num_thread = 0 will use 1 thread from all cores
    • Of course, if callbacks have shared data, there might be a race condition.

Advanced Concurrency

  1. To truly do "concurrent subscriptions", do
    ros::SubscribeOptions ops;
    ops.template init<std_msgs::String>("chatter", 1000, chatterCallback);
    ops.transport_hints = ros::TransportHints();
    ops.allow_concurrent_callbacks = true;
    ros::Subscriber sub = nh.subscribe(ops);   

========================================================================

ROS Plugins

========================================================================

  1. ROS plugin is not a node, but instead a dynamic class that is launched into a node. READ THIS FIRST

    ros::NodeHandle nh("~"+name);   //so this will get the intended namespace
    std::string full_name = nh.resolveName("")  // will prepend the full node name here. 
    • ros::NodeHandle::resolveName(NAME, remap=true);
  2. Principle:

    1. Shared Object, Dynamically Linked Lib
      • package.xml register it.
      • Pluginlib is a dir of header files that's copied to public space, which defines The parent class
  3. Code

    1. cpp parent class polygon_base.h
      namespace polygon_base{
      class RegularPolygon
      {
        public:
          virtual void initialize(double side_length) = 0;    // Use this since ctor is not available
          virtual double area() = 0;
          virtual ~RegularPolygon(){}
        protected:
          RegularPolygon(){}  // required by pluginlib
      };

    }; }; 2. CPP child class (you can have multiple):polygon_child.h cpp #include <pluginlib_tutorials_/polygon_base.h> namespace polygon_base{ 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(); } private: double side_length_; }; }; 3. Export classes:src.cpp #include <pluginlib/class_list_macros.h> #include <pluginlib_tutorials_/polygon_base.h> #include <pluginlib_tutorials_/polygon_plugins.h>

     // Full namespace is necessary
     PLUGINLIB_EXPORT_CLASS(polygon_plugins::Triangle, polygon_base::RegularPolygon)
     ```
    
    1. In class CMakelist.txt, you can compile this now, sowe have a lib (not known for loading yet)

      include_directories(include)
      add_library(polygon_plugins src/polygon_plugins.cpp)
      
    2. Make plugin visible to catkin toolchain, (polygon_plugins.xml)

      <library path="lib/libpolygon_plugins"> 
      # path to devel/lib/libpolygon_plugins.so
        <class type="polygon_plugins::Triangle" base_class_type="polygon_base::RegularPolygon">
        </class>
      </library>
    3. Export plugin

      <export>
        <pluginlib_tutorials_ plugin="${prefix}/polygon_plugins.xml" />
        #${prefix} defines path to the current pkg
        #pluginlib_tutorials_ is the name of the package base class lives in
      </export>
      • Verify you can see the plugin: rospack plugins --attrib=plugin ParentClass: gives full path to the polygon_plugins.xml.
      • tool chain has "ros/package.h"
        ros::package::getPlugins(package, attrib_name, paths, force_recrawl);
    4. Use a plugin cpp

      #include <pluginlib/class_loader.h>
      #include <pluginlib_tutorials_/polygon_base.h>
      
      int main(int argc, char** argv)
      {
        pluginlib::ClassLoader<polygon_base::RegularPolygon> poly_loader("pluginlib_tutorials_", "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);  }
        catch(pluginlib::PluginlibException& ex)
        {
          ROS_ERROR("The plugin failed to load for some reason. Error: %s", ex.what());
        }
    5. Use a plugin, CMakelist.txt

      add_executable(polygon_loader src/polygon_loader.cpp)
      target_link_libraries(polygon_loader ${catkin_LIBRARIES})
  4. rviz plugins

    • if parent class is rviz::Display, then you will see it under display Add. If parent class rviz::Tools, click +

========================================================================

Nodelets

========================================================================

  1. Principle: Tutorial

  2. Motivation: High volume of data transfer between nodes: e.g., TCP's serialization & copying, has a lot of packaging & unpackaging.

    1. On same computer, you can pass a pointer. Assuming message is not changed!
    2. Nodelets do the above. They're "threaded nodes" that
      • Are DYNAMICALLY loaded (so they're plugins) into nodelet manager, then grouped into a single process
      • If two nodelets communicate over the same topic, you can pass the address around. Otherwise, the message type will be the same?
      • Appear to be independent, because their namespaces are preserved.
    3. Only available at roscpp
  3. Caveats: https://www.clearpathrobotics.com/assets/guides/kinetic/ros/Nodelet%20Everything.html

========================================================================

Smaller Tools and Tricks

========================================================================

Package Management

  1. rosinstall file - include your own repository

  2. download things: if its melodic, you can use apt. turn all _ turned into-

  3. Wstool: for downloading packages from multiple repos.

    1. e.g: downloading from github
      wstool init #if path not specified, the rosinstall file should be in the same directory.
      cd src
      wstool set proj_name --git https://github.com:user/project.git   # downloading the package. \# here the local .rosinstall file will be replaced by the .rosinstall file on github.
      wstool merge -t src PATH_TO_ROSINSTALL_FILE  #merge in .rosinstall file
    2. making your own proj.rosinstall - so other ppl can pull using wstool
      1. change name to proj.rosinstall, then use Yaml to include local name, git name. Link
      2. put it up on github repo
    3. if there's another .rosintall file already, do wstool init something.rosinstall

ROS Shortcuts

  1. Ros Shortcuts

    1. roscd
      • roscd beginner_tutorials
    2. Ros editor: rosed?
    3. Powerful combination:
      1. $rospack - providing info about packages. e.g,
        $rospack list |grep turtle # this will give you all items with turtle.
      2. roscd: cd to the directory of the pacakge, once it's you can see in
  2. Rospack 1. roscp: copy files from one to another repository, separate - roscp [package_name] [file_to_copy_path] [copy_path] #copy files from one package to another.

========================================================================

General Errors

========================================================================

  1. no module named 'yaml': turn off your conda environment
  2. 有时,当你看到 msg_type doesn't have attribute, 你有可能一个field 给看漏了!

Debug

  1. launchfile
    <arg name="debug" default="false" />
    <arg unless="$(arg debug)" name="launch_prefix" value="" />
    <arg     if="$(arg debug)" name="launch_prefix" value="gdb --ex run --args" />
⚠️ **GitHub.com Fallback** ⚠️