beefyi

ROS1 Cheatsheet

ROS Noetic

ROS Noetic (Robot Operating System) is the final and most stable release of ROS 1, a framework for building and connecting robot software. It’s designed for Ubuntu 20.04 with Python 3 support and comes packed with tools, libraries, and community packages. However as of writing it has gone out of support from the ROS community and you should start using/migrating to ROS2. All code samples will be written in Python.

Useful resources:
ROS Wiki
ROS Documentation
ROS Answers (Community Q&A)
ROS GitHub

Using apptainer

As I'm learning ROS, it was recommended to use containerisation software to run all the ROS tools in an isolated environment. For my setup, I'll be using Apptainer to manage and run everything inside a container:

apptainer run -B /run/user/$UID /<path_to_container.sif>

Anything in wrapped with "<>" are templates and should not be copied into the command but replaced accordingly to the context the command. For example in the above command "<path_to_container>" should be replaced with the actual path of the .sif file.

Assume that any command shown in this post is run within this container, code can be written in any IDE of your choice but the commands need to be run in the container.

ROS Environment Setup

In ROS, a workspace (ws) is a directory where you build, modify, and organise your ROS packages and projects. We will be running our custom nodes and everything in the packages here.

# Create and source your workspace
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws
catkin_make
source devel/setup.bash

Package Creation

Packages are the core building blocks that contain your code, nodes, launch files, and configuration, essentially everything needed for a specific function or feature in a robot system.

cd ~/catkin_ws/src
catkin_create_pkg <pkg_name> rospy <dep1> <...> <depn>

Where <dep1> <...> <depn> is all the packages needed for the package. Note I leave rospy there since we will be using Python and do not worry you can go back to this and add more dependencies in the packages.xml file that is created after running this command.

Full Package structure

my_package/
├── CMakeLists.txt
├── package.xml
├── src/
│   └── my_node.py
└── scripts/
    └── talker.py

Configure CMakeLists.txt

For custom messages, services, actions please refer to the CMakeLists.txt that is automatically generated with catkin_create_pkg.

Quickish places to add:

find_package(catkin REQUIRED COMPONENTS rospy std_msgs message_generation)

add_<messages/services/actions>_files(
  FILES
  <file.srv>
)

generate_messages(
  DEPENDENCIES
  std_msgs
)

catkin_package(
  CATKIN_DEPENDS message_runtime
)

Writing a Python Node

Example: talker.py

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

def talker():
    pub = rospy.Publisher('chatter', String, queue_size=10)
    rospy.init_node('talker', anonymous=True)
    rate = rospy.Rate(10)  # 10Hz
    while not rospy.is_shutdown():
        msg = "Hello ROS Noetic! %s" % rospy.get_time()
        rospy.loginfo(msg)
        pub.publish(msg)
        rate.sleep()

if __name__ == '__main__':
    try:
        talker()
    except rospy.ROSInterruptException:
        pass

Subscribing to Topics

Example: listener.py

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

def callback(data):
    rospy.loginfo("I heard %s", data.data)

def listener():
    rospy.init_node('listener', anonymous=True)
    rospy.Subscriber("chatter", String, callback)
    rospy.spin()

if __name__ == '__main__':
    listener()

Running Nodes

# Make executable
chmod +x scripts/<name>.py

# Build the workspace
cd ~/catkin_ws
catkin_make

# Source the workspace
source devel/setup.bash

# Run roscore
roscore

# In a new terminal
rosrun <pkg> <name>.py

Node Commands

# Check node info
rosnode info /talker

# List active nodes
rosnode list

# Kill a node
rosnode kill <node_name>

# Kill all nodes
rosnode kill -a

Topic commands

# List topics
rostopic list

# Echo topic messages
rostopic echo /chatter

# Info about a topic
rostopic info /chatter

ROS Launch

Example of a launch file:

<launch>
	<include file="$(find <pkg>)/launch/<file.launch>" />
	<node pkg="<pkg>" type="<node>" name="<name>">
		<remap from="<from_topic>" to="<to_topic>" />
	</node>
</launch>

Run it

roslaunch <pkg> <file.launch>

Rosbag

To record logs from a topic and then can replay it for debugging purposes

rosbag record /<topic>
rosbag play <file.bag>

ROS Plot

We can use rqt_plot to visualise the messages of the topic:

rosrun rqt_plot rqt_plot

Use rosmsg to see what lines you can add to the plot and visualise.

rosmsg info /<topic>

ROS Services

Services are synchronous (request/response) communication between nodes.

Create a Service Definition

Example File: srv/AddTwoInts.srv

int64 a
int64 b
---
int64 sum

Place inside:
<pkg>/srv/<file.srv>

Service Server Example

#!/usr/bin/env python

from your_package.srv import AddTwoInts, AddTwoIntsResponse
import rospy

def handle_add_two_ints(req):
    rospy.loginfo(f"Adding {req.a} + {req.b}")
    return AddTwoIntsResponse(req.a + req.b)

rospy.init_node('add_two_ints_server')
service = rospy.Service('add_two_ints', AddTwoInts, handle_add_two_ints)
rospy.loginfo("Ready to add two ints.")
rospy.spin()

Service Client Server

#!/usr/bin/env python

import rospy
from your_package.srv import AddTwoInts

rospy.init_node('add_two_ints_client')
rospy.wait_for_service('add_two_ints')

try:
    add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)
    resp = add_two_ints(3, 5)
    rospy.loginfo(f"Sum = {resp.sum}")
except rospy.ServiceException as e:
    rospy.logerr(f"Service call failed: {e}")

Command-line Tools

rosservice list
rosservice info /add_two_ints
rosservice call /add_two_ints "a: 2 b: 3"
rossrv show your_package/AddTwoInts

ROS Actions

Actions are asynchronous, providing feedback and allowing preemption.

Create an Action Definition

Example File: action/Fibonacci.action

int32 order
---
int32[] sequence
---
int32[] feedback_sequence

Place inside:
<pkg>/action/<file.action>

Action Server

#!/usr/bin/env python

import rospy
import actionlib
from your_package.msg import FibonacciAction, FibonacciFeedback, FibonacciResult

class FibonacciActionServer:
    def __init__(self):
        self.server = actionlib.SimpleActionServer('fibonacci', FibonacciAction, self.execute, False)
        self.server.start()

    def execute(self, goal):
        feedback = FibonacciFeedback()
        result = FibonacciResult()

        feedback.sequence = [0, 1]

        for i in range(2, goal.order):
            if self.server.is_preempt_requested():
                rospy.loginfo("Goal preempted")
                self.server.set_preempted()
                return

            feedback.sequence.append(feedback.sequence[-1] + feedback.sequence[-2])
            self.server.publish_feedback(feedback)
            rospy.sleep(1.0)

        result.sequence = feedback.sequence
        self.server.set_succeeded(result)

rospy.init_node('fibonacci_server')
server = FibonacciActionServer()
rospy.spin()

Action Client

#!/usr/bin/env python

import rospy
import actionlib
from your_package.msg import FibonacciAction, FibonacciGoal

def feedback_cb(feedback):
    rospy.loginfo(f"Feedback: {feedback.sequence}")

rospy.init_node('fibonacci_client')
client = actionlib.SimpleActionClient('fibonacci', FibonacciAction)
client.wait_for_server()

goal = FibonacciGoal(order=5)
client.send_goal(goal, feedback_cb=feedback_cb)
client.wait_for_result()

result = client.get_result()
rospy.loginfo(f"Result: {result.sequence}")

Command-line Tools

rosaction list
rosaction info fibonacci
rosaction show your_package/Fibonacci

# Using the action client tool (rqt or actionlib_tools)
rostopic pub /fibonacci/goal your_package/FibonacciActionGoal "{goal: {order: 5}}"
rostopic echo /fibonacci/feedback
rostopic echo /fibonacci/result

Service vs Action Summary

Feature Service Action
Communication Style Synchronous (blocking) Asynchronous (non-blocking)
Use Case Quick request/response Long-running task
Feedback None Periodic feedback available
Preemption Not possible Possible
Tools rosservice actionlib, rostopic

ROS Transfomers

TF2 lets you relate coordinate frames at any point in time.

TF tree

# visual
rosrun rqt_tf_tree rqt_tf_tree

# text only
rosrun tf2_tools view_frames.py

# numeric lookups
rosrun tf tf_echo /base_link /map

Broadcasting a static transform

# x y z roll pitch yaw frame_child frame_parent
rosrun tf2_ros static_transform_publisher 0 0 0 0 0 0 camera_link base_link

Broadcasting with Python

#!/usr/bin/env python3

import rospy
import tf_conversions
import tf2_ros
import geometry_msgs.msg

rospy.init_node('tf_broadcaster')
br = tf2_ros.TransformBroadcaster()
t = geometry_msgs.msg.TransformStamped()

rate = rospy.Rate(10)
while not rospy.is_shutdown():
    t.header.stamp = rospy.Time.now()
    t.header.frame_id = "base_link"
    t.child_frame_id = "camera_link"
    t.transform.translation.x = 0.1
    t.transform.translation.y = 0.0
    t.transform.translation.z = 0.2
    q = tf_conversions.transformations.quaternion_from_euler(0, 0, 0.52)
    t.transform.rotation.x = q[0]
    t.transform.rotation.y = q[1]
    t.transform.rotation.z = q[2]
    t.transform.rotation.w = q[3]
    br.sendTransform(t)
    rate.sleep()

Listening in Python

#!/usr/bin/env python3

import rospy
import tf2_ros
import geometry_msgs.msg

rospy.init_node('tf_listener')
tfBuffer = tf2_ros.Buffer()
listener = tf2_ros.TransformListener(tfBuffer)

rate = rospy.Rate(10)
while not rospy.is_shutdown():
    try:
        transf = tfBuffer.lookup_transform('map', 'base_link', rospy.Time(0))
        rospy.loginfo(f"x = {transf.transform.translation.x:.3f}")
    except Exception as e:
        rospy.logwarn(str(e))
    rate.sleep()

Common patterns

What you want How to do it
“where is frame B relative to A now?” tfBuffer.lookup_transform(A, B, now)
periodic static offsets run static_transform_publisher once
convert Euler → quaternion tf_conversions.transformations.quaternion_from_euler

Typical frame names

Frame Meaning
map global / world reference
odom continuous local (drift allowed)
base_link robot body
base_footprint projected body on ground plane
sensors eg lidar_link, camera_link

rule of thumb: only broadcast frames you own, never stomp another node's frame.

Rviz Commands

# Running Stage simulator Rviz
rosrun rviz rviz -d `rospack find rosplan_stage_demo`/config/rosplan_stage_demo.rviz &

# Running Stage Simulator
roslaunch rosplan_stage_demo empty_stage_single_robot.launch &

Common Message Types

Type Import Example Use
std_msgs/String from std_msgs.msg import String Chat or text data
geometry_msgs/Twist from geometry_msgs.msg import Twist Velocity commands
sensor_msgs/Image from sensor_msgs.msg import Image Camera data
nav_msgs/Odometry from nav_msgs.msg import Odometry Robot position data

Workspace Maintenance

# Clean workspace
catkin_make clean

# Rebuild everything
catkin_make -DCMAKE_BUILD_TYPE=Release

# Re-source environment
source devel/setup.bash

Debugging Tips

# Check log files
roscd log
less latest/rosout.log

# Print debug messages
rospy.logdebug("Debug message")
rospy.loginfo("Info message")
rospy.logwarn("Warning message")
rospy.logerr("Error message")