Skip to content

RADU: Controlling Robot Movements with a Gamepad Controller using Python

By Sebastian Günther

Posted in Robots, Radu, Microcontrollers, Rasperry

My robot RADU is a two wheeled robot that combines custom hardware for the sensor and motor, a Raspberry Pico and L293D motor shield, and a purchasable robotic arm that is controlled via a Raspberry Pi shield. For both parts of the robot, I want to use the same control mechanism and device: A game controller. To reach this goal, the last article gave an overview about the different libraries. This article is a tutorial about writing the controller software. You will learn how to use the Python library Piborg Gamepad, and how to bridge pressed buttons or joystick to controll the robot engine.

The Gamepad Library

THe Gamepad Library of my choice is the Piborg Gamepad. This versatile Python library provides simple abstraction to connect custom mapped gamepads, with a slight focus on video game consoles. It can detect the state of all buttons and the axis, e.g. on a playstation pad including the d-pad and the analog stick. And it provides different programming models how to access the gamepad state, e.g. with event listeners that trigger callbacks, or a continous loop that gets the state of all buttons.

After a short time period only to get a simple button press into moving the robot, I decided to use this library to fully program my robot. Let’s start with the simple outline.

Button Configuration

The first part is to import the library and setup variables for the buttons of the chosen gamepad. For this, checkout the gamepad configurations and take the defined button names from this file for your specific gamepad. Then, translate these to meaningful names. Here is my mapping for th accessing the joystick, the dpad and some buttons:

import lib.Gamepad as Gamepad

# Gamepad settings
gamepadType = Gamepad.PS4

# Axis
joystickSpeed = 'LEFT-Y'
joystickSteering = 'RIGHT-X'

digital_x = 'DPAD-X' 
digital_y = 'DPAD-Y' 

# Button
button_ps = 'PS'
button_triangle = "TRIANGLE"

Controller Connection

With the button setup completed, we will now wait write a method that waits for a gamepad to get connected. Once this condition is true, we will create a gamepad object that is instantiated with the configuration. This code looks as follows:

def wait_for_gamepad():

  # Wait for a connection
  if not Gamepad.available():
      log_to_stdout_and_mqtt('Please connect your gamepad...')
      while not Gamepad.available():

  gamepad = gamepadType()
  return gamepad

Detecting Gamepad Button Presses

The next step is to start the main loop of the program that will continually check the button state. I decided to use a simple loop that continually polls the buttons and executes some commands. Let’s start easy and define a listener for the buttons.

def main_loop(gamepad)
  pollInterval = 0.01
    while gamepad.isConnected():
        if gamepad.beenPressed(button_triangle):
            log_to_stdout("Triangle Button is Pressed")

        # Sleep for our polling interval
    # Ensure the background thread is always terminated when we are done

This method does a couple of things:

  • The pollInterval determines how often per second the button states will be checked
  • The complete button detection logic is enclosed in a try...catch block, mainly as a safeguard to release the gamepad if something goes wrong - alternatively you can define a custom context manager to handle the setup and teardown phases
  • A gamepads button state are checked with the beenPressed() method, which gets an argument of the earlier defined button layout. As shown here, you can use if statements to check their state, and then execute some command
  • At the end of the loop, a sleep command is issued with the defined pollInterval

Ok, now let’s see how to check for the d-pad and steering axes.

Detecting the Digital and Analog Axis

During the button setup, we already defined custom names for the analog joysticks and the d-pad. Then, inside the main loop, we can detect their state as shown in the next code snippet.

def main_loop(gamepad)
    while gamepad.isConnected():
      # ...
      speed = - gamepad.axis(joystickSpeed)
      steering = - gamepad.axis(joystickSteering)

      command = gamepad.axis(digital_x)

      log_to_stdout(f'Speed {speed}')
      log_to_stdout(f'Steering {steering}')

      engine_move(speed, steering, command)   

    # Ensure the background thread is always terminated when we are done

To access the gamepad values, you use the axis() method and pass the button name. The values are floats of different granularly: For the d-pad buttons, its either 0.0 or 1.0, but for the analog sticks, it’s a positive or negative 16-bit number with a maximum absolute value of 32.756. Handle these values as they are required by your robot.

Fine Tuning the Controls

The final step is heavily dependent on your particular robots’ behavior. My robot continually listens for commands, processes them, and quits the command after 300ms of inactivity from the sender site. This means that the sender need to continuously send the position of the joystick in order to let the robot only move when either is actually pressed.

This requirement is provided by the following code section:

def main_loop(gamepad)
    while gamepad.isConnected():
          if (mode == MOVE_MODE):
            speed = -gamepad.axis(joystickSpeed)
            steering = -gamepad.axis(joystickSteering)

            if((speed != 0.0 or steering != 0.0) and not isStopped):
                last_speed = speed
                last_steeering = steering
                engine_move(speed, steering)   
              isStopped = True
    # Ensure the background thread is always terminated when we are done


This article showed how to use the Python library Piborg Gamepad. You learned how to define the button layout, how to start a program with waiting for a connected gamepad, and how to continuously check the state of button, the digital pad and joysticks. Putting all of this together is a small and readable script that you can customize to the requirements or your robot. This article also ends the series - the next one will summarize my journey of developing a custom robot.