Control Drive V2

control_drive

The first part of this project was done here

Check out the current project on Github

In this version, I implemented a modular control architecture with multiple input sources (keyboard, gamepad (PS4), and script) that all publish to a common /cmd_vel topic. A relay node then forwards these commands to the robot’s controller topic. This allows for easy addition of new control methods without modifying the core robot logic.

Requirements Link to heading

  • ROS2 Humble
  • Ignition Fortress (Gazebo)
  • Python 3.10+

Building Link to heading

colcon build && source install/setup.bash

Launching the Simulation Link to heading

ros2 launch control_drive gazebo.launch.py

Launching RViz Link to heading

ros2 launch control_drive display.launch.py

Change Fixed Frame in Global Options to odom


Control Modes Link to heading

This package uses a modular control architecture. All input sources publish to /cmd_vel which is relayed to the robot via a cmd_vel_relay node. To add a new input source, inherit from RobotController and implement run().

[keyboard / gamepad / script]
            ↓
         /cmd_vel
            ↓
       [cmd_vel_relay]
            ↓
/control_drive_controller/cmd_vel_unstamped
            ↓
    [diff_drive_controller]
            ↓
       [Gazebo Robot]

Keyboard Controller Link to heading

Full keyboard teleoperation with speed presets and emergency stop.

ros2 run control_drive keyboard_controller
KeyAction
wForward
sBackward
aTurn left
dTurn right
q/eForward + left/right
z/cBackward + left/right
+/-Increase/decrease speed by 10%
1/2/3Speed presets (slow/medium/fast)
SPACEEmergency stop
CTRL+CQuit

Gamepad Controller Link to heading

Works with any HID gamepad — PS4, PS5, Xbox, or generic controllers. Auto-detects the connected device and runs a one-time calibration to map axes.

ros2 run control_drive gamepad_controller
InputAction
Forward/backward stickLinear speed
Turning stickAngular speed
R2 / RTIncrease speed (up to 2x)
L2 / LTDecrease speed (down to 0.3x)
L2 + R2 togetherEmergency stop
OPTIONS / STARTQuit

First run: calibration wizard runs automatically and saves mapping to ~/.ros/gamepad_mapping.json. Subsequent runs load the saved mapping instantly.

To recalibrate:

rm ~/.ros/gamepad_mapping.json
ros2 run control_drive gamepad_controller

Launch file (handles permissions + starts node):

ros2 launch control_drive gamepad.launch.py

WSL2 users: Run this first in PowerShell (Admin) before launching:

usbipd attach --wsl --busid <your-busid>

Find your busid with usbipd list.


Script Controller Link to heading

Write autonomous robot behaviors directly in Python. No keyboard or gamepad needed — the robot executes your script automatically.

ros2 run control_drive script_controller

Edit control_drive/script_controller.py and write your behavior inside run():

def run(self):
    self.move_forward(speed=0.5, duration=2.0)
    self.turn_left(speed=1.0, duration=1.57)   # ~90 degrees
    self.stop()

Available helpers:

MethodDescription
move_forward(speed, duration)Move forward for N seconds
move_backward(speed, duration)Move backward for N seconds
turn_left(speed, duration)Turn left for N seconds
turn_right(speed, duration)Turn right for N seconds
pause(duration)Stop and wait for N seconds
send_command(linear, angular)Direct velocity command
stop()Immediate stop

Architecture Link to heading

FilePurpose
robot_controller.pyBase class — publisher, safety limits, speed helpers
keyboard_controller.pyKeyboard input source
gamepad_controller.pyGeneric gamepad input source with auto-calibration
script_controller.pyScript-based autonomous control
cmd_vel_relay.pyRelays /cmd_vel/control_drive_controller/cmd_vel_unstamped

Manual Testing Link to heading

Test velocity directly without any controller:

ros2 topic pub /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 1.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.5}}"

Or using the original unstamped topic directly:

ros2 topic pub /control_drive_controller/cmd_vel_unstamped geometry_msgs/msg/Twist \
  "{linear: {x: 1.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.5}}"