svenzva_driver.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # Software License Agreement (BSD License)
3 #
4 # Copyright (c) 2017 Svenzva Robotics
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions
9 # are met:
10 #
11 # * Redistributions of source code must retain the above copyright
12 # notice, this list of conditions and the following disclaimer.
13 # * Redistributions in binary form must reproduce the above
14 # copyright notice, this list of conditions and the following
15 # disclaimer in the documentation and/or other materials provided
16 # with the distribution.
17 # * Neither the name of Svenzva Robotics LLC nor the names of its
18 # contributors may be used to endorse or promote products derived
19 # from this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 # POSSIBILITY OF SUCH DAMAGE.
33 
34 """
35 Author: Maxwell Svetlik. Code snippets used and modified where indicated.
36 """
37 
38 import rospy
39 import rospkg
40 import actionlib
41 import yaml
42 import rospkg
43 
44 from collections import defaultdict, deque
45 from threading import Thread
46 from mx_driver import dynamixel_io
47 from mx_driver.dynamixel_const import *
54 from std_msgs.msg import Bool
55 from trajectory_msgs.msg import JointTrajectoryPoint
56 from control_msgs.msg import JointTrajectoryAction, JointTrajectoryGoal, FollowJointTrajectoryAction, FollowJointTrajectoryGoal
57 from svenzva_msgs.msg import MotorState, MotorStateList, SvenzvaJointAction, SvenzvaJointResult
58 
59 
60 
62 
63  #adapted from controller_manager.py [https://github.com/SvenzvaRobotics/mx_dynamixel], 3/17/17
64  def __init__(self,
65  port_name='/dev/ttyUSB0',
66  port_namespace='revel',
67  baud_rate='115200',
68  min_motor_id=1,
69  max_motor_id=7,
70  update_rate=10,
71  diagnostics_rate=0,
72  readback_echo=False):
73 
74  rospy.init_node('svenzva_driver', anonymous=False)
75 
76  self.port_name = port_name
77  self.port_namespace = port_namespace
78  self.baud_rate = baud_rate
79  self.min_motor_id = min_motor_id
80  self.max_motor_id = rospy.get_param('max_motor_id', max_motor_id)
81  self.update_rate = rospy.get_param('~update_rate', update_rate)
82  self.diagnostics_rate = diagnostics_rate
83  self.readback_echo = readback_echo
84 
85  self.actual_rate = update_rate
86  self.error_counts = {'non_fatal': 0, 'checksum': 0, 'dropped': 0}
87  self.current_state = MotorStateList()
89 
90  self.traj_client = None
91  self.motor_states_pub = rospy.Publisher('%s/motor_states' % self.port_namespace, MotorStateList, queue_size=1)
92  rospy.on_shutdown(self.disconnect)
93 
94  self.connect(port_name, baud_rate, False)
95  self.__find_motors()
97 
98  self.start_modules()
99 
100  #adapted from serial_proxy.py [https://github.com/SvenzvaRobotics/mx_dynamixel], 3/17/17
101  def connect(self, port_name, baud_rate, readback_echo):
102  try:
103  self.dxl_io = dynamixel_io.DynamixelIO(port_name, baud_rate, readback_echo)
104  except dynamixel_io.SerialOpenError, e:
105  rospy.logfatal(e.message)
106  sys.exit(1)
107 
108  if self.update_rate > 0: Thread(target=self.__update_motor_states).start()
109 
110  def disconnect(self):
111  return
112 
113  """
114  Check if all motors are reachable on the serial port
115  """
116  #adapted from serial_proxy.py [https://github.com/SvenzvaRobotics/mx_dynamixel], 3/17/17
117  def __find_motors(self):
118  rospy.loginfo('%s: Pinging motor IDs %d through %d...' % (self.port_namespace, self.min_motor_id, self. max_motor_id))
119  self.motors = []
121 
122  for motor_id in range(self.min_motor_id, self.max_motor_id + 1):
123  for trial in range(self.num_ping_retries):
124  try:
125  result = self.dxl_io.ping(motor_id)
126  except Exception as ex:
127  rospy.logerr('Exception thrown while pinging motor %d - %s' % (motor_id, ex))
128  continue
129  if result:
130  self.motors.append(motor_id)
131  break
132  if not self.motors:
133  rospy.logfatal('%s: No motors found.' % self.port_namespace)
134  self.dxl_io.close()
135  sys.exit(1)
136 
137  counts = defaultdict(int)
138 
139  status_str = '%s: Found %d motors - ' % (self.port_namespace, len(self.motors))
140  rospy.loginfo('%s, actuator initialization complete.' % status_str[:-2])
141 
142 
143  #adapted from serial_proxy.py [https://github.com/SvenzvaRobotics/mx_dynamixel], 3/17/17
145  num_events = 50
146  debug_polling_rate = False
147  rates = deque([float(self.update_rate)]*num_events, maxlen=num_events)
148  last_time = rospy.Time.now()
149  gr = [4,7,7,3,4,1,1]
150  rate = rospy.Rate(self.update_rate)
151  id_list = range(self.min_motor_id, self.max_motor_id+1)
152  rad_per_tick = 6.2831853 / 4096.0
153  conseq_drops = 0
154 
155  while not rospy.is_shutdown():
156  motor_states = []
157 
158  try:
159  status_ar = self.dxl_io.get_sync_feedback(id_list)
160  conseq_drops = 0
161  for index, state in enumerate(status_ar):
162  if state:
163  #convert to radians, and resolve multiplicative of gear ratio
164  state['goal'] = self.raw_to_rad(state['goal'] / gr[index])
165  state['position'] = self.raw_to_rad(state['position'] / gr[index])
166  #linear model: -9.539325804e-18 + 1.0837745x
167  state['load'] = (state['load'] )
168  state['speed'] = self.spd_raw_to_rad(state['speed'] / gr[index])
169  motor_states.append(MotorState(**state))
170  if dynamixel_io.exception: raise dynamixel_io.exception
171  self.error_counts['dropped'] = 0
172  except dynamixel_io.FatalErrorCodeError, fece:
173  rospy.logerr(fece)
174  except dynamixel_io.NonfatalErrorCodeError, nfece:
175  self.error_counts['non_fatal'] += 1
176  rospy.logdebug(nfece)
177  except dynamixel_io.ChecksumError, cse:
178  self.error_counts['checksum'] += 1
179  rospy.logdebug(cse)
180  except dynamixel_io.DroppedPacketError, dpe:
181  self.error_counts['dropped'] += 1
182  conseq_drops += 1
183  rospy.loginfo(dpe.message)
184  except OSError, ose:
185  if ose.errno != errno.EAGAIN:
186  rospy.logfatal(errno.errorcode[ose.errno])
187  rospy.signal_shutdown(errno.errorcode[ose.errno])
188 
189  #DroppedPackets can happen due to congestion, or due to loss of connectivity.
190  #The latter will cause 100% drop rate
191  if self.error_counts['dropped'] > 10:
192  rospy.logerr("Lost connectivitity to servo motors.")
193  rospy.logerr("Shutting down driver.")
194  rospy.signal_shutdown("Arm connection lost.")
195 
196  if motor_states:
197  msl = MotorStateList()
198  msl.motor_states = motor_states
199  self.motor_states_pub.publish(msl)
200 
201  self.current_state = msl
202 
203  # calculate actual update rate
204  if debug_polling_rate:
205  current_time = rospy.Time.now()
206  rates.append(1.0 / (current_time - last_time).to_sec())
207  self.actual_rate = round(sum(rates)/num_events, 2)
208  last_time = current_time
209  rospy.loginfo("Actual poling rate: %f", self.actual_rate)
210  rate.sleep()
211 
212  """
213  This enables velocity control mode.
214  Necessary for cartesian movement for remote controls
215  """
216  def velocity_mode(self):
217  tup_list_dis = tuple(((1,0),(2,0),(3,0),(4,0),(5,0),(6,0),(7,0)))
218  self.dxl_io.sync_set_torque_enabled(tup_list_dis)
219 
220 
221  tup_list_op = tuple(((1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(7,0)))
222  self.dxl_io.sync_set_operation_mode(tup_list_op)
223 
224  tup_list_en = tuple(((1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(7,1)))
225  self.dxl_io.sync_set_torque_enabled(tup_list_en)
226 
227 
228 
229  """
230  This enables the teaching mode of the Revel. Teaching mode senses outside forces and assists movement in the direction
231  of the felt force.
232 
233  """
234  def teaching_mode(self):
235 
236  tup_list_dis = tuple(((1,0),(2,0),(3,0),(4,0),(5,0),(6,0),(7,0)))
237  self.dxl_io.sync_set_torque_enabled(tup_list_dis)
238 
239 
240  tup_list_op = tuple(((1,0),(2,0),(3,0),(4,0),(5,0),(6,0),(7,0)))
241  self.dxl_io.sync_set_operation_mode(tup_list_op)
242 
243  tup_list_en = tuple(((1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(7,1)))
244  self.dxl_io.sync_set_torque_enabled(tup_list_en)
245 
247  rospy.sleep(0.1)
248  Thread(target=self.compliance_controller.start).start()
249 
250  """
251  Sets motor mode
252  """
254  tup_list_dis = tuple(((1,0),(2,0),(3,0),(4,0),(5,0),(6,0),(7,0)))
255  self.dxl_io.sync_set_torque_enabled(tup_list_dis)
256 
257  tup_list_op = tuple(((1,5),(2,5),(3,5),(4,5),(5,5),(6,5),(7,0)))
258  self.dxl_io.sync_set_operation_mode(tup_list_op)
259 
260  tup_list_en = tuple(((1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(7,1)))
261  self.dxl_io.sync_set_torque_enabled(tup_list_en)
262 
263 
264 
265  def start_modules(self):
266 
267  open_close_gripper = rospy.get_param('~cycle_gripper_on_start', False)
268 
270  rospy.sleep(1.0)
271  jtac.start()
272 
273  rospy.loginfo("Starting Revel trajectory action")
274  self.traj_client = actionlib.SimpleActionClient('/revel/follow_joint_trajectory', FollowJointTrajectoryAction)
275  self.traj_client.wait_for_server()
276 
277 
278  rospy.loginfo("Starting Revel forward kinematics action")
279  self.fkine_action = actionlib.SimpleActionServer("svenzva_joint_action", SvenzvaJointAction, self.fkine_action, auto_start = False)
280  self.fkine_action.start()
281 
282  rospy.loginfo("Starting Revel Arm Services")
283  arm_utils = RevelArmServices(self.port_namespace, self.dxl_io, self.max_motor_id)
284  Thread(target=arm_utils.start).start()
285 
286  #Only start gripper services if gripper motor is present
287  if self.max_motor_id >= 7:
288  gripper_server = RevelGripperActionServer(self.port_namespace, self.dxl_io)
289  gripper_server.start()
290  rospy.loginfo("Starting Revel gripper server.")
291 
292  rospy.loginfo("Starting dynamic reconfigure.")
294  Thread(target=self.dynamic_reconfigure_srv.start).start()
295 
296 
297  rospy.loginfo("Starting Revel Cartesian controller.")
298  cart_server = RevelCartesianController(self.port_namespace, self.dxl_io)
299  Thread(target=cart_server.loop).start()
300 
301  compliance_demonstration = False
302  if compliance_demonstration:
303  rospy.loginfo("Starting with experimental dynamic compliance.")
305  rospy.sleep(0.1)
306  Thread(target=self.compliance_controller.start).start()
307  #Thread(target=self.compliance_controller.update_state).start()
308 
309  """
310  Initialize internal motor parameters that are reset when powered down.
311  Enables torque mode.
312  """
313  #NOTE: Due to dynamixel limitations, initial encoder values must be [-4096, 4096]
314  #otherwise, the motor_states will be inaccurate
316  mode = rospy.get_param('~mode', "user_defined")
317  teaching_mode = mode == "gravity"
318  vel_mode = mode == "velocity"
319 
320  if teaching_mode:
321  self.teaching_mode()
322  elif vel_mode:
323  self.velocity_mode()
324  else:
325  self.set_user_defined_mode()
326 
327  #set current / torque limit for gripper if present
328  if self.max_motor_id >= 7:
329  self.dxl_io.set_goal_current(7, 0)
330  self.dxl_io.set_current_limit(7, 1900)
331 
332  """
333  TODO
334 
335  To increase reliability of packet transmission and reduce the number of packets required to fetch
336  motor status, set the indirect addresses on each motor.
337  This is REQUIRED to be called before starting any status based callbacks.
338  """
339  """
340  def set_indirect_address(self):
341  bulk write ( INDIR_ADDR_1, (1, MX_PRESENT_CURRENT), (2, MX_PRESENT_CURRENT), ... )
342  bulk write ( INDIR_ADDR_1 + 2, (1, MX_PRESENT_CURRENT+1), (2, MX_PRESENT_CURRENT+1), ...)
343  ...
344 
345  """
346 
347 
348 
349  """
350  Given an array of joint positions (in radians), send request to individual servos
351  """
352  def fkine_action(self, data):
353  goal = FollowJointTrajectoryGoal()
354  goal.trajectory.joint_names = ['joint_1', 'joint_2', 'joint_3', 'joint_4', 'joint_5', 'joint_6']
355  point = JointTrajectoryPoint()
356  point.positions = data.positions
357  #Since this is asynchronous, the time from 2 points is 0 and the action will return immediately
358  point.time_from_start = rospy.Duration(0.1)
359  goal.trajectory.points.append(point)
360  self.traj_client.send_goal_and_wait(goal)
361  res = SvenzvaJointResult()
362  res.is_done = True
363  self.fkine_action.set_succeeded(res)
364 
365  @staticmethod
366  def rad_to_raw(angle):
367  #encoder ticks = resolution / radian range
368  return int(round( angle * 4096.0 / 6.2831853 ))
369 
370  @staticmethod
371  def raw_to_rad(raw):
372  #radians_per = radians_range / resolution
373  return raw * 6.2831853 / 4096.0
374 
375  @staticmethod
376  def spd_rad_to_raw(vel):
377  return max(1, int(round(vel / (RPM_TO_RADSEC * RPM_PER_TICK))))
378 
379  @staticmethod
380  def spd_raw_to_rad(vel):
381  return vel * RPM_PER_TICK * RPM_TO_RADSEC
382 
383 if __name__ == '__main__':
384  try:
386  rospy.spin()
387  sd.disconnect()
388  except rospy.ROSInterruptException:
389  pass
390 
def __init__(self, port_name='/dev/ttyUSB0', port_namespace='revel', baud_rate='115200', min_motor_id=1, max_motor_id=7, update_rate=10, diagnostics_rate=0, readback_echo=False)
def connect(self, port_name, baud_rate, readback_echo)


svenzva_drivers
Author(s): Max Svetlik
autogenerated on Wed Oct 28 2020 03:31:27