beefyi

ROS1 Cheatsheet

ROS Noetic Cheatsheet

ROS Noetic is the final ROS 1 release and targets Ubuntu 20.04 with Python 3. ROS 1 is still common in existing projects, but it is now end-of-life, so for new systems you should plan a ROS 2 migration path.

This guide is a practical flow from setup to day-to-day debugging, with Python examples.

Useful links

Running ROS in Apptainer

If you want a clean and reproducible environment, run your ROS image with Apptainer:

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

Anything inside <> is a placeholder. Replace it with your real value.

For the rest of this post, assume ROS commands are executed inside your container shell.

Workspace Setup

A ROS workspace is where your packages live and build.

mkdir -p ~/catkin_ws/src
cd ~/catkin_ws
catkin_make
source devel/setup.bash

Tip: add source ~/catkin_ws/devel/setup.bash to ~/.bashrc if this is your main workspace.

Create a Package

Packages are the unit of code sharing in ROS: nodes, launch files, messages, configs, scripts.

cd ~/catkin_ws/src
catkin_create_pkg <pkg_name> rospy std_msgs

Add more dependencies later in package.xml and CMakeLists.txt.

Typical structure:

my_package/
├── CMakeLists.txt
├── package.xml
├── launch/
├── msg/
├── srv/
├── action/
└── scripts/
    ├── talker.py
    └── listener.py

First Publisher and Subscriber

scripts/talker.py

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


def main():
    pub = rospy.Publisher("chatter", String, queue_size=10)
    rospy.init_node("talker", anonymous=True)
    rate = rospy.Rate(10)

    while not rospy.is_shutdown():
        msg = f"hello noetic @ {rospy.get_time():.2f}"
        rospy.loginfo(msg)
        pub.publish(msg)
        rate.sleep()


if __name__ == "__main__":
    main()

scripts/listener.py

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


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


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


if __name__ == "__main__":
    main()

Build and run:

chmod +x scripts/talker.py scripts/listener.py
cd ~/catkin_ws && catkin_make
source ~/catkin_ws/devel/setup.bash

roscore
# new terminal
rosrun <pkg_name> talker.py
# new terminal
rosrun <pkg_name> listener.py

Daily CLI Commands

# nodes
rosnode list
rosnode info /talker
rosnode kill /talker

# topics
rostopic list
rostopic info /chatter
rostopic echo /chatter
rostopic hz /chatter
rostopic pub /chatter std_msgs/String "data: 'hi'" -1

# message introspection
rosmsg list
rosmsg show std_msgs/String

Launch Files

launch/demo.launch

<launch>
  <node pkg="<pkg_name>" type="talker.py" name="talker" output="screen" />
  <node pkg="<pkg_name>" type="listener.py" name="listener" output="screen" />
</launch>

Run:

roslaunch <pkg_name> demo.launch

Services

Use services for quick RPC-like interactions.

srv/AddTwoInts.srv

int64 a
int64 b
---
int64 sum

Server (scripts/add_two_ints_server.py):

#!/usr/bin/env python3
import rospy
from your_package.srv import AddTwoInts, AddTwoIntsResponse


def handle(req):
    return AddTwoIntsResponse(req.a + req.b)


if __name__ == "__main__":
    rospy.init_node("add_two_ints_server")
    rospy.Service("add_two_ints", AddTwoInts, handle)
    rospy.loginfo("service ready")
    rospy.spin()

Client (scripts/add_two_ints_client.py):

#!/usr/bin/env python3
import rospy
from your_package.srv import AddTwoInts


if __name__ == "__main__":
    rospy.init_node("add_two_ints_client")
    rospy.wait_for_service("add_two_ints")
    call = rospy.ServiceProxy("add_two_ints", AddTwoInts)
    resp = call(3, 5)
    rospy.loginfo("sum = %d", resp.sum)

CLI tools:

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

Actions

Use actions for long-running jobs that need progress updates or cancel support.

action/Fibonacci.action

int32 order
---
int32[] sequence
---
int32[] partial_sequence

Server (scripts/fibonacci_server.py):

#!/usr/bin/env python3
import rospy
import actionlib
from your_package.msg import FibonacciAction, FibonacciFeedback, FibonacciResult


class FibonacciServer:
    def __init__(self):
        self.server = actionlib.SimpleActionServer(
            "fibonacci", FibonacciAction, execute_cb=self.execute, auto_start=False
        )
        self.server.start()

    def execute(self, goal):
        feedback = FibonacciFeedback(partial_sequence=[0, 1])
        result = FibonacciResult()

        for _ in range(2, goal.order):
            if self.server.is_preempt_requested():
                self.server.set_preempted()
                return
            feedback.partial_sequence.append(
                feedback.partial_sequence[-1] + feedback.partial_sequence[-2]
            )
            self.server.publish_feedback(feedback)
            rospy.sleep(0.2)

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


if __name__ == "__main__":
    rospy.init_node("fibonacci_server")
    FibonacciServer()
    rospy.spin()

Client (scripts/fibonacci_client.py):

#!/usr/bin/env python3
import rospy
import actionlib
from your_package.msg import FibonacciAction, FibonacciGoal


def feedback_cb(msg):
    rospy.loginfo("feedback: %s", msg.partial_sequence)


if __name__ == "__main__":
    rospy.init_node("fibonacci_client")
    client = actionlib.SimpleActionClient("fibonacci", FibonacciAction)
    client.wait_for_server()
    client.send_goal(FibonacciGoal(order=8), feedback_cb=feedback_cb)
    client.wait_for_result()
    rospy.loginfo("result: %s", client.get_result().sequence)

Useful checks:

rostopic list | grep fibonacci
rostopic echo /fibonacci/feedback
rostopic echo /fibonacci/result

State Machines with SMACH

When behavior grows beyond one callback, state machines make control flow explicit and debuggable.

Install tools:

sudo apt install ros-noetic-smach ros-noetic-smach-ros ros-noetic-smach-viewer

Minimal state machine with transitions:

#!/usr/bin/env python3
import rospy
import smach
import smach_ros


class CheckBattery(smach.State):
    def __init__(self):
        smach.State.__init__(self, outcomes=["ok", "low"])

    def execute(self, userdata):
        rospy.sleep(0.5)
        battery_ok = True
        return "ok" if battery_ok else "low"


class Patrol(smach.State):
    def __init__(self):
        smach.State.__init__(self, outcomes=["done"])

    def execute(self, userdata):
        rospy.loginfo("patrolling...")
        rospy.sleep(1.0)
        return "done"


class Dock(smach.State):
    def __init__(self):
        smach.State.__init__(self, outcomes=["charged"])

    def execute(self, userdata):
        rospy.loginfo("docking...")
        rospy.sleep(1.0)
        return "charged"


if __name__ == "__main__":
    rospy.init_node("smach_demo")
    sm = smach.StateMachine(outcomes=["finished"])

    with sm:
        smach.StateMachine.add("CHECK", CheckBattery(), transitions={"ok": "PATROL", "low": "DOCK"})
        smach.StateMachine.add("PATROL", Patrol(), transitions={"done": "finished"})
        smach.StateMachine.add("DOCK", Dock(), transitions={"charged": "finished"})

    sis = smach_ros.IntrospectionServer("smach_server", sm, "/SM_ROOT")
    sis.start()
    outcome = sm.execute()
    rospy.loginfo("state machine finished with: %s", outcome)
    rospy.spin()
    sis.stop()

Visualize live state transitions:

rosrun smach_viewer smach_viewer.py

SMACH rule of thumb:

TF2

TF2 tracks relationships between coordinate frames over time.

Quick commands:

# graph view
rosrun rqt_tf_tree rqt_tf_tree

# static PDF/graph output
rosrun tf2_tools view_frames.py

# numerical transform stream
rosrun tf tf_echo /map /base_link

Static transform example:

# x y z yaw pitch roll parent child
rosrun tf2_ros static_transform_publisher 0 0 0 0 0 0 base_link camera_link

Typical frame naming:

Only publish frames your node owns.

Rosbag and Plotting

Record and replay:

rosbag record /chatter /tf
rosbag play <file.bag>

Plot topic fields:

rosrun rqt_plot rqt_plot

Inspect message fields before plotting:

rostopic type /<topic> | xargs rosmsg show

RViz Example

rosrun rviz rviz

Or load a saved config:

rosrun rviz rviz -d /path/to/config.rviz

Common Message Types

Type Python import Typical use
std_msgs/String from std_msgs.msg import String Text and lightweight status
geometry_msgs/Twist from geometry_msgs.msg import Twist Velocity commands
sensor_msgs/Image from sensor_msgs.msg import Image Camera streams
nav_msgs/Odometry from nav_msgs.msg import Odometry Pose and velocity estimation

Build, Clean, Debug

# clean and rebuild
cd ~/catkin_ws
catkin_make clean
catkin_make -DCMAKE_BUILD_TYPE=Release
source devel/setup.bash

# logs
roscd log
less latest/rosout.log

Logging in Python:

rospy.logdebug("debug")
rospy.loginfo("info")
rospy.logwarn("warn")
rospy.logerr("error")

Service vs Action vs State Machine

Tool Best for Feedback Cancel/preempt Typical pattern
Service Quick request/response No No Config reads, one-shot computation
Action Long task execution Yes Yes Navigation, manipulation jobs
SMACH Orchestration and logic flow Via states/actions Depends on state logic Mission-level behavior