dynamixel_io.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 # -*- coding: utf-8 -*-
00003 #
00004 # Software License Agreement (BSD License)
00005 #
00006 # Copyright (c) 2010-2011, Cody Jorgensen, Antons Rebguns.
00007 # All rights reserved.
00008 #
00009 # Redistribution and use in source and binary forms, with or without
00010 # modification, are permitted provided that the following conditions
00011 # are met:
00012 #
00013 #  * Redistributions of source code must retain the above copyright
00014 #    notice, this list of conditions and the following disclaimer.
00015 #  * Redistributions in binary form must reproduce the above
00016 #    copyright notice, this list of conditions and the following
00017 #    disclaimer in the documentation and/or other materials provided
00018 #    with the distribution.
00019 #  * Neither the name of University of Arizona nor the names of its
00020 #    contributors may be used to endorse or promote products derived
00021 #    from this software without specific prior written permission.
00022 #
00023 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00024 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00025 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00026 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00027 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00028 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00029 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00030 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00031 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00032 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00033 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00034 # POSSIBILITY OF SUCH DAMAGE.
00035 
00036 
00037 __author__ = 'Cody Jorgensen, Antons Rebguns'
00038 __copyright__ = 'Copyright (c) 2010-2011 Cody Jorgensen, Antons Rebguns'
00039 
00040 __license__ = 'BSD'
00041 __maintainer__ = 'Antons Rebguns'
00042 __email__ = 'anton@email.arizona.edu'
00043 
00044 
00045 import time
00046 import serial
00047 from array import array
00048 from binascii import b2a_hex
00049 from threading import Lock
00050 
00051 from dynamixel_const import *
00052 
00053 exception = None
00054 
00055 class DynamixelIO(object):
00056     """ Provides low level IO with the Dynamixel servos through pyserial. Has the
00057     ability to write instruction packets, request and read register value
00058     packets, send and receive a response to a ping packet, and send a SYNC WRITE
00059     multi-servo instruction packet.
00060     """
00061 
00062     def __init__(self, port, baudrate, readback_echo=False):
00063         """ Constructor takes serial port and baudrate as arguments. """
00064         try:
00065             self.serial_mutex = Lock()
00066             self.ser = None
00067             self.ser = serial.Serial(port)
00068             self.ser.setTimeout(0.015)
00069             self.ser.baudrate = baudrate
00070             self.port_name = port
00071             self.readback_echo = readback_echo
00072         except SerialOpenError:
00073            raise SerialOpenError(port, baudrate)
00074 
00075     def __del__(self):
00076         """ Destructor calls DynamixelIO.close """
00077         self.close()
00078 
00079     def close(self):
00080         """
00081         Be nice, close the serial port.
00082         """
00083         if self.ser:
00084             self.ser.flushInput()
00085             self.ser.flushOutput()
00086             self.ser.close()
00087 
00088     def __write_serial(self, data):
00089         self.ser.flushInput()
00090         self.ser.flushOutput()
00091         self.ser.write(data)
00092         if self.readback_echo:
00093             self.ser.read(len(data))
00094 
00095     def __read_response(self, servo_id):
00096         data = []
00097 
00098         try:
00099             data.extend(self.ser.read(4))
00100             if not data[0:2] == ['\xff', '\xff']: raise Exception('Wrong packet prefix %s' % data[0:2])
00101             data.extend(self.ser.read(ord(data[3])))
00102             data = array('B', ''.join(data)).tolist() # [int(b2a_hex(byte), 16) for byte in data]
00103         except Exception, e:
00104             raise DroppedPacketError('Invalid response received from motor %d. %s' % (servo_id, e))
00105 
00106         # verify checksum
00107         checksum = 255 - sum(data[2:-1]) % 256
00108         if not checksum == data[-1]: raise ChecksumError(servo_id, data, checksum)
00109 
00110         return data
00111 
00112     def read(self, servo_id, address, size):
00113         """ Read "size" bytes of data from servo with "servo_id" starting at the
00114         register with "address". "address" is an integer between 0 and 57. It is
00115         recommended to use the constants in module dynamixel_const for readability.
00116 
00117         To read the position from servo with id 1, the method should be called
00118         like:
00119             read(1, DXL_GOAL_POSITION_L, 2)
00120         """
00121         # Number of bytes following standard header (0xFF, 0xFF, id, length)
00122         length = 4  # instruction, address, size, checksum
00123 
00124         # directly from AX-12 manual:
00125         # Check Sum = ~ (ID + LENGTH + INSTRUCTION + PARAM_1 + ... + PARAM_N)
00126         # If the calculated value is > 255, the lower byte is the check sum.
00127         checksum = 255 - ( (servo_id + length + DXL_READ_DATA + address + size) % 256 )
00128 
00129         # packet: FF  FF  ID LENGTH INSTRUCTION PARAM_1 ... CHECKSUM
00130         packet = [0xFF, 0xFF, servo_id, length, DXL_READ_DATA, address, size, checksum]
00131         packetStr = array('B', packet).tostring() # same as: packetStr = ''.join([chr(byte) for byte in packet])
00132 
00133         with self.serial_mutex:
00134             self.__write_serial(packetStr)
00135 
00136             # wait for response packet from the motor
00137             timestamp = time.time()
00138             time.sleep(0.0013)#0.00235)
00139 
00140             # read response
00141             data = self.__read_response(servo_id)
00142             data.append(timestamp)
00143 
00144         return data
00145 
00146     def write(self, servo_id, address, data):
00147         """ Write the values from the "data" list to the servo with "servo_id"
00148         starting with data[0] at "address", continuing through data[n-1] at
00149         "address" + (n-1), where n = len(data). "address" is an integer between
00150         0 and 49. It is recommended to use the constants in module dynamixel_const
00151         for readability. "data" is a list/tuple of integers.
00152 
00153         To set servo with id 1 to position 276, the method should be called
00154         like:
00155             write(1, DXL_GOAL_POSITION_L, (20, 1))
00156         """
00157         # Number of bytes following standard header (0xFF, 0xFF, id, length)
00158         length = 3 + len(data)  # instruction, address, len(data), checksum
00159 
00160         # directly from AX-12 manual:
00161         # Check Sum = ~ (ID + LENGTH + INSTRUCTION + PARAM_1 + ... + PARAM_N)
00162         # If the calculated value is > 255, the lower byte is the check sum.
00163         checksum = 255 - ((servo_id + length + DXL_WRITE_DATA + address + sum(data)) % 256)
00164 
00165         # packet: FF  FF  ID LENGTH INSTRUCTION PARAM_1 ... CHECKSUM
00166         packet = [0xFF, 0xFF, servo_id, length, DXL_WRITE_DATA, address]
00167         packet.extend(data)
00168         packet.append(checksum)
00169 
00170         packetStr = array('B', packet).tostring() # packetStr = ''.join([chr(byte) for byte in packet])
00171 
00172         with self.serial_mutex:
00173             self.__write_serial(packetStr)
00174 
00175             # wait for response packet from the motor
00176             timestamp = time.time()
00177             time.sleep(0.0013)
00178 
00179             # read response
00180             data = self.__read_response(servo_id)
00181             data.append(timestamp)
00182 
00183         return data
00184 
00185     def sync_write(self, address, data):
00186         """ Use Broadcast message to send multiple servos instructions at the
00187         same time. No "status packet" will be returned from any servos.
00188         "address" is an integer between 0 and 49. It is recommended to use the
00189         constants in module dynamixel_const for readability. "data" is a tuple of
00190         tuples. Each tuple in "data" must contain the servo id followed by the
00191         data that should be written from the starting address. The amount of
00192         data can be as long as needed.
00193 
00194         To set servo with id 1 to position 276 and servo with id 2 to position
00195         550, the method should be called like:
00196             sync_write(DXL_GOAL_POSITION_L, ( (1, 20, 1), (2 ,38, 2) ))
00197         """
00198         # Calculate length and sum of all data
00199         flattened = [value for servo in data for value in servo]
00200 
00201         # Number of bytes following standard header (0xFF, 0xFF, id, length) plus data
00202         length = 4 + len(flattened)
00203 
00204         checksum = 255 - ((DXL_BROADCAST + length + \
00205                           DXL_SYNC_WRITE + address + len(data[0][1:]) + \
00206                           sum(flattened)) % 256)
00207 
00208         # packet: FF  FF  ID LENGTH INSTRUCTION PARAM_1 ... CHECKSUM
00209         packet = [0xFF, 0xFF, DXL_BROADCAST, length, DXL_SYNC_WRITE, address, len(data[0][1:])]
00210         packet.extend(flattened)
00211         packet.append(checksum)
00212 
00213         packetStr = array('B', packet).tostring() # packetStr = ''.join([chr(byte) for byte in packet])
00214 
00215         with self.serial_mutex:
00216             self.__write_serial(packetStr)
00217 
00218     def ping(self, servo_id):
00219         """ Ping the servo with "servo_id". This causes the servo to return a
00220         "status packet". This can tell us if the servo is attached and powered,
00221         and if so, if there are any errors.
00222         """
00223         # Number of bytes following standard header (0xFF, 0xFF, id, length)
00224         length = 2  # instruction, checksum
00225 
00226         # directly from AX-12 manual:
00227         # Check Sum = ~ (ID + LENGTH + INSTRUCTION + PARAM_1 + ... + PARAM_N)
00228         # If the calculated value is > 255, the lower byte is the check sum.
00229         checksum = 255 - ((servo_id + length + DXL_PING) % 256)
00230 
00231         # packet: FF  FF  ID LENGTH INSTRUCTION CHECKSUM
00232         packet = [0xFF, 0xFF, servo_id, length, DXL_PING, checksum]
00233         packetStr = array('B', packet).tostring()
00234 
00235         with self.serial_mutex:
00236             self.__write_serial(packetStr)
00237 
00238             # wait for response packet from the motor
00239             timestamp = time.time()
00240             time.sleep(0.0013)
00241 
00242             # read response
00243             try:
00244                 response = self.__read_response(servo_id)
00245                 response.append(timestamp)
00246             except Exception, e:
00247                 response = []
00248 
00249         if response:
00250             self.exception_on_error(response[4], servo_id, 'ping')
00251         return response
00252 
00253     def test_bit(self, number, offset):
00254         mask = 1 << offset
00255         return (number & mask)
00256 
00257 
00258     ######################################################################
00259     # These function modify EEPROM data which persists after power cycle #
00260     ######################################################################
00261 
00262     def set_id(self, old_id, new_id):
00263         """
00264         Sets a new unique number to identify a motor. The range from 1 to 253
00265         (0xFD) can be used.
00266         """
00267         response = self.write(old_id, DXL_ID, [new_id])
00268         if response:
00269             self.exception_on_error(response[4], old_id, 'setting id to %d' % new_id)
00270         return response
00271 
00272     def set_baud_rate(self, servo_id, baud_rate):
00273         """
00274         Sets servo communication speed. The range from 0 to 254.
00275         """
00276         response = self.write(servo_id, DXL_BAUD_RATE, [baud_rate])
00277         if response:
00278             self.exception_on_error(response[4], servo_id, 'setting baud rate to %d' % baud_rate)
00279         return response
00280 
00281     def set_return_delay_time(self, servo_id, delay):
00282         """
00283         Sets the delay time from the transmission of Instruction Packet until
00284         the return of Status Packet. 0 to 254 (0xFE) can be used, and the delay
00285         time per data value is 2 usec.
00286         """
00287         response = self.write(servo_id, DXL_RETURN_DELAY_TIME, [delay])
00288         if response:
00289             self.exception_on_error(response[4], servo_id, 'setting return delay time to %d' % delay)
00290         return response
00291 
00292     def set_angle_limit_cw(self, servo_id, angle_cw):
00293         """
00294         Set the min (CW) angle of rotation limit.
00295         """
00296         loVal = int(angle_cw % 256)
00297         hiVal = int(angle_cw >> 8)
00298 
00299         response = self.write(servo_id, DXL_CW_ANGLE_LIMIT_L, (loVal, hiVal))
00300         if response:
00301             self.exception_on_error(response[4], servo_id, 'setting CW angle limits to %d' % angle_cw)
00302         return response
00303 
00304     def set_angle_limit_ccw(self, servo_id, angle_ccw):
00305         """
00306         Set the max (CCW) angle of rotation limit.
00307         """
00308         loVal = int(angle_ccw % 256)
00309         hiVal = int(angle_ccw >> 8)
00310 
00311         response = self.write(servo_id, DXL_CCW_ANGLE_LIMIT_L, (loVal, hiVal))
00312         if response:
00313             self.exception_on_error(response[4], servo_id, 'setting CCW angle limits to %d' % angle_ccw)
00314         return response
00315 
00316     def set_angle_limits(self, servo_id, min_angle, max_angle):
00317         """
00318         Set the min (CW) and max (CCW) angle of rotation limits.
00319         """
00320         loMinVal = int(min_angle % 256)
00321         hiMinVal = int(min_angle >> 8)
00322         loMaxVal = int(max_angle % 256)
00323         hiMaxVal = int(max_angle >> 8)
00324 
00325         # set 4 register values with low and high bytes for min and max angles
00326         response = self.write(servo_id, DXL_CW_ANGLE_LIMIT_L, (loMinVal, hiMinVal, loMaxVal, hiMaxVal))
00327         if response:
00328             self.exception_on_error(response[4], servo_id, 'setting CW and CCW angle limits to %d and %d' %(min_angle, max_angle))
00329         return response
00330 
00331     def set_drive_mode(self, servo_id, is_slave=False, is_reverse=False):
00332         """
00333         Sets the drive mode for EX-106 motors
00334         """
00335         drive_mode = (is_slave << 1) + is_reverse
00336 
00337         response = self.write(servo_id, DXL_DRIVE_MODE, [drive_mode])
00338         if response:
00339             self.exception_on_error(response[4], servo_id, 'setting drive mode to %d' % drive_mode)
00340         return response
00341 
00342     def set_voltage_limit_min(self, servo_id, min_voltage):
00343         """
00344         Set the minimum voltage limit.
00345         NOTE: the absolute min is 5v
00346         """
00347 
00348         if min_voltage < 5: min_voltage = 5
00349         minVal = int(min_voltage * 10)
00350 
00351         response = self.write(servo_id, DXL_DOWN_LIMIT_VOLTAGE, [minVal])
00352         if response:
00353             self.exception_on_error(response[4], servo_id, 'setting minimum voltage level to %d' % min_voltage)
00354         return response
00355 
00356     def set_voltage_limit_max(self, servo_id, max_voltage):
00357         """
00358         Set the maximum voltage limit.
00359         NOTE: the absolute min is 25v
00360         """
00361 
00362         if max_voltage > 25: max_voltage = 25
00363         maxVal = int(max_voltage * 10)
00364 
00365         response = self.write(servo_id, DXL_UP_LIMIT_VOLTAGE, [maxVal])
00366         if response:
00367             self.exception_on_error(response[4], servo_id, 'setting maximum voltage level to %d' % max_voltage)
00368         return response
00369 
00370     def set_voltage_limits(self, servo_id, min_voltage, max_voltage):
00371         """
00372         Set the min and max voltage limits.
00373         NOTE: the absolute min is 5v and the absolute max is 25v
00374         """
00375 
00376         if min_voltage < 5: min_voltage = 5
00377         if max_voltage > 25: max_voltage = 25
00378 
00379         minVal = int(min_voltage * 10)
00380         maxVal = int(max_voltage * 10)
00381 
00382         response = self.write(servo_id, DXL_DOWN_LIMIT_VOLTAGE, (minVal, maxVal))
00383         if response:
00384             self.exception_on_error(response[4], servo_id, 'setting min and max voltage levels to %d and %d' %(min_voltage, max_voltage))
00385         return response
00386 
00387 
00388     ###############################################################
00389     # These functions can send a single command to a single servo #
00390     ###############################################################
00391 
00392     def set_torque_enabled(self, servo_id, enabled):
00393         """
00394         Sets the value of the torque enabled register to 1 or 0. When the
00395         torque is disabled the servo can be moved manually while the motor is
00396         still powered.
00397         """
00398         response = self.write(servo_id, DXL_TORQUE_ENABLE, [enabled])
00399         if response:
00400             self.exception_on_error(response[4], servo_id, '%sabling torque' % 'en' if enabled else 'dis')
00401         return response
00402 
00403     def set_compliance_margin_cw(self, servo_id, margin):
00404         """
00405         The error between goal position and present position in CW direction.
00406         The range of the value is 0 to 255, and the unit is the same as Goal Position.
00407         The greater the value, the more difference occurs.
00408         """
00409         response = self.write(servo_id, DXL_CW_COMPLIANCE_MARGIN, [margin])
00410         if response:
00411             self.exception_on_error(response[4], servo_id, 'setting CW compliance margin to %d' % margin)
00412         return response
00413 
00414     def set_compliance_margin_ccw(self, servo_id, margin):
00415         """
00416         The error between goal position and present position in CCW direction.
00417         The range of the value is 0 to 255, and the unit is the same as Goal Position.
00418         The greater the value, the more difference occurs.
00419         """
00420         response = self.write(servo_id, DXL_CCW_COMPLIANCE_MARGIN, [margin])
00421         if response:
00422             self.exception_on_error(response[4], servo_id, 'setting CCW compliance margin to %d' % margin)
00423         return response
00424 
00425     def set_compliance_margins(self, servo_id, margin_cw, margin_ccw):
00426         """
00427         The error between goal position and present position in CCW direction.
00428         The range of the value is 0 to 255, and the unit is the same as Goal Position.
00429         The greater the value, the more difference occurs.
00430         """
00431         response = self.write(servo_id, DXL_CW_COMPLIANCE_MARGIN, (margin_cw, margin_ccw))
00432         if response:
00433             self.exception_on_error(response[4], servo_id, 'setting CW and CCW compliance margins to values %d and %d' %(margin_cw, margin_ccw))
00434         return response
00435 
00436     def set_compliance_slope_cw(self, servo_id, slope):
00437         """
00438         Sets the level of Torque near the goal position in CW direction.
00439         Compliance Slope is set in 7 steps, the higher the value, the more flexibility is obtained.
00440         """
00441         response = self.write(servo_id, DXL_CW_COMPLIANCE_SLOPE, [slope])
00442         if response:
00443             self.exception_on_error(response[4], servo_id, 'setting CW compliance slope to %d' %  slope)
00444         return response
00445 
00446     def set_compliance_slope_ccw(self, servo_id, slope):
00447         """
00448         Sets the level of Torque near the goal position in CCW direction.
00449         Compliance Slope is set in 7 steps, the higher the value, the more flexibility is obtained.
00450         """
00451         response = self.write(servo_id, DXL_CCW_COMPLIANCE_SLOPE, [slope])
00452         if response:
00453             self.exception_on_error(response[4], servo_id, 'setting CCW compliance slope to %d' % slope)
00454         return response
00455 
00456     def set_compliance_slopes(self, servo_id, slope_cw, slope_ccw):
00457         """
00458         Sets the level of Torque near the goal position in CW/CCW direction.
00459         Compliance Slope is set in 7 steps, the higher the value, the more flexibility is obtained.
00460         """
00461         response = self.write(servo_id, DXL_CW_COMPLIANCE_SLOPE, (slope_cw, slope_ccw))
00462         if response:
00463             self.exception_on_error(response[4], servo_id, 'setting CW and CCW compliance slopes to %d and %d' %(slope_cw, slope_ccw))
00464         return response
00465 
00466     def set_d_gain(self, servo_id, d_gain):
00467         """
00468         Sets the value of derivative action of PID controller.
00469         Gain value is in range 0 to 254.
00470         """
00471         response = self.write(servo_id, DXL_D_GAIN, [d_gain])
00472         if response:
00473             self.exception_on_error(response[4], servo_id, 'setting D gain value of PID controller to %d' % slope)
00474         return response
00475 
00476     def set_i_gain(self, servo_id, i_gain):
00477         """
00478         Sets the value of integral action of PID controller.
00479         Gain value is in range 0 to 254.
00480         """
00481         response = self.write(servo_id, DXL_I_GAIN, [i_gain])
00482         if response:
00483             self.exception_on_error(response[4], servo_id, 'setting I gain value of PID controller to %d' % slope)
00484         return response
00485 
00486     def set_p_gain(self, servo_id, p_gain):
00487         """
00488         Sets the value of proportional action of PID controller.
00489         Gain value is in range 0 to 254.
00490         """
00491         response = self.write(servo_id, DXL_P_GAIN, [p_gain])
00492         if response:
00493             self.exception_on_error(response[4], servo_id, 'setting P gain value of PID controller to %d' % slope)
00494         return response
00495 
00496     def set_punch(self, servo_id, punch):
00497         """
00498         Sets the limit value of torque being reduced when the output torque is
00499         decreased in the Compliance Slope area. In other words, it is the mimimum
00500         torque. The initial value is 32 (0x20) and can be extended up to 1023
00501         (0x3FF). (Refer to Compliance margin & Slope)
00502         """
00503         loVal = int(punch % 256)
00504         hiVal = int(punch >> 8)
00505         response = self.write(servo_id, DXL_PUNCH_L, (loVal, hiVal))
00506         if response:
00507             self.exception_on_error(response[4], servo_id, 'setting punch to %d' % punch)
00508         return response
00509 
00510     def set_acceleration(self, servo_id, acceleration):
00511         """
00512         Sets the acceleration. The unit is 8.583 Degree / sec^2.
00513         0 - acceleration control disabled, 1-254 - valid range for acceleration. 
00514         """
00515 
00516         model = self.get_model_number(servo_id)
00517         if not model in DXL_MODEL_TO_PARAMS:
00518             raise UnsupportedFeatureError(model, DXL_GOAL_ACCELERATION)
00519 
00520         if DXL_GOAL_ACCELERATION in DXL_MODEL_TO_PARAMS[model]['features']:
00521             response = self.write(servo_id, DXL_GOAL_ACCELERATION, (acceleration, ))
00522             if response:
00523                 self.exception_on_error(response[4], servo_id, 'setting acceleration to %d' % acceleration)
00524             return response
00525         else:
00526             raise UnsupportedFeatureError(model, DXL_GOAL_ACCELERATION)
00527 
00528     def set_position(self, servo_id, position):
00529         """
00530         Set the servo with servo_id to the specified goal position.
00531         Position value must be positive.
00532         """
00533         loVal = int(position % 256)
00534         hiVal = int(position >> 8)
00535 
00536         response = self.write(servo_id, DXL_GOAL_POSITION_L, (loVal, hiVal))
00537         if response:
00538             self.exception_on_error(response[4], servo_id, 'setting goal position to %d' % position)
00539         return response
00540 
00541     def set_speed(self, servo_id, speed):
00542         """
00543         Set the servo with servo_id to the specified goal speed.
00544         Speed can be negative only if the dynamixel is in "freespin" mode.
00545         """
00546         # split speed into 2 bytes
00547         if speed >= 0:
00548             loVal = int(speed % 256)
00549             hiVal = int(speed >> 8)
00550         else:
00551             loVal = int((1023 - speed) % 256)
00552             hiVal = int((1023 - speed) >> 8)
00553 
00554         # set two register values with low and high byte for the speed
00555         response = self.write(servo_id, DXL_GOAL_SPEED_L, (loVal, hiVal))
00556         if response:
00557             self.exception_on_error(response[4], servo_id, 'setting moving speed to %d' % speed)
00558         return response
00559 
00560     def set_torque_limit(self, servo_id, torque):
00561         """
00562         Sets the value of the maximum torque limit for servo with id servo_id.
00563         Valid values are 0 to 1023 (0x3FF), and the unit is about 0.1%.
00564         For example, if the value is 512 only 50% of the maximum torque will be used.
00565         If the power is turned on, the value of Max Torque (Address 14, 15) is used as the initial value.
00566         """
00567         loVal = int(torque % 256)
00568         hiVal = int(torque >> 8)
00569 
00570         response = self.write(servo_id, DXL_TORQUE_LIMIT_L, (loVal, hiVal))
00571         if response:
00572             self.exception_on_error(response[4], servo_id, 'setting torque limit to %d' % torque)
00573         return response
00574 
00575     def set_goal_torque(self, servo_id, torque):
00576         """
00577         Set the servo to torque control mode (similar to wheel mode, but controlling the torque)
00578         Valid values are from -1023 to 1023.
00579         Anything outside this range or 'None' disables torque control.
00580         """
00581 
00582         model = self.get_model_number(servo_id)
00583         if not model in DXL_MODEL_TO_PARAMS:
00584             raise UnsupportedFeatureError(model, DXL_TORQUE_CONTROL_MODE)
00585 
00586         valid_torque = torque is not None and torque >= -1023 and torque <= 1023
00587         if torque is not None and torque < 0:
00588             torque = 1024 - torque
00589 
00590         if DXL_TORQUE_CONTROL_MODE in DXL_MODEL_TO_PARAMS[model]['features']:
00591             if valid_torque:
00592                 loVal = int(torque % 256); hiVal = int(torque >> 8);
00593                 response = self.write(servo_id, DXL_GOAL_TORQUE_L, (loVal, hiVal))
00594                 if response:
00595                     self.exception_on_error(response[4], servo_id, 'setting goal torque to %d' % torque)
00596             response = self.write(servo_id, DXL_TORQUE_CONTROL_MODE, (int(valid_torque), ))
00597             if response:
00598                 self.exception_on_error(response[4], servo_id, 'enabling torque mode')
00599             return response
00600         else:
00601             raise UnsupportedFeatureError(model, DXL_TORQUE_CONTROL_MODE)
00602 
00603     def set_position_and_speed(self, servo_id, position, speed):
00604         """
00605         Set the servo with servo_id to specified position and speed.
00606         Speed can be negative only if the dynamixel is in "freespin" mode.
00607         """
00608         # split speed into 2 bytes
00609         if speed >= 0:
00610             loSpeedVal = int(speed % 256)
00611             hiSpeedVal = int(speed >> 8)
00612         else:
00613             loSpeedVal = int((1023 - speed) % 256)
00614             hiSpeedVal = int((1023 - speed) >> 8)
00615 
00616         # split position into 2 bytes
00617         loPositionVal = int(position % 256)
00618         hiPositionVal = int(position >> 8)
00619 
00620         response = self.write(servo_id, DXL_GOAL_POSITION_L, (loPositionVal, hiPositionVal, loSpeedVal, hiSpeedVal))
00621         if response:
00622             self.exception_on_error(response[4], servo_id, 'setting goal position to %d and moving speed to %d' %(position, speed))
00623         return response
00624 
00625 
00626     #################################################################
00627     # These functions can send multiple commands to multiple servos #
00628     # These commands are used in ROS wrapper as they don't send a   #
00629     # response packet, ROS wrapper gets motor states at a set rate  #
00630     #################################################################
00631 
00632     def set_multi_torque_enabled(self, valueTuples):
00633         """
00634         Method to set multiple servos torque enabled.
00635         Should be called as such:
00636         set_multi_servos_to_torque_enabled( (id1, True), (id2, True), (id3, True) )
00637         """
00638         # use sync write to broadcast multi servo message
00639         self.sync_write(DXL_TORQUE_ENABLE, tuple(valueTuples))
00640 
00641     def set_multi_compliance_margin_cw(self, valueTuples):
00642         """
00643         Set different CW compliance margin for multiple servos.
00644         Should be called as such:
00645         set_multi_compliance_margin_cw( ( (id1, margin1), (id2, margin2), (id3, margin3) ) )
00646         """
00647         self.sync_write(DXL_CW_COMPLIANCE_MARGIN, tuple(valueTuples))
00648 
00649     def set_multi_compliance_margin_ccw(self, valueTuples):
00650         """
00651         Set different CCW compliance margin for multiple servos.
00652         Should be called as such:
00653         set_multi_compliance_margin_ccw( ( (id1, margin1), (id2, margin2), (id3, margin3) ) )
00654         """
00655         self.sync_write(DXL_CCW_COMPLIANCE_MARGIN, tuple(valueTuples))
00656 
00657     def set_multi_compliance_margins(self, valueTuples):
00658         """
00659         Set different CW and CCW compliance margins for multiple servos.
00660         Should be called as such:
00661         set_multi_compliance_margins( ( (id1, cw_margin1, ccw_margin1), (id2, cw_margin2, ccw_margin2) ) )
00662         """
00663         self.sync_write(DXL_CW_COMPLIANCE_MARGIN, tuple(valueTuples))
00664 
00665     def set_multi_compliance_slope_cw(self, valueTuples):
00666         """
00667         Set different CW compliance slope for multiple servos.
00668         Should be called as such:
00669         set_multi_compliance_slope_cw( ( (id1, slope1), (id2, slope2), (id3, slope3) ) )
00670         """
00671         self.sync_write(DXL_CW_COMPLIANCE_SLOPE, tuple(valueTuples))
00672 
00673     def set_multi_compliance_slope_ccw(self, valueTuples):
00674         """
00675         Set different CCW compliance slope for multiple servos.
00676         Should be called as such:
00677         set_multi_compliance_slope_ccw( ( (id1, slope1), (id2, slope2), (id3, slope3) ) )
00678         """
00679         self.sync_write(DXL_CCW_COMPLIANCE_SLOPE, tuple(valueTuples))
00680 
00681     def set_multi_compliance_slopes(self, valueTuples):
00682         """
00683         Set different CW and CCW compliance slopes for multiple servos.
00684         Should be called as such:
00685         set_multi_compliance_slopes( ( (id1, cw_slope1, ccw_slope1), (id2, cw_slope2, ccw_slope2) ) )
00686         """
00687         self.sync_write(DXL_CW_COMPLIANCE_SLOPE, tuple(valueTuples))
00688 
00689     def set_multi_punch(self, valueTuples):
00690         """
00691         Set different punch values for multiple servos.
00692         NOTE: according to documentation, currently this value is not being used.
00693         Should be called as such:
00694         set_multi_punch( ( (id1, punch1), (id2, punch2), (id3, punch3) ) )
00695         """
00696         # prepare value tuples for call to syncwrite
00697         writeableVals = []
00698 
00699         for sid,punch in valueTuples:
00700             # split punch into 2 bytes
00701             loVal = int(punch % 256)
00702             hiVal = int(punch >> 8)
00703             writeableVals.append( (sid, loVal, hiVal) )
00704 
00705         # use sync write to broadcast multi servo message
00706         self.sync_write(DXL_PUNCH_L, writeableVals)
00707 
00708     def set_multi_position(self, valueTuples):
00709         """
00710         Set different positions for multiple servos.
00711         Should be called as such:
00712         set_multi_position( ( (id1, position1), (id2, position2), (id3, position3) ) )
00713         """
00714         # prepare value tuples for call to syncwrite
00715         writeableVals = []
00716 
00717         for vals in valueTuples:
00718             sid = vals[0]
00719             position = vals[1]
00720             # split position into 2 bytes
00721             loVal = int(position % 256)
00722             hiVal = int(position >> 8)
00723             writeableVals.append( (sid, loVal, hiVal) )
00724 
00725         # use sync write to broadcast multi servo message
00726         self.sync_write(DXL_GOAL_POSITION_L, writeableVals)
00727 
00728     def set_multi_speed(self, valueTuples):
00729         """
00730         Set different speeds for multiple servos.
00731         Should be called as such:
00732         set_multi_speed( ( (id1, speed1), (id2, speed2), (id3, speed3) ) )
00733         """
00734         # prepare value tuples for call to syncwrite
00735         writeableVals = []
00736 
00737         for vals in valueTuples:
00738             sid = vals[0]
00739             speed = vals[1]
00740 
00741             # split speed into 2 bytes
00742             if speed >= 0:
00743                 loVal = int(speed % 256)
00744                 hiVal = int(speed >> 8)
00745             else:
00746                 loVal = int((1023 - speed) % 256)
00747                 hiVal = int((1023 - speed) >> 8)
00748 
00749             writeableVals.append( (sid, loVal, hiVal) )
00750 
00751         # use sync write to broadcast multi servo message
00752         self.sync_write(DXL_GOAL_SPEED_L, writeableVals)
00753 
00754     def set_multi_torque_limit(self, valueTuples):
00755         """
00756         Set different torque limits for multiple servos.
00757         Should be called as such:
00758         set_multi_torque_limit( ( (id1, torque1), (id2, torque2), (id3, torque3) ) )
00759         """
00760         # prepare value tuples for call to syncwrite
00761         writeableVals = []
00762 
00763         for sid,torque in valueTuples:
00764             # split torque into 2 bytes
00765             loVal = int(torque % 256)
00766             hiVal = int(torque >> 8)
00767             writeableVals.append( (sid, loVal, hiVal) )
00768 
00769         # use sync write to broadcast multi servo message
00770         self.sync_write(DXL_TORQUE_LIMIT_L, writeableVals)
00771 
00772     def set_multi_position_and_speed(self, valueTuples):
00773         """
00774         Set different positions and speeds for multiple servos.
00775         Should be called as such:
00776         set_multi_position_and_speed( ( (id1, position1, speed1), (id2, position2, speed2), (id3, position3, speed3) ) )
00777         """
00778         # prepare value tuples for call to syncwrite
00779         writeableVals = []
00780 
00781         for vals in valueTuples:
00782             sid = vals[0]
00783             position = vals[1]
00784             speed = vals[2]
00785 
00786             # split speed into 2 bytes
00787             if speed >= 0:
00788                 loSpeedVal = int(speed % 256)
00789                 hiSpeedVal = int(speed >> 8)
00790             else:
00791                 loSpeedVal = int((1023 - speed) % 256)
00792                 hiSpeedVal = int((1023 - speed) >> 8)
00793 
00794             # split position into 2 bytes
00795             loPositionVal = int(position % 256)
00796             hiPositionVal = int(position >> 8)
00797             writeableVals.append( (sid, loPositionVal, hiPositionVal, loSpeedVal, hiSpeedVal) )
00798 
00799         # use sync write to broadcast multi servo message
00800         self.sync_write(DXL_GOAL_POSITION_L, tuple(writeableVals))
00801 
00802 
00803     #################################
00804     # Servo status access functions #
00805     #################################
00806 
00807     def get_model_number(self, servo_id):
00808         """ Reads the servo's model number (e.g. 12 for AX-12+). """
00809         response = self.read(servo_id, DXL_MODEL_NUMBER_L, 2)
00810         if response:
00811             self.exception_on_error(response[4], servo_id, 'fetching model number')
00812         return response[5] + (response[6] << 8)
00813 
00814     def get_firmware_version(self, servo_id):
00815         """ Reads the servo's firmware version. """
00816         response = self.read(servo_id, DXL_VERSION, 1)
00817         if response:
00818             self.exception_on_error(response[4], servo_id, 'fetching firmware version')
00819         return response[5]
00820 
00821     def get_return_delay_time(self, servo_id):
00822         """ Reads the servo's return delay time. """
00823         response = self.read(servo_id, DXL_RETURN_DELAY_TIME, 1)
00824         if response:
00825             self.exception_on_error(response[4], servo_id, 'fetching return delay time')
00826         return response[5]
00827 
00828     def get_angle_limits(self, servo_id):
00829         """
00830         Returns the min and max angle limits from the specified servo.
00831         """
00832         # read in 4 consecutive bytes starting with low value of clockwise angle limit
00833         response = self.read(servo_id, DXL_CW_ANGLE_LIMIT_L, 4)
00834         if response:
00835             self.exception_on_error(response[4], servo_id, 'fetching CW/CCW angle limits')
00836         # extract data valus from the raw data
00837         cwLimit = response[5] + (response[6] << 8)
00838         ccwLimit = response[7] + (response[8] << 8)
00839 
00840         # return the data in a dictionary
00841         return {'min':cwLimit, 'max':ccwLimit}
00842 
00843     def get_drive_mode(self, servo_id):
00844         """ Reads the servo's drive mode. """
00845         response = self.read(servo_id, DXL_DRIVE_MODE, 1)
00846         if response:
00847             self.exception_on_error(response[4], servo_id, 'fetching drive mode')
00848         return response[5]
00849 
00850     def get_voltage_limits(self, servo_id):
00851         """
00852         Returns the min and max voltage limits from the specified servo.
00853         """
00854         response = self.read(servo_id, DXL_DOWN_LIMIT_VOLTAGE, 2)
00855         if response:
00856             self.exception_on_error(response[4], servo_id, 'fetching voltage limits')
00857         # extract data valus from the raw data
00858         min_voltage = response[5] / 10.0
00859         max_voltage = response[6] / 10.0
00860 
00861         # return the data in a dictionary
00862         return {'min':min_voltage, 'max':max_voltage}
00863 
00864     def get_position(self, servo_id):
00865         """ Reads the servo's position value from its registers. """
00866         response = self.read(servo_id, DXL_PRESENT_POSITION_L, 2)
00867         if response:
00868             self.exception_on_error(response[4], servo_id, 'fetching present position')
00869         position = response[5] + (response[6] << 8)
00870         return position
00871 
00872     def get_speed(self, servo_id):
00873         """ Reads the servo's speed value from its registers. """
00874         response = self.read(servo_id, DXL_PRESENT_SPEED_L, 2)
00875         if response:
00876             self.exception_on_error(response[4], servo_id, 'fetching present speed')
00877         speed = response[5] + (response[6] << 8)
00878         if speed > 1023:
00879             return 1023 - speed
00880         return speed
00881 
00882     def get_voltage(self, servo_id):
00883         """ Reads the servo's voltage. """
00884         response = self.read(servo_id, DXL_PRESENT_VOLTAGE, 1)
00885         if response:
00886             self.exception_on_error(response[4], servo_id, 'fetching supplied voltage')
00887         return response[5] / 10.0
00888 
00889     def get_current(self, servo_id):
00890         """ Reads the servo's current consumption (if supported by model) """
00891         model = self.get_model_number(servo_id)
00892         if not model in DXL_MODEL_TO_PARAMS:
00893             raise UnsupportedFeatureError(model, DXL_CURRENT_L)
00894 
00895         if DXL_CURRENT_L in DXL_MODEL_TO_PARAMS[model]['features']:
00896             response = self.read(servo_id, DXL_CURRENT_L, 2)
00897             if response:
00898                 self.exception_on_error(response[4], servo_id, 'fetching sensed current')
00899             current = response[5] + (response[6] << 8)
00900             return 0.0045 * (current - 2048)
00901 
00902         if DXL_SENSED_CURRENT_L in DXL_MODEL_TO_PARAMS[model]['features']:
00903             response = self.read(servo_id, DXL_SENSED_CURRENT_L, 2)
00904             if response:
00905                 self.exception_on_error(response[4], servo_id, 'fetching sensed current')
00906             current = response[5] + (response[6] << 8)
00907             return 0.01 * (current - 512)
00908 
00909         else:
00910             raise UnsupportedFeatureError(model, DXL_CURRENT_L)
00911 
00912 
00913     def get_feedback(self, servo_id):
00914         """
00915         Returns the id, goal, position, error, speed, load, voltage, temperature
00916         and moving values from the specified servo.
00917         """
00918         # read in 17 consecutive bytes starting with low value for goal position
00919         response = self.read(servo_id, DXL_GOAL_POSITION_L, 17)
00920 
00921         if response:
00922             self.exception_on_error(response[4], servo_id, 'fetching full servo status')
00923         if len(response) == 24:
00924             # extract data values from the raw data
00925             goal = response[5] + (response[6] << 8)
00926             position = response[11] + (response[12] << 8)
00927             error = position - goal
00928             speed = response[13] + ( response[14] << 8)
00929             if speed > 1023: speed = 1023 - speed
00930             load_raw = response[15] + (response[16] << 8)
00931             load_direction = 1 if self.test_bit(load_raw, 10) else 0
00932             load = (load_raw & int('1111111111', 2)) / 1024.0
00933             if load_direction == 1: load = -load
00934             voltage = response[17] / 10.0
00935             temperature = response[18]
00936             moving = response[21]
00937             timestamp = response[-1]
00938 
00939             # return the data in a dictionary
00940             return { 'timestamp': timestamp,
00941                      'id': servo_id,
00942                      'goal': goal,
00943                      'position': position,
00944                      'error': error,
00945                      'speed': speed,
00946                      'load': load,
00947                      'voltage': voltage,
00948                      'temperature': temperature,
00949                      'moving': bool(moving) }
00950 
00951     def exception_on_error(self, error_code, servo_id, command_failed):
00952         global exception
00953         exception = None
00954         ex_message = '[servo #%d on %s@%sbps]: %s failed' % (servo_id, self.ser.port, self.ser.baudrate, command_failed)
00955 
00956         if not error_code & DXL_OVERHEATING_ERROR == 0:
00957             msg = 'Overheating Error ' + ex_message
00958             exception = FatalErrorCodeError(msg, error_code)
00959         if not error_code & DXL_OVERLOAD_ERROR == 0:
00960             msg = 'Overload Error ' + ex_message
00961             exception = FatalErrorCodeError(msg, error_code)
00962         if not error_code & DXL_INPUT_VOLTAGE_ERROR == 0:
00963             msg = 'Input Voltage Error ' + ex_message
00964             exception = NonfatalErrorCodeError(msg, error_code)
00965         if not error_code & DXL_ANGLE_LIMIT_ERROR == 0:
00966             msg = 'Angle Limit Error ' + ex_message
00967             exception = NonfatalErrorCodeError(msg, error_code)
00968         if not error_code & DXL_RANGE_ERROR == 0:
00969             msg = 'Range Error ' + ex_message
00970             exception = NonfatalErrorCodeError(msg, error_code)
00971         if not error_code & DXL_CHECKSUM_ERROR == 0:
00972             msg = 'Checksum Error ' + ex_message
00973             exception = NonfatalErrorCodeError(msg, error_code)
00974         if not error_code & DXL_INSTRUCTION_ERROR == 0:
00975             msg = 'Instruction Error ' + ex_message
00976             exception = NonfatalErrorCodeError(msg, error_code)
00977 
00978 class SerialOpenError(Exception):
00979     def __init__(self, port, baud):
00980         Exception.__init__(self)
00981         self.message = "Cannot open port '%s' at %d bps" %(port, baud)
00982         self.port = port
00983         self.baud = baud
00984     def __str__(self):
00985         return self.message
00986 
00987 class ChecksumError(Exception):
00988     def __init__(self, servo_id, response, checksum):
00989         Exception.__init__(self)
00990         self.message = 'Checksum received from motor %d does not match the expected one (%d != %d)' \
00991                        %(servo_id, response[-1], checksum)
00992         self.response_data = response
00993         self.expected_checksum = checksum
00994     def __str__(self):
00995         return self.message
00996 
00997 class FatalErrorCodeError(Exception):
00998     def __init__(self, message, ec_const):
00999         Exception.__init__(self)
01000         self.message = message
01001         self.error_code = ec_const
01002     def __str__(self):
01003         return self.message
01004 
01005 class NonfatalErrorCodeError(Exception):
01006     def __init__(self, message, ec_const):
01007         Exception.__init__(self)
01008         self.message = message
01009         self.error_code = ec_const
01010     def __str__(self):
01011         return self.message
01012 
01013 class ErrorCodeError(Exception):
01014     def __init__(self, message, ec_const):
01015         Exception.__init__(self)
01016         self.message = message
01017         self.error_code = ec_const
01018     def __str__(self):
01019         return self.message
01020 
01021 class DroppedPacketError(Exception):
01022     def __init__(self, message):
01023         Exception.__init__(self)
01024         self.message = message
01025     def __str__(self):
01026         return self.message
01027 
01028 class UnsupportedFeatureError(Exception):
01029     def __init__(self, model_id, feature_id):
01030         Exception.__init__(self)
01031         if model_id in DXL_MODEL_TO_PARAMS:
01032             model = DXL_MODEL_TO_PARAMS[model_id]['name']
01033         else:
01034             model = 'Unknown'
01035         self.message = "Feature %d not supported by model %d (%s)" %(feature_id, model_id, model)
01036     def __str__(self):
01037         return self.message
01038 


dynamixel_driver
Author(s): Antons Rebguns, Cody Jorgensen
autogenerated on Sun Oct 5 2014 23:32:33