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
Install Essential Packages
# Install common robotics packages
pip3 install numpy matplotlib scipy
# Install ROS 2 Python client
sudo apt install python3-rclpy
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)
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
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
- Official Docs: python.org
- NumPy Docs: numpy.org
- Matplotlib Docs: matplotlib.org
- rclpy Docs: docs.ros.org
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:
- Set up ROS 2: See ROS Setup Guide
- Learn Gazebo: See Gazebo Setup
- Start Week 3 lab: See Week 3 Lab
- Practice coding: Work on exercises and small projects