Joint Torque Springs - Code Walkthrough

From sdk-wiki
Jump to: navigation, search

Introduction

The Joint Torque Springs Example demonstrates the usage of torque controller to apply torque commands to Baxter's joints. In the main() function, the dynamic reconfigure from rqt is initialized and passed to an object of the JointSprings class along with the side of the arm on which the user decides to run the example, as parameters. On initialization, the object creates an instance of the limb class for the particular side that is passed, and captures the default spring and damping constants from the dynamic reconfigure rqt plugin. The main() function calls the method, move_to_neutral() to send the limbs to a neutral position. Then, the attach_spring() method captures the initial joint angles of the limb and calls the update_forces() method which calculates the necessary torques based on Hooke's law and publishes them as joint commands.
Interfaces -

  • Limb.set_joint_torques(<double>)
  • Limb.move_to_neutral()
  • Limb.joint_angles
  • Limb.joint_velocities
  • Limb.set_command_timeout(<double>)
  • Limb.exit_control_mode()

Code Walkthrough

Now, let's break down the code.

33 import argparse
34 
35 import rospy
36 
37 from dynamic_reconfigure.server import (
38     Server,
39 )
40 from std_msgs.msg import (
41     Empty,
42 )
43 
44 import baxter_interface
45 
46 from baxter_examples.cfg import (
47     JointSpringsExampleConfig,
48 )
49 from baxter_interface import CHECK_VERSION

This imports the baxter interface for accessing the limb class. The JointSpringsExampleConfig holds the default spring and damping constants for Baxter's limbs. The CHECK_VERSION is imported to check if the software running on the robot would be compatible with this local version. It is not necessary to check the version in custom programs.

52 class JointSprings(object):
53     """
54     Virtual Joint Springs class for torque example.
55 
56     @param limb: limb on which to run joint springs example
57     @param reconfig_server: dynamic reconfigure server
58 
59     JointSprings class contains methods for the joint torque example allowing
60     moving the limb to a neutral location, entering torque mode, and attaching
61     virtual springs.
62     """
63     def __init__(self, limb, reconfig_server):
64         self._dyn = reconfig_server
65 
66         # control parameters
67         self._rate = 1000.0  # Hz
68         self._missed_cmds = 20.0  # Missed cycles before triggering timeout
69 
70         # create our limb instance
71         self._limb = baxter_interface.Limb(limb)

The JointSprings class object is initialized by passing the side of the limb under interest, an object of the reconfigure server that holds the default spring and damping constants for the limb. These default values can be modified at runtime through the dynamic reconfigure rqt plugin.

73         # initialize parameters
74         self._springs = dict()
75         self._damping = dict()
76         self._start_angles = dict()
77 
78         # create cuff disable publisher
79         cuff_ns = 'robot/limb/' + limb + '/suppress_cuff_interaction'
80         self._pub_cuff_disable = rospy.Publisher(cuff_ns, Empty, queue_size=1)
81 
82         # verify robot is enabled
83         print("Getting robot state... ")
84         self._rs = baxter_interface.RobotEnable(CHECK_VERSION)
85         self._init_state = self._rs.state().enabled
86         print("Enabling robot... ")
87         self._rs.enable()
88         print("Running. Ctrl-c to quit")

The zero gravity mode can be enabled by pressing the cuff and so it is important to disable the cuff. A publisher for the cuff interaction suppress topic is created. It is also important to note that the robot should be enabled before trying to manipulate its joints. So, the robot is checked if it is enabled and if not, it is enabled. The initial state of the variable is recorded in _init_state variable. This is to ensure that the robot is sent back to that state once the example program ends.

90     def _update_parameters(self):
91         for joint in self._limb.joint_names():
92             self._springs[joint] = self._dyn.config[joint[-2:] +
93                                                     '_spring_stiffness']
94             self._damping[joint] = self._dyn.config[joint[-2:] +
95                                                     '_damping_coefficient'

The _update_parameters() method captures the dynamically entered user's input for the spring and damping constants through the dynamic reconfigure rqt plugin.

 97      def _update_forces(self):
 98         """
 99         Calculates the current angular difference between the start position
100         and the current joint positions applying the joint torque spring forces
101         as defined on the dynamic reconfigure server.
102         """
103         # get latest spring constants
104         self._update_parameters()
105 
106         # disable cuff interaction
107         self._pub_cuff_disable.publish()

The cuff interaction is disabled as long as the example runs and each time the forces are updated, the _pub_cuff_disable publisher publishes an empty mesage.

109         # create our command dict
110         cmd = dict()
111         # record current angles/velocities
112         cur_pos = self._limb.joint_angles()
113         cur_vel = self._limb.joint_velocities()

The current position and the velocity is captured.

114         # calculate current forces
115         for joint in self._start_angles.keys():
116             # spring portion
117             cmd[joint] = self._springs[joint] * (self._start_angles[joint] -
118                                                    cur_pos[joint])
119             # damping portion
120             cmd[joint] -= self._damping[joint] * cur_vel[joint]
121         # command new joint torques
122         self._limb.set_joint_torques(cmd)

Hooke's law is applied to calculate the necessary torques to be applied on each joints. The updated spring and damping constants are used in the calculation. The set_joint_torques() method of the limb interface publishes the set of joint torques and their corresponding names with the mode as 3 to the topic robot/limb/side/joint_command. The mode indicates that the joint commands are to be passed to the torque controller.

124     def move_to_neutral(self):
125         """
126         Moves the limb to neutral location.
127         """
128         self._limb.move_to_neutral()

The move_to_neutral() method of the limb interface, moves all the joints to their neutral pose. This method employs the position controller to send the joints to a pre-defined neutral position.

130     def attach_springs(self):
131         """
132         Switches to joint torque mode and attached joint springs to current
133         joint positions.
134         """
135         # record initial joint angles
136         self._start_angles = self._limb.joint_angles()
137 
138         # set control rate
139         control_rate = rospy.Rate(self._rate)
140 
141         # for safety purposes, set the control rate command timeout.
142         # if the specified number of command cycles are missed, the robot
143         # will timeout and disable
144         self._limb.set_command_timeout((1.0 / self._rate) * self._missed_cmds)
145 
146         # loop at specified rate commanding new joint torques
147         while not rospy.is_shutdown():
148             if not self._rs.state().enabled:
149                 rospy.logerr("Joint torque example failed to meet "
150                              "specified control rate timeout.")
151                 break
152             self._update_forces()
153             control_rate.sleep()

The start angles recorded here are used by the _update_forces method to determine the dx, which is the displacement caused from the starting position of the joints. The _update_forces method is invoked at 1000Hz, which performs the spring motion as explained above.

155     def clean_shutdown(self):
156         """
157         Switches out of joint torque mode to exit cleanly
158         """
159         print("\nExiting example...")
160         self._limb.exit_control_mode()
161         if not self._init_state and self._rs.state().enabled:
162             print("Disabling robot...")
163             self._rs.disable()

The method exit_control_mode() of the limb interface switches to position controller mode from torque/velocity controller and saves the current joint angles as the current joint position. Finally, it checks if the robot was initially disabled and if so disables it.

166 def main():
167     """RSDK Joint Torque Example: Joint Springs
168 
169     Moves the specified limb to a neutral location and enters
170     torque control mode, attaching virtual springs (Hooke's Law)
171     to each joint maintaining the start position.
172 
173     Run this example on the specified limb and interact by
174     grabbing, pushing, and rotating each joint to feel the torques
175     applied that represent the virtual springs attached.
176     You can adjust the spring constant and damping coefficient
177     for each joint using dynamic_reconfigure.
178     """
179     arg_fmt = argparse.RawDescriptionHelpFormatter
180     parser = argparse.ArgumentParser(formatter_class=arg_fmt,
181                                      description=main.__doc__)
182     parser.add_argument(
183         '-l', '--limb', dest='limb', required=True, choices=['left', 'right'],
184         help='limb on which to attach joint springs'
185     )
186     args = parser.parse_args()

This section includes the parser that lists out the options for the user and captures the one that is inputted as a command line argument.

188     print("Initializing node... ")
189     rospy.init_node("rsdk_joint_torque_springs_%s" % (args.limb,))
190     dynamic_cfg_srv = Server(JointSpringsExampleConfig,
191                              lambda config, level: config)
192     js = JointSprings(args.limb, dynamic_cfg_srv)
193     # register shutdown callback
194     rospy.on_shutdown(js.clean_shutdown)
195     js.move_to_neutral()
196     js.attach_springs()
197 
198 
199 if __name__ == "__main__":
200     main()

The rospy node is initialized and then an instance of the dynamic reconfigure is created. As indicated above, this supplies the default and runtime, spring and dynamic constants. In custom programs this can be avoided by assigning spring and damping constants directly in the code.