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.
Contents
clock.proto¶
The clock model has been encapsulated in a .proto file for easy reuse.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | #VRML_SIM R2021b utf8
# Large clock with hour and minute hands for course exercises. The bezel, hour
# hand, and minute hand were drawn in CAD in the reference frame of the robot.
# The object shapes were specified as external STL files, and object physics parameters
# calculated by the CAD system. No bounding objects are specified.
# The 'bezel' base does not have a physics node.
# documentation url: https://courses.ideate.cmu.edu/16-375
# license: No copyright, 2020-2021 Garth Zeglin. This file is explicitly placed in the public domain.
PROTO clock [
field SFVec3f translation 0 0 0
field SFRotation rotation 0 1 0 0
field SFString controller "clock"
field SFString name "Clock"
]
{
Robot {
# connect properties to user-visible data fields
translation IS translation
rotation IS rotation
controller IS controller
name IS name
children [
DEF minuteJoint HingeJoint {
jointParameters HingeJointParameters {
axis 0 0 -1
dampingConstant 0.0
}
device [
PositionSensor {
name "minuteSensor"
}
RotationalMotor {
name "minuteMotor"
controlPID 4 0 0
maxTorque 0.5
}
]
endPoint DEF minuteSolid Solid {
rotation 0 0 -1 0
children [
Transform {
children [
Shape {
appearance PBRAppearance {
baseColor 0.898787 0 0.0763561
roughness 1
metalness 0
name "DefaultMaterial"
}
geometry Mesh {
url [ "../stl/minute.stl" ]
}
}
]
}
]
name "minuteSolid"
physics Physics {
density -1
mass 0.789
centerOfMass [
0 -0.025 0.11
]
inertiaMatrix [
0.039 0.001 0.041
0 0 0
]
}
}
}
DEF hourJoint HingeJoint {
jointParameters HingeJointParameters {
axis 0 0 -1
dampingConstant 0.0
}
device [
PositionSensor {
name "hourSensor"
}
RotationalMotor {
name "hourMotor"
controlPID 4 0 0
maxTorque 0.5
}
]
endPoint DEF hourSolid Solid {
rotation 0 0 -1 0
children [
Transform {
children [
Shape {
appearance PBRAppearance {
baseColor 0.141939 1 0.0408179
roughness 1
metalness 0
name "DefaultMaterial"
}
geometry Mesh {
url [ "../stl/hour.stl" ]
}
}
]
}
]
physics Physics {
density -1
mass 0.584
centerOfMass [
0 -0.019 0.14
]
inertiaMatrix [
0.019 0.0005081 0.02
0 0 0
]
}
}
}
DEF bezelSolid Solid {
children [
Shape {
appearance PBRAppearance {
baseColor 0.517 0.338 0.279
roughness 0.3
metalness 0
name "DefaultMaterial"
}
geometry Mesh {
url [ "../stl/bezel.stl" ]
}
}
]
name "bezel"
}
]
}
}
|
Sample Clock Control Code¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | # clock.py
# Sample Webots controller file for driving the clock
# model using torque mode and an implementation of a
# linear PD controller on the two independent driven
# joints.
# No copyright, 2020, Garth Zeglin. This file is
# explicitly placed in the public domain.
print("loading clock.py...")
# Import the Webots simulator API.
from controller import Robot
import math, time
# Define the controller update time step in milliseconds.
EVENT_LOOP_DT = 20
# Specify proportional and derivative (damping) gains.
P_gain = 0.10 # units are N-m / radian
D_gain = 0.01 # units are N-m / (radian/sec)
# Specify a soft torque limit. The underlying actuator
# model is limited to a modest 0.1 N-m; this limits the
# request to avoid error messages.
max_tau = 0.1
# Request a proxy object representing the robot to control.
robot = Robot()
robot_name = robot.getName()
print("%s: controller connected." % (robot_name))
# Fetch handles for the minute and hour hand motors.
minute_motor = robot.getDevice('minuteMotor')
hour_motor = robot.getDevice('hourMotor')
# Enter torque motor on both motors; this bypasses
# the Webots low-level PID controllers.
minute_motor.setTorque(0)
hour_motor.setTorque(0)
# Fetch handles for the minute and hour hand sensors.
minute_sensor = robot.getDevice('minuteSensor')
hour_sensor = robot.getDevice('hourSensor')
# Specify the sampling rate for the joint sensors.
minute_sensor.enable(EVENT_LOOP_DT)
hour_sensor.enable(EVENT_LOOP_DT)
# The controller estimates velocities using finite
# differencing of the position sensors; these variables
# hold the previous state.
last_minute_angle = 0
last_hour_angle = 0
# Fetch the current wall clock time.
now = time.localtime(time.time())
# Convert time to initial hand motor angles in radians.
initial_minute_angle = (now.tm_min % 60) * (math.pi/30)
initial_hour_angle = (now.tm_hour % 12) * (math.pi/6)
# Generate some debugging output
print("%s: initial joint angles: minute: %f, hour: %f" % (robot_name, initial_minute_angle, initial_hour_angle))
debug_timer = 2.0
# Run loop to execute a periodic script until the simulation quits.
# If the controller returns -1, the simulator is quitting.
while robot.step(EVENT_LOOP_DT) != -1:
# Read simulator clock time and calculate new position targets based on elapsed time.
sim_t = robot.getTime()
target_minute = initial_minute_angle + sim_t * (2*math.pi / 3600) # one rev per 3600 seconds (hour)
target_hour = initial_hour_angle + sim_t * (4*math.pi / 86400) # two revs per 86400 seconds (day)
# Read the current sensor positions.
minute_angle = minute_sensor.getValue()
hour_angle = hour_sensor.getValue()
# Estimate current velocities in radians/sec using finite differences.
d_minute_dt = (minute_angle - last_minute_angle) / (0.001 * EVENT_LOOP_DT)
d_hour_dt = (hour_angle - last_hour_angle) / (0.001 * EVENT_LOOP_DT)
last_minute_angle = minute_angle
last_hour_angle = hour_angle
# Calculate new motor torques, limit them, and apply them to the system.
tau_minute = P_gain * (target_minute - minute_angle) - D_gain * d_minute_dt
tau_hour = P_gain * (target_hour - hour_angle) - D_gain * d_hour_dt
tau_minute = min(max(tau_minute, -max_tau), max_tau)
tau_hour = min(max(tau_hour, -max_tau), max_tau)
minute_motor.setTorque(tau_minute)
hour_motor.setTorque(tau_hour)
# Occasionally issue a message for debugging.
debug_timer -= 0.001*EVENT_LOOP_DT
if debug_timer < 0.0:
debug_timer += 2.0
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 |