Simple Usage
In version 2.0 of this library, the API has been simplified and made more Python-like. Rather than having to fetch nested objects containing axis and button information, all the most common operations are now available as methods and attributes of the controller class itself. Read on…
Connecting to a controller
To connect to a controller you do the following (including error handling):
from approxeng.input.selectbinder import ControllerResource
while True:
try:
with ControllerResource() as joystick:
print('Found a joystick and connected')
while joystick.connected:
# Do stuff with your joystick here!
# ....
# ....
# Joystick disconnected...
print('Connection to joystick lost')
except IOError:
# No joystick found, wait for a bit before trying again
print('Unable to find any joysticks')
sleep(1.0)
This example will loop forever. It will attempt to connect to any supported joystick - the ControllerResource will raise IOError if it can’t find one, otherwise it will do all the necessary background work, create a controller object and, in this case, bind it to the ‘joystick’ variable which can then be used to read axes, buttons etc.
The connected()
property on the joystick object indicates whether the underlying
device is connected or not - if, for example, you have a controller that goes out of range, runs out of batteries, or is
turned off while in use this will be set to False and you can handle the case correctly (if using this code in a robot,
this would be an excellent time to turn of all your motors, for instance!)
The ControllerResource
class accepts a number of, optional, arguments. These can be
used to tell it which controller type to use (the default is to connect to the first controller it can understand, but
if you have multiple controllers connected for some reason you might want to tell it to use your XBox1 rather than your
PS4 controller). They can also be used to configure the default settings for axes, and there’s an option to print out
debug information. In general though you’ll only ever need the simple form shown here.
Once you have a controller object you can use it to read axes and buttons, both of which are referenced by a standard name (see Standard Names) - this allows you to not worry too much about exactly what controller’s going to be present. Rather than having to know that the XBox controller has ‘A’,’B’… and so on, whereas the Playstation controllers have ‘cross’, ‘circle’ etc the API defines a standard set of names for buttons and axes (they’re based on the PS3 controller as it happens, mostly because that’s the one I first used when I started writing the library!).
Connecting to multiple controllers
To connect to multiple controllers, or to be more specific about the types and capabilities of controllers you need, see Controller Discovery - approxeng.input.controllers. The example below shows binding to two controllers, the first of which must be a PS3 controller, the second of which must have a left X and Y axis:
from approxeng.input.selectbinder import ControllerResource
from approxeng.input.dualshock3 import DualShock3
from approxeng.input.controllers import ControllerRequirement
while True:
try:
with ControllerResource(ControllerRequirement(require_class=DualShock3),
ControllerRequirement(require_snames=['lx','ly'])) as ds3, joystick:
print('Found two joysticks and connected')
while joystick.connected and ds3.connected:
# Do stuff with your joystick here!
# ....
# ....
# One or both joystick(s) disconnected...
print('Connection to joystick(s) lost')
except IOError:
# No matching joystick found, wait for a bit before trying again
print('Unable to find matching joysticks')
sleep(1.0)
Reading Analogue Axes
Analogue axes are those which vary continuously, allowing for fine control of motion. Unlike buttons, which are either on or off, an analogue axis has a floating point value associated with its current position.
Centred axes have a value ranging from -1.0 to 1.0
Trigger axes have a value ranging from 0.0 to 1.0
Axis values are read as properties of the joystick object (in this and other examples we’re not showing the exception handling from the first example, but you should still do it!):
from approxeng.input.selectbinder import ControllerResource
# Get a joystick
with ControllerResource() as joystick:
# Loop until disconnected
while joystick.connected:
# Get a corrected value for the left stick x-axis
left_x = joystick['lx']
# We can also get values as attributes:
left_y = joystick.ly
…and that’s it! You might have used other libraries which require you to do event handling and similar, but in this case all that stuff is taken care of in the background and you just have to read the information you want from the joystick object.
Circular Analogue Axes
As of version 2.4, if a controller defines pairs of (lx,ly), or (rx,ry), a new virtual axis is created called l or
r respectively. This is an instance of CircularCentredAxis
. Unlike other axes which return
a single floating point value, this axis type returns a tuple of (x,y) floats. Obviously you could do this yourself
by calling the individual horizontal and vertical axes, but this circular axis has a subtle improvement to how it
handles dead and hot zones - areas where you want either no output (the deadzone in the middle) or full output (the hot
zones at either end of the range). Specifically, it judges whether a position is in the dead or hot zone based on the
overall distance of the stick from the centre, taking both axes into account, and then scales both values such that the
direction is preserved and the magnitude scaled. For cases where you genuinely want to control a two dimensional
quantity with the stick this will result in much smoother, more consistent, motion, without the pauses in response as
the stick crosses each of the independent x and y axis dead zones.
If you’re using a single stick to control two different quantities, such as a four wheel robot where you’ve mapped acceleration and steering onto a single stick, you probably want to continue to use the individual axes! Experiment and see which setting feels best for you.
As with other axes and buttons, you can fetch the values of these extra axes in a couple of different ways:
from approxeng.input.selectbinder import ControllerResource
# Get a joystick
with ControllerResource() as joystick:
# Loop until disconnected
while joystick.connected:
# Get a corrected tuple of values from the left stick, assign the two values to x and y
x, y = joystick['l']
# We can also get values as attributes:
x, y = joystick.l
Standard Names
Most of the controllers supported by this library are fairly similar - they have two analogue joysticks, a bunch of buttons, some triggers etc. It would be helpful therefore to be able to make use of one controller type but make it as easy as possible to use others without substantial code changes in your own code.
To do this the library assigns a standard name, or sname to each button and axis on every controller. These are based loosely on the buttons found on a PS3 controller, at the cost of minor confusion for the XBox users (where, for example, the X button is referred to by the name square). As long as you use controls which are common to all three controllers you should be able to transparently make use of whichever of them is available at the time. You can also choose to make use of facilities which are only available on specific hardware (such as the analogue triggers on the PS4 and XBoxOne controllers) but you should bear in mind that this will preclude use of a less well equipped controller. Up to you.
A look at the source for each of the controller subclasses should make it obvious what names are available, but the standard ones are as follows:
Axis Names
Note
With a new kernel (4.13 upwards, tested with 4.15) the Sony controllers expose their motion events in a way we can handle, so I’ve added pitch and roll for both controllers, and yaw rate for the DS4. There is no absolute yaw value available, you’d have to calculate this from the rates (tricky to do with any accuracy). Roll is positive clockwise when holding the controller, pitch is positive aiming the front of the controller towards the ceiling. Available in 2.1.0 of this library onwards.
Standard name |
PS3 |
PS4 |
XBoxOne |
Rock Candy |
Steam |
Wii Pro |
lx |
Left X |
Left X |
Left X |
Left X |
Left X |
Left X |
ly |
Left Y |
Left Y |
Left Y |
Left Y |
Left Y |
Left Y |
rx |
Right X |
Right X |
Right X |
Right X |
Right X |
Right X |
ry |
Right Y |
Right Y |
Right Y |
Right Y |
Right Y |
Right Y |
lt |
L2 Trigger |
L2 Trigger |
LT Trigger |
— |
Left Trigger |
— |
rt |
R2 Trigger |
R2 Trigger |
RT Trigger |
— |
Right Trigger |
— |
tx |
— |
Touch X |
— |
— |
— |
— |
ty |
— |
Touch Y |
— |
— |
— |
— |
pitch |
Motion |
Motion |
— |
— |
— |
— |
roll |
Motion |
Motion |
— |
— |
— |
— |
yaw_rate |
— |
Motion |
— |
— |
— |
— |
Standard name |
PiHut |
SF30 Pro |
lx |
Left X |
Left X |
ly |
Left Y |
Left Y |
rx |
Right X |
Right X |
ry |
Right Y |
Right Y |
lt |
L Trigger |
L Trigger |
rt |
R Trigger |
R Trigger |
tx |
— |
— |
ty |
— |
— |
pitch |
— |
— |
roll |
— |
— |
yaw_rate |
— |
— |
Force feedback effects
As of version 2.6.3 you can drive the rumble mechanism of any controller which supports it through the rumble()
method on Controller
. It takes a number of milliseconds for the effect, defaulting to 1000 for a one second
vibration.