wiki:Tutorial4AddInputFrontend

Version 2 (modified by fma, 8 years ago) ( diff )

--

Tutorial 4: Add input frontend support

Files for this tutorial are available in py4bot/examples/tutorial_4/.

Here, we will see how to create a custom input frontend class for a new gamepad. This tutorial assumes you are running a linux OS.

Again, we create a new empty dir in our home dir:

$ mkdir tutorial_4
$ cd tutorial_4

As example, we are going to add support for the gamepad of the Freebox Revolution v6 ADSL box, widely used in France:

(Note that this model has been replaced by another one)

First, we need to know how this gamepad is detected by the kernel. For that, we just launch, from a console, the command:

$ sudo journalctl -f

and plug the gamepad. Here is what the kernel outputs:

[3687970.593351] usb 2-1.7: new low-speed USB device number 15 using ehci-pci
[3687970.706423] usb 2-1.7: New USB device found, idVendor=0079, idProduct=0006
[3687970.706426] usb 2-1.7: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[3687970.706428] usb 2-1.7: Product: Generic   USB  Joystick  
[3687970.706430] usb 2-1.7: Manufacturer: DragonRise Inc.  
[3687974.416506] input: DragonRise Inc.   Generic   USB  Joystick   as /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.7/2-1.7:1.0/0003:0079:0006.0004/input/input15
[3687974.416759] dragonrise 0003:0079:0006.0004: input,hidraw3: USB HID v1.10 Joystick [DragonRise Inc.   Generic   USB  Joystick  ] on usb-0000:00:1d.0-1.7/input0
[3687974.416780] dragonrise 0003:0079:0006.0004: Force Feedback for DragonRise Inc. game controllers by Richard Walmsley <richwalm@gmail.com>

Py4bot comes with py4bot-gui-evcal.py, a graphical tool to retreive an usb input device mapping. To use it, we need to give it the device path in /dev. Let's see what is our gamepad path:

$ ls -1 /dev/input/by-id/
usb-1bcf_USB_Optical_Mouse-event-mouse
usb-1bcf_USB_Optical_Mouse-mouse
usb-DragonRise_Inc._Generic_USB_Joystick-event-joystick
usb-DragonRise_Inc._Generic_USB_Joystick-joystick
usb-Microsoft_Microsoft®_Digital_Media_Keyboard_3000-event-kbd
usb-Microsoft_Microsoft®_Digital_Media_Keyboard_3000-if01-event-kbd

Here, the path to use is /dev/input/by-id/usb-DragonRise_Inc._Generic_USB_Joystick-event-joystick:

$ py4bot-gui-evcal.py /dev/input/by-id/usb-DragonRise_Inc._Generic_USB_Joystick-event-joystick 

{('EV_ABS', 3L): [(('ABS_X', 0L),
                   AbsInfo(value=127, min=0, max=255, fuzz=0, flat=15, resolution=0)),
                  (('ABS_Y', 1L),
                   AbsInfo(value=127, min=0, max=255, fuzz=0, flat=15, resolution=0)),
                  (('ABS_Z', 2L),
                   AbsInfo(value=131, min=0, max=255, fuzz=0, flat=15, resolution=0)),
                  (('ABS_RX', 3L),
                   AbsInfo(value=127, min=0, max=255, fuzz=0, flat=15, resolution=0)),
                  (('ABS_RZ', 5L),
                   AbsInfo(value=127, min=0, max=255, fuzz=0, flat=15, resolution=0)),
                  (('ABS_HAT0X', 16L),
                   AbsInfo(value=0, min=-1, max=1, fuzz=0, flat=0, resolution=0)),
                  (('ABS_HAT0Y', 17L),
                   AbsInfo(value=0, min=-1, max=1, fuzz=0, flat=0, resolution=0))],
 ('EV_FF', 21L): [(['FF_EFFECT_MIN', 'FF_RUMBLE'], 80L),
                  ('FF_PERIODIC', 81L),
                  (['FF_SQUARE', 'FF_WAVEFORM_MIN'], 88L),
                  ('FF_TRIANGLE', 89L),
                  ('FF_SINE', 90L),
                  (['FF_GAIN', 'FF_MAX_EFFECTS'], 96L)],
 ('EV_KEY', 1L): [(['BTN_JOYSTICK', 'BTN_TRIGGER'], 288L),
                  ('BTN_THUMB', 289L),
                  ('BTN_THUMB2', 290L),
                  ('BTN_TOP', 291L),
                  ('BTN_TOP2', 292L),
                  ('BTN_PINKIE', 293L),
                  ('BTN_BASE', 294L),
                  ('BTN_BASE2', 295L),
                  ('BTN_BASE3', 296L),
                  ('BTN_BASE4', 297L),
                  ('BTN_BASE5', 298L),
                  ('BTN_BASE6', 299L)],
 ('EV_MSC', 4L): [('MSC_SCAN', 4L)],
 ('EV_SYN', 0L): [('SYN_REPORT', 0L),
                  ('SYN_CONFIG', 1L),
                  ('SYN_DROPPED', 3L),
                  ('?', 4L),
                  ('?', 21L)]}

The script automatically retreives the mapping, and creates a nice GUI:

Here, we can play with buttons. For analog axis to work, we need to press the Analog button; the red light should turn on.

When playing with analog axis, we can see that a few of them are inverted. Py4bot expects than values increase when you push axis front/right. If it is not the case, we need to click the Invert checkbox.

Note that on this specific gamepad, the ABS_Z axis is a combination of ABS_X and ABS_RZ. Don't know what is the purpose... I suggest we don't use it.

Anyway, we should now have the following config:

Then, we just quit the script by closing the window. In the console, we see the final config:

BUTTON_MAPPING = {
    0x120:   0,  # BTN_JOYSTICK
    0x121:   1,  # BTN_THUMB
    0x122:   2,  # BTN_THUMB2
    0x123:   3,  # BTN_TOP
    0x124:   4,  # BTN_TOP2
    0x125:   5,  # BTN_PINKIE
    0x126:   6,  # BTN_BASE
    0x127:   7,  # BTN_BASE2
    0x128:   8,  # BTN_BASE3
    0x129:   9,  # BTN_BASE4
    0x12a:  10,  # BTN_BASE5
    0x12b:  11,  # BTN_BASE6
}

AXIS_MAPPING = {
    0x00:  0,  # ABS_X
    0x01:  1,  # ABS_Y
    0x02:  2,  # ABS_Z
    0x03:  3,  # ABS_RX
    0x05:  4,  # ABS_RZ
    0x10:  5,  # ABS_HAT0X
    0x11:  6,  # ABS_HAT0Y
}

DEFAULT_CALIBRATION = {
     0: (  +0, +255),  # ABS_X
     1: (+255,   +0),  # ABS_Y
     2: (  +0, +255),  # ABS_Z
     3: (  +0, +255),  # ABS_RX
     4: (+255,   +0),  # ABS_RZ
     5: (  -1,   +1),  # ABS_HAT0X
     6: (  +1,   -1),  # ABS_HAT0Y
}

Let's now create a file named dragonRise.py. At the top of the file, we add a few imports:

from py4bot.services.logger import Logger
from py4bot.common.intervalMapper import IntervalMapper
from py4bot.inputs.frontends.frontendUsb import FrontendUsb

Then, we copy/paste the output of the script:

# Button mapping
BUTTON_MAPPING = {
    0x120:   0,  # BTN_JOYSTICK
    0x121:   1,  # BTN_THUMB
    0x122:   2,  # BTN_THUMB2
    0x123:   3,  # BTN_TOP
    0x124:   4,  # BTN_TOP2
    0x125:   5,  # BTN_PINKIE
    0x126:   6,  # BTN_BASE
    0x127:   7,  # BTN_BASE2
    0x128:   8,  # BTN_BASE3
    0x129:   9,  # BTN_BASE4
    0x12a:  10,  # BTN_BASE5
    0x12b:  11,  # BTN_BASE6
}

# Axis mapping
AXIS_MAPPING = {
    0x00:  0,  # ABS_X
    0x01:  1,  # ABS_Y
    0x02:  2,  # ABS_Z
    0x03:  3,  # ABS_RX
    0x05:  4,  # ABS_RZ
    0x10:  5,  # ABS_HAT0X
    0x11:  6,  # ABS_HAT0Y
}

# Axis calibration
DEFAULT_CALIBRATION = {
    0: (  +0, +255),  # ABS_X
    1: (+255,   +0),  # ABS_Y
    2: (  +0, +255),  # ABS_Z
    3: (  +0, +255),  # ABS_RX
    4: (+255,   +0),  # ABS_RZ
    5: (  -1,   +1),  # ABS_HAT0X
    6: (  +1,   -1),  # ABS_HAT0Y
}

Finally, we create the class itself:

class DragonRise(FrontendUsb):
    """ Freebox Revolution v6 gamepad implementation (first model)
    """
    def __init__(self, path, calibration=DEFAULT_AXIS_CALIBRATION):
        """ Init DragonRise object
        """
        super(DragonRise, self).__init__("DragonRise", path)

        self._intervalMapper = {}
        for axis in calibration.keys():
            self._intervalMapper[axis] = IntervalMapper(calibration[axis])

    def _keyToButton(self, eventCode):
        code = BUTTON_MAPPING[eventCode]

        return code

    def _absToAnalog(self, eventCode, eventValue):
        axis = AXIS_MAPPING[eventCode]
        value = self._intervalMapper[axis](eventValue)

        return axis, value

That's it, our new gamepad support is added. We can then use it as fronted for our Gamepad class, instead of Thrustmaster (see tutorial 1).

Note that this class is already in Py4bot.

In the next tutorial, we will go deeper in controllers usage.

Attachments (3)

Download all attachments as: .zip

Note: See TracWiki for help on using the wiki.