Controller Failover

New in version 1.0.7 is the ability to detect when a controller has disconnected. This means you can write code which will not only wait for a controller, but will gracefully handle the controller failing (generally from going out of range or batteries dying!). Because the simplest form of the controller resource or explicit binding will bind to any available controller, you can easily make your robot able to switch from one controller to another without any extra code. PS4 controller battery dead? No problem, just turn on your spare one, or even your spare XBox1 controller that you had in your bag for some reason. As long as the controller has been previously paired with the Pi (or other computer) it’ll be possible for your code to find it.

The example below waits for a controller, prints some details about it, then goes into a loop where it prints the values of any active (non-zero) axes, along with any buttons that are pressed. If you disconnect the controller, the code will detect this and go back into the search mode. If you then (or previously) pair another controller it’ll go back into the loop, and so on.

 1from time import sleep
 2
 3# All we need is a single import.
 4from approxeng.input.selectbinder import ControllerResource
 5
 6while 1:
 7    try:
 8        # Attempt to acquire a controller. If there's more than one we'll get one of them, if there aren't
 9        # any we'll throw an IOError. Assuming we succeed, the controller will be initialised, a new thread will be
10        # created in the background to pull events out of evdev and update the state of the controller object, and all
11        # the buttons and axes will be available as properties of 'controller'
12        #
13        # For most controllers, the same driver class can be used whether the controller is connected over bluetooth or
14        # over a USB cable. For controllers where this isn't the case there will be different driver classes for each
15        # kind of connection. In this particular case we're binding to whatever controllers we can find rather than
16        # specifying a particular one.
17        #
18        # The print_events can be set to show evdev events as they're received, whether handled or not. Any additional
19        # keyword args are passed through to the constructor of the controller class, so if you know exactly what class
20        # you're using (because you've supplied a controller_class argument here) you can provide controller-specific
21        # configuration in your resource. Setting controller_class to None just means 'find whatever joystick you can'
22        with ControllerResource(print_events=False, hot_zone=0.1, dead_zone=0.1) as controller:
23
24            # We've got a joystick, loop until it goes away. Print some details about the controller to the console.
25            print('Found a controller, {}'.format(controller))
26
27            # New in version 1.0.7 is detection for when a controller disconnects. This is handy, because it means we
28            # know if your robot has run out of controller range (for example) and can stop it! Rather than looping
29            # while True we loop while the 'connected' property on the controller is true.
30            while controller.connected:
31
32                # The next line is used to extract all buttons which were pressed since the last time this call was
33                # made. This means you don't have to worry about whether you're catching buttons while they're held
34                # down - this will catch a case where you check, then the user clicks and releases the button, and then
35                # you check again. Even though the click happened while your code was off doing something else, the
36                # event handling thread has monitored it and stored that the click happened. You can use methods on the
37                # approxeng.input.ButtonPresses instance this returns to query for specific buttons (by their standard
38                # name) or as in this case to see whether anything was pressed and print out the list of presses if so.
39                button_presses = controller.check_presses()
40                if button_presses.has_presses:
41                    # Printing the ButtonPresses object prints a list of the snames of buttons which were pressed
42                    print(button_presses)
43
44                # The controller.axes property contains all analogue (and potentially digital) axes of your controller.
45                # As with the button presses these are updated behind the scenes, with dead and hot zones applied,
46                # centre and range calibration and normalisation to either -1 to 1 (for centred axes) or 0 to 1 for
47                # triggers. The convenience method active_axes will return all axis objects which aren't at their
48                # resting position, but you can use other methods on this to get the corrected value for a specific
49                # axis.
50                for axis in controller.axes.active_axes:
51                    # Printing an axis object (CentredAxis, TriggerAxis or BinaryAxis) will print a summary of the
52                    # axis including its current corrected value
53                    print(axis)
54
55                # Note - in real cases we probably want to get axis values even when they're not active, in which case
56                # we would use the methods on the Controller class to get single or multiple corrected axis values by
57                # standard name.
58
59                # Sleep for a tenth of a second - spamming the console with print messages can upset the event reading
60                # loop. In real cases your code will be off doing other things and you probably won't want to have
61                # explicit delays!
62                sleep(0.1)
63
64            # We get here if controller.connected is False, this is set by the binder if it at any point fails to talk
65            # to the controller. The result is that if you turn your controller off you should see a disconnection
66            # message and the code will go back into looking for controllers.
67            print('Controller disconnected!')
68
69    except IOError:
70        # This is thrown when attempting to create the controller resource if no controllers are present. In this case
71        # we just pause for a second and try again.
72        print('Waiting for controller...')
73        sleep(1)