triangula.input: PlayStation3 Controller Support

The SixAxis class contains the logic to read from a PlayStation3 controller connected over BlueTooth, including axis calibration, centering and dead-zones, and the ability to bind functions to button presses. This uses evdev for event handling, attempting to use this on any other platform than Linux will probably not work.

class triangula.input.SixAxis(dead_zone=0.05, hot_zone=0.0, connect=False)[source]

Class to handle the PS3 SixAxis controller

This class will process events from the evdev event queue and calculate positions for each of the analogue axes on the SixAxis controller (motion sensing is not currently supported). It will also extract button press events and call any handler functions bound to those buttons.

Once the connect() call is made, a thread is created which will actively monitor the device for events, passing them to the SixAxis class for processing. There is no need to poll the event queue manually.

Consuming code can get the current position of any of the sticks from this class through the axes instance property. This contains a list of triangula.input.SixAxis.Axis objects, one for each distinct axis on the controller. The list of axes is, in order: left x, left y, right x, right y.

class Axis(name, invert=False, dead_zone=0.0, hot_zone=0.0)[source]

A single analogue axis on the SixAxis controller

__init__(name, invert=False, dead_zone=0.0, hot_zone=0.0)[source]
corrected_value()[source]

Get a centre-compensated, scaled, value for the axis, taking any dead-zone into account. The value will scale from 0.0 at the edge of the dead-zone to 1.0 (positive) or -1.0 (negative) at the extreme position of the controller or the edge of the hot zone, if defined as other than 1.0. The axis will auto-calibrate for maximum value, initially it will behave as if the highest possible value from the hardware is 0.9 in each direction, and will expand this as higher values are observed. This is scaled by this function and should always return 1.0 or -1.0 at the extreme ends of the axis.

Returns:a float value, negative to the left or down and ranging from -1.0 to 1.0
SixAxis.BUTTON_CIRCLE = 13

Circle

SixAxis.BUTTON_CROSS = 14

Cross

SixAxis.BUTTON_D_DOWN = 6

D-pad down

SixAxis.BUTTON_D_LEFT = 7

D-pad left

SixAxis.BUTTON_D_RIGHT = 5

D-pad right

SixAxis.BUTTON_D_UP = 4

D-pad up

SixAxis.BUTTON_L1 = 10

L1 upper shoulder trigger

SixAxis.BUTTON_L2 = 8

L2 lower shoulder trigger

SixAxis.BUTTON_LEFT_STICK = 1

Left stick click button

SixAxis.BUTTON_PS = 16

PS button

SixAxis.BUTTON_R1 = 11

R1 upper shoulder trigger

SixAxis.BUTTON_R2 = 9

R2 lower shoulder trigger

SixAxis.BUTTON_RIGHT_STICK = 2

Right stick click button

SixAxis.BUTTON_SELECT = 0

The Select button

SixAxis.BUTTON_SQUARE = 15

Square

SixAxis.BUTTON_START = 3

Start button

SixAxis.BUTTON_TRIANGLE = 12

Triangle

SixAxis.__init__(dead_zone=0.05, hot_zone=0.0, connect=False)[source]

Discover and initialise a PS3 SixAxis controller connected to this computer.

Parameters:
  • dead_zone (float) – Creates a dead zone centred on the centre position of the axis (which may or may not be zero depending on calibration). The axis values range from 0 to 1.0, but will be locked to 0.0 when the measured value less centre offset is lower in magnitude than this supplied value. Defaults to 0.05, which makes the PS3 analogue sticks easy to centre but still responsive to motion. The deadzone is applies to each axis independently, so e.g. moving the stick far right won’t affect the deadzone for that sticks Y axis.
  • hot_zone (float) – Creates a zone of maximum value, any readings from the sensor which are within this value of the max or min values will be mapped to 1.0 and -1.0 respectively. This can be useful because, while the PS3 controllers sticks have independent axes, they are constrained to move within a circle, so it’s impossible to have e.g. 1.0,1.0 for both x and y axes. Setting this value to non-zero in effect partially squares the circle, allowing for controls which require full range control. Setting this value to 1/sqrt(2) will create a square zone of variability within the circular range of motion of the controller, with any stick motions outside this square mapping to the maximum value for the respective axis. The value is actually scaled by the max and min magnitude for upper and lower ranges respectively, so e.g. setting 0.5 will create a hot-zone at above half the maximum value and below half the minimum value, and not at +0.5 and -0.5 (unless max and min are 1.0 and -1.0 respectively). As with the dead zone, these are applied separately to each axis, so in the case where the hot zone is set to 1/sqrt(2), a circular motion of the stick will map to x and y values which trace the outline of a square of unit size, allowing for all values to be emitted from the stick.
  • connect – If true, call connect(), otherwise you need to call it elsewhere. Note that connect() may raise IOError if it can’t find a PS3 controller, so it’s best to call it explicitly yourself and handle the error. Defaults to False.
Returns:

an initialised link to an attached PS3 SixAxis controller.

SixAxis.connect()[source]

Connect to the first PS3 controller available within /dev/inputX, identifying it by name (this may mean that the code doesn’t work with non-genuine PS3 controllers, I only have original ones so haven’t had a chance to test).

This also creates a new thread to run the asyncore loop, and uses a file dispatcher monitoring the corresponding device to handle input events. All events are passed to the handle_event function in the parent, this is then responsible for interpreting the events and updating any internal state, calling button handlers etc.

Returns:True if a controller was found and connected, False if we already had a connection
Raises IOError:If we didn’t already have a controller but couldn’t find a new one, this normally means there’s no controller paired with the Pi
SixAxis.disconnect()[source]

Disconnect from any controllers, shutting down the channel and allowing the monitoring thread to terminate if there’s nothing else bound into the evdev loop. Doesn’t do anything if we’re not connected to a controller

SixAxis.get_and_clear_button_press_history()[source]

Return the button press bitfield, clearing it as we do.

Returns:A bit-field where bits are set to 1 if the corresponding button has been pressed since the last call to this method. Test with e.g. ‘if button_press_field & SixAxis.BUTTON_CIRCLE:...’
SixAxis.handle_event(event)[source]

Handle a single evdev event, this updates the internal state of the Axis objects as well as calling any registered button handlers.

Internal:
Parameters:event – The evdev event object to parse
SixAxis.is_connected()[source]

Check whether we have a connection

Returns:True if we’re connected to a controller, False otherwise.
SixAxis.register_button_handler(button_handler, buttons)[source]

Register a handler function which will be called when a button is pressed

Parameters:
  • handler – a function which will be called when any of the specified buttons are pressed. The function is called with the integer code for the button as the sole argument.
  • buttons ([int]) – a list or one or more buttons which should trigger the handler when pressed. Buttons are specified as ints, for convenience the PS3 button assignments are mapped to names in SixAxis, i.e. SixAxis.BUTTON_CIRCLE. This includes the buttons in each of the analogue sticks. A bare int value is also accepted here and will be treated as if a single element list was supplied.
Returns:

a no-arg function which can be used to remove this registration

SixAxis.reset_axis_calibration(*args)[source]

Resets any previously defined axis calibration to 0.0 for all axes

SixAxis.set_axis_centres(*args)[source]

Sets the centre points for each axis to the current value for that axis. This centre value is used when computing the value for the axis and is subtracted before applying any scaling.

An additional class allows for use within a ‘with’ binding. The connection and disconnection is managed automatically by the resource, so there’s no need to call connect() on the generated triangula.input.SixAxis instance.

class triangula.input.SixAxisResource(bind_defaults=False, dead_zone=0.05, hot_zone=0.0)[source]

Resource class which will automatically connect and disconnect to and from a joystick, creating a new SixAxis object and passing it to the ‘with’ clause. Also binds a handler to the START button which resets the axis calibration, and to the SELECT button which centres the analogue sticks on the current position.

__init__(bind_defaults=False, dead_zone=0.05, hot_zone=0.0)[source]

Resource class, produces a triangula.input.SixAxis for use in a ‘with’ binding.

Parameters:
  • dead_zone (float) – See SixAxis class documentation
  • hot_zone (float) – See SixAxis class documentation
  • bind_defaults – Defaults to False, if True will automatically bind two actions to the START and SELECT buttons to reset the axis calibration and to set the axis centres respectively.

As an example, the following code will bind to an already paired PS3 controller and continuously print its axes:

from triangula.input import SixAxisResource
# Get a joystick, this will fail unless the SixAxis controller is paired and active
with SixAxisResource() as joystick:
    while 1:
        # Default behaviour is to print the values of the four analogue axes
        print joystick

Handling Buttons

The PS3 controller has a large number of buttons. You can use these in two ways:

Firstly you can register callback functions which will be called whenever a button is pressed. These functions will be called from the internal thread running in the background, so you’ll have to deal with potential synchronisation, but you get the relative convenience of an asynchronous framework in return. To use this method you need to create a handler function, then register that function with the triangula.input.SixAxis instance:

from triangula.input import SixAxisResource, SixAxis
# Get a joystick
with SixAxisResource() as joystick:
    # Create a handler function
    def button_handler(button):
        print 'Button clicked {}'.format(button)
    # Register the handler to the SQUARE button
    joystick.register_button_handler(button_handler, SixAxis.BUTTON_SQUARE)
    # We can also register a handler to multiple buttons in one call
    joystick.register_button_handler(button_handler, [SixAxis.BUTTON_CIRCLE, SixAxis.BUTTON_TRIANGLE])
    while 1:
        # Do stuff here, only register the button handlers once, not in this loop!
        # If the buttons are pressed, your handlers will be called but not from this thread.
        pass

The register_button_handler function actually returns a function which can be called to de-register the handler, you should do this to stop your handler being called when it’s no longer needed.

As you can see, there’s quite a lot of thinking required to make button handlers work properly. They may be the right way to do things (for example, you might want a handler which reset the centre point of the analogue sticks, this would be best done as a handler because it could be called at any time from anywhere else in your code and you wouldn’t have to worry about it). If, however, you’re in a polling loop such as Triangula’s task framework or PyGame’s event loop you probably just want to know whether a button was pressed since you last checked.

You can do this with the get_and_clear_button_press_history function. The triangula.input.SixAxis instance maintains a set of flags, one for each button, indicating whether that button has been pressed. These flags are set when the button is pressed, and all cleared when the function is called, so in effect a flag will be set if the button was pressed since the last time you asked. The return value from this function is an integer, which is used in this case as a bit-field - a binary number where each digit represents whether a single button has been pressed (1) or not (0). This sounds like it might be hard to use, but actually it’s very simple due to Python’s bitwise operators (if you’re not familiar with these don’t worry, but you should probably go and look them up, they’re really useful!).

from triangula.input import SixAxisResource, SixAxis
# Get a joystick as before
with SixAxisResource() as joystick:
    # No need for any button handlers, go straight into our loop
    while 1:
        buttons_pressed = joystick.get_and_clear_button_press_history()
        if buttons_pressed & 1 << SixAxis.BUTTON_SQUARE:
            print 'SQUARE pressed since last check'

How does this work? Well, SixAxis.SQUARE is actually an integer, in this particular case it’s 15. The expression 1 << SixAxis.BUTTON_SQUARE means ‘Take the number 1, and then shift its binary representation 15 places to the left adding zeros as we go’ so we end up with 0b1000000000000000. The buttons_pressed value is a similar looking number, it has ‘1’ where a button was pressed and ‘0’ where it wasn’t. The & operator is a so called ‘bitwise AND’, it takes two numbers and compares their binary representations - if both numbers have a ‘1’ in a column it will put a ‘1’ in the result, otherwise it puts a ‘0’. So what this is doing is masking out everything that isn’t the BUTTON_SQUARE value, and then seeing if that particular bit (a binary digit is called a ‘bit’) is set. Finally, the if ... statement relies on the Python behaviour that ‘0’ is False, and anything that isn’t ‘0’ is True when using numbers. If this all feels a bit mysterious, don’t worry! You don’t need to know it to use this class, but it’s all good basic knowledge - probably worth learning at some point.