$npx -y skills add arpitg1304/robotics-agent-skills --skill ros1Best practices, design patterns, and common pitfalls for ROS1 (Robot Operating System 1) development. Use this skill when building ROS1 nodes, packages, launch files, or debugging ROS1 systems. Trigger whenever the user mentions ROS1, catkin, rospy, roscpp, roslaunch, roscore, ro
| 1 | # ROS1 Development Skill |
| 2 | |
| 3 | ## When to Use This Skill |
| 4 | - Building or maintaining ROS1 packages and nodes |
| 5 | - Writing launch files, message types, or services |
| 6 | - Debugging ROS1 communication (topics, services, actions) |
| 7 | - Configuring catkin workspaces and build systems |
| 8 | - Working with tf/tf2 transforms, URDF, or robot models |
| 9 | - Using actionlib for long-running tasks |
| 10 | - Optimizing nodelets for zero-copy transport |
| 11 | - Planning ROS1 → ROS2 migration |
| 12 | |
| 13 | ## Core Architecture Principles |
| 14 | |
| 15 | ### 1. Node Design |
| 16 | |
| 17 | **Single Responsibility Nodes**: Each node should do ONE thing well. Resist the temptation to build monolithic "do-everything" nodes. |
| 18 | |
| 19 | ```python |
| 20 | # BAD: Monolithic node |
| 21 | class RobotNode: |
| 22 | def __init__(self): |
| 23 | self.sub_camera = rospy.Subscriber('/camera/image', Image, self.camera_cb) |
| 24 | self.sub_lidar = rospy.Subscriber('/lidar/points', PointCloud2, self.lidar_cb) |
| 25 | self.pub_cmd = rospy.Publisher('/cmd_vel', Twist, queue_size=10) |
| 26 | self.pub_map = rospy.Publisher('/map', OccupancyGrid, queue_size=1) |
| 27 | # This node does perception, planning, AND control |
| 28 | |
| 29 | # GOOD: Decomposed nodes |
| 30 | class PerceptionNode: # Fuses sensor data → publishes /obstacles |
| 31 | class PlannerNode: # Subscribes /obstacles → publishes /path |
| 32 | class ControllerNode: # Subscribes /path → publishes /cmd_vel |
| 33 | ``` |
| 34 | |
| 35 | **Node Initialization Pattern**: |
| 36 | ```python |
| 37 | #!/usr/bin/env python |
| 38 | import rospy |
| 39 | from std_msgs.msg import String |
| 40 | |
| 41 | class MyNode: |
| 42 | def __init__(self): |
| 43 | rospy.init_node('my_node', anonymous=False) |
| 44 | |
| 45 | # 1. Load parameters FIRST |
| 46 | self.rate = rospy.get_param('~rate', 10.0) |
| 47 | self.frame_id = rospy.get_param('~frame_id', 'base_link') |
| 48 | |
| 49 | # 2. Set up publishers BEFORE subscribers |
| 50 | # (prevents callbacks firing before publisher is ready) |
| 51 | self.pub = rospy.Publisher('~output', String, queue_size=10) |
| 52 | |
| 53 | # 3. Set up subscribers LAST |
| 54 | self.sub = rospy.Subscriber('~input', String, self.callback) |
| 55 | |
| 56 | rospy.loginfo(f"[{rospy.get_name()}] Initialized with rate={self.rate}") |
| 57 | |
| 58 | def callback(self, msg): |
| 59 | # Process and republish |
| 60 | result = String(data=msg.data.upper()) |
| 61 | self.pub.publish(result) |
| 62 | |
| 63 | def run(self): |
| 64 | rate = rospy.Rate(self.rate) |
| 65 | while not rospy.is_shutdown(): |
| 66 | # Periodic work here |
| 67 | rate.sleep() |
| 68 | |
| 69 | if __name__ == '__main__': |
| 70 | try: |
| 71 | node = MyNode() |
| 72 | node.run() |
| 73 | except rospy.ROSInterruptException: |
| 74 | pass |
| 75 | ``` |
| 76 | |
| 77 | ### 2. Topic Design |
| 78 | |
| 79 | **Naming Conventions**: |
| 80 | ``` |
| 81 | /robot_name/sensor_type/data_type |
| 82 | |
| 83 | # Examples: |
| 84 | /ur5/joint_states # Robot joint states |
| 85 | /realsense/color/image_raw # Camera color image |
| 86 | /realsense/depth/points # Depth point cloud |
| 87 | /mobile_base/cmd_vel # Velocity commands |
| 88 | /gripper/command # Gripper commands |
| 89 | ``` |
| 90 | |
| 91 | **Queue Sizes Matter**: |
| 92 | ```python |
| 93 | # For sensor data (high frequency, OK to drop old messages): |
| 94 | rospy.Subscriber('/camera/image', Image, self.cb, queue_size=1) |
| 95 | |
| 96 | # For commands (don't want to miss any): |
| 97 | rospy.Publisher('/cmd_vel', Twist, queue_size=10) |
| 98 | |
| 99 | # For large data (point clouds, images) - use small queues to prevent memory bloat: |
| 100 | rospy.Subscriber('/lidar/points', PointCloud2, self.cb, queue_size=1) |
| 101 | |
| 102 | # NEVER use queue_size=0 (infinite) for high-frequency topics |
| 103 | # This WILL cause memory leaks under load |
| 104 | ``` |
| 105 | |
| 106 | **Latched Topics** for data that changes infrequently: |
| 107 | ```python |
| 108 | # Robot description, static maps, calibration data |
| 109 | pub = rospy.Publisher('/robot_description', String, queue_size=1, latch=True) |
| 110 | ``` |
| 111 | |
| 112 | ### 3. Launch File Best Practices |
| 113 | |
| 114 | ```xml |
| 115 | <launch> |
| 116 | <!-- ALWAYS use args for configurability --> |
| 117 | <arg name="robot_name" default="ur5"/> |
| 118 | <arg name="sim" default="false"/> |
| 119 | <arg name="debug" default="false"/> |
| 120 | |
| 121 | <!-- Group by subsystem with namespaces --> |
| 122 | <group ns="$(arg robot_name)"> |
| 123 | |
| 124 | <!-- Conditional loading based on sim vs real --> |
| 125 | <group if="$(arg sim)"> |
| 126 | <include file="$(find my_pkg)/launch/sim_drivers.launch"/> |
| 127 | </group> |
| 128 | <group unless="$(arg sim)"> |
| 129 | <include file="$(find my_pkg)/launch/real_drivers.launch"/> |
| 130 | </group> |
| 131 | |
| 132 | <!-- Node with proper remapping --> |
| 133 | <node pkg="my_pkg" type="perception_node.py" name="perception" |
| 134 | output="screen" respawn="true" respawn_delay="5"> |
| 135 | <param name="rate" value="30.0"/> |
| 136 | <param name="frame_id" value="$(arg robot_na |