dynamixel_serial_proxy.py
Go to the documentation of this file.
1 # -*- coding: utf-8 -*-
2 #
3 # Software License Agreement (BSD License)
4 #
5 # Copyright (c) 2010-2011, Antons Rebguns.
6 # All rights reserved.
7 #
8 # Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions
10 # are met:
11 #
12 # * Redistributions of source code must retain the above copyright
13 # notice, this list of conditions and the following disclaimer.
14 # * Redistributions in binary form must reproduce the above
15 # copyright notice, this list of conditions and the following
16 # disclaimer in the documentation and/or other materials provided
17 # with the distribution.
18 # * Neither the name of University of Arizona nor the names of its
19 # contributors may be used to endorse or promote products derived
20 # from this software without specific prior written permission.
21 #
22 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 # POSSIBILITY OF SUCH DAMAGE.
34 
35 
36 __author__ = 'Antons Rebguns'
37 __copyright__ = 'Copyright (c) 2010-2011 Antons Rebguns'
38 __credits__ = 'Cody Jorgensen, Cara Slutter'
39 
40 __license__ = 'BSD'
41 __maintainer__ = 'Antons Rebguns'
42 __email__ = 'anton@email.arizona.edu'
43 
44 
45 import math
46 import sys
47 import errno
48 from collections import deque
49 from threading import Thread
50 from collections import defaultdict
51 
52 import roslib
53 roslib.load_manifest('dynamixel_driver')
54 
55 import rospy
56 import dynamixel_io
58 
59 from diagnostic_msgs.msg import DiagnosticArray
60 from diagnostic_msgs.msg import DiagnosticStatus
61 from diagnostic_msgs.msg import KeyValue
62 
63 from dynamixel_msgs.msg import MotorState
64 from dynamixel_msgs.msg import MotorStateList
65 
66 class SerialProxy():
67  def __init__(self,
68  port_name='/dev/ttyUSB0',
69  port_namespace='ttyUSB0',
70  baud_rate='1000000',
71  min_motor_id=1,
72  max_motor_id=25,
73  update_rate=5,
74  diagnostics_rate=1,
75  error_level_temp=75,
76  warn_level_temp=70,
77  readback_echo=False):
78  self.port_name = port_name
79  self.port_namespace = port_namespace
80  self.baud_rate = baud_rate
81  self.min_motor_id = min_motor_id
82  self.max_motor_id = max_motor_id
83  self.update_rate = update_rate
84  self.diagnostics_rate = diagnostics_rate
85  self.error_level_temp = error_level_temp
86  self.warn_level_temp = warn_level_temp
87  self.readback_echo = readback_echo
88 
89  self.actual_rate = update_rate
90  self.error_counts = {'non_fatal': 0, 'checksum': 0, 'dropped': 0}
91  self.current_state = MotorStateList()
93 
94  self.motor_states_pub = rospy.Publisher('motor_states/%s' % self.port_namespace, MotorStateList, queue_size=1)
95  self.diagnostics_pub = rospy.Publisher('/diagnostics', DiagnosticArray, queue_size=1)
96 
97  def connect(self):
98  try:
100  self.__find_motors()
102  rospy.logfatal(e.message)
103  sys.exit(1)
104 
105  self.running = True
106  if self.update_rate > 0: Thread(target=self.__update_motor_states).start()
107  if self.diagnostics_rate > 0: Thread(target=self.__publish_diagnostic_information).start()
108 
109  def disconnect(self):
110  self.running = False
111 
112  def __fill_motor_parameters(self, motor_id, model_number):
113  """
114  Stores some extra information about each motor on the parameter server.
115  Some of these paramters are used in joint controller implementation.
116  """
117  angles = self.dxl_io.get_angle_limits(motor_id)
118  voltage = self.dxl_io.get_voltage(motor_id)
119  voltages = self.dxl_io.get_voltage_limits(motor_id)
120 
121  rospy.set_param('dynamixel/%s/%d/model_number' %(self.port_namespace, motor_id), model_number)
122  rospy.set_param('dynamixel/%s/%d/model_name' %(self.port_namespace, motor_id), DXL_MODEL_TO_PARAMS[model_number]['name'])
123  rospy.set_param('dynamixel/%s/%d/min_angle' %(self.port_namespace, motor_id), angles['min'])
124  rospy.set_param('dynamixel/%s/%d/max_angle' %(self.port_namespace, motor_id), angles['max'])
125 
126  torque_per_volt = DXL_MODEL_TO_PARAMS[model_number]['torque_per_volt']
127  rospy.set_param('dynamixel/%s/%d/torque_per_volt' %(self.port_namespace, motor_id), torque_per_volt)
128  rospy.set_param('dynamixel/%s/%d/max_torque' %(self.port_namespace, motor_id), torque_per_volt * voltage)
129 
130  velocity_per_volt = DXL_MODEL_TO_PARAMS[model_number]['velocity_per_volt']
131  rpm_per_tick = DXL_MODEL_TO_PARAMS[model_number]['rpm_per_tick']
132  rospy.set_param('dynamixel/%s/%d/velocity_per_volt' %(self.port_namespace, motor_id), velocity_per_volt)
133  rospy.set_param('dynamixel/%s/%d/max_velocity' %(self.port_namespace, motor_id), velocity_per_volt * voltage)
134  rospy.set_param('dynamixel/%s/%d/radians_second_per_encoder_tick' %(self.port_namespace, motor_id), rpm_per_tick * RPM_TO_RADSEC)
135 
136  encoder_resolution = DXL_MODEL_TO_PARAMS[model_number]['encoder_resolution']
137  range_degrees = DXL_MODEL_TO_PARAMS[model_number]['range_degrees']
138  range_radians = math.radians(range_degrees)
139  rospy.set_param('dynamixel/%s/%d/encoder_resolution' %(self.port_namespace, motor_id), encoder_resolution)
140  rospy.set_param('dynamixel/%s/%d/range_degrees' %(self.port_namespace, motor_id), range_degrees)
141  rospy.set_param('dynamixel/%s/%d/range_radians' %(self.port_namespace, motor_id), range_radians)
142  rospy.set_param('dynamixel/%s/%d/encoder_ticks_per_degree' %(self.port_namespace, motor_id), encoder_resolution / range_degrees)
143  rospy.set_param('dynamixel/%s/%d/encoder_ticks_per_radian' %(self.port_namespace, motor_id), encoder_resolution / range_radians)
144  rospy.set_param('dynamixel/%s/%d/degrees_per_encoder_tick' %(self.port_namespace, motor_id), range_degrees / encoder_resolution)
145  rospy.set_param('dynamixel/%s/%d/radians_per_encoder_tick' %(self.port_namespace, motor_id), range_radians / encoder_resolution)
146 
147  # keep some parameters around for diagnostics
148  self.motor_static_info[motor_id] = {}
149  self.motor_static_info[motor_id]['model'] = DXL_MODEL_TO_PARAMS[model_number]['name']
150  self.motor_static_info[motor_id]['firmware'] = self.dxl_io.get_firmware_version(motor_id)
151  self.motor_static_info[motor_id]['delay'] = self.dxl_io.get_return_delay_time(motor_id)
152  self.motor_static_info[motor_id]['min_angle'] = angles['min']
153  self.motor_static_info[motor_id]['max_angle'] = angles['max']
154  self.motor_static_info[motor_id]['min_voltage'] = voltages['min']
155  self.motor_static_info[motor_id]['max_voltage'] = voltages['max']
156 
157  def __find_motors(self):
158  rospy.loginfo('%s: Pinging motor IDs %d through %d...' % (self.port_namespace, self.min_motor_id, self.max_motor_id))
159  self.motors = []
161 
162  for motor_id in range(self.min_motor_id, self.max_motor_id + 1):
163  for trial in range(self.num_ping_retries):
164  try:
165  result = self.dxl_io.ping(motor_id)
166  except Exception as ex:
167  rospy.logerr('Exception thrown while pinging motor %d - %s' % (motor_id, ex))
168  continue
169 
170  if result:
171  self.motors.append(motor_id)
172  break
173 
174  if not self.motors:
175  rospy.logfatal('%s: No motors found.' % self.port_namespace)
176  sys.exit(1)
177 
178  counts = defaultdict(int)
179 
180  to_delete_if_error = []
181  for motor_id in self.motors:
182  for trial in range(self.num_ping_retries):
183  try:
184  model_number = self.dxl_io.get_model_number(motor_id)
185  self.__fill_motor_parameters(motor_id, model_number)
186  except Exception as ex:
187  rospy.logerr('Exception thrown while getting attributes for motor %d - %s' % (motor_id, ex))
188  if trial == self.num_ping_retries - 1: to_delete_if_error.append(motor_id)
189  continue
190 
191  counts[model_number] += 1
192  break
193 
194  for motor_id in to_delete_if_error:
195  self.motors.remove(motor_id)
196 
197  rospy.set_param('dynamixel/%s/connected_ids' % self.port_namespace, self.motors)
198 
199  status_str = '%s: Found %d motors - ' % (self.port_namespace, len(self.motors))
200  for model_number,count in counts.items():
201  if count:
202  model_name = DXL_MODEL_TO_PARAMS[model_number]['name']
203  status_str += '%d %s [' % (count, model_name)
204 
205  for motor_id in self.motors:
206  if self.motor_static_info[motor_id]['model'] == model_name:
207  status_str += '%d, ' % motor_id
208 
209  status_str = status_str[:-2] + '], '
210 
211  rospy.loginfo('%s, initialization complete.' % status_str[:-2])
212 
214  num_events = 50
215  rates = deque([float(self.update_rate)]*num_events, maxlen=num_events)
216  last_time = rospy.Time.now()
217 
218  rate = rospy.Rate(self.update_rate)
219  while not rospy.is_shutdown() and self.running:
220  # get current state of all motors and publish to motor_states topic
221  motor_states = []
222  for motor_id in self.motors:
223  try:
224  state = self.dxl_io.get_feedback(motor_id)
225  if state:
226  motor_states.append(MotorState(**state))
227  if dynamixel_io.exception: raise dynamixel_io.exception
229  rospy.logerr(fece)
231  self.error_counts['non_fatal'] += 1
232  rospy.logdebug(nfece)
233  except dynamixel_io.ChecksumError, cse:
234  self.error_counts['checksum'] += 1
235  rospy.logdebug(cse)
237  self.error_counts['dropped'] += 1
238  rospy.logdebug(dpe.message)
239  except OSError, ose:
240  if ose.errno != errno.EAGAIN:
241  rospy.logfatal(errno.errorcode[ose.errno])
242  rospy.signal_shutdown(errno.errorcode[ose.errno])
243 
244  if motor_states:
245  msl = MotorStateList()
246  msl.motor_states = motor_states
247  self.motor_states_pub.publish(msl)
248 
249  self.current_state = msl
250 
251  # calculate actual update rate
252  current_time = rospy.Time.now()
253  rates.append(1.0 / (current_time - last_time).to_sec())
254  self.actual_rate = round(sum(rates)/num_events, 2)
255  last_time = current_time
256 
257  rate.sleep()
258 
260  diag_msg = DiagnosticArray()
261 
262  rate = rospy.Rate(self.diagnostics_rate)
263  while not rospy.is_shutdown() and self.running:
264  diag_msg.status = []
265  diag_msg.header.stamp = rospy.Time.now()
266 
267  status = DiagnosticStatus()
268 
269  status.name = 'Dynamixel Serial Bus (%s)' % self.port_namespace
270  status.hardware_id = 'Dynamixel Serial Bus on port %s' % self.port_name
271  status.values.append(KeyValue('Baud Rate', str(self.baud_rate)))
272  status.values.append(KeyValue('Min Motor ID', str(self.min_motor_id)))
273  status.values.append(KeyValue('Max Motor ID', str(self.max_motor_id)))
274  status.values.append(KeyValue('Desired Update Rate', str(self.update_rate)))
275  status.values.append(KeyValue('Actual Update Rate', str(self.actual_rate)))
276  status.values.append(KeyValue('# Non Fatal Errors', str(self.error_counts['non_fatal'])))
277  status.values.append(KeyValue('# Checksum Errors', str(self.error_counts['checksum'])))
278  status.values.append(KeyValue('# Dropped Packet Errors', str(self.error_counts['dropped'])))
279  status.level = DiagnosticStatus.OK
280  status.message = 'OK'
281 
282  if self.actual_rate - self.update_rate < -5:
283  status.level = DiagnosticStatus.WARN
284  status.message = 'Actual update rate is lower than desired'
285 
286  diag_msg.status.append(status)
287 
288  for motor_state in self.current_state.motor_states:
289  mid = motor_state.id
290 
291  status = DiagnosticStatus()
292 
293  status.name = 'Robotis Dynamixel Motor %d on port %s' % (mid, self.port_namespace)
294  status.hardware_id = 'DXL-%d@%s' % (motor_state.id, self.port_namespace)
295  status.values.append(KeyValue('Model Name', str(self.motor_static_info[mid]['model'])))
296  status.values.append(KeyValue('Firmware Version', str(self.motor_static_info[mid]['firmware'])))
297  status.values.append(KeyValue('Return Delay Time', str(self.motor_static_info[mid]['delay'])))
298  status.values.append(KeyValue('Minimum Voltage', str(self.motor_static_info[mid]['min_voltage'])))
299  status.values.append(KeyValue('Maximum Voltage', str(self.motor_static_info[mid]['max_voltage'])))
300  status.values.append(KeyValue('Minimum Position (CW)', str(self.motor_static_info[mid]['min_angle'])))
301  status.values.append(KeyValue('Maximum Position (CCW)', str(self.motor_static_info[mid]['max_angle'])))
302 
303  status.values.append(KeyValue('Goal', str(motor_state.goal)))
304  status.values.append(KeyValue('Position', str(motor_state.position)))
305  status.values.append(KeyValue('Error', str(motor_state.error)))
306  status.values.append(KeyValue('Velocity', str(motor_state.speed)))
307  status.values.append(KeyValue('Load', str(motor_state.load)))
308  status.values.append(KeyValue('Voltage', str(motor_state.voltage)))
309  status.values.append(KeyValue('Temperature', str(motor_state.temperature)))
310  status.values.append(KeyValue('Moving', str(motor_state.moving)))
311 
312  if motor_state.temperature >= self.error_level_temp:
313  status.level = DiagnosticStatus.ERROR
314  status.message = 'OVERHEATING'
315  elif motor_state.temperature >= self.warn_level_temp:
316  status.level = DiagnosticStatus.WARN
317  status.message = 'VERY HOT'
318  else:
319  status.level = DiagnosticStatus.OK
320  status.message = 'OK'
321 
322  diag_msg.status.append(status)
323 
324  self.diagnostics_pub.publish(diag_msg)
325  rate.sleep()
326 
327 if __name__ == '__main__':
328  try:
329  serial_proxy = SerialProxy()
330  serial_proxy.connect()
331  rospy.spin()
332  serial_proxy.disconnect()
333  except rospy.ROSInterruptException: pass
334 
def __init__(self, port_name='/dev/ttyUSB0', port_namespace='ttyUSB0', baud_rate='1000000', min_motor_id=1, max_motor_id=25, update_rate=5, diagnostics_rate=1, error_level_temp=75, warn_level_temp=70, readback_echo=False)


dynamixel_driver
Author(s): Antons Rebguns, Cody Jorgensen
autogenerated on Tue May 12 2020 03:10:57