[[PageOutline(2-5, Table of Contents, floated)]] = Tutorial 5: Advanced remote control usage = As we've seen in [wiki:Tutorial1FirstRobot tutorial 1], we created a {{{Gamepad}}} class in order to control our robot. This class inherits from {{{RemoteControl}}}, and implements a few virtual methods. As an example, we will use my setup for my big 4 DoF hexapod, '''Cronos'''. We just create a class which inherits from {{{RemoteControl}}}: {{{ #!python class Gamepad(RemoteControl): def _createFrontend(self): return Thrustmaster(THRUSTMASTER_PATH) }}} The method {{{_createFrontend()}}} must return a valid {{{Frontend}}} (we just saw in [wiki:Tutorial4AddInputFrontend previous tutorial] how to create such {{{Frontend}}}s). This methods takes as first argument the {{{Frontend}}} path in '''{{{/dev}}}'''. An additional argument can be given to override the {{{DEFAULT_AXIS_CALIBRATION}}} table. {{{ #!python def _buildComponents(self): self._addConfig("walk") self._addConfig("body") self._addComponent(Button, command=self.selectNextConfig, key="digital_003", trigger="hold") self._addComponent(Button, configs="walk", command=GaitSequencer().walkStop, key="digital_000") self._addComponent(Button, configs="walk", command=GaitSequencer().walkStep, key="digital_002") self._addComponent(Button, configs="walk", command=GaitSequencer().selectNextGait, key="digital_006", trigger="hold") self._addComponent(Button, configs="walk", command=GaitSequencer().selectPrevGait, key="digital_007", trigger="hold") self._addComponent(Joystick, configs="walk", command=GaitSequencer().walk, keys=("analog_02", "analog_03", "analog_00"), mapper=MapperWalk()) # Body inc. translation self._addComponent(Axis, command=self.robot.incBodyPosition, key="analog_04", modifier="digital_008", mapper=MapperSetMultiply('dx', coef=5)) self._addComponent(Axis, command=self.robot.incBodyPosition, key="analog_05", modifier="digital_008", mapper=MapperSetMultiply('dy', coef=5)) self._addComponent(Button, command=self.robot.incBodyPosition, key="digital_004", modifier="digital_008", mapper=MapperSetValue(dz=+10)) self._addComponent(Button, command=self.robot.incBodyPosition, key="digital_005", modifier="digital_008", mapper=MapperSetValue(dz=-10)) # Body inc. rotation self._addComponent(Axis, command=self.robot.incBodyPosition, key="analog_04", modifier="digital_009", mapper=MapperSetMultiply('droll', coef=5)) self._addComponent(Axis, command=self.robot.incBodyPosition, key="analog_05", modifier="digital_009", mapper=MapperSetMultiply('dpitch', coef=-5)) self._addComponent(Button, command=self.robot.incBodyPosition, key="digital_004", modifier="digital_009", mapper=MapperSetValue(dyaw=+5)) self._addComponent(Button, command=self.robot.incBodyPosition, key="digital_006", modifier="digital_009", mapper=MapperSetValue(dyaw=-5)) # Body extra translation self._addComponent(Axis, configs="body", command=self.robot.setBodyExtraPosition, key="analog_02", modifier="digital_008", mapper=MapperSetMultiply('x', coef=30)) self._addComponent(Axis, configs="body", command=self.robot.setBodyExtraPosition, key="analog_03", modifier="digital_008", mapper=MapperSetMultiply('y', coef=30)) self._addComponent(Axis, configs="body", command=self.robot.setBodyExtraPosition, key="analog_01", modifier="digital_008", mapper=MapperSetMultiply('z', coef=20)) # Body extra rotation self._addComponent(Axis, configs="body", command=self.robot.setBodyExtraPosition, key="analog_00", modifier="digital_009", mapper=MapperSetMultiply('yaw', coef=-10)) self._addComponent(Axis, configs="body", command=self.robot.setBodyExtraPosition, key="analog_03", modifier="digital_009", mapper=MapperSetMultiply('pitch', coef=-10)) self._addComponent(Axis, configs="body", command=self.robot.setBodyExtraPosition, key="analog_02", modifier="digital_009", mapper=MapperSetMultiply('roll', coef=10)) # Inc feet neutral position self._addComponent(Button, command=self.robot.incFeetNeutral, key="digital_005", mapper=MapperSetValue(dneutral=-10)) self._addComponent(Button, command=self.robot.incFeetNeutral, key="digital_007", mapper=MapperSetValue(dneutral=+10)) }}} Interesting things take place in {{{_buildComponents()}}}: this is where {{{Component}}}s ({{{Button}}}, {{{Axis}}}...) of our remote control are created. We first define 2 remote control configs, '''walk''' and '''body''', as we don't have enough buttons to do all we want. In the first config, '''walk''', we define some components to make the robot walk, but also to change the body position (by increments). In the second config, '''body''', we define additional components to control the body. If no '''config''' is specified in the {{{Component}}} definition, it means it is available in all configs. Then we add the components, using the {{{_addComponent()}}} method helper. See [wiki:UserGuideGit#Remotecontrols user guide] for complete description. But let's see how things really work. {{{RemoteControl}}} is a thread, running a infinite main loop. During an iteration, each {{{Component}}} is checked. If triggered, the {{{Component}}} sends its output to the {{{Mapper}}}, which in turn sends its output to the '''command''' callable. For {{{Button}}}, triggering depends on the optional '''trigger''' param. For {{{Switch}}}, triggering occurs when the state toggles. For {{{Axis}}}, triggering occurs when the value changes. For {{{Joystick}}}, triggering occurs when any value changes. If a '''modifier''' is specified, it must be pressed for the triggering to occur. It acts as a keyboard shift or control key. This way, the main key can be used in different {{{Component}}}s. For {{{Axis}}} and {{{Joystick}}}, a '''threshold''' param avoids false changes near 0: the value will only be triggered if it changed '''and''' if it is greater or equal than '''threshold'''. For {{{Axis}}} and {{{Joystick}}}, a {{{Curve}}} can be used to change the response; a linear curve is used as default. Ok, now let's dig a little bit into the {{{Mapper}}}s. To control the robot walk, we created a {{{Joystick}}} {{{Component}}} with 3 axes, X, Y and RZ. These 3 values will be send as a tuple as soon as one of them changes. But the {{{GaitSequencer}}} {{{walk()}}} method expects '''speed''', '''direction''', '''length''' and '''angle''' params. We used the {{{MapperWalk}}} mapper, which computes all these params from X/Y/RZ values, which can be sent to the {{{walk()}}} method. See [https://framagit.org/fma38/Py4bot/blob/master/py4bot/inputs/mappers/mapper.py py4bot/inputs/mappers/mapper.py] to see how mappers are defined. Of course, we can define our own {{{Mappers}}}; they don't need to be in the {{{mapper.py}}} file, we can put them in our own code. In the [wiki:Tutorial6AddGait next tutorial], we will dive into gaits.