Skip to content

ENME480 · Wiki

Python Basics for Robotics

Learn the Python fundamentals you'll need for ROS 2, robot control, and simulation

Overview

This is a nonexhaustive guide to soem of the Python concepts used in this course. It should be enough to get you going on most assignments or at least get you far enough that you can ask an informed quesiton on Piazza or Google your issue.

Prerequisites

Before starting, ensure you have: - Ubuntu with Python 3.8+ installed - Basic Terminal knowledge - Text/Code editor (VS Code, gedit, or nano)

Getting Started

Check Python Installation

# Check Python version
python3 --version

# Check pip version
pip3 --version

# Start Python interpreter
python3
ROS2 versions are compiled against specific Python versions, ensuring the correct one is installed is a good way to make sure everyhting is working. Similarly, python uses pip to install new packages, so bad pip installs are a good first thing to check.

Install Essential Packages

# Install common robotics packages
pip3 install numpy matplotlib scipy

# Install ROS 2 Python client
sudo apt install python3-rclpy
numpy, matplotlib and scipy are commonly used extensions that allow a wide variety of math and plotting operations. python3-rclpy is the ROS2 client library for python, and is needed to allow your python scripts to interact with ROS.

Core Python Concepts

1. Variables and Data Types

# Numbers
x = 10          # integer
y = 3.14        # float
z = 2 + 3j      # complex

# Strings
name = "Robot"
message = 'Hello, World!'

# Booleans
is_robot = True
is_human = False

# Lists (mutable)
joint_angles = [0.0, 1.57, 0.0, 0.0, 0.0, 0.0]
joint_names = ["shoulder", "elbow", "wrist"]

# Tuples (immutable)
position = (1.0, 2.0, 3.0)

# Dictionaries
robot_config = {
    "name": "UR3e",
    "dof": 6,
    "max_payload": 3.0
}

2. Control Flow

# If statements
if joint_angle > 1.57:
    print("Joint limit exceeded!")
elif joint_angle < -1.57:
    print("Joint limit exceeded!")
else:
    print("Joint angle is safe")

# For loops
for i in range(6):
    print(f"Joint {i}: {joint_angles[i]}")

# While loops
count = 0
while count < 5:
    print(f"Count: {count}")
    count += 1

3. Functions

def calculate_distance(point1, point2): # no default input values
    """Calculate Euclidean distance between two points."""
    import math
    dx = point2[0] - point1[0]
    dy = point2[1] - point1[1]
    dz = point2[2] - point1[2]
    return math.sqrt(dx*dx + dy*dy + dz*dz)

# Function with default parameters
def move_robot(x=0.0, y=0.0, z=0.0, speed=1.0):
    """Move robot to specified position."""
    print(f"Moving to ({x}, {y}, {z}) at speed {speed}")
    return True # typically, there would be a blocking call here that would only reach this statement when the robot finishes moving

# Call functions
distance = calculate_distance((0, 0, 0), (1, 1, 1))
move_robot(1.0, 2.0, 3.0)
Note that python has significant whitespace; the indentation after the funiton block tells python where functinos begin and end.

Mathematics and NumPy

NumPy Basics

import numpy as np

# Create arrays
joint_angles = np.array([0.0, 1.57, 0.0, 0.0, 0.0, 0.0])
position = np.array([1.0, 2.0, 3.0])

# Array operations
angles_deg = np.degrees(joint_angles)
angles_rad = np.radians(angles_deg)

# Matrix operations
rotation_matrix = np.array([
    [1, 0, 0],
    [0, 1, 0],
    [0, 0, 1]
])

# Matrix multiplication
new_position = rotation_matrix @ position

Common Mathematical Operations

import numpy as np
import math

# Trigonometric functions
angle = np.pi / 4
sin_val = np.sin(angle)
cos_val = np.cos(angle)
tan_val = np.tan(angle)

# Square root and power
distance = np.sqrt(16)
squared = np.power(4, 2)

# Random numbers
random_angle = np.random.uniform(-np.pi, np.pi)
random_position = np.random.randn(3)  # 3D normal distribution

Data Visualization with Matplotlib

Basic Plotting

import matplotlib.pyplot as plt
import numpy as np

# Create data
time = np.linspace(0, 10, 100)
joint_angle = np.sin(time)

# Create plot
plt.figure(figsize=(10, 6))
plt.plot(time, joint_angle, 'b-', linewidth=2, label='Joint Angle')
plt.xlabel('Time (s)')
plt.ylabel('Angle (rad)')
plt.title('Joint Angle vs Time')
plt.grid(True)
plt.legend()
plt.show()

Multiple Plots

# Create subplots
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))

# First subplot
ax1.plot(time, np.sin(time), 'r-', label='Sine')
ax1.set_ylabel('Amplitude')
ax1.legend()
ax1.grid(True)

# Second subplot
ax2.plot(time, np.cos(time), 'g-', label='Cosine')
ax2.set_xlabel('Time (s)')
ax2.set_ylabel('Amplitude')
ax2.legend()
ax2.grid(True)

plt.tight_layout()
plt.show()

ROS 2 Integration

Basic ROS 2 Node

#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
# the below messages canbe viewed in rqts Message Type Browser
from std_msgs.msg import String, Float64MultiArray
from geometry_msgs.msg import Pose

class RobotController(Node):
    def __init__(self):
        super().__init__('robot_controller')

        # Create publisher
        self.joint_pub = self.create_publisher(
            Float64MultiArray, 
            '/joint_commands', 
            10
        )

        # Create subscriber
        self.pose_sub = self.create_subscription(
            Pose,
            '/robot_pose',
            self.pose_callback,
            10
        )

        # Create timer
        self.timer = self.create_timer(0.1, self.timer_callback)

        self.get_logger().info('Robot controller node started')

    def pose_callback(self, msg):
        """Callback for robot pose updates."""
        x, y, z = msg.position.x, msg.position.y, msg.position.z
        self.get_logger().info(f'Robot at: ({x:.2f}, {y:.2f}, {z:.2f})')

    def timer_callback(self):
        """Timer callback for periodic tasks."""
        # Send joint commands
        joint_msg = Float64MultiArray()
        joint_msg.data = [0.0, 1.57, 0.0, 0.0, 0.0, 0.0]
        self.joint_pub.publish(joint_msg)

def main(args=None):
    rclpy.init(args=args)
    node = RobotController()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

File I/O and Data Handling

Reading and Writing Files

# Write data to file
joint_data = [0.0, 1.57, 0.0, 0.0, 0.0, 0.0]

with open('joint_angles.txt', 'w') as f:
    for angle in joint_data:
        f.write(f"{angle}\n")

# Read data from file
angles = []
with open('joint_angles.txt', 'r') as f:
    for line in f:
        angles.append(float(line.strip()))

print(f"Read angles: {angles}")

CSV Files

import csv

# Write CSV
with open('robot_data.csv', 'w', newline='') as f:
    writer = csv.writer(f)
    writer.writerow(['Time', 'Joint1', 'Joint2', 'Joint3'])
    writer.writerow([0.0, 0.0, 1.57, 0.0])
    writer.writerow([0.1, 0.1, 1.67, 0.1])

# Read CSV
with open('robot_data.csv', 'r') as f:
    reader = csv.reader(f)
    header = next(reader)  # Skip header
    for row in reader:
        time, j1, j2, j3 = float(row[0]), float(row[1]), float(row[2]), float(row[3])
        print(f"Time: {time}, Joints: [{j1}, {j2}, {j3}]")

Testing and Debugging

Debugging Tips

# Use print statements
print(f"Debug: joint_angles = {joint_angles}")

# Use pdb debugger
import pdb
pdb.set_trace()  # Code stops here for debugging

# Use logging
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.debug(f"Joint angles: {joint_angles}")

# Use a try/except/finally block
try:
    raise Runtimeerror("This code will crash!")
except _ as e: # grab all errors from the try block and assign them to e
    logging.info(f"The code threw the error: {type(e)}")
finally: # this code will ALWAYS RUN LAST, regardless of anything that happens before
    0
There are many ways to debug code in python, and how you accomplish this is largely up to you. In addition to the methods above you can alos use the built in debugger in VSCode for testing purposes, which will automatically pause the code and drop you into an interactive terminal wherever an error is raised. Note that the code for this course makes extensive use of try/except/finally blocks for safe execution.

Common Robotics Patterns

State Machine

class RobotState:
    IDLE = "IDLE"
    MOVING = "MOVING"
    ERROR = "ERROR"

class RobotController:
    def __init__(self):
        self.state = RobotState.IDLE
        self.target_position = None

    def update(self):
        if self.state == RobotState.IDLE:
            if self.target_position:
                self.state = RobotState.MOVING
                print("Starting movement...")

        elif self.state == RobotState.MOVING:
            if self.reached_target():
                self.state = RobotState.IDLE
                print("Movement completed")

        elif self.state == RobotState.ERROR:
            print("Robot in error state")

    def reached_target(self):
        # Simulate reaching target
        return True

Getting Help

Python Resources

Course Support

  • Piazza: Ask questions on course forum
  • Office Hours: Get help from TA or instructor
  • Lab Sessions: Hands-on help during labs

Next Steps

After mastering Python basics:

  1. Set up ROS 2: See ROS Setup Guide
  2. Learn Gazebo: See Gazebo Setup
  3. Start Week 3 lab: See Week 3 Lab
  4. Practice coding: Work on exercises and small projects

ROS Setup Gazebo Setup Back to Resources