00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034 import serial
00035 import struct
00036 import sys, optparse
00037 import math
00038 import numpy as np
00039
00040 class USB2Dynamixel_Device():
00041 ''' Class that manages serial port contention between servos on same bus
00042 '''
00043 def __init__( self, dev_name = '/dev/ttyUSB0', baudrate = 57600 ):
00044 try:
00045 self.dev_name = string.atoi( dev_name )
00046 except:
00047 self.dev_name = dev_name
00048
00049 self.servo_dev = self._open_serial( baudrate )
00050
00051 def _open_serial(self, baudrate):
00052 servo_dev = None
00053 try:
00054 servo_dev = serial.Serial(self.dev_name, baudrate, timeout=1.0)
00055
00056
00057 servo_dev.close()
00058 servo_dev.setParity('N')
00059 servo_dev.setStopbits(1)
00060 servo_dev.open()
00061
00062 servo_dev.flushOutput()
00063 servo_dev.flushInput()
00064 servo_dev.flush()
00065
00066 except (serial.serialutil.SerialException), e:
00067 raise RuntimeError('lib_dynamixel: Serial port not found!\n')
00068 if(servo_dev == None):
00069 raise RuntimeError('lib_dynamixel: Serial port not found!\n')
00070 return servo_dev
00071
00072 def _write_serial(self, msg):
00073 self.servo_dev.flushInput()
00074 self.servo_dev.write( msg )
00075
00076 def _send_serial(self, msg):
00077 """ sends the command to the servo
00078 """
00079 out = struct.pack('%dB' %len(msg), *msg)
00080 self._write_serial( out )
00081
00082 def _read_serial(self, nBytes=1):
00083 ''' Reads data from the servo
00084 '''
00085 self.servo_dev.flushOutput()
00086 return self.servo_dev.read( nBytes )
00087
00088 def _receive_reply(self, id):
00089 ''' Reads the status packet returned by the servo
00090 '''
00091 start = self._read_serial( 2 )
00092 if start != '\xff\xff':
00093 raise RuntimeError('lib_dynamixel: Failed to receive start bytes\n')
00094 servo_id = ord(self._read_serial( 1 ))
00095 if servo_id != id:
00096 raise RuntimeError('lib_dynamixel: Incorrect servo ID received: %d\n' %servo_id)
00097 data_len = ord(self._read_serial( 1 ))
00098 err = ord(self._read_serial( 1 ))
00099 raw_data = self._read_serial( data_len - 2 )
00100 data = [ord(d) for d in raw_data]
00101 chksum = servo_id + data_len + err + sum(data)
00102 chksum = ( ~chksum ) % 256
00103 checksum = ord(self._read_serial( 1 ))
00104 if checksum != chksum:
00105 raise RuntimeError('lib_dynamixel: Error in Received Checksum')
00106 return id, data, err
00107
00108 def __calc_checksum(self, msg):
00109 chksum = sum(msg)
00110 return ( ~chksum ) % 256
00111
00112 def _send_instruction(self, instruction, id, status_return=True):
00113 ''' Fills out packet metadata, manages mutex, sends packet, and handles response.
00114 '''
00115 msg = [ id, len(instruction) + 1 ] + instruction
00116 chksum = self.__calc_checksum( msg )
00117 msg = [ 0xff, 0xff ] + msg + [chksum]
00118 try:
00119 self._send_serial( msg )
00120 if status_return:
00121 id, data, err = self._receive_reply(id)
00122 else:
00123 id = 0xFE
00124 data = []
00125 err = 0
00126 except:
00127 raise
00128 if err != 0:
00129 self._process_err( err, id )
00130 return data
00131
00132 def _process_err( self, err, id ):
00133 ''' Process and raise errors received from the robotis servo.
00134 '''
00135 msg = "Error(s) reported by Dynamixel ID %d:" %id
00136 if err & 1:
00137 msg += "\n\tInput Voltage Error: Applied Voltage outside of set operating range."
00138 if err & 2:
00139 msg += "\n\tAngle Limit Error: Received Goal position outside of set limits."
00140 if err & 4:
00141 msg += "\n\tOverHeating Error: Temperature exceeds set temperature limit."
00142 if err & 8:
00143 msg += "\n\tRange Error: Received command beyond usage range."
00144 if err & 16:
00145 msg += "\n\tCheckSum Error: Invalid Checksum in Instruction Packet."
00146 if err & 32:
00147 msg += "\n\tOverload Error: Current load exceeds set torque limit"
00148 if err & 64:
00149 msg += "\n\tInstruction Error: Received undefined instruction, "+\
00150 "or action command received before command was registered\n"
00151 raise RuntimeError(msg)
00152
00153 def read_address(self, id, address, nBytes=1):
00154 ''' reads nBytes from address on the servo at id.
00155 returns [n1,n2 ...] (list of parameters)
00156 '''
00157 msg = [ 0x02, address, nBytes ]
00158 return self._send_instruction( msg, id )
00159
00160 def write_address(self, id, address, data):
00161 ''' writes data at the address on the servo of id.
00162 data = [n1,n2 ...] list of numbers.
00163 return [n1,n2 ...] (list of return parameters)
00164 '''
00165 msg = [ 0x03, address ] + data
00166 return self._send_instruction( msg, id )
00167
00168 def ping(self, id):
00169 ''' pings servo at id
00170 '''
00171 msg = [ 0x01 ]
00172 return self._send_instruction( msg, id )
00173
00174 def reset_to_factory(self, id):
00175 '''Send the reset instruction to the servo at id.
00176 This will return the servo to ID 1 with a baudrate of 57600,
00177 and reset the EEPROM to factory defaults. REQUIRES CONFIRMATION.
00178 '''
00179 msg = [0x06]
00180 resp = raw_input("This will reset all parameters on the Dynamixel "+\
00181 "to factory defaults.\n"+\
00182 "Are you sure you wish to continue? (y/n)")
00183 if resp == 'y' or resp == 'Y' or resp == 'yes':
00184 print "Resetting to factory defaults: ID = 1, baudrate=57600"
00185 return self._send_instruction(msg, id)
00186 else:
00187 print "Aborting..."
00188 return None
00189
00190 def sync_write(self, data):
00191 '''writes data to address 0xFE (254), the broadcast address.
00192 sends data to all servos on a bus
00193 '''
00194 msg = [ 0x83 ] + data
00195 return self._send_instruction(msg, id=0xFE, status_return=False)
00196
00197
00198 class Dynamixel_Chain(USB2Dynamixel_Device):
00199 ''' Class that manages multiple servos on a single Dynamixel Device
00200 '''
00201 def __init__(self, dev='/dev/ttyUSB0', baudrate='57600', ids=None):
00202 ''' Accepts device file, baudrate, and a list of id numbers for servos (if known).
00203 '''
00204 USB2Dynamixel_Device.__init__(self, dev, baudrate)
00205
00206 valid_servo_ids = self._find_servos(ids)
00207 if len(valid_servo_ids) == 0:
00208 raise RuntimeError("No valid servo IDs Found")
00209
00210 series = [self._determine_series(self.read_model_number(id)) for id in valid_servo_ids]
00211
00212 self.servos = {}
00213 for id, series in zip(valid_servo_ids, series):
00214 self.servos[id] = Robotis_Servo(id, series)
00215
00216 def _find_servos(self, ids=None):
00217 ''' Finds all servo IDs on the USB2Dynamixel, or check given ids
00218 '''
00219 if ids is None:
00220 print 'Scanning for servos on all possible ID\'s'
00221 ids = range(254)
00222 suggested_ids = False
00223 else:
00224 print 'Scanning for servos with ID(\'s): %s' %ids
00225 suggested_ids = True
00226 self.servo_dev.setTimeout( 0.05 )
00227 servos = []
00228 for i in ids:
00229 if self._id_on_device(i):
00230 print '\n FOUND A SERVO @ ID %d\n' % i
00231 servos.append(i)
00232 else:
00233 if suggested_ids:
00234 print "Cannot find ID %s on %s" %(i, self.dev_name)
00235
00236 self.servo_dev.setTimeout( 1.0 )
00237 return servos
00238
00239 def _id_on_device(self, id):
00240 '''check for a specific id on the dynamixel device. pings servo and reads id.
00241 '''
00242 try:
00243 self.ping(id)
00244 servo_id = self.read_address(id,3)[0]
00245 assert servo_id == id, "ID value received from servo did not match id of call"
00246 return True
00247 except:
00248 return False
00249
00250 def _determine_series(self, code):
00251 ''' Determine the series from the model number of servo with id.
00252 '''
00253 if code in [29, 310, 320]:
00254 if code == 320:
00255 print ("Warning: MX-106 Not fully supported. (Drive Mode/Dual Motor Joints)")
00256 return 'MX'
00257 elif code in [24, 28, 64]:
00258 return 'RX'
00259 elif code == 107:
00260 print ("WARNING: EX-106 Devices not directly supported. Treating as 'MX'.")
00261 return 'MX'
00262 elif code in [300, 12, 18]:
00263 print ("WARNING: AX-series devices not directly supported. Treating as 'MX'.")
00264 return 'MX'
00265 else:
00266 raise RuntimeError("Servo ID: %d has unknown servo model code: %d" %(id, code))
00267
00268 def read_model_number(self, id):
00269 ''' Read the model number (byte-code) of the servo at id.
00270 '''
00271 data = self.read_address(id, 0x00, 2)
00272 return data[0] + data[1]*256
00273
00274 def read_firmware_version(self, id):
00275 ''' Read the firmware version on the servo at id.
00276 '''
00277 return self.read_address(id, 0x02)[0]
00278
00279 def set_id(self, current_id, new_id):
00280 ''' changes the servo id from current_id to new_id
00281 '''
00282 if (new_id < 0) or (new_id > 253):
00283 raise RuntimeWarning("Robotis ID must be between 0 and 253")
00284 resp = self.write_address(current_id, 0x03, [new_id])
00285 valid_servo_ids = self._find_servos([new_id])
00286 self.servos[new_id] = Robotis_Servo(new_id, series=self.servos[current_id].series)
00287 self.servos.pop(current_id)
00288 return resp
00289
00290 def set_baudrate(self, id, baudrate=0x22):
00291 ''' Set the baudrate of the servo at id. Smaller == Faster. Default: 34 -> 57600.
00292 '''
00293 return self.write_address(id, 0x04, [baudrate])
00294
00295 def read_baudrate(self, id):
00296 ''' Read the currently set baudrate digit of servo with ID.
00297 '''
00298 code = self.read_address(id, 0x04)[0]
00299 if code < 249:
00300 return int(2000000./(code+1))
00301 elif code == 250:
00302 return 2250000
00303 elif code == 251:
00304 return 2500000
00305 elif code == 252:
00306 return 3000000
00307 else:
00308 raise RuntimeError("Received unknown code for baudrate: %d" %code)
00309
00310 def set_return_delay(self, id, delay=250):
00311 ''' Set Return Delay Time (0-500 microseconds). Default=250.
00312 '''
00313 if delay < 0:
00314 delay = 0
00315 print("Return Delay Time must be non-negative. Setting to 0 microseconds.")
00316 elif delay > 500:
00317 delay = 500
00318 print("Return Delay Time must be less than 500 (microseconds). Setting to 500us.")
00319 elif (delay % 2 != 0):
00320 delay = 2*int(delay/2)
00321 print("Return Delay Time must be specified by 2 microsecond increments. Rounding to %dus" %delay)
00322 return self.write_address(id, 0x05, [int(delay/2)])
00323
00324 def read_return_delay(self, id):
00325 '''Read the currently set Return Delay Time (in microseconds)'''
00326 ret_dly = self.read_address(id, 0x05)
00327 return 2*ret_dly[0]
00328
00329 def set_angle_limits(self, id, cw_limit=0., ccw_limit=2*math.pi):
00330 ''' Set the angular limits (in radians) on the motor. Should specify both cw and ccw limits
00331 '''
00332 cw_enc = self.servos[id].angle_to_encoder(cw_limit)
00333 ccw_enc = self.servos[id].angle_to_encoder(ccw_limit)
00334 cw_hi, cw_lo = self.__encoder_to_bytes(id, cw_enc)
00335 ccw_hi, ccw_lo = self.__encoder_to_bytes(id, ccw_enc)
00336 return self.write_address(id, 0x06, [cw_lo, cw_hi, ccw_lo, ccw_hi])
00337
00338 def read_angle_limits(self, id):
00339 ''' Read the angle limits (in radians) set on the servo. Returns [cw_limit, ccw_limit]
00340 '''
00341 data = self.read_address(id, 0x06, 4)
00342 cw = data[0]+data[1]*256
00343 ccw = data[2]+data[3]*256
00344 cw_lim = self.servos[id].encoder_to_angle(cw)
00345 ccw_lim = self.servos[id].encoder_to_angle(ccw)
00346 return [cw_lim, ccw_lim]
00347
00348 def is_cont_turn_enabled(self, id):
00349 ''' Return whether continuous turn is enabled based on the joint angle limits.
00350 '''
00351 return (self.read_address(id, 6, 4) == [0]*4)
00352
00353 def enable_cont_turn(self, id):
00354 ''' Sets angle limits to zero, allowing continuous turning (good for wheels).
00355 After calling this method, simply use 'set_angvel' to command rotation. This
00356 rotation is proportional to torque according to Robotis documentation.
00357 '''
00358 return self.write_address(id, 6, [0]*4)
00359
00360 def disable_cont_turn(self, id):
00361 ''' Resets CCW angle limits to defaults to allow commands through 'move_angle' again.
00362 '''
00363 return self.set_angle_limits(id)
00364
00365 def read_temperature_limit(self, id):
00366 ''' Read the temperature alarm threshold in degrees C. Default: 80C.
00367 Should not change.
00368 '''
00369 return self.read_address(id, 0x0B, 1)[0]
00370
00371 def read_temperature(self, id):
00372 ''' returns the temperature (Celcius) of servo (id).
00373 '''
00374 data = self.read_address(id, 0x2B, 1 )
00375 return data[0]
00376
00377 def read_voltage_limits(self, id):
00378 ''' Read the lower and upper voltage alarm limits. Defaults: 6.0V - 16.0V.
00379 '''
00380 data = self.read_address(id, 0x0C, 2)
00381 return [ data[0]*0.1, data[1]*0.1 ]
00382
00383 def set_voltage_limits(self, id, lower=6.0, upper=16.0):
00384 ''' Set the lower and upper voltage alarm limits. Defaults: 6.0V - 16.0V.
00385 '''
00386 low_limit = int( 10. * lower )
00387 high_limit = int( 10. * upper )
00388 return self.write_address(id, 0x0C, [low_limit, high_limit])
00389
00390 def read_voltage(self, id):
00391 ''' returns voltage (Volts) seen by servo (id).
00392 '''
00393 data = self.read_address(id, 0x2A, 1 )
00394 return data[0] / 10.
00395
00396 def read_max_torque(self, id):
00397 ''' Read the current max torque setting (as a percentage of possible torque).
00398 '''
00399 data = self.read_address(id, 0x0E, 2)
00400 return (data[0] + data[1]*256) / 10.23
00401
00402 def set_max_torque(self, id, percent=100):
00403 ''' Set the max torque as a percent of possible torque.
00404 Will trigger alarm and shutdown if exceeded.
00405 '''
00406 data = 10.23 * percent
00407 hi = int( data / 256 )
00408 lo = int( data % 256 )
00409 return self.write_address(id, 0x0E, [lo, hi])
00410
00411 def read_status_return_level(self, id):
00412 ''' Read the current status return label of servo at id.
00413 0 - returns status packet only for PING command
00414 1 - returns status packet only for read commands
00415 2 - returns sttaus packet for all commands
00416 '''
00417 return self.read_address(id, 0x10)
00418
00419 def set_status_return_level(self, id, level=2):
00420 ''' Set the current status return label of servo at id.
00421 0 - returns status packet only for PING command
00422 1 - returns status packet only for read commands
00423 2 - returns sttaus packet for all commands
00424 '''
00425 if not level in [0, 1, 2]:
00426 raise RuntimeError('Status Return Level must be one of: \n' \
00427 '\t0 - No return except ping \n'\
00428 '\t1 - return only for read commands \n'\
00429 '\t2 - return for all commands')
00430 return self.write_address(id, 0x10, [level])
00431
00432 def is_torque_enabled(self, id):
00433 ''' Return True if sending power to motor, False otherwise.
00434 '''
00435 data = self.read_address(id, 0x18)
00436 return bool( data[0] )
00437
00438 def enable_torque(self, id):
00439 ''' Enable torque production by impressing power to the motor.
00440 '''
00441 return self.write_address(id, 0x18, [1])
00442
00443 def disable_torque(self, id):
00444 ''' Disable torque production by interrupting power to the motor.
00445 '''
00446 return self.write_address(id, 0x18, [0])
00447
00448 def read_load(self, id):
00449 ''' Alias for read_torque.
00450 '''
00451 return self.read_torque(id)
00452
00453 def read_torque(self, id):
00454 ''' Read the current load as a percentage of (possibly) maximum torque.
00455 CW -> load < 0. CCW -> load > 0.
00456 Should be used for direction of torque only.
00457 '''
00458 data = self.read_address(id, 0x28, 2)
00459 hi = data[1] & 3
00460 val = data[0] + hi*256
00461 load = val/10.24
00462 if (data[1] & 4) != self.servos[id].settings['flipped']:
00463 load *= -1.
00464 return load
00465
00466 def is_led_on(self, id):
00467 ''' Return True if LED is ON, False if LED is off.
00468 '''
00469 data = self.read_address(id, 0x19, 1)
00470 return bool( data[0] )
00471
00472 def set_led(self, id, on=True):
00473 ''' Set the status of the LED on or off.
00474 '''
00475 return self.write_address(id, 0x19, [on])
00476
00477 def read_compliance_margins(self, id):
00478 ''' Read the compliance margin (deadband around goal position) of servo with id.
00479 Returns [CW, CCW] angular deadband in radians (always positive).
00480 '''
00481 cw, ccw = self.read_address(id, 0x1A, 2)
00482 cw_rad = cw * self.servos[id].settings['rad_per_enc']
00483 ccw_rad = ccw * self.servos[id].settings['rad_per_enc']
00484 return [cw_rad, ccw_rad]
00485
00486 def set_compliance_margins(self, id, cw=None, ccw=None):
00487 if (cw is None) and (ccw is None):
00488 print("Setting CW and CCW compliance margins to %f (one encoder step)."
00489 %self.servos[id].settings['rad_per_enc'])
00490 cw_enc = ccw_enc = 1
00491 elif (cw is None) or (ccw is None):
00492 print("Setting both CW and CCW compliance margins to %f."
00493 %max(cw, ccw))
00494 cw_enc = ccw_enc = int(round(max(cw, ccw) / self.servos[id].settings['rad_per_enc']))
00495 else:
00496 cw_enc = int(round( cw / self.servos[id].settings['rad_per_enc']))
00497 ccw_enc = int(round( ccw / self.servos[id].settings['rad_per_enc']))
00498
00499 encs = [cw_enc, ccw_enc]
00500 for i, enc in enumerate(encs):
00501 if (enc > 254):
00502 print("WARNING: Compliance margin must be less than {0}, clipping to {0}".format(
00503 254*self.servos[id].settings['rad_per_enc']))
00504 encs[i] = 254
00505 return self.write_address(id, 0x1A, encs)
00506
00507 def _compliance_slope_to_step(self, val):
00508 if val in range(3):
00509 return 1
00510 elif val in range(4,7):
00511 return 2
00512 elif val in range(8, 15):
00513 return 3
00514 elif val in range(16, 31):
00515 return 4
00516 elif val in range(32, 63):
00517 return 5
00518 elif val in range(64, 127):
00519 return 6
00520 elif val in range(128, 254):
00521 return 7
00522 else:
00523 raise RuntimeError("Received out-of-range compliance slope: %s. Must be in range(254)" %val)
00524
00525 def _compliance_step_to_slope(self, step):
00526 try:
00527 return {1: 2,
00528 2: 4,
00529 3: 8,
00530 4: 16,
00531 5: 32,
00532 6: 64,
00533 7: 128}[step]
00534 except:
00535 print "Compliance slope must be in range(7)"
00536 raise
00537
00538 def read_compliance_slopes(self, id):
00539 ''' Read the CW and CCW compliance slopes as steps from 1-7 (1=stiffer, 7=more flexible').
00540 '''
00541 data = self.read_address(id, 0x1C, 2)
00542 return [self._compliance_slope_to_step(v) for v in data]
00543
00544 def set_compliance_slopes(self, id, cw=None, ccw=None):
00545 if (cw is None) and (ccw is None):
00546 print("Setting CW and CCW compliance slopes to level 5 (default).")
00547 data = [32, 32]
00548 elif (cw is None) or (ccw is None):
00549 data = [self._compliance_step_to_slope(max(cw, ccw))] * 2
00550 print("Setting both CW and CCW compliance slopes to level %d." %data[0])
00551 else:
00552 cw_step = self._compliance_step_to_slope(cw)
00553 ccw_step = self._compliance_step_to_slope(ccw)
00554 data = [cw_step, ccw_step]
00555 return self.write_address(id, 0x1C, data)
00556
00557 def read_pid_gains(self, id):
00558 ''' Read the PID gains currently set on the servo.
00559 Returns: [kp, ki, kd] (gain coefficients)
00560 '''
00561 data = self.read_address(id, 0x1A, 3)
00562 kd = data[0] * 4. / 1000.
00563 ki = data[1] * 1000. / 2048.
00564 kp = data[2] / 8.
00565 return [kp, ki, kd]
00566
00567 def set_pid_gains(self, id, kp=4., ki=0., kd=0.):
00568 ''' Set the PID gain coefficients on the servo.
00569 0 <= kp < 31.875
00570 0 <= ki < 124.5177
00571 0 <= kd < 1.02
00572 '''
00573 if (kp < 0) or (kd < 0 ) or (ki < 0 ):
00574 raise RuntimeError('All PID gains must be positive.')
00575 if kp >= 32.:
00576 print "Warning: Kp gain must be within: 0 <= kp < 31.875. Setting to max."
00577 p_data = 254
00578 else:
00579 p_data = int( kp * 8. )
00580 if ki >= 125.:
00581 print "Warning: Ki gain must be within: 0 <= ki < 124.5177. Setting to max."
00582 i_data = 254
00583 else:
00584 i_data = int( ki * 2048. / 1000. )
00585 if kd >= 1.02:
00586 print "Warning: Kd gain must be within: 0 <= kd < 1.02. Setting to max."
00587 d_data = 254
00588 else:
00589 d_data = int( kd * 1000. / 4. )
00590 return self.write_address(id, 0x1A, [d_data, i_data, p_data])
00591
00592 def read_goal_position(self, id):
00593 ''' Read the currently set goal angle in radians of servo with id.
00594 '''
00595 data = self.read_address(id, 0x1E, 2)
00596 enc_goal = data[0] + data[1]*256
00597 return self.servos[id].encoder_to_angle(enc_goal)
00598
00599 def read_goal_angvel(self, id):
00600 ''' Read the currently set desired moving speed of servo with id.
00601 '''
00602 data = self.read_address(id, 0x20, 2)
00603 return self.servos[id].bytes_to_angvel(data[1], data[0])
00604
00605 def read_torque_limit(self, id):
00606 ''' Read the currently set torque limit as a percentage of acheivable torque.
00607 Torque produced by the motor will be capped to this value.
00608 '''
00609 data = self.read_address(id, 0x22, 2)
00610 return (data[0] + data[1]*256) / 10.23
00611
00612 def set_torque_limit(self, id, percent=None):
00613 ''' Set the torque limit as a percent of possible torque.
00614 Torque produced by the motor will be capped to this value.
00615 '''
00616 if percent is None:
00617 percent = self.read_max_torque(id)
00618 print "No percent specified. Setting to %s, "\
00619 "the current torque alarm threshold." %percent
00620 data = 10.23 * percent
00621 hi = int( data / 256 )
00622 lo = int( data % 256 )
00623 return self.write_address(id, 0x22, [lo, hi])
00624
00625 def read_encoder(self, id):
00626 ''' returns position in encoder ticks of servo at id.
00627 '''
00628 data = self.read_address(id, 0x24, 2)
00629 enc_val = data[0] + data[1] * 256
00630 return enc_val
00631
00632 def read_angle(self, id):
00633 ''' returns the angle (radians) of servo at id.
00634 '''
00635 return self.servos[id].encoder_to_angle(self.read_encoder(id))
00636
00637 def read_angles(self, ids=None):
00638 ''' return a list of current joint angles for servos with given ids
00639 '''
00640 if ids is None:
00641 ids = self.servos.keys()
00642 angles = [self.read_angle(id) for id in ids]
00643 return angles, ids
00644
00645 def read_angvel(self, id):
00646 ''' returns the angular velocity (rad/s) of servo at id.
00647 '''
00648 data = self.read_address(id, 38, 2)
00649 angvel = self.servos[id].bytes_to_angvel(data[1], data[0])
00650 return angvel
00651
00652 def read_angvels(self, ids=None):
00653 '''return a list of current angular velocities for servos with given ids
00654 '''
00655 if ids is None:
00656 ids = self.servos.keys()
00657 angvels = [self.read_angvel(id) for id in ids]
00658 return angvels, ids
00659
00660 def read_ang_angvel(self, id):
00661 '''returns the angular position and angular velocity from a single read
00662 '''
00663 data = self.read_address(id, 36, 4)
00664 enc_val = data[0] + data[1] * 256
00665 ang = self.servos[id].encoder_to_angle(enc_val)
00666 angvel = self.servos[id].bytes_to_angvel(data[3], data[2])
00667 return ang, angvel
00668
00669 def read_angs_angvels(self, ids=None):
00670 '''return lists of current angular positions and velocities for given ids
00671 '''
00672 if ids is None:
00673 ids = self.servos.keys()
00674 angles = []
00675 angvels = []
00676 for id in ids:
00677 ang, angvel = self.read_ang_angvel(id)
00678 angles.append(ang)
00679 angvels.append(angvel)
00680 return angles, angvels, ids
00681
00682 def move_angle(self, id, ang, angvel=None, blocking=False):
00683 ''' move servo with id to angle (radians) with velocity (rad/s)
00684 '''
00685 if angvel is None:
00686 angvel = self.servos[id].settings['max_speed']
00687 else:
00688 angvel = self.servos[id].clip_angvel(angvel)
00689 av_hi, av_lo = self.servos[id].angvel_to_bytes(angvel)
00690 ang = self.servos[id].clip_angle(ang)
00691 enc_val = self.servos[id].angle_to_encoder(ang)
00692 ang_hi, ang_lo = self.__encoder_to_bytes(id, enc_val)
00693 self.write_address(id, 30, [ang_lo, ang_hi, av_lo, av_hi])
00694
00695 if blocking == True:
00696 while(self.is_moving(id)):
00697 continue
00698
00699 def move_angles_sync(self, ids, angs, angvels=None) :
00700 ''' move servos with id's to angles with angvels using a single sync_write.
00701 clips angles to allowed range, and limits angvel to max allowed.
00702 '''
00703 if angvels is None:
00704 angvels = [self.servos[id_].settings['max_speed'] for id_ in ids]
00705 else:
00706 if len(angvels) != len(ids):
00707 raise RuntimeError("Number of ids and anvels do not match.")
00708 else:
00709 angvels = [ self.servos[id_].clip_angvel(angvel) for id_, angvel in zip(ids, angvels) ]
00710
00711 assert len(ids) == len(angvels) , "Number of ids and angvels do not match"
00712 assert len(ids) == len(angs) , "Number of ids and angles do not match"
00713
00714 msg = [0x1E, 0x04]
00715 for id, ang, vel in zip(ids, angs, angvels):
00716 servo = self.servos[id]
00717 ang = servo.clip_angle(ang)
00718 enc_tics = servo.angle_to_encoder(ang)
00719 ang_hi, ang_lo = self.__encoder_to_bytes(id, enc_tics)
00720 new_vel = servo.clip_angvel(vel)
00721 vel_hi, vel_lo = self.servos[id].angvel_to_bytes(vel)
00722 msg.extend([id, ang_lo, ang_hi, vel_lo, vel_hi])
00723 self.sync_write(msg)
00724
00725 def move_to_encoder(self, id, n):
00726 ''' move to encoder position n
00727 '''
00728 hi, lo = self.__encoder_to_bytes(id, n)
00729 return self.write_address(id, 0x1E, [lo,hi] )
00730
00731 def __encoder_to_bytes(self, id, n):
00732 ''' convert encoder value to hi, lo bytes
00733 '''
00734
00735
00736 n = min( max( n, 0 ), self.servos[id].settings['max_encoder'] )
00737 hi, lo = n / 256, n % 256
00738 return hi, lo
00739
00740
00741 def set_angvel(self, id, angvel):
00742 ''' set angvel (rad/s) of servo id
00743 '''
00744 hi, lo = self.servos[id].angvel_to_bytes(angvel)
00745 return self.write_address(id, 0x20, [lo, hi] )
00746
00747 def is_moving(self, id):
00748 ''' Returns True if servo (id) is moving, False otherwise.
00749 '''
00750 data = self.read_address(id, 0x2e, 1 )
00751 return data[0] != 0
00752
00753 def is_eeprom_locked(self, id):
00754 ''' Return True if the EEPROM of servo at id is locked, False if not.
00755 '''
00756 return bool( self.read_address(id, 0x2F)[0] )
00757
00758 def lock_eeprom(self, id):
00759 ''' Lock the EEPROM of servo at ID (will prevent changes to EEPROM bits).
00760 Lock can only be reset by power-cycling the servo.
00761 '''
00762 return self.write_address(id, 0x2F, [0x01])
00763
00764 def read_punch(self, id):
00765 ''' Read the currently set minimum motor current.
00766 UNITS UNKNOWN. Values in range 0 - 1023. Default: 0.
00767 '''
00768 data = self.read_address(id, 0x30, 2)
00769 return data[0] + data[1]*256
00770
00771 def set_punch(self, id, value=0):
00772 ''' Set the minimum motor current.
00773 UNITS UNKNOWN. Values in range 0 - 1023. Default: 0.
00774 '''
00775 hi, lo = value / 256, value % 256
00776 return self.write_address(id, 0x30, [lo, hi])
00777
00778 def read_current(self, id):
00779 ''' Read the current (in Amps) currently in the motor.
00780 '''
00781 data = self.read_address(id, 0x44, 2)
00782 val = data[0] + data[1]*256
00783 return 0.0045 * (val - 2048.)
00784
00785 def is_torque_control_enabled(self, id):
00786 ''' Return True if servo at id is currently set for torque control, False otherwise.
00787 '''
00788 return bool( self.read_address(id, 0x46, 1)[0] )
00789
00790 def enable_torque_control(self, id):
00791 ''' Enable torque control mode. Goal position and moving speed will be ignored.
00792 '''
00793 return self.write_address(id, 0x46, [1])
00794
00795 def disable_torque_control(self, id):
00796 ''' Disable torque control mode. Goal position and moving speed will be used instead.
00797 '''
00798 return self.write_address(id, 0x46, [0])
00799
00800 def read_goal_torque(self, id):
00801 ''' Read the currently set goal torque (used in torque control mode). Units in Amps. Torque produces is a function of motor current.
00802 '''
00803 data = self.read_address(id, 0x47, 2)
00804 hi = data[1] & 3
00805 val = data[0] + hi*256
00806 pct = val/10.24
00807 if data[1] & 4:
00808 return -pct
00809 else:
00810 return pct
00811
00812 def set_goal_torque(self, id, percent=100):
00813 ''' Set the goal torque as a percentage of the maximum torque.
00814 100 % -> Max torque CCW. -100 % -> Max torque CW.
00815 '''
00816 if percent <= 0:
00817 hi = 4
00818 percent *= -1
00819 else:
00820 hi = 0
00821 val = int(round(percent * 10.23))
00822 hi = hi | (val >> 8)
00823 lo = val & 255
00824 return self.write_address( id, 0x47, [lo, hi] )
00825
00826 def read_goal_acceleration(self, id):
00827 data = self.read_address(id, 0x49)
00828 return data[0] * 8.583
00829
00830 def set_goal_acceleration(self, id, ang_acc=0):
00831 ''' Set the goal acceleration (0 <--> 2018 rads/sec^2)
00832 If goal angular velocity is set to 0, will move with max acceleration.
00833 If goal acceleration is set to 0 (default), will move with max acceleration.
00834 '''
00835 val = int(round(ang_acc/8.583))
00836 return self.write_address(id, 0x49, [val])
00837
00838 class Robotis_Servo():
00839 ''' Class to use a robotis RX-28 or RX-64 servo.
00840 '''
00841 def __init__(self, servo_id, series = 'RX' ):
00842 '''servo_id - servo ids connected to USB2Dynamixel 1,2,3,4 ... (1 to 253)
00843 [0 is broadcast if memory serves]
00844 series - Just a convenience for defining "good" defaults on MX series.
00845 When set to "MX" it uses these values, otherwise it uses values
00846 better for AX / RX series. Any of the defaults can be overloaded
00847 on a servo-by-servo bases in servo_config.py
00848 '''
00849 self.servo_id = servo_id
00850 self.series = series
00851
00852 if series == 'MX':
00853 defaults = {
00854 'home_encoder': 0x7FF,
00855 'max_encoder': 0xFFF,
00856 'rad_per_enc': 2*math.pi / 0xFFF,
00857 'max_ang': math.pi,
00858 'min_ang': -math.pi,
00859 'flipped': False,
00860 'max_speed': 0.
00861 }
00862 elif series == 'RX':
00863 defaults = {
00864 'home_encoder': 0x200,
00865 'max_encoder': 0x3FF,
00866 'rad_per_enc': math.radians(300.0) / 1024.0,
00867 'max_ang': math.radians(150),
00868 'min_ang': math.radians(-150),
00869 'flipped': False,
00870 'max_speed': 0.
00871 }
00872 else:
00873 raise RuntimeError('Servo ID %d has unrecognized Series name: %s' %(self.servo_id, self.series))
00874
00875
00876 self.settings = defaults
00877 try:
00878 import servo_config as sc
00879 if sc.servo_param.has_key( self.servo_id ):
00880 print "Using servo_config.py settings for Servo %d" %self.servo_id
00881 for key, val in sc.servo_param [ self.servo_id ].iteritems():
00882 self.settings[key] = val
00883 else:
00884 print 'Servo ID %d not found in servo_config.py. Using defaults.' %self.servo_id
00885 except:
00886 print 'Servo_config.py configuration file not found. Using defaults.'
00887 print "Created new %s-series Robotis Servo at ID %d" %(series, servo_id)
00888
00889
00890 if (self.settings['max_speed'] < 0) or (self.settings['max_speed'] > 12.2595):
00891 print "Servo %d: Setting default servo angular velocity to maximum possible." %self.servo_id
00892 self.settings['max_speed'] = 0
00893
00894 def angle_to_encoder(self, ang):
00895 ''' return encoder position for given angle (radians)
00896 '''
00897 if self.settings['flipped']:
00898 ang *= -1.0
00899 enc_tics = int(round( ang / self.settings['rad_per_enc'] ))
00900 enc_tics += self.settings['home_encoder']
00901 return enc_tics
00902
00903 def encoder_to_angle(self, enc_val):
00904 '''return angular position (rad) from given encoder position
00905 '''
00906 ang = ((enc_val - self.settings['home_encoder']) *
00907 self.settings['rad_per_enc'])
00908 if self.settings['flipped']:
00909 ang *= -1.0
00910 return ang
00911
00912 def clip_angle(self, ang):
00913 ''' Clip commanded joint angles to within the allowed range.
00914 '''
00915 if ang < self.settings['min_ang']:
00916 print "Servo %d: Commanded angle (%f) below minimum (%f), commanding to minimum."\
00917 %(self.servo_id, ang, self.settings['min_ang'])
00918 return self.settings['min_ang']
00919 elif ang > self.settings['max_ang']:
00920 print "Servo %d: Commanded angle (%f) above maximum (%f), commanding to maximum."\
00921 %(self.servo_id, ang, self.settings['max_ang'])
00922 return self.settings['max_ang']
00923 else:
00924 return ang
00925
00926 def angvel_to_bytes(self, angvel):
00927 ''' Convert Angular velocity, in rad/sec, to hi, lo bytes.
00928 '''
00929 if self.settings['flipped']:
00930 angvel *= -1.
00931 rpm = angvel / (2 * math.pi) * 60.0
00932 angvel_enc = int(round( rpm / 0.11443 ))
00933 hi = abs(angvel_enc) / 256
00934 lo = abs(angvel_enc) % 256
00935 if angvel_enc < 0:
00936 hi += 4
00937 return hi, lo
00938
00939 def bytes_to_angvel(self, hi, lo):
00940 '''returns the current angular velocity from hi, lo bytes
00941 '''
00942 val = lo + hi * 256
00943 raw_mag = (val % 1024) * 0.11443
00944 mag = (raw_mag / 60.) * 2 * math.pi
00945 if hi >> 2 == 1:
00946 mag *= -1
00947 if self.settings['flipped']:
00948 mag *= -1
00949 return mag
00950
00951 def clip_angvel(self, angvel):
00952 '''Clip commanded velocity to below the allowed maximum.
00953 negative angvels will be set to maximum.
00954 '''
00955 if self.settings['max_speed'] == 0.:
00956 if abs(angvel) > 12.2595:
00957 print("Servo %d: Tried to set ang vel to %f, "
00958 "above robotis allowed range (%f), "
00959 "setting to maximum (%f)."
00960 %(self.servo_id, angvel, 12.2595 , 12.2595))
00961 return np.clip(angvel, -12.2595, 12.2595)
00962 else:
00963 return angvel
00964 elif abs(angvel) > self.settings['max_speed']:
00965 print("Servo %d: Tried to set ang vel to %f, "
00966 "above configured maximum (%f), "
00967 "setting to maximum (%f)."
00968 %(self.servo_id, angvel,
00969 self.settings['max_speed'], self.settings['max_speed']))
00970 return np.clip(angvel, -self.settings['max_speed'], self.settings['max_speed'])
00971 else:
00972 return angvel
00973
00974
00975 def discover_servos(dev='/dev/ttyUSB0', ids=None, baudrates=None, number=255):
00976 '''Discover all servos on a USB2Dynamixel_Device using PING command.
00977 Checks all servo IDs at all Baudrates, stopping after 'number' of servos are found.
00978 Can specify smaller ranges to check instead.
00979 '''
00980 if baudrates is None:
00981 baudrates = [9600, 19200, 57600, 115200, 200000, 250000, 400000, 500000, 1000000, 2250000, 2500000, 3000000]
00982 print "Searching for ID's on %s" %dev
00983 num = 0
00984 for baudrate in baudrates:
00985 print "\nBaudrate %d:" %baudrate
00986 try:
00987 dyn = Dynamixel_Chain(dev, baudrate=baudrate, ids = ids)
00988 num += len(dyn.servos.keys())
00989 dyn.servo_dev.close()
00990 del(dyn)
00991 if num >= number:
00992 print "Found %d servos. Stopping." %num
00993 break
00994 except RuntimeError as rte:
00995 print rte
00996
00997 def recover_servo(dyn):
00998 import time
00999 ''' Recovers a bricked servo by booting into diagnostic bootloader and resetting '''
01000 raw_input('Make sure only one servo connected to USB2Dynamixel Device [ENTER]')
01001 raw_input('Disconnect power from the servo, but leave USB2Dynamixel connected to USB. [ENTER]')
01002
01003 dyn.servo_dev.setBaudrate( 57600 )
01004
01005 print 'Get Ready. Be ready to reconnect servo power when I say \'GO!\''
01006 print 'After a second, the red LED should become permanently lit.'
01007 print 'After that happens, Ctrl + C to kill this program.'
01008 print
01009 print 'Then, you will need to use a serial terminal to issue additional commands.',
01010 print 'Here is an example using screen as serial terminal:'
01011 print
01012 print 'Command Line: screen /dev/robot/servo_left 57600'
01013 print 'Type: \'h\''
01014 print 'Response: Command : L(oad),G(o),S(ystem),A(pplication),R(eset),D(ump),C(lear)'
01015 print 'Type: \'C\''
01016 print 'Response: * Clear EEPROM '
01017 print 'Type: \'A\''
01018 print 'Response: * Application Mode'
01019 print 'Type: \'G\''
01020 print 'Response: * Go'
01021 print
01022 print 'Should now be able to reconnect to the servo using ID 1'
01023 print
01024 print
01025 raw_input('Ready to reconnect power? [ENTER]')
01026 print 'GO!'
01027
01028 while True:
01029 s.write('#')
01030 time.sleep(0.0001)
01031
01032 def test_servos(dyn, ids=None):
01033 ''' An incomplete test script. Will call most, but not all,
01034 functions on a servo and check for obvious problems.
01035 '''
01036 def confirm():
01037 rsp = raw_input("Do you wish to proceed? (y/n)")
01038 if rsp == 'y':
01039 return True
01040 elif rsp == 'n':
01041 return False
01042 else:
01043 print "Please enter 'y' or 'n'"
01044 return confirm()
01045
01046 print("This test function will test almost all functions available on the "
01047 "specified servos, including continuous motion, and should not be "
01048 "performed if the servo is attached to external hardware "
01049 "that cannot rotate freely.")
01050 if not confirm():
01051 print "Aborting..."
01052 return
01053
01054 import time
01055 if ids is None:
01056 ids = dyn.servos.keys()
01057 for id in ids:
01058 servo = dyn.servos[id]
01059 assert dyn.ping(id) == []
01060 assert dyn.dev_name == dyn.servo_dev.port
01061 print "Device Name: %s" %dyn.dev_name
01062 model_code = dyn.read_model_number(id)
01063 print "Model Code: %s" %model_code
01064 assert dyn._determine_series(model_code) == servo.series
01065 print "Series: %s" %servo.series
01066 print "Firmware version: %s" %dyn.read_firmware_version(id)
01067 assert dyn.read_address(id, 3, 1) == [id]
01068 print "ID: %d" %id
01069 assert np.allclose(dyn.read_baudrate(id), dyn.servo_dev.baudrate, rtol=0.01)
01070 print "Baudrate: %s" %dyn.servo_dev.baudrate
01071 assert dyn.read_return_delay(id) in range(0,502,2)
01072 print "Return Delay Time: %s (microseconds)" %dyn.read_return_delay(id)
01073 print "Is EEPROM Locked? %s" %dyn.is_eeprom_locked(id)
01074 cw, ccw = dyn.read_angle_limits(id)
01075 print "Servo EEPROM angle limits: %s" %[cw, ccw]
01076 print "Software angle limits: %s" %[servo.settings['min_ang'], servo.settings['max_ang']]
01077 assert np.allclose([cw, ccw], [servo.settings['min_ang'], servo.settings['max_ang']], rtol=0.005)
01078 print "Temperature Alarm Limit: %d C" %dyn.read_temperature_limit(id)
01079 print "Present Temperature: %d C" %dyn.read_temperature(id)
01080 assert dyn.read_temperature(id) < dyn.read_temperature_limit(id)
01081
01082
01083 voltage_limits = dyn.read_voltage_limits(id)
01084 print "Voltage Limits: %sV -- %sV" %(voltage_limits[0], voltage_limits[1])
01085 voltage = dyn.read_voltage(id)
01086 print "Current Voltage: %sV" %voltage
01087 assert (voltage >= voltage_limits[0]) and (voltage <= voltage_limits[1])
01088 print
01089
01090
01091 torque_limit = dyn.read_max_torque(id)
01092 print "Torque Limit: %s%%" %torque_limit
01093 torque_cap = dyn.read_torque_limit(id)
01094 print "Torque output cap: %s%%" %torque_cap
01095 load = dyn.read_load(id)
01096 torque = dyn.read_torque(id)
01097 print "Current Torque: %s%%" %torque
01098 assert torque == load
01099 assert torque < torque_limit
01100 assert torque < torque_cap
01101 assert torque_cap <= torque_limit
01102 print "Enabling Torque"
01103 dyn.enable_torque(id)
01104 print "Torque Enabled: %s" %dyn.is_torque_enabled(id)
01105 assert dyn.is_torque_enabled(id)
01106 print "Disabling Torque"
01107 dyn.disable_torque(id)
01108 print "Torque Enabled: %s" %dyn.is_torque_enabled(id)
01109 assert not dyn.is_torque_enabled(id)
01110 print "Re-enabling torque"
01111 dyn.enable_torque(id)
01112 print
01113
01114
01115 dyn.set_status_return_level(id, 2)
01116 srl = dyn.read_status_return_level(id)[0]
01117 print "Status Return Level: %s" %srl
01118 assert srl == 2
01119 print
01120
01121
01122 print "Turning LED On"
01123 dyn.set_led(id, True)
01124 assert dyn.is_led_on(id)
01125 time.sleep(2)
01126 dyn.set_led(id, False)
01127 assert not dyn.is_led_on(id)
01128 print
01129
01130
01131 if servo.series == 'MX':
01132 p, i, d = dyn.read_pid_gains(id)
01133 print "PID Gains:\n\tP:%s\n\tI:%s\n\tD:%s" %(p, i, d)
01134 print "Setting PID Gains to near max. (31, 124, 1)"
01135 dyn.set_pid_gains(id, kp=31, ki=124, kd=1)
01136 p1, i1, d1 = dyn.read_pid_gains(id)
01137 print "PID Gains:\n\tP:%s\n\tI:%s\n\tD:%s" %(p1, i1, d1)
01138 assert np.allclose(p1, 31, rtol=0.01)
01139 assert np.allclose(i1, 124, rtol=0.01)
01140 assert np.allclose(d1, 1., rtol=0.01)
01141 dyn.set_pid_gains(id, kp=p, ki=i, kd=d)
01142 elif servo.series == 'RX':
01143 cms = dyn.read_compliance_margins(id)
01144 print "Compliance Margins:\n\tCW: %s\n\tCCW: %s" %tuple(cms)
01145 print "Setting compliance margins to [PI/8, PI/7]"
01146 dyn.set_compliance_margins(id, cw=np.pi/8, ccw=np.pi/7)
01147 print "Compliance Margins:\n\tCW: %s\n\tCCW: %s" %tuple(dyn.read_compliance_margins(id))
01148 assert np.allclose([np.pi/8, np.pi/7], dyn.read_compliance_margins(id), rtol=0.01)
01149 dyn.set_compliance_margins(id, cw=cms[0], ccw=cms[1])
01150 css = dyn.read_compliance_slopes(id)
01151 print "Compliance Slopes:\n\tCW: %s\n\tCCW: %s" %tuple(css)
01152 print "Setting compliance slopes to steps: [2, 4]"
01153 dyn.set_compliance_slopes(id, cw=2, ccw=4)
01154 assert dyn.read_compliance_slopes(id) == [2,4]
01155 print "Compliance Slopes:\n\tCW: %s\n\tCCW: %s" %tuple(dyn.read_compliance_slopes(id))
01156 dyn.set_compliance_slopes(id, css[0], css[1])
01157 print "Punch: %s" %dyn.read_punch(id)
01158 print
01159
01160
01161 gp = dyn.read_goal_position(id)
01162 print "Goal Angle: %s" %gp
01163 gav = dyn.read_goal_angvel(id)
01164 print "Goal Angular Velocity: %s" %gav
01165 ang = dyn.read_angle(id)
01166 print "Present Position: %s" %ang
01167 av = dyn.read_angvel(id)
01168 print "Present Angular Velocity: %s" %av
01169 assert np.allclose(gp, ang, rtol=0.01)
01170 assert av <= gav
01171 print "Currently Moving? %s" %dyn.is_moving(id)
01172 while dyn.is_moving(id):
01173 time.sleep(0.1)
01174 assert dyn.read_angvel(id) == 0
01175 print
01176
01177
01178 for goal in [servo.settings['min_ang'], servo.settings['max_ang']]:
01179 print "Moving to angle: %s at %s rad/sec." %(goal, np.pi/4)
01180 dyn.move_angle(id, goal, np.pi/4)
01181 time.sleep(0.5)
01182 while dyn.is_moving(id):
01183 poss, vels, ids = dyn.read_angs_angvels()
01184 idx = ids.index(id)
01185 print "Position: %s\nVelocity: %s\n" %(poss[idx], vels[idx])
01186 time.sleep(1.5)
01187
01188
01189 print "Returning to 0 at %s rad/sec via syncronous call." %(np.pi/4)
01190 dyn.move_angles_sync(ids=[id], angs=[0], angvels=[np.pi/4])
01191 time.sleep(0.25)
01192 while dyn.is_moving(id):
01193 poss, vels, ids = dyn.read_angs_angvels()
01194 idx = ids.index(id)
01195 print "Position: %s\nVelocity: %s\n" %(poss[idx], vels[idx])
01196 time.sleep(1)
01197 print
01198
01199
01200 print "Enabling Continuous turn"
01201 als = dyn.read_angle_limits(id)
01202 dyn.enable_cont_turn(id)
01203 assert dyn.is_cont_turn_enabled(id)
01204 print "Moving clockwise at 180 deg/sec (1.57 rad/s)"
01205 dyn.set_angvel(id, -np.pi)
01206 time.sleep(0.5)
01207 av = dyn.read_angvel(id)
01208 print "Moving Velocity: %s" %av
01209 time.sleep(4)
01210 print "Moving counter-clockwise at 180 deg/sec (1.57 rad/s)"
01211 dyn.set_angvel(id, np.pi)
01212 time.sleep(0.5)
01213 av = dyn.read_angvel(id)
01214 print "Moving Velocity: %s" %av
01215 time.sleep(4)
01216 print "Disabling Continuous turn"
01217 dyn.disable_cont_turn(id)
01218 assert not dyn.is_cont_turn_enabled(id)
01219 dyn.set_angle_limits(id, als[0], als[1])
01220 print "Resetting angle limits to: ", dyn.read_angle_limits(id)
01221 assert dyn.read_angle_limits(id) == als
01222 print
01223
01224
01225 if servo.series == 'RX':
01226 print "Tests completed successfully"
01227 return
01228
01229
01230 print "Is torque contro enabled? %s" %dyn.is_torque_enabled(id)
01231 print "Enabling torque control"
01232 dyn.enable_torque_control(id)
01233 print "Is torque contro enabled? %s" %dyn.is_torque_enabled(id)
01234 assert dyn.is_torque_control_enabled(id)
01235
01236 gt = dyn.read_goal_torque(id)
01237 print "Goal Torque: %s" %gt
01238 print "Setting goal torque to ~ +50%"
01239 dyn.set_goal_torque(id, 50)
01240 assert np.allclose(dyn.read_goal_torque(id), 50, rtol=0.01)
01241 time.sleep(1)
01242 print "Present torque: %s" %dyn.read_torque(id)
01243 print "Present current: %sA" %dyn.read_current(id)
01244 time.sleep(1)
01245
01246 print "Setting goal torque to ~ -50% (reverse)"
01247 dyn.set_goal_torque(id, -50)
01248 assert np.allclose(dyn.read_goal_torque(id), -50, rtol=0.01)
01249 time.sleep(1)
01250 print "Present torque: %s" %dyn.read_torque(id)
01251 time.sleep(1)
01252
01253 print "Disabling torque control"
01254 dyn.disable_torque_control(id)
01255 assert not dyn.is_torque_control_enabled(id)
01256 print "Present current: %sA" %dyn.read_current(id)
01257 print
01258
01259 ga = dyn.read_goal_acceleration(id)
01260 print "Goal Acceleration: %s rad/s^2" %ga
01261 print "Setting goal acceleration to ~450 deg/s^2"
01262 dyn.set_goal_acceleration(id, 450)
01263 nga = dyn.read_goal_acceleration(id)
01264 assert np.allclose(nga, 450, rtol=0.01)
01265 print "Setting goal acceleration back to %s" %ga
01266 dyn.set_goal_acceleration(id, ga)
01267 assert np.allclose(dyn.read_goal_acceleration(id), ga, rtol=0.01)
01268 print "Tests completed successfully"
01269
01270 if __name__ == '__main__':
01271 usage = ("Interface for controlling one or more robotis servos on a single bus\n"+
01272 "\tUse as below from the commandline, or:\n"+
01273 "\t\timport lib_dynamixel as ld\n"+
01274 "\t\tdyn = ld.Dynamixel_Chain()\n"+
01275 "\t\tdyn.move_angle(ang, id)")
01276 p = optparse.OptionParser(usage=usage)
01277 p.add_option('-d', action='store', type='string', dest='dev_name',
01278 help='Required: Device string for USB2Dynamixel. [i.e. /dev/ttyUSB0 for Linux, \'0\' (for COM1) on Windows]')
01279 p.add_option('--scan', action='store_true', dest='scan', default=False,
01280 help='Scan the device for servo IDs attached.')
01281 p.add_option('--recover', action='store_true', dest='recover', default=False,
01282 help='Recover from a bricked servo (restores to factory defaults).')
01283 p.add_option('--ang', action='store', type='float', dest='ang',
01284 help='Angle to move the servo to (degrees).')
01285 p.add_option('--ang_vel', action='store', type='float', dest='ang_vel',
01286 help='angular velocity. (degrees/sec) [default = 50]', default=50)
01287 p.add_option('--id', action='store', type='int', dest='id',
01288 help='id of servo to connect to, [default = 1]', default=1)
01289 p.add_option('--baud', action='store', type='int', dest='baud',
01290 help='baudrate for USB2Dynamixel connection [default = 57600]', default=57600)
01291
01292 opt, args = p.parse_args()
01293
01294 if opt.dev_name == None:
01295 p.print_help()
01296 sys.exit(0)
01297
01298 dyn = Dynamixel_Chain(opt.dev_name, opt.baud)
01299
01300 if opt.scan:
01301 discover_servos( dyn )
01302
01303 if opt.recover:
01304 recover_servo( dyn )
01305
01306 if opt.ang != None:
01307 dyn.move_angle(opt.id, math.radians(opt.ang), math.radians(opt.ang_vel) )