Clock Robot Model

The clock robot model simulates a large one-meter diameter analog clock with hour and minute hands. This model is intended as a demonstration testbed for single-axis control.

The hands are independently actuated with motors and have position sensors. The two hands have different inertia properties and so respond differently to the same control inputs.

The object geometry was modeled in Fusion 360 and exported as three STL files. The ‘bezel’ base does not participate in the dynamics, and none of the objects participate in collision. The mass properties were directly specified as computed by the CAD system.

This model is demonstrated in the clock-demo.wbt world.

../_images/clock-sim.jpg

Screenshot of Webots model of clock with independently driven hour and minute hands. The robot is shown in the 5:08 position. The neutral pose is at 12:00, with positive joint angles increasing clockwise.

clock.proto

The clock model has been encapsulated in a .proto file for easy reuse.

  1#VRML_SIM R2022a utf8
  2# Large clock with hour and minute hands for course exercises.  The bezel, hour
  3# hand, and minute hand were drawn in CAD in the reference frame of the robot.
  4# The object shapes were specified as external STL files, and object physics parameters
  5# calculated by the CAD system.  No bounding objects are specified.
  6# The 'bezel' base does not have a physics node.
  7# documentation url: https://courses.ideate.cmu.edu/16-375
  8# license: No copyright, 2020-2022 Garth Zeglin.  This file is explicitly placed in the public domain.
  9PROTO clock [
 10  field SFVec3f    translation  0 0 0
 11  field SFRotation rotation     0 1 0 0
 12  field SFString   controller   "clock"
 13  field SFString   name         "Clock"
 14]
 15{
 16  Robot {
 17    # connect properties to user-visible data fields
 18    translation IS translation
 19    rotation IS rotation
 20    controller IS controller
 21    name IS name
 22
 23    children [
 24      DEF minuteJoint HingeJoint {
 25        jointParameters HingeJointParameters {
 26          axis 0 0 -1
 27	  dampingConstant 0.0
 28        }
 29        device [
 30          PositionSensor {
 31            name "minuteSensor"
 32          }
 33          RotationalMotor {
 34            name "minuteMotor"
 35            controlPID 4 0 0
 36            maxTorque 0.5
 37          }
 38        ]
 39
 40        endPoint DEF minuteSolid Solid {
 41          rotation 0 0 -1 0
 42          children [
 43            Transform {
 44              children [
 45                Shape {
 46                  appearance PBRAppearance {
 47                    baseColor 0.898787 0 0.0763561
 48                    roughness 1
 49                    metalness 0
 50                    name "DefaultMaterial"
 51                  }
 52		  geometry Mesh {
 53 		    url	[ "../stl/minute.stl" ]
 54                  }
 55                }
 56              ]
 57            }
 58          ]
 59          name "minuteSolid"
 60          physics Physics {
 61            density -1
 62            mass 0.789
 63            centerOfMass [
 64              0 -0.025 0.11
 65            ]
 66            inertiaMatrix [
 67              0.039 0.001 0.041
 68              0 0 0
 69            ]
 70          }
 71        }
 72      }
 73      DEF hourJoint HingeJoint {
 74        jointParameters HingeJointParameters {
 75          axis 0 0 -1
 76	  dampingConstant 0.0
 77        }
 78        device [
 79          PositionSensor {
 80            name "hourSensor"
 81          }
 82          RotationalMotor {
 83            name "hourMotor"
 84            controlPID 4 0 0
 85            maxTorque 0.5
 86          }
 87        ]
 88        endPoint DEF hourSolid Solid {
 89          rotation 0 0 -1 0
 90          children [
 91            Transform {
 92              children [
 93                Shape {
 94                  appearance PBRAppearance {
 95                    baseColor 0.141939 1 0.0408179
 96                    roughness 1
 97                    metalness 0
 98                    name "DefaultMaterial"
 99                  }
100		  geometry Mesh {
101 		    url	[ "../stl/hour.stl" ]
102                  }
103                }
104              ]
105            }
106          ]
107          physics Physics {
108            density -1
109            mass 0.584
110            centerOfMass [
111              0 -0.019 0.14
112            ]
113            inertiaMatrix [
114              0.019 0.0005081 0.02
115              0 0 0
116            ]
117          }
118        }
119      }
120      DEF bezelSolid Solid {
121        children [
122          Shape {
123            appearance PBRAppearance {
124              baseColor 0.517 0.338 0.279
125              roughness 0.3
126              metalness 0
127              name "DefaultMaterial"
128            }
129	    geometry Mesh {
130	      url [ "../stl/bezel.stl" ]
131            }
132          }
133        ]
134        name "bezel"
135      }
136    ]
137  }
138}

Sample Clock Control Code

  1# clock.py
  2
  3# Sample Webots controller file for driving the clock
  4# model using torque mode and an implementation of a
  5# linear PD controller on the two independent driven
  6# joints.
  7
  8# No copyright, 2020, Garth Zeglin.  This file is
  9# explicitly placed in the public domain.
 10
 11print("loading clock.py...")
 12
 13# Import the Webots simulator API.
 14from controller import Robot
 15import math, time
 16
 17# Define the controller update time step in milliseconds.
 18EVENT_LOOP_DT = 20
 19
 20# Specify proportional and derivative (damping) gains.
 21P_gain = 0.10    # units are N-m / radian
 22D_gain = 0.01    # units are N-m / (radian/sec)
 23
 24# Specify a soft torque limit.  The underlying actuator
 25# model is limited to a modest 0.1 N-m; this limits the
 26# request to avoid error messages.
 27max_tau = 0.1
 28
 29# Request a proxy object representing the robot to control.
 30robot = Robot()
 31robot_name = robot.getName()
 32print("%s: controller connected." % (robot_name))
 33
 34# Fetch handles for the minute and hour hand motors.
 35minute_motor = robot.getDevice('minuteMotor')
 36hour_motor   = robot.getDevice('hourMotor')
 37
 38# Enter torque motor on both motors; this bypasses
 39# the Webots low-level PID controllers.
 40minute_motor.setTorque(0)
 41hour_motor.setTorque(0)
 42
 43# Fetch handles for the minute and hour hand sensors.
 44minute_sensor = robot.getDevice('minuteSensor')
 45hour_sensor   = robot.getDevice('hourSensor')
 46
 47# Specify the sampling rate for the joint sensors.
 48minute_sensor.enable(EVENT_LOOP_DT)
 49hour_sensor.enable(EVENT_LOOP_DT)
 50
 51# The controller estimates velocities using finite
 52# differencing of the position sensors; these variables
 53# hold the previous state.
 54last_minute_angle = 0
 55last_hour_angle   = 0
 56
 57# Fetch the current wall clock time.
 58now = time.localtime(time.time())
 59
 60# Convert time to initial hand motor angles in radians.
 61initial_minute_angle = (now.tm_min  % 60) * (math.pi/30)
 62initial_hour_angle   = (now.tm_hour % 12) * (math.pi/6)
 63
 64# Generate some debugging output
 65print("%s: initial joint angles: minute: %f, hour: %f" % (robot_name, initial_minute_angle, initial_hour_angle))
 66debug_timer = 2.0
 67
 68# Run loop to execute a periodic script until the simulation quits.
 69# If the controller returns -1, the simulator is quitting.
 70while robot.step(EVENT_LOOP_DT) != -1:
 71
 72    # Read simulator clock time and calculate new position targets based on elapsed time.
 73    sim_t = robot.getTime()
 74    target_minute = initial_minute_angle + sim_t * (2*math.pi / 3600)  # one rev  per  3600 seconds (hour)
 75    target_hour   = initial_hour_angle   + sim_t * (4*math.pi / 86400) # two revs per 86400 seconds (day)
 76
 77    # Read the current sensor positions.
 78    minute_angle = minute_sensor.getValue()
 79    hour_angle   = hour_sensor.getValue()
 80
 81    # Estimate current velocities in radians/sec using finite differences.
 82    d_minute_dt = (minute_angle - last_minute_angle) / (0.001 * EVENT_LOOP_DT)
 83    d_hour_dt   = (hour_angle   - last_hour_angle)   / (0.001 * EVENT_LOOP_DT)
 84    last_minute_angle = minute_angle
 85    last_hour_angle   = hour_angle
 86
 87    # Calculate new motor torques, limit them, and apply them to the system.
 88    tau_minute = P_gain * (target_minute - minute_angle) - D_gain * d_minute_dt
 89    tau_hour   = P_gain * (target_hour   - hour_angle)   - D_gain * d_hour_dt
 90
 91    tau_minute = min(max(tau_minute, -max_tau), max_tau)
 92    tau_hour   = min(max(tau_hour,   -max_tau), max_tau)
 93
 94    minute_motor.setTorque(tau_minute)
 95    hour_motor.setTorque(tau_hour)
 96
 97    # Occasionally issue a message for debugging.
 98    debug_timer -= 0.001*EVENT_LOOP_DT
 99    if debug_timer < 0.0:
100        debug_timer += 2.0
101        print("%s: motor torques: minute: %f, hour: %f" % (robot_name, tau_minute, tau_hour))

Hour Hand Parameters

Each object was modeled in the neutral pose position, so the center of mass respect to the center of the bezel base. Physics parameters as computed by Fusion 360:

Volume

8.678E-04

m^3

Density

673.00

kg/m^3

Mass

0.584

kg

Physical Material

Oak, Red

Center of Mass: 0.00 m, -0.019 m, 0.14 m

Moment of Inertia at Center of Mass (kg m^2)

Ixx = 0.019

Ixy = 0.00

Ixz = 0.00

Iyx = 0.00

Iyy = 5.081E-04

Iyz = 0.00

Izx = 0.00

Izy = 0.00

Izz = 0.02

Minute Hand Parameters

Each object was modeled in the neutral pose position, so the center of mass vector is with respect to the center of the bezel base. Physics parameters as computed by Fusion 360:

Volume

0.001

m^3

Density

673.00

kg / m^3

Mass

0.789

kg

Physical Material

Oak, Red

Center of Mass: 0.00 m, -0.025 m, 0.11 m

Moment of Inertia at Center of Mass (kg m^2)

Ixx = 0.039

Ixy = 0.00

Ixz = 0.00

Iyx = 0.00

Iyy = 0.001

Iyz = 0.00

Izx = 0.00

Izy = 0.00

Izz = 0.041