= Tutorial 4: Add input frontend support = ''Files for this tutorial are available in [https://framagit.org/fma38/Py4bot/tree/master/py4bot/examples/tutorial_4 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: [[Image(gamepad_free.jpg, 300px)]] (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 }}} '''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: [[Image(py4bot-gui-evcal_1.png, 300px)]] 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: [[Image(py4bot-gui-evcal_2.png, 300px)]] 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: {{{ #!python 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: {{{ #!python # 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: {{{ #!python 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.