The first thing I wanted to do after finishing building my GoPiGo robot was to try and set it up with a gamepad so that I could manually drive it around. It seemed that the work had already been done for me here, as one of the example python scripts included with the software implements the ability to connect a PS3 controller and send commands with bluetooth. However, having discovered that it seems the current version (at time of writing) of the GoPiGo software can’t make use of the built in bluetooth I decided to try and make it work anyway.
Since what I really wanted to do was make my Xbox One gamepad work with the robot rather than a PS3 controller, I decided on having the controller connected to my PC rather than the Pi directly. Then, the plan was to send commands to the Pi over WiFi rather than directly through bluetooth. On the Pi, a python server would run that would listen for commands over the network and move the robot accordingly. On the PC, a python client would interface with the Xbox controller and send on input information to the server.
Reading gamepad input
Reading input from the gamepad actually turned out to be very simple using a library called PyGame. Whilst this does a whole lot more than just reading gamepad input, it abstracts away the details nicely and works out of the box for a variety of standard gamepads, including my Xbox One gamepad.
The first thing I did was try and use the gamepad input to simply move a sprite around the PyGame window. Initially, the sprite moved far too quickly, but I soon able to identify that the frame rate was the problem. I wasn’t capping the frame rate in any way, and without the speed of the sprite being frame-rate-independent the sprite was lost off the edge of the screen almost immediately after moving. As I wasn’t planning on using the gamepad input for anything within the PyGame window for this project, I fixed the problem simply by capping the frame rate as 60fps.
PyGame triggers JOYAXISMOTION events when the analog sticks on the gamepad are moved, which contain data about where they actually moved. Now I just needed to send this data to the robot itself and interpret it as robot movements.
# Get joypad input if event.type == JOYAXISMOTION: if event.axis == 0: speed = int(event.value * 10) elif event.axis == 1: speed = int(event.value * 10)
Sending commands over the network
Although there are plenty of options for transferring the input data from the PC to the Pi, I decided on using RPC as it seemed the simplest option via the XMLRPC python library.
All I had to do was make a new proxy object on the PC client to represent the Pi server and call the exposed “move_robot(turn, forward)” function on it to transfer the input data to the Pi. The JOYAXISMOTION event triggers twice for vertical and horizontal movements of an analog stick respectively, so the values of the two parameters were simply the values returned by these two events. Horizontal movements would turn the robot left and right, and vertical movements would accelerate or decelerate the robot.
# Initialise RPC server = xmlrpc.client.ServerProxy("http://192.168.1.103:8000") ... server.move_robot(speed, speed)
The Pi client simply had to define the move_robot function, publish it as an RPC function and then listen for calls to the function from the client.
server.register_function(move_robot, "move_robot") server.serve_forever()
Moving the robot
With the axis values successfully being transferred across the network, I needed to interpret these into commands for the robot. The GoPiGo python API provides a lot of functions for manipulating the wheels in different ways, but since I had high fidelity analog data about how fast the robot should turn or move forward, all I really wanted to do was control the speed of each wheel individually. However, the functions that do that don’t accept negative values, so you can’t move the robot backwards with just those. I had to tell the robot which general direction to move in to begin with based on the vertical axis value.
# throttle if forward < 0: fwd() elif forward > 0: bwd() else: stop()
With that out of the way, I set the base line speed to the absolute value of the vertical stick and figure out how much to turn based on the absolute value of the horizontal stick.
set_speed(abs(forward)*speed_multiplier) # turning if turn < 0: # turn left set_right_speed(abs(forward) + abs(turn) * turn_multiplier) set_left_speed(abs(forward) - abs(turn) * turn_multiplier) elif turn > 0: # turn right set_right_speed(abs(forward) - abs(turn) * turn_multiplier) set_left_speed(abs(forward) + abs(turn) * turn_multiplier)
And that was it! I was able to drive the robot around using the analog stick on the Xbox One gamepad, albeit through the medium of my PC.