Changes between Version 48 and Version 49 of Tutorials


Ignore:
Timestamp:
Apr 7, 2016, 4:32:24 PM (10 years ago)
Author:
fma
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Tutorials

    v48 v49  
    2929Now, let's create the robot! A multi-legs robot is mainly build from a body, and several legs. In most cases, the defaut body provided by Py4bot is OK, but in any case, we need to create legs.
    3030
    31 The second thing we also need to create is an ''actuator pool'', which contains all legs joints, in order to move them syncrhonized. In our example, joints are standard (virtual) servos. So, here is what the main robot class looks like:
     31The second thing we also need to create is an ''actuator pool'', which contains all legs joints, in order to move them syncrhonized. In our example, joints are standard servos. In this example, we use the '''Veyron''' board, from '''DFRobot''':
    3232
    3333{{{
     
    4848
    4949    def _createActuatorPool(self):
    50         driver = VPython3Dof(settings.LEGS_INDEX, settings.LEGS_GEOMETRY, settings.LEGS_ORIGIN, settings.LEGS_ACTUATORS_MAPPING)
    51         pool = ActuatorPool(driver)
     50        driver = FakeDriver()
     51        pool = ServoPool(driver)
    5252
    5353        for leg in self._legs.values():
    5454
    5555            # Create joints actuators
    56             num = settings.LEGS_ACTUATORS_MAPPING[leg.index]['coxa']
    57             actuator = Actuator(leg.coxa, num)
    58             pool.add(actuator)
    59 
    60             num = settings.LEGS_ACTUATORS_MAPPING[leg.index]['femur']
    61             actuator = Actuator(leg.femur, num)
    62             pool.add(actuator)
    63 
    64             num = settings.LEGS_ACTUATORS_MAPPING[leg.index]['tibia']
    65             actuator = Actuator(leg.tibia, num)
    66             pool.add(actuator)
     56            num = settings.LEGS_SERVOS_MAPPING[leg.index]['coxa']
     57            servo = Servo(leg.coxa, num, **settings.SERVOS_CALIBRATION[num])
     58            pool.add(servo)
     59
     60            num = settings.LEGS_SERVOS_MAPPING[leg.index]['femur']
     61            servo = Servo(leg.femur, num, **settings.SERVOS_CALIBRATION[num])
     62            pool.add(servo)
     63
     64            num = settings.LEGS_SERVOS_MAPPING[leg.index]['tibia']
     65            servo = Servo(leg.tibia, num, **settings.SERVOS_CALIBRATION[num])
     66            pool.add(servo)
     67
    6768        return pool
    6869}}}
     
    7071As you can see, we just implemented 2 virtual methods, {{{_createLegs()}}} and {{{_createActuatorPool()}}}.
    7172
    72 Also note that we used some values from the '''{{{settings.py}}}''' module. This module is just a simple way to centralise the configuration of our hexapod.
    73 
    74 For now, it should contain something like:
    75 
    76 {{{
    77 #!python
    78 
    79 # -*- coding: utf-8 -*-
    80 
    81 from py4bot.common import config
    82 
    83 
    84 # Legs index
     73Also note that we used some values from the '''{{{settings.py}}}''' module. This module is just a simple way to centralise the configuration of our hexapod. We will describe this module later.
     74
     75To drive a robot, we need a remote control. In this tutorial, we are going to use an old gamepad, a [http://www.thrustmaster.com/en_IN/products/firestorm-dual-analog-3 Thrustmaster Firestorm Dual Analog 3]. As I own such gamepad, I already added support in Py4bot:
     76
     77{{{
     78#!python
     79
     80class Gamepad(RemoteControl):
     81    def _createFrontend(self):
     82        return Thrustmaster("/dev/input/by-id/usb-Mega_World_Thrustmaster_dual_analog_3.2-event-joystick")
     83
     84    def _buildComponents(self):
     85        self._addConfig("walk")
     86        self._addConfig("body")
     87
     88        self._addComponent(Button, command=self.selectNextConfig, key="button_08", trigger="hold")
     89        self._addComponent(Button, command=self.selectPreviousConfig, key="button_09", trigger="hold")
     90
     91        self._addComponent(Button, command=self.robot.incBodyPosition, key="button_004", mapper=MapperSetValue(dz=+5))
     92        self._addComponent(Button, command=self.robot.incBodyPosition, key="button_005", mapper=MapperSetValue(dz=-5))
     93        self._addComponent(Joystick, configs="walk", command=GaitSequencer().walk, keys=("analog_02", "analog_03", "analog_00"), mapper=MapperWalk())
     94
     95        self._addComponent(Analog, configs="body", command=self.robot.setBodyExtraPosition, key="analog_00", mapper=MapperSetMultiply('yaw', coef=-15))
     96        self._addComponent(Analog, configs="body", command=self.robot.setBodyExtraPosition, key="analog_03", mapper=MapperSetMultiply('pitch', coef=-15))
     97        self._addComponent(Analog, configs="body", command=self.robot.setBodyExtraPosition, key="analog_02", mapper=MapperSetMultiply('roll', coef=15))
     98}}}
     99
     100We won't deep into details, here; refer to the [[documentation]] for more informations about remote controls. This is an important part of the framework, and you  can do very smart things with it, like like having several configurations depending on the mode you want, using several remotes on the same robot...
     101
     102Now, let's create our robot:
     103
     104{{{
     105#!python
     106
     107def main():
     108    def addGait(gaitClass, gaitName):
     109        gait = gaitClass(gaitName, settings.GAIT_LEGS_GROUPS[gaitName], settings.GAIT_PARAMS[gaitName])
     110        GaitManager().add(gait)
     111
     112    addGait(GaitTripod, "tripod")
     113    addGait(GaitTetrapod, "tetrapod")
     114    addGait(GaitRiple, "riple")
     115    addGait(GaitWave, "metachronal")
     116    addGait(GaitWave, "wave")
     117    GaitManager().select("riple")
     118
     119    robot = Hexapod()
     120
     121    remote = Gamepad(robot)
     122
     123    GaitSequencer().start()
     124    remote.start()
     125
     126    robot.setBodyPosition(z=30)
     127
     128    robot.mainLoop()
     129
     130    remote.stop()
     131    remote.join()
     132    GaitSequencer().stop()
     133    GaitSequencer().join()
     134
     135
     136if __name__ == "__main__":
     137    main()
     138}}}
     139
     140In addition to the robot itself, we added a few pre-defined gaits, in order to make it walk.
     141
     142=== Settings ===
     143
     144Let's discuss about settings used in the previous part. See the [[documentation]] for the body/legs geometry conventions used.
     145
     146{{{
     147#!python
     148
    85149LEGS_INDEX = ('RF', 'RM', 'RR', 'LR', 'LM', 'LF')
    86 
    87 # Legs geometry
     150}}}
     151
     152{{{LEGS_INDEX}}} contains the names used to define legs; they can be freely chosen, but these values must be used as keys for other params.
     153
     154{{{
     155#!python
     156
    88157LEGS_GEOMETRY = {
    89158    'RM': {'coxa': 25, 'femur': 45, 'tibia': 65},
     
    94163    'RR': {'coxa': 25, 'femur': 45, 'tibia': 65}
    95164}
    96 
    97 # Legs origin
     165}}}
     166
     167{{{LEGS_GEOMETRY}}} dict contains the lengths of the differents parts of the legs, in mm.
     168
     169{{{
     170#!python
     171
    98172LEGS_ORIGIN = {
    99173    'RM': {'x':  50., 'y':   0., 'gamma0' :   0.},
     
    105179}
    106180config.initOrigin(LEGS_INDEX, LEGS_ORIGIN)
    107 
    108 # Legs (feet) neutral position (femur horizontal, tibia vertical)
    109 LEGS_NEUTRAL = {
    110     'RM': {
    111         'l': LEGS_GEOMETRY['RM']['coxa'] + LEGS_GEOMETRY['RM']['femur'],
    112         'z': LEGS_GEOMETRY['RM']['tibia']
    113     },
    114     'RF': {
    115         'l': LEGS_GEOMETRY['RF']['coxa'] + LEGS_GEOMETRY['RF']['femur'],
    116         'z': LEGS_GEOMETRY['RF']['tibia']
    117     },
    118     'LF': {
    119         'l': LEGS_GEOMETRY['LF']['coxa'] + LEGS_GEOMETRY['LF']['femur'],
    120         'z': LEGS_GEOMETRY['LF']['tibia']
    121     },
    122     'LM': {
    123         'l': LEGS_GEOMETRY['LM']['coxa'] + LEGS_GEOMETRY['LM']['femur'],
    124         'z': LEGS_GEOMETRY['LM']['tibia']
    125     },
    126     'LR': {
    127         'l': LEGS_GEOMETRY['LR']['coxa'] + LEGS_GEOMETRY['LR']['femur'],
    128         'z': LEGS_GEOMETRY['LR']['tibia']
    129     },
    130     'RR': {
    131         'l': LEGS_GEOMETRY['RR']['coxa'] + LEGS_GEOMETRY['RR']['femur'],
    132         'z': LEGS_GEOMETRY['RR']['tibia']
    133     }
    134 }
    135 config.initNeutral(LEGS_INDEX, LEGS_NEUTRAL)
    136 
    137 # Legs / actuators mapping
    138 LEGS_ACTUATORS_MAPPING = {
     181}}}
     182
     183{{{LEGS_ORIGIN}}} dict contains the positions and orientation of the origin of the legs: {{{(x, y)}}} defines the center of rotation of '''coxa''' joint, and {{{gamma0}}} is the angle of the legs at neutral position.
     184
     185Note that we need to call the {{{initOrigin()}}} function of the '''{{{config.py}}}''' module, in order to initialize internal configuration (some matrix are defined, in order to avoid further maths).
     186
     187{{{
     188#!python
     189
     190LEGS_FEET_NEUTRAL = {
     191    'RM': LEGS_GEOMETRY['RM']['coxa'] + LEGS_GEOMETRY['RM']['femur'],
     192    'RF': LEGS_GEOMETRY['RF']['coxa'] + LEGS_GEOMETRY['RF']['femur'],
     193    'LF': LEGS_GEOMETRY['LF']['coxa'] + LEGS_GEOMETRY['LF']['femur'],
     194    'LM': LEGS_GEOMETRY['LM']['coxa'] + LEGS_GEOMETRY['LM']['femur'],
     195    'LR': LEGS_GEOMETRY['LR']['coxa'] + LEGS_GEOMETRY['LR']['femur'],
     196    'RR': LEGS_GEOMETRY['RR']['coxa'] + LEGS_GEOMETRY['RR']['femur'],
     197}
     198config.initFeetNeutral(LEGS_INDEX, LEGS_FEET_NEUTRAL)
     199}}}
     200
     201{{{LEGS_FEET_NEUTRAL}}} dict contains the feet neutral positions af all legs.
     202
     203Here too, we need to call the {{{initFeetNeutral()}}} function of the '''{{{config.py}}}''' module, in order to initialize internal configuration.
     204
     205{{{
     206#!python
     207
     208LEGS_SERVOS_MAPPING = {
    139209    'RF': {'coxa':  0, 'femur':  1, 'tibia':  2},
    140210    'RM': {'coxa':  4, 'femur':  5, 'tibia':  6},
     
    144214    'LF': {'coxa': 23, 'femur': 22, 'tibia': 21}
    145215}
    146 }}}
    147 
    148 Some of these values seem obvious, some don't; we'll discuss all this later.
    149 
    150 Now, let's create our robot. Still in '''{{{hexapod.py}}}''', add:
    151 
    152 {{{
    153 #!python
    154 
    155 def main():
    156     robot = Hexapod()
    157 }}}
    158 
    159 The main interrest of a multi-legs robot is to make it walk! But by default, the {{{Robot}}} class does not know any '''gait''', so, we have to teach it how to actually walk.
    160 
    161 Again, as Py4bot is a framework, it comes with some pre-defined gaits. So, all we have to do is to tell our robot which gaits to use (at least one), and give some params:
    162 
    163 {{{
    164 #!python
    165 
    166     gait = GaitRiple("riple", settings.GAIT_LEGS_GROUPS, settings.GAIT_PARAMS)
    167     GaitManager().add(gait)
    168 }}}
    169 
    170 Let's see what wee need to add in the '''{{{settings.py}}}''' module:
    171 
    172 {{{
    173 #!python
    174 
    175 # Gaits
    176 GAIT_LEGS_GROUPS = (('RR',), ('LM',), ('RF',), ('LR',), ('RM',), ('LF',))
     216
     217SERVOS_CALIBRATION = {
     218    0: {'offset':   0., 'pulse90': 1500, 'ratio': 1000/90.},  # coxa leg RF
     219    1: {'offset': -90., 'pulse90': 1500, 'ratio': 1000/90.},  # femur leg RF
     220    2: {'offset':   0., 'pulse90': 1500, 'ratio': 1000/90.},  # tibia leg RF
     221
     222    4: {'offset':   0., 'pulse90': 1500, 'ratio': 1000/90.},  # coxa leg RM
     223    5: {'offset': -90., 'pulse90': 1500, 'ratio': 1000/90.},  # femur leg RM
     224    6: {'offset':   0., 'pulse90': 1500, 'ratio': 1000/90.},  # tibia leg RM
     225
     226    8: {'offset':   0., 'pulse90': 1500, 'ratio': 1000/90.},  # coxa leg RR
     227    9: {'offset': -90., 'pulse90': 1500, 'ratio': 1000/90.},  # femur leg RR
     228    10: {'offset':   0., 'pulse90': 1500, 'ratio': 1000/90.},  # tibia leg RR
     229
     230    15: {'offset':   0., 'pulse90': 1500, 'ratio': 1000/90.},  # coxa leg LR
     231    14: {'offset': -90., 'pulse90': 1500, 'ratio': 1000/90.},  # femur leg LR
     232    13: {'offset':   0., 'pulse90': 1500, 'ratio': 1000/90.},  # tibia leg LR
     233
     234    19: {'offset':   0., 'pulse90': 1505, 'ratio': 1000/90.},  # coxa leg LM
     235    18: {'offset': -90., 'pulse90': 1500, 'ratio': 1000/90.},  # femur leg LM
     236    17: {'offset':   0., 'pulse90': 1500, 'ratio': 1000/90.},  # tibia leg LM
     237
     238    23: {'offset':   0., 'pulse90': 1500, 'ratio': 1000/90.},  # coxa leg LF
     239    22: {'offset': -90., 'pulse90': 1500, 'ratio': 1000/90.},  # femur leg LF
     240    21: {'offset':   0., 'pulse90': 1500, 'ratio': 1000/90.},  # tibia leg LF
     241}
     242}}}
     243
     244{{{LEGS_SERVOS_MAPPING}}} is a table to map all joints to actuators nums.
     245
     246{{{SERVOS_CALIBRATION}}} contains some calibration:
     247
     248* {{{offset}}} is the angle between the servo reference and the real angle. See the [[FAQ#Howisgeometrydefined|FAQ]] for the real angles definition;
     249* {{{pulse90}}} is the pulse value for the neutral position of the servo, which is usually 90°;
     250* {{{ratio}}} is the pulse width per degree.
     251
     252Offsets may vary, depending how you mount the servos. Same, ratio sign may have to be inverted on one side, if you have a symetrical design; all angle are always computed using trigonometric direction (CCW).
     253
     254See the [[UserGuideGit#Servoscalibration|user guide]] how to fine tune servos calibration.
     255
     256Finally:
     257
     258{{{
     259#!python
     260
     261GAIT_LEGS_GROUPS = {
     262    'tripod':      (('RM', 'LF', 'LR'), ('RF', 'LM', 'RR')),
     263    'tetrapod':    (('RR', 'LM'), ('RF', 'LR'), ('RM', 'LF')),
     264    'riple':       (('RR',), ('LM',), ('RF',), ('LR',), ('RM',), ('LF',)),
     265    'metachronal': (('RR',), ('LM',), ('RF',), ('LR',), ('RM',), ('LF',)),
     266    'wave':        (('RR',), ('RM',), ('RF',), ('LR',), ('LM',), ('LF',))
     267}
    177268
    178269GAIT_PARAMS = {
    179     'length': 40.,
    180     'angle': 10.,
    181     'height': 30.,
    182     'minLength': 4.,
    183     'minAngle': 2.,
    184     'speedMin':  50.,
    185     'speedMax': 300.
    186 }
    187 }}}
    188 
    189 Final steps are: launch the gait sequencer, move a little bit the body position, ask the robot to start walking, and run the robot main loop:
    190 
    191 {{{
    192 #!python
    193 
    194     GaitSequencer().start()
    195 
    196     robot.setBodyPosition(z=-30)
    197 
    198     GaitSequencer().walk(0.75, 90., 0., 1)
    199 
    200     robot.mainLoop(5)
    201 
    202     GaitSequencer().walkStop()
    203     time.sleep(5)
    204 
    205     GaitSequencer().stop()
    206     GaitSequencer().join()
    207 
    208 
    209 if __name__ == "__main__":
    210     main()
    211 }}}
    212 
    213 You should then see the robot walking for a few seconds:
    214 
    215 [[Image(tutorial_1.png, nolink)]]
    216 
    217 === Settings ===
    218 
    219 Let's discuss about settings used in the previous part. See the [[FAQ#Bodylegsgeometry|FAQ]] for the Body/legs geometry conventions used.
    220 
    221 {{{
    222 #!python
    223 
    224 LEGS_INDEX = ('RF', 'RM', 'RR', 'LR', 'LM', 'LF')
    225 }}}
    226 
    227 {{{LEGS_INDEX}}} contains the names used to define legs; they can be freely chosen, but these values must be used as keys for other params.
    228 
    229 {{{
    230 #!python
    231 
    232 LEGS_GEOMETRY = {
    233     'RM': {'coxa': 25, 'femur': 45, 'tibia': 50},
    234     'RF': {'coxa': 25, 'femur': 45, 'tibia': 50},
    235     'LF': {'coxa': 25, 'femur': 45, 'tibia': 50},
    236     'LM': {'coxa': 25, 'femur': 45, 'tibia': 50},
    237     'LR': {'coxa': 25, 'femur': 45, 'tibia': 50},
    238     'RR': {'coxa': 25, 'femur': 45, 'tibia': 50}
    239 }
    240 }}}
    241 
    242 {{{LEGS_GEOMETRY}}} dict contains the lengths of the differents parts of the legs, in mm.
    243 
    244 {{{
    245 #!python
    246 
    247 LEGS_ORIGIN = {
    248     'RM': {'x':  50., 'y':   0., 'gamma0' :   0.},
    249     'RF': {'x':  35., 'y':  80., 'gamma0' :  30.},
    250     'LF': {'x': -35., 'y':  80., 'gamma0' : 150.},
    251     'LM': {'x': -50., 'y':   0., 'gamma0' : 180.},
    252     'LR': {'x': -35., 'y': -80., 'gamma0' : 210.},
    253     'RR': {'x':  35., 'y': -80., 'gamma0' : 330.},
    254 }
    255 config.initOrigin(LEGS_INDEX, LEGS_ORIGIN)
    256 }}}
    257 
    258 {{{LEGS_ORIGIN}}} dict contains the positions and orientation of the origin of the legs: {{{(x, y)}}} defines the center of rotation of '''coxa''' joint, and {{{gamma0}}} is the angle of the legs at neutral position.
    259 
    260 Note that we need to call the {{{initOrigin()}}} function of the '''{{{config.py}}}''' module, in order to initialize internal configuration (some matrix are defined, in order to avoid further maths).
    261 
    262 {{{
    263 #!python
    264 
    265 LEGS_NEUTRAL = {
    266     'RM': {
    267         'l': LEGS_GEOMETRY['RM']['coxa'] + LEGS_GEOMETRY['RM']['femur'],
    268         'z': LEGS_GEOMETRY['RM']['tibia']
    269     },
    270     'RF': {
    271         'l': LEGS_GEOMETRY['RF']['coxa'] + LEGS_GEOMETRY['RF']['femur'],
    272         'z': LEGS_GEOMETRY['RF']['tibia']
    273     },
    274     'LF': {
    275         'l': LEGS_GEOMETRY['LF']['coxa'] + LEGS_GEOMETRY['LF']['femur'],
    276         'z': LEGS_GEOMETRY['LF']['tibia']
    277     },
    278     'LM': {
    279         'l': LEGS_GEOMETRY['LM']['coxa'] + LEGS_GEOMETRY['LM']['femur'],
    280         'z': LEGS_GEOMETRY['LM']['tibia']
    281     },
    282     'LR': {
    283         'l': LEGS_GEOMETRY['LR']['coxa'] + LEGS_GEOMETRY['LR']['femur'],
    284         'z': LEGS_GEOMETRY['LR']['tibia']
    285     },
    286     'RR': {
    287         'l': LEGS_GEOMETRY['RR']['coxa'] + LEGS_GEOMETRY['RR']['femur'],
    288         'z': LEGS_GEOMETRY['RR']['tibia']
    289     }
    290 }
    291 config.initNeutral(LEGS_INDEX, LEGS_NEUTRAL)
    292 }}}
    293 
    294 {{{LEGS_NEUTRAL}}} dict contains the
    295 
    296 Here too, we need to call the {{{initNeutral()}}} function of the '''{{{config.py}}}''' module, in order to initialize internal configuration.
    297 
    298 {{{
    299 #!python
    300 
    301 LEGS_ACTUATORS_MAPPING = {
    302     'RF': {'coxa':  0, 'femur':  1, 'tibia':  2},
    303     'RM': {'coxa':  4, 'femur':  5, 'tibia':  6},
    304     'RR': {'coxa':  8, 'femur':  9, 'tibia': 10},
    305     'LR': {'coxa': 15, 'femur': 14, 'tibia': 13},
    306     'LM': {'coxa': 19, 'femur': 18, 'tibia': 17},
    307     'LF': {'coxa': 23, 'femur': 22, 'tibia': 21}
    308 }
    309 }}}
    310 
    311 {{{LEGS_ACTUATORS_MAPPING}}} dict contains a table to map all joints to actuators nums.
    312 
    313 {{{
    314 #!python
    315 
    316 GAIT_LEGS_GROUPS = (('RR',), ('LM',), ('RF',), ('LR',), ('RM',), ('LF',))
     270    'tripod': {'length': 40., 'angle': 10., 'height': 40., 'minLength': 4., 'minAngle': 2., 'speedMin':  50., 'speedMax': 200.},
     271    'tetrapod': {'length': 40., 'angle': 10., 'height': 30., 'minLength': 4., 'minAngle': 2., 'speedMin':  50., 'speedMax': 200.},
     272    'riple': {'length': 40., 'angle': 10., 'height': 30., 'minLength': 4., 'minAngle': 2., 'speedMin':  50., 'speedMax': 300.},
     273    'metachronal': {'length': 40., 'angle': 10., 'height': 30., 'minLength': 4., 'minAngle': 2., 'speedMin':  50., 'speedMax': 200.},
     274    'wave': {'length': 40., 'angle': 10., 'height': 30., 'minLength': 4., 'minAngle': 2., 'speedMin':  50., 'speedMax': 200.}
     275}
    317276}}}
    318277
    319278{{{GAIT_LEGS_GROUPS}}} contains the legs grouped together and controlled at the same time during the gait usage.
    320279
    321 In this example, the '''riple''' gait is made of 6 groups of 1 legs.
    322 
    323280Note that we need to define sequences, so don't forget the comma to define a tuple with a unique element.
    324 
    325 {{{
    326 #!python
    327 
    328 GAIT_PARAMS = {
    329     'length': 40.,
    330     'angle': 10.,
    331     'height': 30.,
    332     'minLength': 4.,
    333     'minAngle': 2.,
    334     'speedMin':  50.,
    335     'speedMax': 300.
    336 }
    337 }}}
    338281
    339282{{{GAIT_PARAMS}}} contains some additional gaits params:
     
    350293
    351294''Files for this tutorial are available in [source:/py4bot/examples/tutorial_2/]''
    352 
    353 In this tutorial, we are just going to add a remote control in order to drive our previous simulated robot.
    354 
    355 Let's use an old gamepad, a [http://www.thrustmaster.com/en_IN/products/firestorm-dual-analog-3 Thrustmaster Firestorm Dual Analog 3]. As I own such gamepad, I already added support in Py4bot.
    356 
    357 Add the following code between the {{{Hexapod}}} class, and the {{{main()}}} function:
    358 
    359 {{{
    360 #!python
    361 
    362 class Gamepad(RemoteControl):
    363     def _createFrontend(self):
    364         return FrontendFactory().create("thrustmaster", path="/dev/input/by-id/usb-Mega_World_Thrustmaster_dual_analog_3.2-event-joystick")
    365 
    366     def _buildComponents(self):
    367         self._addComponent(Joystick, command=GaitSequencer().walk, keys=("analog_02", "analog_03", "analog_00"), mapper=MapperWalk())
    368         self._addComponent(Button, command=GaitSequencer().walkStop, key="button_000")
    369         self._addComponent(Button, command=GaitSequencer().walkStep, key="button_003")
    370         self._addComponent(Button, command=self.robot.incBodyPosition, key="button_004", mapper=MapperSetValue(dz=+5))
    371         self._addComponent(Button, command=self.robot.incBodyPosition, key="button_005", mapper=MapperSetValue(dz=-5))
    372 }}}
    373295
    374296Then, modify the {{{main()}}} function as following:
     
    534456
    535457
    536 We renamed {{{LEGS_ACTUATOR_MAPPING}}} to {{{LEGS_SERVOS_MAPPING}}}, and added the {{{SERVOS_CALIBRATION}}} dict, which contains:
    537 
    538 * {{{offset}}} is the angle between the servo reference and the real angle. See the [[FAQ#Howisgeometrydefined|FAQ]] for the real angles definition;
    539 * {{{pulse90}}} is the pulse value for the neutral position of the servo, which is usually 90°;
    540 * {{{ratio}}} is the pulse width per degree.
    541 
    542 Offsets may vary, depending how you mount the servos. Same, ratio sign may have to be inverted on one side, if you have a symetrical design; all angle are always computed using trigonometric direction (CCW).
    543 
    544 See the [[UserGuideGit#Servoscalibration|user guide]] how to fine tune servos calibration.
     458We renamed {{{LEGS_ACTUATOR_MAPPING}}} to {{{LEGS_SERVOS_MAPPING}}}, and added the {{{SERVOS_CALIBRATION}}} dict, which contains
    545459
    546460{{{GAIT_LEGS_GROUPS}}} and {{{GAIT_PARAMS}}} are now dicts, to handle all our gaits params.