wiki:Tutorial4AddInputFrontend

Version 3 (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 [Tutorial1FirstRobot tutorial 1]).

Note that this class is already in Py4bot.

In the [Tutorial5AdvancedRemoteControlUsage 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.