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
-
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
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.
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
-
class
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:
-
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.