Jethro's Braindump

Robot Operating System (ROS)

Introduction to ROS

What is ROS?

Left

  • Meta-operating system, providing low level services:
    • process communication over a network
    • device control
    • hardware abstraction
  • Distributed framework of processes

Why use ROS?

  • “Lightweight” framework that speeds up large-scale robotic development
  • Many libraries developed on top of this framework that can be reused:

ROS Concepts

Computational Graph

  • All computation is organized as a peer-to-peer network of communicating processes.

Nodes

  • Processes that perform any form of computation.
  • Nodes can communicate with one another.
  • Example of nodes:
    • Publish sensor readings
    • Receiving teleop commands and running them
  • Written with ROS client libraries (rospy, roscpp)

Master (Primary) Node

  • Provides name registration, node lookup to all nodes in the computational graph.
  • Enables communication between nodes.

Parameter Server

  • “Distributed” key-value store: all nodes can access data stored in these keys.

Topics

  • Nodes communicating via the publish-subscribe semantics do so by publishing and subscribing to topics.
  • Every topic has a name, e.g. /sensors/temp1
  • No access permissions

Services

  • Request-response semantics (think Web servers)
  • Requests are blocking

Example Computational Graph

  graph G {
        master[label="Master Node (RPI)", style=filled, fillcolor=yellow]
        mega[label="Servo Microcontroller (Arduino Mega)", style=filled, fillcolor=yellow]
        lws[label="Lewansoul MC"]
        ws[label="ROS Websocket Server (RPI)", style=filled, fillcolor=yellow]
        phone[label="Phone Joystick"]
        master -- mega
        master -- ws
        ws -- mega
        mega -- lws
        phone -- ws[style=dotted];
  }

Getting Started With ROS

ROS Environment Setup

Here I assume you have the ROS environment set up. If not, see the appendix.

Creating a ROS Workspace

Catkin is ROS’ package manager, built on top of CMake.

  mkdir -p ~/catkin_ws/src        # Create the directories
  cd ~/catkin_ws/                 # Change to the directory
  catkin_make                     # Initial setup

Exploring ROS shell commands 1

rospack

rospack find locates ROS packages.

  rospack find roscpp # /opt/ros/melodic/share/roscpp

roscd

roscd changes you to the directory of the ros package.

  roscd roscpp
  pwd # /opt/ros/melodic/share/roscpp

Creating a ROS package

We use the convenience script catkin_create_pkg to instantiate our package.

  cd ~/catkin_ws/src
  catkin_create_pkg workshop std_msgs rospy roscpp
  # Created file workshop/CMakeLists.txt
  # Created file workshop/package.xml
  # Created folder workshop/include/workshop
  # Created folder workshop/src
  # Successfully created files in /home/jethro/catkin_ws/src/workshop. Please adjust the values in package.xml.

What’s in a ROS package?

  workshop
      CMakeLists.txt          # Build instructions
      include                 # For cpp deps, if any
         workshop
      package.xml             # Details about the package
      src                     # Contains source code

Starting ROS

We initialize the ROS master node with roscore.

  roscore

  # ...
  # process[master]: started with pid [16206]
  # ROS_MASTER_URI=http://jethro:11311/

  # setting /run_id to 05bf8c5e-efed-11e9-957b-382c4a4f3d31
  # process[rosout-1]: started with pid [16217]

To kill it, press Ctrl-C in the same terminal.

ROS Nodes

rosnode

rosnode let’s us inspect available nodes:

  rosnode list                    # /rosout
  rosnode info /rosout

What happens if master is not running?

  rosnode list               # ERROR: Unable to communicate with master!

Running a ROS node

A ROS package may contain many ROS nodes.

  rosrun turtlesim <TAB>
  # draw_square        mimic              turtlesim_node     turtle_teleop_key
  rosrun turtlesim turtlesim_node
  # [ INFO] [1571214245.786246078]: Starting turtlesim with node name /turtlesim
  # [ INFO] [1571214245.790986159]: Spawning turtle [turtle1] at x=[5.544445], y=[5.544445], theta=[0.000000]

Exercise: reinspect the node list.

ROS Topics

Now we have a visual simulation of a turtle. How do we make it move?

rosrun turtesim turtle_teleop_key

What’s going on?

  • turtle_teleop_key advertises on a ROS topic, and publishes each keystroke:
  rostopic list
  rostopic echo /turtle1/cmd_vel

ROS Messages

  • ROS messages are pre-defined formats. They are binarized and compressed before they are sent over the wire.
  rostopic type /turtle1/cmd_vel   # geometry_msgs/Twist

Monitoring the Topic

  • The rate at which messages is published is good to monitor (in Hz).
  • A topic that has too many messages can get congested, and buffer/drop many messages, or congest the ROS network.
  rostopic hz /turtle1/cmd_vel
  # subscribed to [/turtle1/cmd_vel]
  # average rate: 13.933
  # min: 0.072s max: 0.072s std dev: 0.00000s window: 2

Rosbag

  • A bag is subscribes to one or more topics, and stores serialized data that is received (for logging/replay)
  rosbag record /turtle1/cmd_vel
  # [ INFO] [1571294982.145679913]: Subscribing to /turtle1/cmd_vel
  # [ INFO] [1571294982.168808833]: Recording to 2019-10-17-14-49-42.bag

ROS Services

  • Services allow request-response interactions between nodes.
rosservice list
rosservice call /clear
rosservice type /spawn | rossrv show

ROS Params

the rosparams commandline interface allows us to store and manipulate data on the ROS Parameter server. 2

  rosparam set            # set parameter
  rosparam get            # get parameter
  rosparam load           # load parameters from file
  rosparam dump           # dump parameters to file
  rosparam delete         # delete parameter
  rosparam list           # list parameter names

Pubsub

When do we use topics?

Previously we looked at ready-made ROS packages and how they used topics and services. Now, we’ll write our own publisher and subscriber.

The pubsub interface is useful in situations where a response for each request is not required:

  • Sensor readings
  • Log info

A Simple Publisher

We use rospy, but roscpp is fine as well. We create a new file in our workshop package workshop/src/talker.py:

  #!/usr/bin/env python
  import rospy
  from std_msgs.msg import String

  pub = rospy.Publisher('my_topic', String, queue_size=10) # initializes topic
  rospy.init_node('talker', anonymous=True) # required to talk to Master

  while not rospy.is_shutdown():
      pub.publish("Hello")

Executing the Publisher Node

We need to make our Python file executable:

  chmod +x talker.py
  rosrun workshop talker.py

Exercise: monitor the output. What’s wrong? (hint: Hz)

Setting the rate of publishing

We use the Rate object, and the rate.sleep() to set the rate of publishing:

  rate = rospy.Rate(10)           # 10 hz
  # ...
  rate.sleep()
  # ...

Good Practice

We often wrap all our logic in a function, and catch the ROSInterruptException exception:

  #!/usr/bin/env python
  import rospy
  from std_msgs.msg import String

  def talker():
      pub = rospy.Publisher('my_topic', String, queue_size=10) # initializes topic
      # ...

  try:
      talker()
  except rospy.ROSInterruptException:
      pass

Exercise: Write a time publisher (5 minutes)

Goal: publish the current date-time onto a topic /datetime.

Hint: Python has a datetime library.

Subscriber

We create a listener in workshop/src/listener.py

  #!/usr/bin/env python
  import rospy
  from std_msg.msg import String

  def echo(data):
      print(data.data)

  def listener():
      rospy.init_node("listener", anonymous=True)
      rospy.Subscriber("my_topic", String, echo)
      rospy.spin() # prevents python from exiting

  listener()

Summary

  rospy.init_node(name)           # create node
  rospy.Publisher(topic_name, msg_type) # create publisher
  rospy.Subscriber(topic_name, msg_type, callback) # create subscriber
  rospy.Rate(10)                  # rate object
  rospy.spin()                    # spin

Services

Msg and Srv

msg
message files that define the format of a ROS message. These generate source code for different languages (think Apache Thrift, Protobuf).
srv
describes a service (request/response)

Creating a msg

mkdir -p workshop/msg

Create a file workshop/msg/Num.msg:

  int64 num

Compiling the msg

In package.xml:

  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>

In CMakeLists.txt:

  find_package(catkin REQUIRED COMPONENTS
     roscpp
     rospy
     std_msgs
     message_generation
  )

  catkin_package(
    ...
    CATKIN_DEPENDS message_runtime ...
    ...)

  add_message_files(
    FILES
    Num.msg
  )

  generate_messages()

Compile the message:

  cd ~/catkin_ws
  catkin_make
  catkin_make install
  # ...
  # [100%] Built target workshop_generate_messages_cpp
  # [100%] Built target workshop_generate_messages_py
  # [100%] Built target workshop_generate_messages_eus
  # Scanning dependencies of target workshop_generate_messages
  # [100%] Built target workshop_generate_messages

Using the ROS msg

  rosmsg list                     # ... workshop/Num
  rosmsg show workshop/Num        # int64 num

Creating a ROS srv

mkdir -p workshop/srv

In workshop/srv/SumInts.srv:

  int64 a
  int64 b
  ---
  int64 sum

Compiling the ROS srv

Since srv files are also compiled, the setup is similar to compiling msgs.

Writing a Service Node

We can create a server that uses the service file we defined earlier:

  #!/usr/bin/env python
  from workshop.srv import SumInts, SumIntsResponse
  import rospy

  def handler(req):
      return SumIntsResponse(req.a + req.b)

  def sumints_server():
      rospy.init_node("sumints_server")
      s = rospy.Service("sumints", SumInts, handler)
      rospy.spin()

  sumints_server()

Writing a Client

  #!/usr/bin/env python
  import sys
  import rospy
  from workshop.srv import SumInts

  def sumints_client(x, y):
      rospy.wait_for_service('sumints')
      try:
          sumints = rospy.ServiceProxy('sumints', SumInts)
          resp1 = sumints(x, y)
          return resp1.sum
      except rospy.ServiceException, e:
          print "Service call failed: %s"%e

  x = int(sys.argv[1])
  y = int(sys.argv[2])
  print "%s + %s = %s"%(x, y, sumints_client(x, y))
  rosrun workshop sumint_client.py 1 2
  # 1 + 2 = 3

Exercise: Time Service (15 minutes)

Write a service that:

  • requests nothing
  • responds with the current time

Write a client that sends the request and prints this response.

What’s Next?

What’s Next?

  • Run a simulator, model the robot using URDF
  • Look at community ROS packages
    • tf2: maintain robotic coordinate frames (pose estimation)
    • gmapping/slam etc.: navigation
  • Look at ROS 2

Appendix

Common Pitfalls

  1. Not sourcing your devel/setup.bash:
  source devel/setup.bash
  1. This is necessary to make available all the C++ and python ROS packages that you have built
  2. I recommend using direnv, and sourcing it every time you enter the Catkin workspace.

ROS Installation

Ubuntu

Follow the instructions on ROS Wiki.

VM

Download the VM image and load it.


  1. Almost all these commands have tab completion! ↩︎

  2. can also be done programatically ↩︎

Links to this note