Pendulum de Deux

This sample project includes a pair of double pendula configured as actuated two-link arms. This model demonstrates several key concepts: rigid body dynamics, collision modeling, PD control, and keyboard input.

This model is demonstrated in the pendulum-de-deux.wbt world.

../_images/pendulum-de-deux.png

Screenshot of Webots model of the pendulum-de-deux robots. Each robot is independent; one has scripted motion, the other is keyboard driven.

Sample Robot Control Code

The controller implements keyboard input to trigger stored poses. The two-link structure is regulated using a PD controller using the arm actuators in torque mode.

  1# dbl_pend.py
  2#
  3# Sample Webots controller file for driving a
  4# fully actuated double pendulum.  With both actuators
  5# set to zero torque the device will act like a chaotic
  6# double pendulum. Otherwise this code implements
  7# PD position control to drive towards a pose.
  8#
  9# No copyright, 2020-2022, Garth Zeglin.  This file is
 10# explicitly placed in the public domain.
 11
 12print("loading dbl_pend.py...")
 13
 14# Import the Webots simulator API.
 15from controller import Robot
 16from controller import Keyboard
 17
 18# Import the standard Python math library.
 19import math
 20
 21# Define the time step in milliseconds between
 22# controller updates.
 23EVENT_LOOP_DT = 20
 24
 25################################################################
 26
 27# Request a proxy object representing the robot to control.
 28robot = Robot()
 29name = robot.getName()
 30print(f"dbl_pend.py waking up for {name}...")
 31
 32# Enable computer keyboard input for user control.
 33keyboard = Keyboard()
 34keyboard.enable(EVENT_LOOP_DT)
 35
 36# Fetch handles for the joint sensors.
 37j1 = robot.getDevice('joint1')
 38j2 = robot.getDevice('joint2')
 39
 40# Specify the sampling rate for the joint sensors.
 41j1.enable(EVENT_LOOP_DT)
 42j2.enable(EVENT_LOOP_DT)
 43
 44# Fetch handle for the 'base' joint motor.  In this
 45# example the motor will be controlled as a torque
 46# motor, bypassing the Webots PID control.
 47motor1 = robot.getDevice('motor1')
 48motor1.setTorque(0.0)
 49
 50motor2 = robot.getDevice('motor2')
 51motor2.setTorque(0.0)
 52
 53# set properties in RotationalMotor nodes:
 54motor1.setAvailableTorque(4)
 55motor2.setAvailableTorque(1)
 56
 57# Specify proportional and derivative (damping) gains
 58# for the position controllers in the main event loop.
 59P_gain1 = 8.0     # units are N-m / radian
 60D_gain1 = 0.2     # units are N-m / (radian/sec)
 61
 62P_gain2 = 4.0     # units are N-m / radian
 63D_gain2 = 0.1     # units are N-m / (radian/sec)
 64
 65# Soft torque limits to avoid error messages.
 66max_tau1 = 4.0  # units are N-m
 67max_tau2 = 1.0  # units are N-m
 68
 69# Initial position targets.
 70q_d1 = None
 71q_d2 = None
 72
 73# The controller estimates velocities using finite
 74# differencing of the position sensors; these variables
 75# hold the previous state.
 76last_q1 = 0
 77last_q2 = 0
 78
 79################################################################
 80# Define reference poses mapped to keyboard keys.
 81
 82poses = {
 83    't' : (0.0,  0.0),          # straight up
 84    'r' : (0.5, -1.0),          # elbow to machine right (-Y)
 85    'l' : (-0.5, 1.0),          # elbow to machine left (+Y)
 86    'b' : (math.pi, -math.pi),  # elbow at bottom, link2 pointing up
 87    'w' : (2*math.pi, 0.0),     # straight up, but wound a full positive rotation
 88}
 89
 90# Set joint position targets to a reference pose indicated by a single character string.
 91def set_pose(key):
 92    global q_d1, q_d2
 93    # special case: 'p' will enter a passive zero-torque mode
 94    if key == 'p':
 95        q_d1 = None
 96        q_d2 = None
 97    else:
 98        # look for a reference pose
 99        pose = poses.get(key)
100        if pose is not None:
101            q_d1 = pose[0]
102            q_d2 = pose[1]
103
104################################################################
105# Metronomic choreography for automatic machine.
106def set_automatic_targets(t):
107    phase = math.fmod(t, 5.0) / 5.0
108    if phase < 0.5:
109        set_pose('l')
110    else:
111        set_pose('r')
112
113################################################################
114
115# Run an event loop until the simulation quits,
116# indicated by the step function returning -1.
117while robot.step(EVENT_LOOP_DT) != -1:
118
119    # Read simulator clock time.
120    t = robot.getTime()
121
122    # Read the new joint positions.
123    q1 = j1.getValue()
124    q2 = j2.getValue()
125
126    # Estimate current velocities in radians/sec using finite differences.
127    qd1 = (q1 - last_q1) / (0.001 * EVENT_LOOP_DT)
128    qd2 = (q2 - last_q2) / (0.001 * EVENT_LOOP_DT)
129    last_q1 = q1
130    last_q2 = q2
131
132    # Read any computer keyboard keypresses.  Returns -1 or an integer keycode while a key is held down.
133    if name == 'left':
134        key = keyboard.getKey()
135        if key != -1:
136            # convert the integer key number to a lowercase single-character string,
137            # then set the target pose indicated by the key
138            set_pose(chr(key).lower())
139
140    else:
141        # the other devices run an automatic timed sequence
142        set_automatic_targets(t)
143
144    # Calculate new motor torques, limit them, and apply them to the system.
145    if q_d1 is None:
146        tau1 = 0.0
147    else:
148        tau1 = P_gain1 * (q_d1 - q1) - D_gain1 * qd1
149        tau1 = min(max(tau1, -max_tau1), max_tau1)
150
151    if q_d2 is None:
152        tau2 = 0.0
153    else:
154        tau2 = P_gain2 * (q_d2 - q2) - D_gain2 * qd2
155        tau2 = min(max(tau2, -max_tau2), max_tau2)
156
157    motor1.setTorque(tau1)
158    motor2.setTorque(tau2)

World File

The robot is directly modeled in the scene tree so all parameters can be visible. In general it is more flexible to transfer models to .proto files so they can be instantiated more than once.

  1#VRML_SIM R2022a utf8
  2WorldInfo {
  3}
  4Viewpoint {
  5  orientation 0.1275445447336565 0.09988879918262115 -0.9867900571583291 2.2125166207147218
  6  position 3.627435367384922 5.521290034201931 2.6622807309719057
  7  followType "None"
  8}
  9Background {
 10  skyColor [
 11    0.1 0.1 0.1
 12  ]
 13}
 14DirectionalLight {
 15  direction 0.4 -0.5 -1
 16  intensity 3
 17  castShadows TRUE
 18}
 19RectangleArena {
 20  rotation 1.8366025517039032e-06 -1.836602551703903e-06 0.9999999999966269 1.5707963267982696
 21  floorSize 2 2
 22}
 23Robot {
 24  translation 0.5 0 0
 25  rotation 0 0 1 1.5708
 26  children [
 27    DEF baseObject Transform {
 28      translation 0 0 0.6
 29      children [
 30        Shape {
 31          appearance DEF baseColor PaintedWood {
 32            colorOverride 0.21529 0.543008 0.99855
 33          }
 34          geometry Box {
 35            size 0.3 0.5 1.2
 36          }
 37        }
 38      ]
 39    }
 40    HingeJoint {
 41      jointParameters HingeJointParameters {
 42        anchor 0 0 1
 43      }
 44      device [
 45        PositionSensor {
 46          name "joint1"
 47        }
 48        RotationalMotor {
 49          name "motor1"
 50          controlPID 1 0 0
 51          maxVelocity 3.14
 52          minPosition -10
 53          maxPosition 10
 54          maxTorque 20
 55        }
 56      ]
 57      endPoint Solid {
 58        translation 0.15 0 1
 59        rotation 1 0 0 0
 60        children [
 61          DEF link1Shape Transform {
 62            translation 0.08 0 0.1
 63            children [
 64              Shape {
 65                appearance DEF link1Color PaintedWood {
 66                  colorOverride 0.990494 0.516915 0.468254
 67                }
 68                geometry Box {
 69                  size 0.1 0.3 1
 70                }
 71              }
 72            ]
 73          }
 74          HingeJoint {
 75            jointParameters HingeJointParameters {
 76              anchor 0 0 0.5
 77              dampingConstant 0.1
 78            }
 79            device [
 80              PositionSensor {
 81                name "joint2"
 82              }
 83              RotationalMotor {
 84                name "motor2"
 85                controlPID 1 0 0
 86                maxVelocity 3.14
 87                minPosition -10
 88                maxPosition 10
 89                maxTorque 20
 90              }
 91            ]
 92            endPoint Solid {
 93              translation 0.13 0 0.5
 94              rotation 1 0 0 0
 95              children [
 96                DEF link2AShape Transform {
 97                  translation 0.07 0 0.2
 98                  children [
 99                    Shape {
100                      appearance PaintedWood {
101                        colorOverride 0.413001 1 0.33489
102                      }
103                      geometry Box {
104                        size 0.1 0.1 0.6
105                      }
106                    }
107                  ]
108                }
109              ]
110              name "link2"
111              boundingObject USE link2AShape
112              physics Physics {
113                density -1
114                mass 0.5
115              }
116            }
117          }
118          Transform {
119            translation 0.18 0 0.5
120            rotation 0 1 0 1.5708
121            children [
122              Shape {
123                appearance USE link1Color
124                geometry Cylinder {
125                  height 0.2
126                  radius 0.01
127                }
128              }
129            ]
130          }
131        ]
132        name "link1"
133        boundingObject USE link1Shape
134        physics Physics {
135          density -1
136          mass 0.5
137        }
138      }
139    }
140    Transform {
141      translation 0.2 0 1
142      rotation 0 1 0 1.5708
143      children [
144        Shape {
145          appearance USE baseColor
146          geometry Cylinder {
147            height 0.2
148            radius 0.01
149          }
150        }
151      ]
152    }
153  ]
154  name "left"
155  boundingObject USE baseObject
156  physics Physics {
157    density -1
158    mass 20
159  }
160  controller "dbl_pend"
161}
162Robot {
163  translation -0.5 0 0
164  rotation 0 0 1 1.5708
165  children [
166    DEF baseObject Transform {
167      translation 0 0 0.6
168      children [
169        Shape {
170          appearance DEF baseColor PaintedWood {
171            colorOverride 0.21529 0.543008 0.99855
172          }
173          geometry Box {
174            size 0.3 0.5 1.2
175          }
176        }
177      ]
178    }
179    HingeJoint {
180      jointParameters HingeJointParameters {
181        anchor 0 0 1
182      }
183      device [
184        PositionSensor {
185          name "joint1"
186        }
187        RotationalMotor {
188          name "motor1"
189          controlPID 1 0 0
190          maxVelocity 3.14
191          minPosition -10
192          maxPosition 10
193          maxTorque 20
194        }
195      ]
196      endPoint Solid {
197        translation 0.15 0 1
198        rotation 1 0 0 0
199        children [
200          DEF link1Shape Transform {
201            translation 0.08 0 0.1
202            children [
203              Shape {
204                appearance DEF link1Color PaintedWood {
205                  colorOverride 0.990494 0.516915 0.468254
206                }
207                geometry Box {
208                  size 0.1 0.3 1
209                }
210              }
211            ]
212          }
213          HingeJoint {
214            jointParameters HingeJointParameters {
215              anchor 0 0 0.5
216              dampingConstant 0.1
217            }
218            device [
219              PositionSensor {
220                name "joint2"
221              }
222              RotationalMotor {
223                name "motor2"
224                controlPID 1 0 0
225                maxVelocity 3.14
226                minPosition -10
227                maxPosition 10
228                maxTorque 20
229              }
230            ]
231            endPoint Solid {
232              translation 0.13 0 0.5
233              rotation 1 0 0 0
234              children [
235                DEF link2AShape Transform {
236                  translation 0.07 0 0.2
237                  children [
238                    Shape {
239                      appearance PaintedWood {
240                        colorOverride 0.413001 1 0.33489
241                      }
242                      geometry Box {
243                        size 0.1 0.1 0.6
244                      }
245                    }
246                  ]
247                }
248              ]
249              name "link2"
250              boundingObject USE link2AShape
251              physics Physics {
252                density -1
253                mass 0.5
254              }
255            }
256          }
257          Transform {
258            translation 0.18 0 0.5
259            rotation 0 1 0 1.5708
260            children [
261              Shape {
262                appearance USE link1Color
263                geometry Cylinder {
264                  height 0.2
265                  radius 0.01
266                }
267              }
268            ]
269          }
270        ]
271        name "link1"
272        boundingObject USE link1Shape
273        physics Physics {
274          density -1
275          mass 0.5
276        }
277      }
278    }
279    Transform {
280      translation 0.2 0 1
281      rotation 0 1 0 1.5708
282      children [
283        Shape {
284          appearance USE baseColor
285          geometry Cylinder {
286            height 0.2
287            radius 0.01
288          }
289        }
290      ]
291    }
292  ]
293  name "right"
294  boundingObject USE baseObject
295  physics Physics {
296    density -1
297    mass 20
298  }
299  controller "dbl_pend"
300}