#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
pyHerkuleX - smart HerkuleX servo motors Python package
Copyright (C) 2020 Guenhael LE QUILLIEC
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
To obtain permission to use this code for commercial purposes,
contact Guenhael LE QUILLIEC (mailto:contact@guenhael.com).
.. Online documentation: https://guenhael.frama.io/pyherkulex/
.. Latest release archive: https://framagit.org/guenhael/pyherkulex/-/archive/master/pyherkulex-master.zip
"""
# Python 3 compatibility
from __future__ import print_function
from functools import reduce
__author__ = 'Guenhael LE QUILLIEC'
__copyright__ = 'Copyright 2020, Guenhael LE QUILLIEC'
__credits__ = ['Guenhael LE QUILLIEC']
__license__ = 'GNU General Public License - 3.0-or-later'
__version__ = '1.1.0'
__maintainer__ = 'Guenhael LE QUILLIEC'
__email__ = 'contact@guenhael.com'
__status__ = 'Production'
import time
import struct
import serial as srl
import platform
import glob
# REQuest and Acknowledgment packet commands
REQ_EEP_WRITE = 0x01 ; ACK_EEP_WRITE = 0x41
REQ_EEP_READ = 0x02 ; ACK_EEP_READ = 0x42
REQ_RAM_WRITE = 0x03 ; ACK_RAM_WRITE = 0x43
REQ_RAM_READ = 0x04 ; ACK_RAM_READ = 0x44
REQ_I_JOG = 0x05 ; ACK_I_JOG = 0x45
REQ_S_JOG = 0x06 ; ACK_S_JOG = 0x46
REQ_STAT = 0x07 ; ACK_STAT = 0x47
REQ_ROLLBACK = 0x08 ; ACK_ROLLBACK = 0x48
REQ_REBOOT = 0x09 ; ACK_REBOOT = 0x49
# EEPROM and RAM register addresses
EEP_MODEL_NO1 = 0
EEP_MODEL_NO2 = 1
EEP_VERSION1 = 2
EEP_VERSION2 = 3
EEP_BAUD_RATE = 4
EEP_ID = 6 ; RAM_ID = 0
EEP_ACK_POLICY = 7 ; RAM_ACK_POLICY = 1
EEP_ALARM_LED_POLICY = 8 ; RAM_ALARM_LED_POLICY = 2
EEP_TORQUE_POLICY = 9 ; RAM_TORQUE_POLICY = 3
EEP_MAX_TEMPERATURE = 11 ; RAM_MAX_TEMPERATURE = 5 ; RANGE_TEMPERATURE = [0, 110]
EEP_MIN_VOLTAGE = 12 ; RAM_MIN_VOLTAGE = 6 ; RANGE_VOLTAGE = ([0, 0xFF], [92, 200])
EEP_MAX_VOLTAGE = 13 ; RAM_MAX_VOLTAGE = 7
EEP_ACCELERATION_RATIO = 14 ; RAM_ACCELERATION_RATIO = 8 ; RANGE_ACCELERATION_RATIO = [0, 50]
EEP_MAX_ACCELERATION_TIME = 15 ; RAM_MAX_ACCELERATION_TIME = 9 ; RANGE_MAX_ACCELERATION_TIME = [0, 0xFE]
EEP_DEAD_ZONE = 16 ; RAM_DEAD_ZONE = 10 ; RANGE_DEAD_ZONE = [0, 0xFE]
EEP_SATURATOR_OFFSET = 17 ; RAM_SATURATOR_OFFSET = 11 ; RANGE_SATURATOR_OFFSET = [0, 0xFE]
EEP_SATURATOR_SLOPE = 18 ; RAM_SATURATOR_SLOPE = 12 ; RANGE_SATURATOR_SLOPE = [0, 0x7FFF]
EEP_PWM_OFFSET = 20 ; RAM_PWM_OFFSET = 14 ; RANGE_PWM_OFFSET = [-127, 127] # -128 in datasheets but actually -127
EEP_MIN_PWM = 21 ; RAM_MIN_PWM = 15 ; RANGE_MIN_PWM = [0, 254]
EEP_MAX_PWM = 22 ; RAM_MAX_PWM = 16 ; RANGE_MAX_PWM = [0, 0x03FF]
EEP_OVERLOAD_PWM_THRESHOLD = 24 ; RAM_OVERLOAD_PWM_THRESHOLD = 18 ; RANGE_OVERLOAD_PWM_THRESHOLD = ([0, 0x7FFE], [0, 0x03FF])
EEP_MIN_POSITION = 26 ; RAM_MIN_POSITION = 20
EEP_MAX_POSITION = 28 ; RAM_MAX_POSITION = 22
EEP_POSITION_KP = 30 ; RAM_POSITION_KP = 24 ; RANGE_POSITION_KP = [0, 0x7FFF]
EEP_POSITION_KD = 32 ; RAM_POSITION_KD = 26 ; RANGE_POSITION_KD = [0, 0x7FFF]
EEP_POSITION_KI = 34 ; RAM_POSITION_KI = 28 ; RANGE_POSITION_KI = [0, 0x7FFF]
EEP_POSITION_FEEDFORWARD_1ST_GAIN = 36 ; RAM_POSITION_FEEDFORWARD_1ST_GAIN = 30 ; RANGE_POSITION_FEEDFORWARD_1ST_GAIN = [0, 0x7FFF]
EEP_POSITION_FEEDFORWARD_2ND_GAIN = 38 ; RAM_POSITION_FEEDFORWARD_2ND_GAIN = 32 ; RANGE_POSITION_FEEDFORWARD_2ND_GAIN = [0, 0x7FFF]
EEP_VELOCITY_KP = 40 ; RAM_VELOCITY_KP = 34 ; RANGE_VELOCITY_KP = [0, 0x7FFF]
EEP_VELOCITY_KI = 42 ; RAM_VELOCITY_KI = 36 ; RANGE_VELOCITY_KI = [0, 0x7FFF]
EEP_LED_BLINK_PERIOD = 44 ; RAM_LED_BLINK_PERIOD = 38 ; RANGE_LED_BLINK_PERIOD = [0, 0xFE]
EEP_ADC_FAULT_CHECK_PERIOD = 45 ; RAM_ADC_FAULT_CHECK_PERIOD = 39 ; RANGE_ADC_FAULT_CHECK_PERIOD = [0, 0xFE]
EEP_PACKET_GARBAGE_CHECK_PERIOD = 46 ; RAM_PACKET_GARBAGE_CHECK_PERIOD = 40 ; RANGE_PACKET_GARBAGE_CHECK_PERIOD = [0, 0xFE]
EEP_STOP_DETECTION_PERIOD = 47 ; RAM_STOP_DETECTION_PERIOD = 41 ; RANGE_STOP_DETECTION_PERIOD = [0, 0xFE]
EEP_OVERLOAD_DETECTION_PERIOD = 48 ; RAM_OVERLOAD_DETECTION_PERIOD = 42 ; RANGE_OVERLOAD_DETECTION_PERIOD = [0, 0xFE]
EEP_STOP_THRESHOLD = 49 ; RAM_STOP_THRESHOLD = 43 ; RANGE_STOP_THRESHOLD = [0, 0xFE]
EEP_INPOSITION_MARGIN = 50 ; RAM_INPOSITION_MARGIN = 44 ; RANGE_INPOSITION_MARGIN = [0, 0xFE]
EEP_CALIBRATION_DIFFERENCE_LOWER = 52 ; RAM_CALIBRATION_DIFFERENCE_LOWER = 46 ; RANGE_CALIBRATION_DIFFERENCE_1 = [-255, 255]
EEP_CALIBRATION_DIFFERENCE_UPPER = 53 ; RAM_CALIBRATION_DIFFERENCE_UPPER = 47 ; RANGE_CALIBRATION_DIFFERENCE_2 = [-1495, 1495]
RAM_STATUS_ERROR = 48 ; RANGE_STATUS_ERROR = [0, 0x7F]
RAM_STATUS_DETAIL = 49 ; RANGE_STATUS_DETAIL = [0, 0x7F]
RAM_AUX_1 = 50 ; RANGE_AUX_1 = [0, 0x06]
RAM_TORQUE_CONTROL = 52
RAM_LED_CONTROL = 53
RAM_VOLTAGE = 54
RAM_TEMPERATURE = 55
RAM_CURRENT_CONTROL_MODE = 56
RAM_TICK = 57
RAM_CALIBRATED_POSITION = 58
RAM_ABSOLUTE_POSITION = 60
RAM_DIFFERENTIAL_POSITION = 62
RAM_PWM = 64
RAM_ABSOLUTE_2ND_POSITION = 66
RAM_ABSOLUTE_GOAL_POSITION = 68
RAM_ABSOLUTE_DESIRED_TRAJECTORY_POSITION = 70
RAM_DESIRED_VELOCITY = 72
# Acknolegement policy
ACK_MUTE = 0
ACK_READ = 1
ACK_ALL = 2
# LED color
LED_OFF = 0x00
LED_GREEN = 0x01
LED_BLUE = 0x02
LED_CYAN = 0x03
LED_RED = 0x04
LED_ORANGE = 0x05
LED_VIOLET = 0x06
LED_WHITE = 0x07
# Servo mode
MODE_FREE = 0x00
MODE_BRAKE = 0x40
MODE_CONTROL = 0x60
# Control mode
CONTROL_POSITION_DEGREE = 0x00 # degree
CONTROL_POSITION_STEP = 0x01 # step
CONTROL_SPEED_DEGREE = 0x10 # degree/second
CONTROL_SPEED_STEP = 0x11 # step/second
CONTROL_SPEED_NATIVE = 0x12
# Max positions
POSITION_MAX_1 = 1023
POSITION_MAX_2 = 2047
POSITION_MAX_3 = 32767
# Resolutions
POSITION_RESOLUTION_1 = (360.-26.7)/1023 # about 0.326 deg./step
POSITION_RESOLUTION_2 = (360.-26.7)/2047 # about 0.163 deg./step
POSITION_RESOLUTION_3 = 360./12962 # about 0.02777 deg./step
SPEED_OUTPUT_RESOLUTION_1 = 29.09 # deg./sec.
SPEED_OUTPUT_RESOLUTION_2 = 3.634 # deg./sec.
SPEED_OUTPUT_RESOLUTION_3 = 0.620 # deg./sec.
GOAL_SPEED_RESOLUTION_1 = 0.199 # deg./sec.
GOAL_SPEED_RESOLUTION_2 = 0.034 # deg./sec.
VOLTAGE_RESOLUTION_1 = 18.889/255 # about 0.074 volts
VOLTAGE_RESOLUTION_2 = 0.1 # volts
# Analog digital converter temperature list (for DRS-0101 and DRS-0201)
ADC_TEMPERATURE = (-80.57,-72.89,-64.26,-58.84,-54.80,-51.55,-48.81,-46.43,
-44.32,-42.41,-40.68,-39.08,-37.59,-36.20,-34.89,-33.66,
-32.49,-31.37,-30.31,-29.29,-28.31,-27.36,-26.45,-25.57,
-24.72,-23.89,-23.09,-22.31,-21.54,-20.80,-20.08,-19.37,
-18.68,-18.00,-17.34,-16.69,-16.05,-15.42,-14.81,-14.20,
-13.61,-13.02,-12.45,-11.88,-11.32,-10.76,-10.22, -9.68,
-9.15, -8.62, -8.10, -7.59, -7.08, -6.58, -6.08, -5.59,
-5.10, -4.62, -4.14, -3.66, -3.19, -2.72, -2.26, -1.80,
-1.34, -0.89, -0.44, 0.01, 0.46, 0.90, 1.34, 1.78,
2.21, 2.64, 3.07, 3.50, 3.93, 4.35, 4.77, 5.19,
5.61, 6.03, 6.45, 6.86, 7.27, 7.68, 8.09, 8.50,
8.91, 9.32, 9.72, 10.13, 10.53, 10.94, 11.34, 11.74,
12.14, 12.55, 12.95, 13.35, 13.75, 14.15, 14.54, 14.94,
15.34, 15.74, 16.14, 16.54, 16.94, 17.34, 17.74, 18.13,
18.53, 18.93, 19.33, 19.73, 20.13, 20.54, 20.94, 21.34,
21.74, 22.15, 22.55, 22.96, 23.36, 23.77, 24.18, 24.59,
25.00, 25.41, 25.82, 26.24, 26.65, 27.07, 27.49, 27.91,
28.33, 28.75, 29.18, 29.60, 30.03, 30.46, 30.89, 31.32,
31.76, 32.20, 32.64, 33.08, 33.52, 33.97, 34.42, 34.87,
35.33, 35.78, 36.24, 36.71, 37.17, 37.64, 38.11, 38.59,
39.07, 39.55, 40.04, 40.53, 41.02, 41.52, 42.02, 42.52,
43.03, 43.55, 44.07, 44.59, 45.12, 45.65, 46.19, 46.74,
47.29, 47.84, 48.40, 48.97, 49.54, 50.12, 50.71, 51.30,
51.90, 52.51, 53.13, 53.75, 54.38, 55.02, 55.67, 56.33,
56.99, 57.67, 58.36, 59.05, 59.76, 60.48, 61.21, 61.96,
62.71, 63.48, 64.27, 65.06, 65.88, 66.71, 67.55, 68.41,
69.29, 70.19, 71.11, 72.05, 73.01, 74.00, 75.01, 76.04,
77.10, 78.19, 79.31, 80.46, 81.65, 82.87, 84.13, 85.44,
86.78, 88.17, 89.62, 91.12, 92.67, 94.29, 95.98, 97.75,
99.59,101.53,103.57,105.71,107.98,110.38,112.93,115.65,
118.57,121.72,125.12,128.83,132.89,137.38,142.40,148.06,
154.56,162.13,171.18,182.34,196.72,216.58,247.46,310.08)
# Baud rates
BAUDRATES = (57600, 115200, 200000, 250000, 400000, 500000, 666666, 1000000)
BAUDRATES_CODE = ( 0x22, 0x10, 0x09, 0x07, 0x04, 0x03, 0x02, 0x01)
MAX_BAUDRATE_1 = 666666
MAX_BAUDRATE_2 = 1000000
# Broadcasting ID
BROADCASTING_ID = 0xFE
#Status messages
MSG_SATUS_ERROR = ('Exceed input voltage',
'Exceed allowed position limit', # Exceed allowed POT limit (potentiometer)
'Exceed temperature limit',
'Invalid packet',
'Overload detected',
'Driver fault detected',
'EEPROM register distorted', # EEP REG distorted
'Unknown') # Reserved
MSG_SATUS_DETAIL = ('Moving', # Moving flag
'Arrived at goal position', # Inposition flag
'Checksum error',
'Unknown command',
'Exceed register range', # Exceed REG range
'Garbage detected',
'Control mode enabled', # MOTOR_ON flag
'Unknown') # Reserved
# Packet header
HEADER = struct.pack('2B',0xFF,0xFF)
# Last opened serial connection
LAST_SERIAL = None
# XOR lambda function
xor = lambda a,b:a^b
def _closest(value, tpl):
"""
Find closest value index in a sorted tuple
(We don't use external tool such as Numpy to avoid dependencies)
"""
value = float(value)
if value <= tpl[0]:
return 0
elif value >= tpl[-1]:
return len(tpl)-1
else:
for i in range(1,len(tpl)):
if tpl[i] > value:
if (tpl[i] - value) > (value - tpl[i-1]):
return i-1
else:
return i
def _check_serial(s):
"""
Return the default serial port or open it if no serial instance
is given as argument.
"""
global LAST_SERIAL
if s is None:
if LAST_SERIAL is None:
return serial()
else:
if not LAST_SERIAL.isOpen():
LAST_SERIAL.open()
return LAST_SERIAL
else:
LAST_SERIAL = s
return s
def _check_port(port):
"""
Return the default port name if no serial port name
is given as argument.
"""
if port is None:
if platform.system() == 'Windows':
port = 'COM3'
elif platform.system() == 'Linux':
ports = glob.glob('/dev/ttyUSB*')
if ports:
port = ports[0]
else:
ports = glob.glob('/dev/ttyS*')
if ports:
port = ports[0]
elif platform.system() == 'Darwin':
from serial.tools.list_ports import comports
for p in comports():
if p.serial_number:
port = '/dev/tty.usbserial-'+p.serial_number
break
if port is None:
raise PyHerkuleX_Exception("Cannot find any serial port")
return port
[docs]def serial(port = None, baudrate = 115200, timeout = 1.0):
"""
Open the serial port where HerkuleX servo communication
bus is attached.
The output serial port will be considered as default serial port
anytime a serial port instance is required but not specified.
:param str port: Serial port name.
If not specified, its considered value will be
``'COM3'`` if using Windows,
the first ``'/dev/ttyUSB*'`` or ``'/dev/ttyS*'`` found if using Linux or
the first ``'/dev/tty.usbserial-*'`` found if using OSx.
:param int baudrate: Set baud rate value among the possible
values supported by the servo model.
:param float timeout: Set a read timeout value.
:return: Connection instance of :obj:`serial.Serial`.
:rtype: serial.Serial
:exception SerialException:
In case the device can not be found or can not be configured.
"""
global LAST_SERIAL
port = _check_port(port)
LAST_SERIAL = srl.Serial(port, baudrate, timeout = timeout)
try:
pass
except:
raise PyHerkuleX_Exception("Cannot open serial port")
return LAST_SERIAL
[docs]def find(number = None, port = None, baudrate = BAUDRATES, timeout = 0.05, fast = False, verbose = True):
"""
Scan all possible ID with each supported baud rate
and return a dictionary which maps connected servo IDs to tuples
containing HerkuleX servo model and baudrate.
:param int number: Number of servos supposed to be connected to serial bus.
:param str port: Serial port name or tuple of several port names to be successively tested.
If not specified, each available port will be checked successively,
``'COM1'`` to ``'COM256'`` if using Windows,
any ``'/dev/ttyUSB*'`` and ``'/dev/ttyS*'`` if using Linux or
any ``'/dev/tty.usbserial-'`` if using OSx.
:param int baudrate: Baud rate, or tuple of several baud rate values
to be successively tested.
:param float timeout: Set serial read timeout value.
:param bool fast: For each baud rate, incremental scanning is
performed on all possible IDs separately only if any signal is
received to a prior call broadcasted to all servo IDs together.
:param bool verbose: Print scanning progression.
:return: Dictionary mapping successfully tested ports
to dictionaries mapping connected servo IDs
to tuples containing two items each (servo model, baudrate).
:rtype: dict
"""
ports = port
if ports is None:
if platform.system() == 'Windows':
ports = ['COM%s' % (i + 1) for i in range(256)]
elif platform.system() == 'Linux':
ports = glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyS*')
elif platform.system() == 'Darwin':
from serial.tools.list_ports import comports
ports = []
for p in comports():
if p.serial_number:
ports.append('/dev/tty.usbserial-'+p.serial_number)
elif ports is str:
ports = [ports]
if isinstance(baudrate, int):
baudrate = (baudrate,)
servos = {}
for port in ports:
servos[port]= {}
try:
serial = srl.Serial(port, baudrate[0], timeout = timeout)
except (OSError, srl.SerialException):
break
if verbose:
print("Scanning port "+port)
_servos = {}
for br in baudrate:
serial.baudrate = br
if fast:
data = [7, BROADCASTING_ID, REQ_STAT]
check_sum1 = reduce(xor,data)&0xFE
data.insert(3, check_sum1)
check_sum2 = (~check_sum1)&0xFE
data.insert(4, check_sum2)
found = False
if verbose:
print(" "*4+"Broadcast fast scanning with serial baudrate %i"%br)
try:
serial.write(HEADER + struct.pack(str(5)+'B',*data))
found = serial.read(9)
except:
pass
else:
found = True
if found:
for servoid in range(0x00, 0xFE):
if servoid in servos[port]:
continue
data = [7, servoid, REQ_STAT]
check_sum1 = reduce(xor,data)&0xFE
data.insert(3, check_sum1)
check_sum2 = (~check_sum1)&0xFE
data.insert(4, check_sum2)
found = False
if verbose:
print(" "*4+"Serial baudrate %i"%br+", scanning id %i"%servoid+" (0x%02X"%servoid+")")
try:
serial.write(HEADER + struct.pack(str(5)+'B',*data))
found = serial.read(9)
except:
pass
if found:
serial.timeout = 1.0
srv = Servo(servoid, serial)
servos[port][servoid] = (srv.MODEL ,br)
_servos[servoid] = (srv, srv.status)
serial.timeout = timeout
if verbose:
print(" "*25+"Found servo id %i"%servoid+" (0x%02X"%servoid+") model %i"%servos[port][servoid][0])
if number == len(servos[port]):
break
if number == len(servos[port]):
break
for servoid in servos[port]:
serial.timeout = 1.0
serial.baudrate = servos[port][servoid][1]
srv = _servos[servoid][0]
# Restore the initial error state as it might have been affected during scanning
srv._ram_write(RAM_STATUS_ERROR, _servos[servoid][1][0], _servos[servoid][1][1])
serial.close()
if verbose:
found = False
for port in servos:
if len(servos[port])>0:
found = True
message = '\n'
if number:
if number != len(servos[port]):
message = 'Only '
if len(servos[port])>1:
print(message+"%i servos found"%len(servos[port]) + " on port "+port+":")
else:
print(message+"%i servo found"%len(servos[port]) + " on port "+port+":")
for servoid in servos[port]:
print(" id %i"%servoid+" (0x%02X"%servoid+") model %i"%servos[port][servoid][0]+" baudrate %i"%servos[port][servoid][1])
if not found:
print("No servo found.")
out = {}
for port in servos:
if len(servos[port])>0:
out[port] = servos[port]
return out
[docs]def independent_control(*instructions):
"""
Send control instructions to several servos at the same time. Each servo has
its own goal time to complete its instruction.
:param tuples \*instructions:
Each instruction is stored in a tuple containing the following items:
- *goal time*, the operating time to complete the instruction
- *servo*, an instance of :class:`pyherkulex.Servo`
- *goal value*, desired position value or desired speed value, depending on *control mode*, or ``None`` for ignoring this control instruction
- *control mode* (optional), among the :ref:`position and speed control constants <control-constants>` (default is :obj:`pyherkulex.CONTROL_POSITION_DEGREE`)
- *led color* (optional), integer among the :ref:`LED color constants <led-constants>` (default is :obj:`pyherkulex.LED_OFF`)
- *stop* (optional), either ``True`` to stop servo at current position or ``False`` (default) to continue control instructions
- *velocity overide* - VOR (optional), either ``True`` (default) to enable VOR or ``False`` to disable VOR (if the servo model allows to disable VOR)
.. note::
Each servo must have its control mode enabled before sending control instructions.
:example:
Sending independent control instructions to two servos with respective IDs 0x01 and 0x02.
The first servo has 1.5 second to reach a prescribed position set to -10 degree and its LED switched off.
The second servo has 2.0 second to reach a prescribed speed set to 3.5 degree/second and its LED lighting blue.
.. code-block:: python
#!/usr/bin/env python
import pyherkulex as hx
srv1 = hx.Servo(0x01)
srv2 = hx.Servo(0x02)
srv1.mode = srv2.mode = hx.MODE_CONTROL
hx.independent_instructions((1.5, srv1, -10.0),
(2.0, srv2, 3.5, hx.SPEED_DEGREE, hx.LED_BLUE))
"""
len_data = len(instructions)*5
data = [len_data+7, BROADCASTING_ID, REQ_I_JOG]
for jog in instructions:
goaltime = int(round(jog[0]/0.0112))
jog = jog[1:]
if jog[1] is None:
goalvalue = 0
set0 = 0b100000 # Ignore this jog
else:
goalvalue = jog[1] # goal value
set0 = 0
if len(jog) > 2:
if jog[2] == CONTROL_POSITION_DEGREE: # Adapt goal value (not needed for POSITION_STEP)
goalvalue = jog[0]._angle_to_position(goalvalue) #
elif jog[2] == CONTROL_SPEED_DEGREE: #
set0 |= 2 #
goalvalue = jog[0]._signed_native_speed(goalvalue/jog[0]._speed_res) #
elif jog[2] == CONTROL_SPEED_STEP: #
set0 |= 2 #
goalvalue = jog[0]._signed_native_speed(goalvalue/jog[0]._absolute_speed_res) #
elif jog[2] == CONTROL_SPEED_NATIVE: #
set0 |= 2 #
goalvalue = jog[0]._signed_native_speed(goalvalue) #
if len(jog) > 3:
set0 |= (jog[3]&0x7)<<2 # led color
if len(jog) > 4:
set0 |= bool(jog[4]) # stop
if len(jog) == 6:
set0 |= (bool(jog[5])^1)<<6 # VOR
if len(jog) < 3:
goalvalue = jog[0]._angle_to_position(goalvalue) # Adapt goal value
goalvalue = int(round(goalvalue))
data += [goalvalue&0xFF, goalvalue>>8&0xFF, set0, jog[0]._id, goaltime&0xFF]
check_sum1 = reduce(xor,data)&0xFE
data.insert(3, check_sum1)
check_sum2 = (~check_sum1)&0xFE
data.insert(4, check_sum2)
try:
instructions[0][0].serial.write(HEADER + struct.pack(str(len_data+5)+'B',*data))
except:
raise PyHerkuleX_Exception("Independent control failed")
[docs]def simultaneous_control(time, *instructions):
"""
Send control instructions to several servos at the same time. All servos have
the same operating goal time to complete their instruction.
:param float time: Operating goal time.
:param tuples \*instructions:
Each control instruction is stored in a tuple containing the following items:
- *servo*, an instance of :class:`pyherkulex.Servo`
- *goal value*, desired position value or desired speed value, depending on *control mode*, or ``None`` for ignoring this control instruction
- *control mode* (optional), among the :ref:`position and speed control constants <control-constants>` (default is :obj:`pyherkulex.CONTROL_POSITION_DEGREE`)
- *led color* (optional), integer among the :ref:`LED color constants <led-constants>` (default is :obj:`pyherkulex.LED_OFF`)
- *stop* (optional), either ``True`` to stop servo at current position or ``False`` (default) to continue control instructions
- *velocity override* - VOR (optional), either ``True`` (default) to enable VOR or ``False`` to disable VOR (if the servo model allows to disable VOR)
.. note::
Each servo must have its control mode enabled before sending control instructions.
:example:
Sending simultaneous instructions to two servos with respective IDs 0x01 and 0x02.
The first servo has its position set to -10 degree with its LED switched off.
The second servo has its speed set to 3.5 degree/second with its LED lighting green.
Both servos have the same operating goal time of 1.5 second.
.. code-block:: python
#!/usr/bin/env python
import pyherkulex as hx
srv1 = hx.Servo(0x01)
srv2 = hx.Servo(0x02)
srv1.mode = srv2.mode = hx.MODE_CONTROL
hx.simultaneous_control(1.5, (srv1, -10.0),
(srv2, 3.5, hx.SPEED_DEGREE, hx.LED_GREEN))
"""
len_data = len(instructions)*4
data = [len_data+8, BROADCASTING_ID, REQ_S_JOG, int(round(time/0.0112))&0xFF]
for jog in instructions:
if jog[1] is None:
goalvalue = 0
set0 = 0b100000 # Ignore this jog
else:
goalvalue = jog[1] # goal value
set0 = 0
if len(jog) > 2:
if jog[2] == CONTROL_POSITION_DEGREE: # Adapt goal value (not needed for POSITION_STEP)
goalvalue = jog[0]._angle_to_position(goalvalue) #
elif jog[2] == CONTROL_SPEED_DEGREE: #
set0 |= 2 #
goalvalue = jog[0]._signed_native_speed(goalvalue/jog[0]._speed_res) #
elif jog[2] == CONTROL_SPEED_STEP: #
set0 |= 2 #
goalvalue = jog[0]._signed_native_speed(goalvalue/jog[0]._absolute_speed_res) #
elif jog[2] == CONTROL_SPEED_NATIVE: #
set0 |= 2 #
goalvalue = jog[0]._signed_native_speed(goalvalue) #
if len(jog) > 3:
set0 |= (jog[3]&0x7)<<2 # led color
if len(jog) > 4:
set0 |= bool(jog[4]) # stop
if len(jog) == 6:
set0 |= (bool(jog[5])^1)<<6 # VOR
if len(jog) < 3:
goalvalue = jog[0]._angle_to_position(goalvalue) # Adapt goal value
goalvalue = int(round(goalvalue))
data += [goalvalue&0xFF, goalvalue>>8&0xFF, set0, jog[0]._id]
check_sum1 = reduce(xor,data)&0xFE
data.insert(3, check_sum1)
check_sum2 = (~check_sum1)&0xFE
data.insert(4, check_sum2)
try:
instructions[0][0].serial.write(HEADER + struct.pack(str(len_data+6)+'B',*data))
except:
raise PyHerkuleX_Exception("Simultaneous control failed")
[docs]class PyHerkuleX_Exception(Exception):
"""
Base class that sets how HerkuleX servo raised exceptions are handled.
"""
def __init__(self, message):
"""
Args:
message (string): Message to be displayed to user.
"""
self.message = message
super(PyHerkuleX_Exception, self).__init__(message)
[docs]class Servo(object):
"""
Represent a connected Smart HerkuleX Actuator.
Servo class allows to establish serial connection with servos,
access configuration parameters, get sensor feedbacks and
send control instructions.
:param int servoid: The ID of the servo.
If not provided, the :class:`Servo` instance will broadcast
any command to all connected servo.
:param serial.Serial serial: port instance of :obj:`serial.Serial` .
If not provided, the last used serial port will be used.
If no serial port were used yet, an auto detection will be attempted.
:example:
.. code-block:: python
#!/usr/bin/env python
import pyherkulex as hx
import time
# Open the serial port (auto detected) with default baud rate 115200
serial0 = hx.serial()
# Broadcast reboot command to all servos connected to serial0
# and light their LED in white
broadcast_srv = hx.Servo(serial = serial0)
broadcast_srv.reboot()
broadcast_srv.led = hx.LED_WHITE
# Create an instance of servo ID 0x01
# (considering the last used serial port
# as no specific port parameter is provided)
srv1 = hx.Servo(0x01)
# Check first if there is any error status
if srv1.status[0]:
# Print status and raise an exception
srv1.print_status()
raise Exception('Please check your HerkuleX servo error status.')
# Print initial position (in step)
print('Servo 1 initial position: ', srv1.position)
# Get initial angular position (in degree)
angle0 = srv1.angle
# Enable control mode before sending control instructions
srv1.mode = hx.MODE_CONTROL
# Increase angular position of 10 degree
# to be completed in an operating goal time of 2 second
# and light LED in blue
srv1.control_angle(angle0+10.0, 2.0, hx.LED_BLUE)
# Wait for control instruction to be completed
time.sleep(2.0)
# Print current position (in step)
print('Servo 1 current position: ', srv1.position)
# Go back to initial position
# to be reached in an operating goal time of 1.5 second
# and light LED in green
srv1.control_angle(angle0, 1.5, hx.LED_GREEN)
# Wait for control instruction to be completed
time.sleep(1.5)
# Switch off LED of servo 1
srv1.led = hx.LED_OFF
"""
def __init__(self, servoid = None, serial = None):
"""
Servo instance initialization.
"""
self.serial = _check_serial(serial)
self._alarm_led_policy = {}
if servoid is None:
self._id = BROADCASTING_ID
self._ack_policy = None
self._model = None
else:
# If this ID is not valid, we will know right after, when trying to read its model number
self._id = int(servoid)
# Set _ack_policy by calling _init_ack_policy() and _model
self._init_ack_policy()
if self._ack_policy == ACK_MUTE:
self.ack_policy = ACK_READ
data = self._eep_read(EEP_MODEL_NO1)
self._model = data[0]*100+data[1]
self.ack_policy = ACK_MUTE
else:
data = self._eep_read(EEP_MODEL_NO1)
self._model = data[0]*100+data[1]
if data[0] == 1 or data[0] == 2:
self._position_max = POSITION_MAX_1
self._position_res = POSITION_RESOLUTION_1
self._angular_speed_output_res = SPEED_OUTPUT_RESOLUTION_1
self._angular_goal_speed_res = GOAL_SPEED_RESOLUTION_1
self._voltage_res = VOLTAGE_RESOLUTION_1
self._baudrates = [i for i in BAUDRATES if i <= MAX_BAUDRATE_1]
elif data[0] == 4 or data[0] == 6:
if data[1] == 1:
self._position_max = POSITION_MAX_2
self._position_res = POSITION_RESOLUTION_2
self._angular_speed_output_res = SPEED_OUTPUT_RESOLUTION_2
self._angular_goal_speed_res = GOAL_SPEED_RESOLUTION_1
else:
self._position_max = POSITION_MAX_3
self._position_res = POSITION_RESOLUTION_3
self._angular_speed_output_res = SPEED_OUTPUT_RESOLUTION_3
self._angular_goal_speed_res = GOAL_SPEED_RESOLUTION_2
self._voltage_res = VOLTAGE_RESOLUTION_2
self._baudrates = [i for i in BAUDRATES if i <= MAX_BAUDRATE_2]
else:
raise PyHerkuleX_Exception("Unknown model "+str(self._model)+".")
self._raw_position_res = POSITION_RESOLUTION_2
self._angular_speed_input_res = 0.62
self._absolute_speed_input_res = self._angular_speed_input_res/self._position_res
self._absolute_speed_output_res = self._angular_speed_output_res/self._position_res
self._absolute_goal_speed_res = self._angular_goal_speed_res/self._position_res
self._position_middle = self._position_max/2+1
self._magnetic_encoder = bool(data[1]-1)
self._model_4or6 = int(data[0] == 4 or data[0] == 6)
def _write(self, *data):
"""
Write data to the HerkuleX servo port.
"""
len0 = len(data)
data = list(data)
data.insert(0, self._id)
data.insert(0, len0+6)
check_sum1 = reduce(xor,data)&0xFE
data.insert(3, check_sum1)
check_sum2 = (~check_sum1)&0xFE
data.insert(4, check_sum2)
try:
self.serial.write(HEADER + struct.pack(str(len0+4)+'B',*data))
except:
raise PyHerkuleX_Exception("\nCould not communicate with servo ID: 0x%02X"%self._id+" while sending command:\n"+"0xFF, 0xFF, "+', '.join("0x%02X"%i for i in data))
if self._ack_policy == ACK_ALL:
if data[2] not in (REQ_EEP_READ, REQ_RAM_READ, REQ_STAT) and self._id != BROADCASTING_ID:
len0 = struct.unpack('1B',self.serial.read(3)[2:3])[0]-3
if struct.unpack('1B',self.serial.read(len0)[1:2])[0] != data[2]+0x40:
raise PyHerkuleX_Exception("Wrong acknowledgment with servo ID: 0x%02X"%self._id)
def _ram_read(self, register, length = 2):
"""
Read the RAM register from the HerkuleX servo port.
"""
self._write(REQ_RAM_READ, register, length)
try:
return struct.unpack(str(length)+'B',self.serial.read(11+length)[9:9+length])
except:
if self._id == BROADCASTING_ID:
raise PyHerkuleX_Exception("Can not get any reply when using broadcast ID")
elif self._ack_policy == ACK_MUTE:
raise PyHerkuleX_Exception("Acknowledgment policy does not allow to communicate with servo ID: 0x%02X"%self._id+". Change acknowledgment policy and try again.")
else:
raise PyHerkuleX_Exception("Could not communicate with servo ID: 0x%02X"%self._id)
def _ram_read_single(self, register):
"""
Read one single RAM register byte from the HerkuleX servo port.
"""
self._write(REQ_RAM_READ, register, 1)
try:
return struct.unpack('1B',self.serial.read(12)[9:10])[0]
except:
if self._id == BROADCASTING_ID:
raise PyHerkuleX_Exception("Can not get any reply when using broadcast ID")
elif self._ack_policy == ACK_MUTE:
raise PyHerkuleX_Exception("Acknowledgment policy does not allow to communicate with servo ID: 0x%02X"%self._id+". Change acknowledgment policy and try again.")
else:
raise PyHerkuleX_Exception("Could not communicate with servo ID: 0x%02X"%self._id)
def _ram_write(self, register, *data):
"""
Write data to the RAM register from the HerkuleX servo port.
"""
self._write(REQ_RAM_WRITE, register, len(data), *data)
def _eep_read(self, register, length = 2):
"""
Read the EEPROM register from the HerkuleX servo port.
"""
self._write(REQ_EEP_READ, register, length)
try:
return struct.unpack(str(length)+'B',self.serial.read(11+length)[9:9+length])
except:
if self._id == BROADCASTING_ID:
raise PyHerkuleX_Exception("Can not get any reply when using broadcast ID")
elif self._ack_policy == ACK_MUTE:
raise PyHerkuleX_Exception("Acknowledgment policy does not allow to communicate with servo ID: 0x%02X"%self._id+". Change acknowledgment policy and try again.")
else:
raise PyHerkuleX_Exception("Could not communicate with servo ID: 0x%02X"%self._id)
def _eep_read_single(self, register):
"""
Read one single EEPROM register byte from the HerkuleX servo port.
"""
self._write(REQ_EEP_READ, register, 1)
try:
return struct.unpack('1B',self.serial.read(12)[9:10])[0]
except:
if self._id == BROADCASTING_ID:
raise PyHerkuleX_Exception("Can not get any reply when using broadcast ID")
elif self._ack_policy == ACK_MUTE:
raise PyHerkuleX_Exception("Acknowledgment policy does not allow to communicate with servo ID: 0x%02X"%self._id+". Change acknowledgment policy and try again.")
else:
raise PyHerkuleX_Exception("Could not communicate with servo ID: 0x%02X"%self._id)
def _eep_write(self, register, *data):
"""
Write data to the EEPROM register from the HerkuleX servo port.
"""
self._write(REQ_EEP_WRITE, register, len(data), *data)
# def _ram2eep(ramregister, eepregister, length = 1):
# """
# Copy RAM register data to corresponding EEPROM register from the HerkuleX servo port.
# """
# self._write(REQ_EEP_WRITE, eepregister, length, self._ram_read(self, ramregister, length))
def _check_range(self, value, rng):
"""
Check if the given value is in the permitted range.
"""
if value < rng[0]:
raise PyHerkuleX_Exception("Attempt to set a value ("+str(value)+") bellow the minimal value ("+str(rng[0])+") to servo ID: 0x%02X"%self._id)
if value > rng[1]:
raise PyHerkuleX_Exception("Attempt to set a value ("+str(value)+") over the maximal value ("+str(rng[1])+") to servo ID: 0x%02X"%self._id)
def _check_range_coef(self, value, rng, coef):
"""
Check if the given value is in the permitted range multiplied by a given coefficient.
"""
self._check_range(value, [coef*i for i in rng])
def _position_to_angle(self, position):
"""
Convert position to angle.
"""
return self._position_res*(position - self._position_middle)
def _angle_to_position(self, angle):
"""
Convert angle to position.
"""
return int(round(angle/self._position_res))+self._position_middle
def _signed_native_speed(self, speed):
"""
Convert signed speed into native speed value.
"""
if speed<0:
return abs(int(round(speed)))&0x3FFF|0x4000
else:
return int(round(speed))&0x3FFF
# 1 & 2
@property
def MODEL(self):
"""
HerkuleX servo model value (int) corresponding to the model number
(e.g. 201 corresponds to DRS-0201)
"""
return self._model
# 3 & 4
@property
def VERSION(self):
"""
HerkuleX servo firmware version (int)
"""
data = self._eep_read(EEP_VERSION1)
return data[0]<<8|data[1]
# Various properties
@property
def POSITION_RESOLUTION_DEGREE(self):
"""
HerkuleX servo angular position resolution in degree/step (float)
"""
return self._position_res
#
@property
def POSITION_MAX(self):
"""
HerkuleX servo maximum supported operating position in step (int)
"""
return self._position_max
#
@property
def SPEED_INPUT_RESOLUTION_STEP(self):
"""
HerkuleX servo absolute speed input resolution in step/second (float)
"""
return self._absolute_speed_input_res
#
@property
def SPEED_OUTPUT_RESOLUTION_STEP(self):
"""
HerkuleX servo absolute speed output resolution in step/second (float)
"""
return self._absolute_speed_output_res
#
@property
def SPEED_INPUT_RESOLUTION_DEGREE(self):
"""
HerkuleX servo angular speed input resolution in degree/second (float)
"""
return self._angular_speed_input_res
#
@property
def SPEED_OUTPUT_RESOLUTION_DEGREE(self):
"""
HerkuleX servo angular speed output resolution in degree/second (float)
"""
return self._angular_speed_output_res
#
@property
def GOAL_SPEED_RESOLUTION_STEP(self):
"""
HerkuleX servo absolute goal speed resolution in step/second (float)
"""
return self._absolute_goal_speed_res
#
@property
def GOAL_SPEED_RESOLUTION_DEGREE(self):
"""
HerkuleX servo angular goal speed resolution in degree/second (float)
"""
return self._angular_goal_speed_res
# Various methods
[docs] def reset(self, idskip = True, baudrateskip = True, sleep = 2.0):
"""
Reset all memory registers to factory default values.
:param bool idskip: Set to True to skip ID reset.
:param bool baudrateskip: Set to True to skip baud rate reset.
:param float sleep: Suspension time for rebooting.
"""
self._write(REQ_ROLLBACK, int(bool(idskip)), int(bool(baudrateskip)))
self.reboot(sleep = sleep)
self._ack_policy = 1
if not idskip:
self._id = None
#
[docs] def reboot(self, sleep = 2.0):
"""
Reboot and copy data from EEPROM register to RAM register.
:param float sleep: Suspension time for rebooting.
"""
self._write(REQ_REBOOT)
time.sleep(sleep)
if self._id != BROADCASTING_ID:
# Reset _ack_policy by calling _init_ack_policy()
self._init_ack_policy()
#
[docs] def clear_status(self):
"""
Clear error status and detailed status.
"""
self._ram_write(RAM_STATUS_ERROR, 0, 0)
#
[docs] def print_status(self):
"""
Print status error and status detail.
"""
stat0, stat1 = self.status
print('Status error:')
if stat0:
for i in range(8):
if stat0&2**i:
if stat0 == 8:
for j in range(2,6):
if stat1&2**j:
print(' - '+MSG_SATUS_ERROR[i]+': '+MSG_SATUS_DETAIL[j])
else:
print(' - '+MSG_SATUS_ERROR[i])
else:
print(' - None')
print('Status details:')
detail = False
if stat1:
for i in (0,1,6,7):
if stat1&0x02**i:
detail = True
print(' - '+MSG_SATUS_DETAIL[i])
if not detail:
print(' - None')
@property
def status(self):
"""
:getter: Get status
:setter: Manually set status
:type: tuple of two int values (error status code, detailed status code)
"""
# Always reply regadless of acknowledgment policy.
if self._id != BROADCASTING_ID:
self._write(REQ_STAT)
try:
return struct.unpack('2B',self.serial.read(9)[7:])
except:
raise PyHerkuleX_Exception("Could not communicate with servo ID: 0x%02X"%self._id)
else:
raise PyHerkuleX_Exception("Could not get status when using broadcast ID")
@status.setter
def status(self, value):
self._check_range(value[0], RANGE_STATUS_ERROR)
self._check_range(value[1], RANGE_STATUS_DETAIL)
self._ram_write(RAM_STATUS_ERROR, value[0], value[1])
# 43
@property
def _status_error(self):
"""
:getter: Get error status
:setter: Manually set error status
:type: int
"""
return self._ram_read_single(RAM_STATUS_ERROR)
@_status_error.setter
def _status_error(self, value):
self._check_range(value, RANGE_STATUS_ERROR)
self._ram_write(RAM_STATUS_ERROR, int(value))
# 44
@property
def _status_detail(self):
"""
:getter: Get detailed status
:setter: Manually set detailed status
:type: int
"""
return self._ram_read_single(RAM_STATUS_DETAIL)
@_status_detail.setter
def _status_detail(self, value):
self._check_range(value, RANGE_STATUS_DETAIL)
self._ram_write(RAM_STATUS_DETAIL, int(value))
# 5
@property
def baudrate(self):
"""
:getter: Get baud rate in bps
:setter: Set baud rate in bps (save it in EEPROM)
:type: int among the values supported by the HerkuleX servo model
"""
return BAUDRATES[BAUDRATES_CODE.index(self._eep_read_single(EEP_BAUD_RATE))]
@baudrate.setter
def baudrate(self, value):
if value in self._baudrates:
self._eep_write(EEP_BAUD_RATE, BAUDRATES_CODE[BAUDRATES.index(value)])
else:
raise PyHerkuleX_Exception("Not supported baudrate %i for this servo model."%value)
# 7
@property
def id(self):
"""
:getter: Get servo ID
:setter: Set servo ID
:type: int from 0 to 254 (0xFE), with 254 corresponding to broadcast ID.
"""
return self._id
@id.setter
def id(self, value):
value = int(value)
# If it is a new ID value
if value != self._id:
# Test if the new ID value is permitted
if value < 0 or value >= BROADCASTING_ID:
raise PyHerkuleX_Exception("ID value %i is not permitted."%value)
# If there is no ID so far
if self._id is None:
# This is possible only if a prior factory rollback was called
self.__init__(servoid = value, serial = self.serial)
else:
# Set the new ID
self._ram_write(RAM_ID, value)
# Update the ID variable
self._id = value
@property
def id_eeprom(self):
"""
:getter: Get saved ID from EEPROM
:setter: Save ID in EEPROM
:type: int
"""
return self._eep_read_single(EEP_SERVO_ID)
@id_eeprom.setter
def id_eeprom(self, value):
value = int(value)
# Test if the ID value is permitted
if value < 0 or value >= BROADCASTING_ID:
raise PyHerkuleX_Exception("ID value %i is not permitted."%value)
self._eep_write(EEP_ID, value)
# 48
@property
def led(self):
"""
:getter: Get LED color
:setter: Set LED color
:type: int
Supported values are given as :ref:`LED color constants <led-constants>`.
"""
return self._ram_read_single(RAM_LED_CONTROL)
@led.setter
def led(self, value):
value = int(value)
if value>>3:
raise PyHerkuleX_Exception("LED color value %i is not supported."%value)
self._ram_write(RAM_LED_CONTROL, value)
# 8
def _init_ack_policy(self):
"""
Init _ack_policy by reading its value from the RAM.
"""
# Either _ack_policy is defined or is None, we assume it is ACK_READ
self._ack_policy = ACK_READ
try:
self._ack_policy = self._ram_read_single(RAM_ACK_POLICY)
except:
# Change ack policy in case it was ACK_MUTE
self.ack_policy = ACK_READ
# Read again and see if it works now, otherwise an error will be raised
self._ram_read_single(RAM_ACK_POLICY)
# If no error has been raised, that means it was ACK_MUTE and we reset its initial value
self.ack_policy = ACK_MUTE
@property
def ack_policy(self):
"""
:getter: Get acknowledgment policy
:setter: Set acknowledgment policy
:type: int
Three acknowlegement policy values are supported:
- :ref:`ACK_MUTE <ack-constants>`:
With this policy, servo will not reply to any request
except status requests.
- :ref:`ACK_READ <ack-constants>`:
With this policy, servo will only reply to read (getter)
and status requests.
- :ref:`ACK_ALL <ack-constants>`:
With this policy, servo will reply to all requests.
"""
return self._ack_policy
@ack_policy.setter
def ack_policy(self, value):
value = int(value)
if not value in (ACK_MUTE, ACK_READ, ACK_ALL):
raise PyHerkuleX_Exception("Attempt to set a wrong value of acknowledgment policy to servo ID: 0x%02X"%self._id)
# Update ack_policy variable
self._ack_policy = value
# Set the new ack_policy
self._ram_write(RAM_ACK_POLICY, value)
@property
def ack_policy_eeprom(self):
"""
:getter: Get saved acknowledgment policy from EEPROM
:setter: Save acknowledgment policy in EEPROM
:type: int
"""
return self._eep_read_single(EEP_ACK_POLICY)
@ack_policy_eeprom.setter
def ack_policy_eeprom(self, value):
value = int(value)
if not value in (ACK_MUTE, ACK_READ, ACK_ALL):
raise PyHerkuleX_Exception("Attempt to save a wrong value of acknowledgment policy to servo ID: 0x%02X"%self._id)
self._eep_write(EEP_ACK_POLICY, value)
# 9
@property
def voltage_alarm_led_policy(self):
"""
:getter: Get voltage alarm led policy
:setter: Set voltage alarm led policy
:type: bool
"""
return bool(self._ram_read_single(RAM_ALARM_LED_POLICY)&0x01)
@voltage_alarm_led_policy.setter
def voltage_alarm_led_policy(self, value):
mask = 1<<0
self._ram_write(RAM_ALARM_LED_POLICY, (self._ram_read_single(RAM_ALARM_LED_POLICY) & ~mask)|((bool(value)<<0)&mask))
@property
def voltage_alarm_led_policy_eeprom(self):
"""
:getter: Get saved voltage alarm led policy from EEPROM
:setter: Save voltage alarm led policy in EEPROM
:type: bool
"""
return bool(self._eep_read_single(EEP_ALARM_LED_POLICY)&0x01)
@voltage_alarm_led_policy_eeprom.setter
def voltage_alarm_led_policy_eeprom(self, value):
mask = 1<<0
self._eep_write(EEP_ALARM_LED_POLICY, (self._eep_read_single(EEP_ALARM_LED_POLICY) & ~mask)|((bool(value)<<0)&mask))
#
@property
def position_alarm_led_policy(self):
"""
:getter: Get position alarm led policy
:setter: Set position alarm led policy
:type: bool
"""
return bool(self._ram_read_single(RAM_ALARM_LED_POLICY)&0x02)
@position_alarm_led_policy.setter
def position_alarm_led_policy(self, value):
mask = 1<<1
self._ram_write(RAM_ALARM_LED_POLICY, (self._ram_read_single(RAM_ALARM_LED_POLICY) & ~mask)|((bool(value)<<1)&mask))
@property
def position_alarm_led_policy_eeprom(self):
"""
:getter: Get saved position alarm led policy from EEPROM
:setter: Save position alarm led policy in EEPROM
:type: bool
"""
return bool(self._eep_read_single(EEP_ALARM_LED_POLICY)&0x02)
@position_alarm_led_policy_eeprom.setter
def position_alarm_led_policy_eeprom(self, value):
mask = 1<<1
self._eep_write(EEP_ALARM_LED_POLICY, (self._eep_read_single(EEP_ALARM_LED_POLICY) & ~mask)|((bool(value)<<1)&mask))
#
@property
def packet_alarm_led_policy(self):
"""
:getter: Get packet alarm led policy
:setter: Set packet alarm led policy
:type: bool
"""
return bool(self._ram_read_single(RAM_ALARM_LED_POLICY)&0x04)
@packet_alarm_led_policy.setter
def packet_alarm_led_policy(self, value):
mask = 1<<2
self._ram_write(RAM_ALARM_LED_POLICY, (self._ram_read_single(RAM_ALARM_LED_POLICY) & ~mask)|((bool(value)<<2)&mask))
@property
def packet_alarm_led_policy_eeprom(self):
"""
:getter: Get saved packet alarm led policy from EEPROM
:setter: Save packet alarm led policy in EEPROM
:type: bool
"""
return bool(self._eep_read_single(EEP_ALARM_LED_POLICY)&0x04)
@packet_alarm_led_policy_eeprom.setter
def packet_alarm_led_policy_eeprom(self, value):
mask = 1<<2
self._eep_write(EEP_ALARM_LED_POLICY, (self._eep_read_single(EEP_ALARM_LED_POLICY) & ~mask)|((bool(value)<<2)&mask))
#
@property
def overload_alarm_led_policy(self):
"""
:getter: Get overload alarm led policy
:setter: Set overload alarm led policy
:type: bool
"""
return bool(self._ram_read_single(RAM_ALARM_LED_POLICY)&0x08)
@overload_alarm_led_policy.setter
def overload_alarm_led_policy(self, value):
mask = 1<<3
self._ram_write(RAM_ALARM_LED_POLICY, (self._ram_read_single(RAM_ALARM_LED_POLICY) & ~mask)|((bool(value)<<3)&mask))
@property
def overload_alarm_led_policy_eeprom(self):
"""
:getter: Get saved overload alarm led policy from EEPROM
:setter: Save overload alarm led policy in EEPROM
:type: bool
"""
return bool(self._eep_read_single(EEP_ALARM_LED_POLICY)&0x08)
@overload_alarm_led_policy_eeprom.setter
def overload_alarm_led_policy_eeprom(self, value):
mask = 1<<3
self._eep_write(EEP_ALARM_LED_POLICY, (self._eep_read_single(EEP_ALARM_LED_POLICY) & ~mask)|((bool(value)<<3)&mask))
#
@property
def driver_alarm_led_policy(self):
"""
:getter: Get driver alarm led policy
:setter: Set driver alarm led policy
:type: bool
"""
return bool(self._ram_read_single(RAM_ALARM_LED_POLICY)&0x10)
@driver_alarm_led_policy.setter
def driver_alarm_led_policy(self, value):
mask = 1<<4
self._ram_write(RAM_ALARM_LED_POLICY, (self._ram_read_single(RAM_ALARM_LED_POLICY) & ~mask)|((bool(value)<<4)&mask))
@property
def driver_alarm_led_policy_eeprom(self):
"""
:getter: Get saved driver alarm led policy from EEPROM
:setter: Save driver alarm led policy in EEPROM
:type: bool
"""
return bool(self._eep_read_single(EEP_ALARM_LED_POLICY)&0x10)
@driver_alarm_led_policy_eeprom.setter
def driver_alarm_led_policy_eeprom(self, value):
mask = 1<<4
self._eep_write(EEP_ALARM_LED_POLICY, (self._eep_read_single(EEP_ALARM_LED_POLICY) & ~mask)|((bool(value)<<4)&mask))
#
@property
def register_alarm_led_policy(self):
"""
:getter: Get register alarm led policy
:setter: Set register alarm led policy
:type: bool
"""
return bool(self._ram_read_single(RAM_ALARM_LED_POLICY)&0x20)
@register_alarm_led_policy.setter
def register_alarm_led_policy(self, value):
mask = 1<<5
self._ram_write(RAM_ALARM_LED_POLICY, (self._ram_read_single(RAM_ALARM_LED_POLICY) & ~mask)|((bool(value)<<5)&mask))
@property
def register_alarm_led_policy_eeprom(self):
"""
:getter: Get saved register alarm led policy from EEPROM
:setter: Save register alarm led policy in EEPROM
:type: bool
"""
return bool(self._eep_read_single(EEP_ALARM_LED_POLICY)&0x20)
@register_alarm_led_policy_eeprom.setter
def register_alarm_led_policy_eeprom(self, value):
mask = 1<<5
self._eep_write(EEP_ALARM_LED_POLICY, (self._eep_read_single(EEP_ALARM_LED_POLICY) & ~mask)|((bool(value)<<5)&mask))
# 10
@property
def voltage_mode_free_policy(self):
"""
:getter: Get voltage mode free policy
:setter: Set voltage mode free policy
:type: bool
"""
return bool(self._ram_read_single(RAM_TORQUE_POLICY)&0x01)
@voltage_mode_free_policy.setter
def voltage_mode_free_policy(self, value):
mask = 1<<0
self._ram_write(RAM_TORQUE_POLICY, (self._ram_read_single(RAM_TORQUE_POLICY) & ~mask)|((bool(value)<<0)&mask))
@property
def voltage_mode_free_policy_eeprom(self):
"""
:getter: Get saved voltage mode free policy from EEPROM
:setter: Save voltage mode free policy in EEPROM
:type: bool
"""
return bool(self._eep_read_single(EEP_TORQUE_POLICY)&0x01)
@voltage_mode_free_policy_eeprom.setter
def voltage_mode_free_policy_eeprom(self, value):
mask = 1<<0
self._eep_write(EEP_TORQUE_POLICY, (self._eep_read_single(EEP_TORQUE_POLICY) & ~mask)|((bool(value)<<0)&mask))
#
@property
def position_mode_free_policy(self):
"""
:getter: Get position mode free policy
:setter: Set position mode free policy
:type: bool
"""
return bool(self._ram_read_single(RAM_TORQUE_POLICY)&0x02)
@position_mode_free_policy.setter
def position_mode_free_policy(self, value):
mask = 1<<0
self._ram_write(RAM_TORQUE_POLICY, (self._ram_read_single(RAM_TORQUE_POLICY) & ~mask)|((bool(value)<<0)&mask))
@property
def position_mode_free_policy_eeprom(self):
"""
:getter: Get saved position mode free policy from EEPROM
:setter: Save position mode free policy in EEPROM
:type: bool
"""
return bool(self._eep_read_single(EEP_TORQUE_POLICY)&0x02)
@position_mode_free_policy_eeprom.setter
def position_mode_free_policy_eeprom(self, value):
mask = 1<<0
self._eep_write(EEP_TORQUE_POLICY, (self._eep_read_single(EEP_TORQUE_POLICY) & ~mask)|((bool(value)<<0)&mask))
#
@property
def packet_mode_free_policy(self):
"""
:getter: Get packet mode free policy
:setter: Set packet mode free policy
:type: bool
"""
return bool(self._ram_read_single(RAM_TORQUE_POLICY)&0x04)
@packet_mode_free_policy.setter
def packet_mode_free_policy(self, value):
mask = 1<<1
self._ram_write(RAM_TORQUE_POLICY, (self._ram_read_single(RAM_TORQUE_POLICY) & ~mask)|((bool(value)<<1)&mask))
@property
def packet_mode_free_policy_eeprom(self):
"""
:getter: Get saved packet mode free policy from EEPROM
:setter: Save packet mode free policy in EEPROM
:type: bool
"""
return bool(self._eep_read_single(EEP_TORQUE_POLICY)&0x04)
@packet_mode_free_policy_eeprom.setter
def packet_mode_free_policy_eeprom(self, value):
mask = 1<<1
self._eep_write(EEP_TORQUE_POLICY, (self._eep_read_single(EEP_TORQUE_POLICY) & ~mask)|((bool(value)<<1)&mask))
#
@property
def overload_mode_free_policy(self):
"""
:getter: Get overload mode free policy
:setter: Set overload mode free policy
:type: bool
"""
return bool(self._ram_read_single(RAM_TORQUE_POLICY)&0x08)
@overload_mode_free_policy.setter
def overload_mode_free_policy(self, value):
mask = 1<<2
self._ram_write(RAM_TORQUE_POLICY, (self._ram_read_single(RAM_TORQUE_POLICY) & ~mask)|((bool(value)<<2)&mask))
@property
def overload_mode_free_policy_eeprom(self):
"""
:getter: Get saved overload mode free policy from EEPROM
:setter: Save overload mode free policy in EEPROM
:type: bool
"""
return bool(self._eep_read_single(EEP_TORQUE_POLICY)&0x08)
@overload_mode_free_policy_eeprom.setter
def overload_mode_free_policy_eeprom(self, value):
mask = 1<<3
self._eep_write(EEP_TORQUE_POLICY, (self._eep_read_single(EEP_TORQUE_POLICY) & ~mask)|((bool(value)<<3)&mask))
#
@property
def driver_mode_free_policy(self):
"""
:getter: Get driver mode free policy
:setter: Set driver mode free policy
:type: bool
"""
return bool(self._ram_read_single(RAM_TORQUE_POLICY)&0x10)
@driver_mode_free_policy.setter
def driver_mode_free_policy(self, value):
mask = 1<<4
self._ram_write(RAM_TORQUE_POLICY, (self._ram_read_single(RAM_TORQUE_POLICY) & ~mask)|((bool(value)<<4)&mask))
@property
def driver_mode_free_policy_eeprom(self):
"""
:getter: Get saved driver mode free policy from EEPROM
:setter: Save driver mode free policy in EEPROM
:type: bool
"""
return bool(self._eep_read_single(EEP_TORQUE_POLICY)&0x10)
@driver_mode_free_policy_eeprom.setter
def driver_mode_free_policy_eeprom(self, value):
mask = 1<<4
self._eep_write(EEP_TORQUE_POLICY, (self._eep_read_single(EEP_TORQUE_POLICY) & ~mask)|((bool(value)<<4)&mask))
#
@property
def register_mode_free_policy(self):
"""
:getter: Get register mode free policy
:setter: Set register mode free policy
:type: bool
"""
return bool(self._ram_read_single(RAM_TORQUE_POLICY)&0x20)
@register_mode_free_policy.setter
def register_mode_free_policy(self, value):
mask = 1<<5
self._ram_write(RAM_TORQUE_POLICY, (self._ram_read_single(RAM_TORQUE_POLICY) & ~mask)|((bool(value)<<5)&mask))
@property
def register_mode_free_policy_eeprom(self):
"""
:getter: Get saved register mode free policy from EEPROM
:setter: Save register mode free policy in EEPROM
:type: bool
"""
return bool(self._eep_read_single(EEP_TORQUE_POLICY)&0x20)
@register_mode_free_policy_eeprom.setter
def register_mode_free_policy_eeprom(self, value):
mask = 1<<5
self._eep_write(EEP_TORQUE_POLICY, (self._eep_read_single(EEP_TORQUE_POLICY) & ~mask)|((bool(value)<<5)&mask))
# 12
@property
def max_temperature(self):
"""
:getter: Get maximum allowed temperature (in degree Celsius)
:setter: Set maximum allowed temperature (in degree Celsius)
:type: float
"""
if self._model_4or6:
return float(self._ram_read_single(RAM_MAX_TEMPERATURE))
else:
return ADC_TEMPERATURE[self._ram_read_single(RAM_MAX_TEMPERATURE)]
@max_temperature.setter
def max_temperature(self, value):
if self._model_4or6:
self._check_range(value, RANGE_TEMPERATURE)
self._ram_write(RAM_MAX_TEMPERATURE, int(round(value)))
else:
self._check_range(value, (ADC_TEMPERATURE[0], ADC_TEMPERATURE[-1]))
self._ram_write(RAM_MAX_TEMPERATURE, _closest(value, ADC_TEMPERATURE))
@property
def max_temperature_eeprom(self):
"""
:getter: Get saved maximum allowed temperature (in degree Celsius) from EEPROM
:setter: Save maximum allowed temperature (in degree Celsius) in EEPROM
:type: float
"""
if self._model_4or6:
return float(self._eep_read_single(EEP_MAX_TEMPERATURE))
else:
return ADC_TEMPERATURE[self._eep_read_single(EEP_MAX_TEMPERATURE)]
@max_temperature_eeprom.setter
def max_temperature_eeprom(self, value):
if self._model_4or6:
self._check_range(value, RANGE_TEMPERATURE)
self._eep_write(EEP_MAX_TEMPERATURE, int(round(value)))
else:
self._check_range(value, (ADC_TEMPERATURE[0], ADC_TEMPERATURE[-1]))
self._eep_write(EEP_MAX_TEMPERATURE, _closest(value, ADC_TEMPERATURE))
# 13
@property
def min_voltage(self):
"""
:getter: Get minimum operating voltage (in volt)
:setter: Set minimum operating voltage (in volt)
:type: float
"""
return self._voltage_res*self._ram_read_single(RAM_MIN_VOLTAGE)
@min_voltage.setter
def min_voltage(self, value):
self._check_range_coef(value, RANGE_VOLTAGE[self._model_4or6], self._voltage_res)
self._ram_write(RAM_MIN_VOLTAGE, int(round(value/self._voltage_res)))
@property
def min_voltage_eeprom(self):
"""
:getter: Get saved minimum operating voltage (in volt) from EEPROM
:setter: Save minimum operating voltage (in volt) in EEPROM
:type: float
"""
return self._voltage_res*self._eep_read_single(EEP_MIN_VOLTAGE)
@min_voltage_eeprom.setter
def min_voltage_eeprom(self, value = None):
self._check_range_coef(value, RANGE_VOLTAGE[self._model_4or6], self._voltage_res)
self._eep_write(EEP_MIN_VOLTAGE, int(round(value/self._voltage_res)))
# 14
@property
def max_voltage(self):
"""
:getter: Get maximum operating voltage (in volt)
:setter: Set maximum operating voltage (in volt)
:type: float
"""
return self._voltage_res*self._ram_read_single(RAM_MAX_VOLTAGE)
@max_voltage.setter
def max_voltage(self, value):
self._check_range_coef(value, RANGE_VOLTAGE[self._model_4or6], self._voltage_res)
self._ram_write(RAM_MAX_VOLTAGE, int(round(value/self._voltage_res)))
@property
def max_voltage_eeprom(self):
"""
:getter: Get saved maximum operating voltage (in volt) from EEPROM
:setter: Save maximum operating voltage (in volt) in EEPROM
:type: float
"""
return self._voltage_res*self._eep_read_single(EEP_MAX_VOLTAGE)
@max_voltage_eeprom.setter
def max_voltage_eeprom(self, value):
self._check_range_coef(value, RANGE_VOLTAGE[self._model_4or6], self._voltage_res)
self._eep_write(EEP_MAX_VOLTAGE, int(round(value/self._voltage_res)))
# 33
@property
def alarm_led_period(self):
"""
:getter: Get blink period of alarm LED
:setter: Set blink period of alarm LED
:type: float
"""
return 0.0112*self._ram_read_single(RAM_LED_BLINK_PERIOD)
@alarm_led_period.setter
def alarm_led_period(self, value):
self._check_range_coef(value, RANGE_LED_BLINK_PERIOD, 0.0112)
self._ram_write(RAM_LED_BLINK_PERIOD, int(round(value/0.0112)))
@property
def alarm_led_period_eeprom(self):
"""
:getter: Get saved blink period of alarm LED from EEPROM
:setter: Save blink period of alarm LED in EEPROM
:type: float
"""
return 0.0112*self._eep_read_single(EEP_LED_BLINK_PERIOD)
@alarm_led_period_eeprom.setter
def alarm_led_period_eeprom(self, value):
self._check_range_coef(value, RANGE_LED_BLINK_PERIOD, 0.0112)
self._eep_write(EEP_LED_BLINK_PERIOD, int(round(value/0.0112)))
# 34
@property
def fault_check_period(self):
"""
:getter: Get fault check period for temperature and input voltage
:setter: Set fault check period for temperature and input voltage
:type: float
"""
return 0.0112*self._ram_read_single(RAM_ADC_FAULT_CHECK_PERIOD)
@fault_check_period.setter
def fault_check_period(self, value):
self._check_range_coef(value, RANGE_ADC_FAULT_CHECK_PERIOD, 0.0112)
self._ram_write(RAM_ADC_FAULT_CHECK_PERIOD, int(round(value/0.0112)))
@property
def fault_check_period_eeprom(self):
"""
:getter: Get saved fault check period for temperature and input voltage from EEPROM
:setter: Save fault check period for temperature and input voltage in EEPROM
:type: float
"""
return 0.0112*self._eep_read_single(EEP_ADC_FAULT_CHECK_PERIOD)
@fault_check_period_eeprom.setter
def fault_check_period_eeprom(self, value):
self._check_range_coef(value, RANGE_ADC_FAULT_CHECK_PERIOD, 0.0112)
self._eep_write(EEP_ADC_FAULT_CHECK_PERIOD, int(round(value/0.0112)))
# 35
@property
def packet_garbage_check_period(self):
"""
:getter: Get packet garbage check period
:setter: Set packet garbage check period
:type: float
"""
return 0.0112*self._ram_read_single(RAM_PACKET_GARBAGE_CHECK_PERIOD)
@packet_garbage_check_period.setter
def packet_garbage_check_period(self, value):
self._check_range_coef(value, RANGE_PACKET_GARBAGE_CHECK_PERIOD, 0.0112)
self._ram_write(RAM_PACKET_GARBAGE_CHECK_PERIOD, int(round(value/0.0112)))
@property
def packet_garbage_check_period_eeprom(self):
"""
:getter: Get saved packet garbage check period from EEPROM
:setter: Save packet garbage check period in EEPROM
:type: float
"""
return 0.0112*self._eep_read_single(EEP_PACKET_GARBAGE_CHECK_PERIOD)
@packet_garbage_check_period_eeprom.setter
def packet_garbage_check_period_eeprom(self, value):
self._check_range_coef(value, RANGE_PACKET_GARBAGE_CHECK_PERIOD, 0.0112)
self._eep_write(EEP_PACKET_GARBAGE_CHECK_PERIOD, int(round(value/0.0112)))
# 15
@property
def acceleration_ratio(self):
"""
:getter: Get acceleration ratio
:setter: Set acceleration ratio
:type: float
from 0 (rectangle speed profile)
to 0.5 (triangle speed profile)
"""
return 0.01*self._ram_read_single(RAM_ACCELERATION_RATIO)
@acceleration_ratio.setter
def acceleration_ratio(self, value):
self._check_range_coef(value, RANGE_ACCELERATION_RATIO, 0.01)
self._ram_write(RAM_ACCELERATION_RATIO, int(round(value*100)))
@property
def acceleration_ratio_eeprom(self):
"""
:getter: Get saved acceleration ratio from EEPROM
:setter: Save acceleration_ratio in EEPROM
:type: float
from 0 (rectangle speed profile)
to 0.5 (triangle speed profile)
"""
return 0.01*self._eep_read_single(EEP_ACCELERATION_RATIO)
@acceleration_ratio_eeprom.setter
def acceleration_ratio_eeprom(self, value):
self._check_range_coef(value, RANGE_ACCELERATION_RATIO, 0.01)
self._eep_write(EEP_ACCELERATION_RATIO, int(round(value*100)))
# 16
@property
def max_acceleration_time(self):
"""
:getter: Get max acceleration time
:setter: Set max acceleration time
:type: float
"""
return 0.0112*self._ram_read_single(RAM_MAX_ACCELERATION_TIME)
@max_acceleration_time.setter
def max_acceleration_time(self, value):
self._check_range_coef(value, RANGE_MAX_ACCELERATION_TIME, 0.0112)
self._ram_write(RAM_MAX_ACCELERATION_TIME, int(round(value/0.0112)))
@property
def max_acceleration_time_eeprom(self):
"""
:getter: Get saved max acceleration time from EEPROM
:setter: Save max acceleration time in EEPROM
:type: float
"""
return 0.0112*self._eep_read_single(EEP_MAX_ACCELERATION_TIME)
@max_acceleration_time_eeprom.setter
def max_acceleration_time_eeprom(self, value):
self._check_range_coef(value, RANGE_MAX_ACCELERATION_TIME, 0.0112)
self._eep_write(EEP_MAX_ACCELERATION_TIME, int(round(value/0.0112)))
# 17
@property
def dead_zone(self):
"""
:getter: Get dead zone
:setter: Set dead zone
:type: int
The dead zone value corresponds to the permitted difference (in step)
between goal position and actual position.
If the difference (error) is less than the dead zone value,
servo assumes it has reached the goal position and stops.
"""
return self._ram_read_single(RAM_DEAD_ZONE)
@dead_zone.setter
def dead_zone(self, value):
self._check_range(value, RANGE_DEAD_ZONE)
self._ram_write(RAM_DEAD_ZONE, int(round(value)))
@property
def dead_zone_eeprom(self):
"""
:getter: Get saved dead zone from EEPROM
:setter: Save dead zone in EEPROM
:type: int
"""
return self._eep_read_single(EEP_DEAD_ZONE)
@dead_zone_eeprom.setter
def dead_zone_eeprom(self, value):
self._check_range(value, RANGE_DEAD_ZONE)
self._eep_write(EEP_DEAD_ZONE, int(round(value)))
# 18
@property
def saturator_offset(self):
"""
:getter: Get saturator offset
:setter: Set saturator offset
:type: int
Saturator offset corresponds to the PWM prescribed value
when the servo reaches the dead zone boundary.
"""
return self._ram_read_single(RAM_SATURATOR_OFFSET)
@saturator_offset.setter
def saturator_offset(self, value):
self._check_range(value, RANGE_SATURATOR_OFFSET)
self._ram_write(RAM_SATURATOR_OFFSET, int(round(value)))
@property
def saturator_offset_eeprom(self):
"""
:getter: Get saved saturator offset from EEPROM
:setter: Save saturator offset in EEPROM
:type: int
"""
return self._eep_read_single(EEP_SATURATOR_OFFSET)
@saturator_offset_eeprom.setter
def saturator_offset_eeprom(self, value):
self._check_range(value, RANGE_SATURATOR_OFFSET)
self._eep_write(EEP_SATURATOR_OFFSET, int(round(value)))
# 19
@property
def saturator_slope(self):
"""
:getter: Get saturator slop
:setter: Set saturator slop
:type: int
This value corresponds to the slop of the linear transition
of the PWM from the saturator offset value
to the maximum PWM value versus position.
"""
data = self._ram_read(RAM_SATURATOR_SLOPE)
return (data[1]<<8|data[0])/256.0
@saturator_slope.setter
def saturator_slope(self, value):
self._check_range_coef(value, RANGE_SATURATOR_SLOPE, 0.00390625)
value = int(round(value * 256))
self._ram_write(RAM_SATURATOR_SLOPE, value&0xFF, value>>8)
@property
def saturator_slope_eeprom(self):
"""
:getter: Get saved saturator slop from EEPROM
:setter: Save saturator slop in EEPROM
:type: int
"""
data = self._eep_read(EEP_SATURATOR_SLOPE)
return (data[1]<<8|data[0])/256.0
@saturator_slope_eeprom.setter
def saturator_slope_eeprom(self, value):
self._check_range_coef(value, RANGE_SATURATOR_SLOPE, 0.00390625)
value = int(round(value * 256))
self._eep_write(EEP_SATURATOR_SLOPE, int(value)&0xFF, int(value)>>8)
# 20
@property
def pwm_offset(self):
"""
:getter: Get PWM offset
:setter: Set PWM offset
:type: int
Prescribed PWM will be increased by the amount of the offset.
This offset acts similar to a compensator when constant loads
are applied on the servo (e.g. from gravity).
"""
value = self._ram_read_single(RAM_PWM_OFFSET)
if value > 127:
value -= 255
return value
@pwm_offset.setter
def pwm_offset(self, value):
self._check_range(value, RANGE_PWM_OFFSET)
value = int(round(value))
if value < 0:
value += 255
self._ram_write(RAM_PWM_OFFSET, value)
@property
def pwm_offset_eeprom(self):
"""
:getter: Get saved PWM offset from EEPROM
:setter: Save PWM offset in EEPROM
:type: int
"""
value = self._ram_read_single(EEP_PWM_OFFSET)
if value > 127:
value -= 255
return value
@pwm_offset_eeprom.setter
def pwm_offset_eeprom(self, value):
self._check_range(value, RANGE_PWM_OFFSET)
value = int(round(value))
if value < 0:
value += 255
self._eep_write(PWM_OFFSET, value)
# 21
@property
def pwm_min(self):
"""
:getter: Get minimum PWM
:setter: Set minimum PWM
:type: int
Minimum PWM can be used when there is jerky movement due to
tight fitting or friction in the servo application system
but assigning a minimum PWM value that is too large may lead
to unstable system.
"""
return self._ram_read_single(RAM_MIN_PWM)
@pwm_min.setter
def pwm_min(self, value):
self._check_range(value, RANGE_MIN_PWM)
self._ram_write(RAM_MIN_PWM, int(round(value)))
@property
def pwm_min_eeprom(self):
"""
:getter: Get saved minimum PWM from EEPROM
:setter: Save minimum PWM in EEPROM
:type: int
"""
return self._eep_read_single(EEP_MIN_PWM)
@pwm_min_eeprom.setter
def pwm_min_eeprom(self, value):
self._check_range(value, RANGE_MIN_PWM)
self._eep_write(EEP_MIN_PWM, int(round(value)))
# 22
@property
def pwm_max(self):
"""
:getter: Get maximum PWM
:setter: Set maximum PWM
:type: int
Maximum PWM affects the output maximum torque.
Decreasing maximum PWM can also increase battery life.
"""
data = self._ram_read(RAM_MAX_PWM)
return data[1]<<8|data[0]
@pwm_max.setter
def pwm_max(self, value):
self._check_range(value, RANGE_MAX_PWM)
value = int(round(value))
self._ram_write(RAM_MAX_PWM, value&0xFF, value>>8)
@property
def pwm_max_eeprom(self):
"""
:getter: Get saved maximum PWM from EEPROM
:setter: Save maximum PWM in EEPROM
:type: int
"""
data = self._eep_read(EEP_MAX_PWM)
return data[1]<<8|data[0]
@pwm_max_eeprom.setter
def pwm_max_eeprom(self, value):
self._check_range(value, RANGE_MAX_PWM)
value = int(round(value))
self._eep_write(EEP_MAX_PWM, value&0xFF, value>>8)
# 23
@property
def pwm_overload_threshold(self):
"""
:getter: Get PWM overload threshold
:setter: Set PWM overload threshold
:type: int
Overload activates when external load is greater than
PWM overload threshold.
Overload never activates when the PWM overload threshold
is set to its maximum permitted value.
"""
data = self._ram_read(RAM_OVERLOAD_PWM_THRESHOLD)
return data[1]<<8|data[0]
@pwm_overload_threshold.setter
def pwm_overload_threshold(self, value):
self._check_range(value, RANGE_OVERLOAD_PWM_THRESHOLD[self._model_4or6])
value = int(round(value))
self._ram_write(RAM_OVERLOAD_PWM_THRESHOLD, value&0xFF, value>>8)
@property
def pwm_overload_threshold_eeprom(self):
"""
:getter: Get saved PWM overload threshold from EEPROM
:setter: Save PWM overload threshold in EEPROM
:type: int
"""
data = self._eep_read(EEP_OVERLOAD_PWM_THRESHOLD)
return data[1]<<8|data[0]
@pwm_overload_threshold_eeprom.setter
def pwm_overload_threshold_eeprom(self, value):
self._check_range(value, RANGE_OVERLOAD_PWM_THRESHOLD[self._model_4or6])
value = int(round(value))
self._eep_write(EEP_OVERLOAD_PWM_THRESHOLD, value&0xFF, value>>8)
# 24
@property
def min_position(self):
"""
:getter: Get min operating position
:setter: Set min operating position
:type: int
"""
data = self._ram_read(RAM_MIN_POSITION)
return data[1]<<8|data[0]
@min_position.setter
def min_position(self, value):
if not self._position_max > value > 0:
raise PyHerkuleX_Exception("Min position value "+str(value)+" out of bounds (from 0 to "+str(self._position_max)+" for servo model "+str(self._model)+").")
value = int(round(value))
self._ram_write(RAM_MIN_POSITION, value&0xFF, value>>8)
@property
def min_position_eeprom(self):
"""
:getter: Get saved min operating position from EEPROM
:setter: Save min operating position in EEPROM
:type: int
"""
data = self._eep_read(EEP_MIN_POSITION)
return data[1]<<8|data[0]
@min_position_eeprom.setter
def min_position_eeprom(self, value):
if not self._position_max > value > 0:
raise PyHerkuleX_Exception("Min position value "+str(value)+" out of bounds (from 0 to "+str(self._position_max)+" for servo model "+str(self._model)+").")
value = int(round(value))
self._eep_write(EEP_MIN_POSITION, value&0xFF, value>>8)
# 25
@property
def max_position(self):
"""
:getter: Get max operating position
:setter: Set max operating position
:type: int
"""
data = self._ram_read(RAM_MAX_POSITION)
return data[1]<<8|data[0]
@max_position.setter
def max_position(self, value):
if not self._position_max > value > 0:
raise PyHerkuleX_Exception("Max position value "+str(value)+" out of bounds (from 0 to "+str(self._position_max)+" for servo model "+str(self._model)+").")
value = int(round(value))
self._ram_write(RAM_MAX_POSITION, value&0xFF, value>>8)
@property
def max_position_eeprom(self):
"""
:getter: Get saved max operating position from EEPROM
:setter: Save max operating position in EEPROM
:type: int
"""
data = self._eep_read(EEP_MAX_POSITION)
return data[1]<<8|data[0]
@max_position_eeprom.setter
def max_position_eeprom(self, value):
if not self._position_max > value > 0:
raise PyHerkuleX_Exception("Max position value "+str(value)+" out of bounds (from 0 to "+str(self._position_max)+" for servo model "+str(self._model)+").")
value = int(round(value))
self._eep_write(EEP_MAX_POSITION, value&0xFF, value>>8)
# 26 to 28
@property
def position_gain_p(self):
"""
:getter: Get proportional gain of the position PID
:setter: Set proportional gain of the position PID
:type: float
"""
data = self._ram_read(RAM_POSITION_KP)
return (data[1]<<8|data[0])/8
@position_gain_p.setter
def position_gain_p(self, value):
self._check_range_coef(value, RANGE_POSITION_KP, 0.125)
value = int(round(value * 8))
self._ram_write(RAM_POSITION_KP, value&0xFF, value>>8)
@property
def position_gain_p_eeprom(self):
"""
:getter: Get saved proportional gain of the position PID from EEPROM
:setter: Save proportional gain of the position PID in EEPROM
:type: float
"""
data = self._eep_read(EEP_POSITION_KP)
return (data[1]<<8|data[0])/8
@position_gain_p_eeprom.setter
def position_gain_p_eeprom(self, value):
self._check_range_coef(value, RANGE_POSITION_KP, 0.125)
value = int(round(value * 8))
self._eep_write(EEP_POSITION_KP, value&0xFF, value>>8)
#
@property
def position_gain_i(self):
"""
:getter: Get integral gain of the position PID
:setter: Set integral gain of the position PID
:type: float
"""
data = self._ram_read(RAM_POSITION_KI)
return (data[1]<<8|data[0])/16384
@position_gain_i.setter
def position_gain_i(self, value):
self._check_range_coef(value, RANGE_POSITION_KI, 1.0/16384.0)
value = int(round(value * 16384))
self._ram_write(RAM_POSITION_KI, value&0xFF, value>>8)
@property
def position_gain_i_eeprom(self):
"""
:getter: Get saved integral gain of the position PID from EEPROM
:setter: Save integral gain of the position PID in EEPROM
:type: float
"""
data = self._eep_read(EEP_POSITION_KI)
return (data[1]<<8|data[0])/16384
@position_gain_i_eeprom.setter
def position_gain_i_eeprom(self, value):
self._check_range_coef(value, RANGE_POSITION_KI, 1.0/16384.0)
value = int(round(value * 16384))
self._eep_write(EEP_POSITION_KI, value&0xFF, value>>8)
#
@property
def position_gain_d(self):
"""
:getter: Get derivative gain of the position PID
:setter: Set derivative gain of the position PID
:type: float
"""
data = self._ram_read(RAM_POSITION_KD)
return (data[1]<<8|data[0])/8
@position_gain_d.setter
def position_gain_d(self, value):
self._check_range_coef(value, RANGE_POSITION_KD, 0.125)
value = int(round(value * 8))
self._ram_write(RAM_POSITION_KD, value&0xFF, value>>8)
@property
def position_gain_d_eeprom(self):
"""
:getter: Get saved derivative gain of the position PID from EEPROM
:setter: Save derivative gain of the position PID in EEPROM
:type: float
"""
data = self._eep_read(EEP_POSITION_KD)
return (data[1]<<8|data[0])/8
@position_gain_d_eeprom.setter
def position_gain_d_eeprom(self, value):
self._check_range_coef(value, RANGE_POSITION_KD, 0.125)
value = int(round(value * 8))
self._eep_write(EEP_POSITION_KD, value&0xFF, value>>8)
# 29
@property
def feedforward_gain_1(self):
"""
:getter: Get position feedforward first gain (Kd)
:setter: Set position feedforward first gain (Kd)
:type: float
"""
data = self._ram_read(RAM_POSITION_FEEDFORWARD_1ST_GAIN)
return data[1]<<8|data[0]
@feedforward_gain_1.setter
def feedforward_gain_1(self, value):
self._check_range(value, RANGE_POSITION_FEEDFORWARD_1ST_GAIN)
value = int(round(value))
self._ram_write(RAM_POSITION_FEEDFORWARD_1ST_GAIN, value&0xFF, value>>8)
@property
def feedforward_gain_1_eeprom(self):
"""
:getter: Get saved position feedforward first gain (Kd) from EEPROM
:setter: Save position feedforward first gain (Kd) in EEPROM
:type: float
"""
data = self._eep_read(EEP_POSITION_FEEDFORWARD_1ST_GAIN)
return data[1]<<8|data[0]
@feedforward_gain_1_eeprom.setter
def feedforward_gain_1_eeprom(self, value):
self._check_range(value, RANGE_POSITION_FEEDFORWARD_1ST_GAIN)
value = int(round(value))
self._eep_write(EEP_POSITION_FEEDFORWARD_1ST_GAIN, value&0xFF, value>>8)
# 30
@property
def feedforward_gain_2(self):
"""
:getter: Get position feedforward second gain (Kdd)
:setter: Set position feedforward second gain (Kdd)
:type: float
"""
data = self._ram_read(RAM_POSITION_FEEDFORWARD_2ND_GAIN)
return data[1]<<8|data[0]
@feedforward_gain_2.setter
def feedforward_gain_2(self, value):
self._check_range(value, RANGE_POSITION_FEEDFORWARD_2ND_GAIN)
value = int(round(value))
self._ram_write(RAM_POSITION_FEEDFORWARD_2ND_GAIN, value&0xFF, value>>8)
@property
def feedforward_gain_2_eeprom(self):
"""
:getter: Get saved position feedforward second gain (Kdd) from EEPROM
:setter: Save position feedforward second gain (Kdd) in EEPROM
:type: float
"""
data = self._eep_read(EEP_POSITION_FEEDFORWARD_2ND_GAIN)
return data[1]<<8|data[0]
@feedforward_gain_2_eeprom.setter
def feedforward_gain_2_eeprom(self, value):
self._check_range(value, RANGE_POSITION_FEEDFORWARD_2ND_GAIN)
value = int(round(value))
self._eep_write(EEP_POSITION_FEEDFORWARD_2ND_GAIN, value&0xFF, value>>8)
# 31
@property
def velocity_gain_p(self):
"""
:getter: Get proportional gain of the velocity PID
:setter: Set proportional gain of the velocity PID
:type: float
.. note::
This argument is available only with servo models DRS-0402 and DRS-0602.
"""
if self._magnetic_encoder:
data = self._ram_read(RAM_VELOCITY_KP)
return (data[1]<<8|data[0])/64
else:
raise PyHerkuleX_Exception("Can't get proportional gain of the velocity PID for this servo model.")
@velocity_gain_p.setter
def velocity_gain_p(self, value):
if self._magnetic_encoder:
self._check_range_coef(value, RANGE_VELOCITY_KP, 0.015625)
value = int(round(value * 64))
self._ram_write(RAM_VELOCITY_KP, value&0xFF, value>>8)
else:
raise PyHerkuleX_Exception("Can't set proportional gain of the velocity PID for this servo model.")
@property
def velocity_gain_p_eeprom(self):
"""
:getter: Get saved proportional gain of the velocity PID from EEPROM
:setter: Save proportional gain of the velocity PID in EEPROM
:type: float
.. note::
This argument is available only with servo models DRS-0402 and DRS-0602.
"""
if self._magnetic_encoder:
data = self._eep_read(EEP_VELOCITY_KP)
return (data[1]<<8|data[0])/64
else:
raise PyHerkuleX_Exception("Can't get saved proportional gain of the velocity PID for this servo model.")
@velocity_gain_p_eeprom.setter
def velocity_gain_p_eeprom(self, value):
if self._magnetic_encoder:
self._check_range_coef(value, RANGE_VELOCITY_KP, 0.015625)
value = int(round(value * 64))
self._eep_write(EEP_VELOCITY_KP, value&0xFF, value>>8)
else:
raise PyHerkuleX_Exception("Can't save proportional gain of the velocity PID for this servo model.")
# 32
@property
def velocity_gain_i(self):
"""
:getter: Get integral gain of the velocity PID
:setter: Set integral gain of the velocity PID
:type: float
.. note::
This argument is available only with servo models DRS-0402 and DRS-0602.
"""
if self._magnetic_encoder:
data = self._ram_read(RAM_VELOCITY_KI)
return (data[1]<<8|data[0])/16384
else:
raise PyHerkuleX_Exception("Can't get integral gain of the velocity PID for this servo model.")
@velocity_gain_i.setter
def velocity_gain_i(self, value):
if self._magnetic_encoder:
self._check_range_coef(value, RANGE_VELOCITY_KI, 1.0/16384.0)
value = int(round(value * 16384))
self._ram_write(RAM_VELOCITY_KI, value&0xFF, value>>8)
else:
raise PyHerkuleX_Exception("Can't set integral gain of the velocity PID for this servo model.")
@property
def velocity_gain_i_eeprom(self):
"""
:getter: Get saved integral gain of the velocity PID from EEPROM
:setter: Save integral gain of the velocity PID in EEPROM
:type: float
.. note::
This argument is available only with servo models DRS-0402 and DRS-0602.
"""
if self._magnetic_encoder:
data = self._eep_read(EEP_VELOCITY_KI)
return (data[1]<<8|data[0])/16384
else:
raise PyHerkuleX_Exception("Can't get saved integral gain of the velocity PID for this servo model.")
@velocity_gain_i_eeprom.setter
def velocity_gain_i_eeprom(self, value):
if self._magnetic_encoder:
self._check_range_coef(value, RANGE_VELOCITY_KI, 1.0/16384.0)
value = int(round(value * 16384))
self._eep_write(EEP_VELOCITY_KI, value&0xFF, value>>8)
else:
raise PyHerkuleX_Exception("Can't save integral gain of the velocity PID for this servo model.")
# 36
@property
def stop_detection_period(self):
"""
:getter: Get stop detection period
:setter: Set stop detection period
:type: float
Servo stop is confirmed if stoppage lasts for detection period time.
"""
return 0.0112*self._ram_read_single(RAM_STOP_DETECTION_PERIOD)
@stop_detection_period.setter
def stop_detection_period(self, value):
self._check_range_coef(value, RANGE_STOP_DETECTION_PERIOD, 0.0112)
self._ram_write(RAM_STOP_DETECTION_PERIOD, int(round(value/0.0112)))
@property
def stop_detection_period_eeprom(self):
"""
:getter: Get saved stop detection period from EEPROM
:setter: Save stop detection period in EEPROM
:type: float
"""
return 0.0112*self._eep_read_single(EEP_STOP_DETECTION_PERIOD)
@stop_detection_period_eeprom.setter
def stop_detection_period_eeprom(self, value):
self._check_range_coef(value, RANGE_STOP_DETECTION_PERIOD, 0.0112)
self._eep_write(EEP_STOP_DETECTION_PERIOD, int(round(value/0.0112)))
# 37
@property
def overload_detection_period(self):
"""
:getter: Get overload detection period
:setter: Set overload detection period
:type: float
"""
return 0.0112*self._ram_read_single(RAM_OVERLOAD_DETECTION_PERIOD)
@overload_detection_period.setter
def overload_detection_period(self, value):
self._check_range_coef(value, RANGE_OVERLOAD_DETECTION_PERIOD, 0.0112)
self._ram_write(RAM_OVERLOAD_DETECTION_PERIOD, int(round(value/0.0112)))
@property
def overload_detection_period_eeprom(self):
"""
:getter: Get saved overload detection period from EEPROM
:setter: Save overload detection period in EEPROM
:type: float
"""
return 0.0112*self._eep_read_single(EEP_OVERLOAD_DETECTION_PERIOD)
@overload_detection_period_eeprom.setter
def overload_detection_period_eeprom(self, value):
self._check_range_coef(value, RANGE_OVERLOAD_DETECTION_PERIOD, 0.0112)
self._eep_write(EEP_OVERLOAD_DETECTION_PERIOD, int(round(value/0.0112)))
# 38
@property
def stop_threshold(self):
"""
:getter: Get stop threshold
:setter: Set stop threshold
:type: float
The servo is seen as not moving (stopped) when the position
movement of the servo is less than the stop threshold value.
The servo is determined to be stopped if the stoppage lasts
longer than the stop detection period.
"""
return self._ram_read_single(RAM_STOP_THRESHOLD)
@stop_threshold.setter
def stop_threshold(self, value):
self._check_range(value, RANGE_STOP_THRESHOLD)
self._ram_write(RAM_STOP_THRESHOLD, int(round(value)))
@property
def stop_threshold_eeprom(self):
"""
:getter: Get saved stop threshold from EEPROM
:setter: Save stop threshold in EEPROM
:type: float
"""
return self._eep_read_single(EEP_STOP_THRESHOLD)
@stop_threshold_eeprom.setter
def stop_threshold_eeprom(self, value):
self._check_range(value, RANGE_STOP_THRESHOLD)
self._eep_write(EEP_STOP_THRESHOLD, int(round(value)))
# 39
@property
def inposition_margin(self):
"""
:getter: Get inposition margin
:setter: Set inposition margin
:type: float
Standard value to determine if the prescribed position has been reached.
"""
return self._ram_read_single(RAM_INPOSITION_MARGIN)
@inposition_margin.setter
def inposition_margin(self, value):
self._check_range(value, RANGE_INPOSITION_MARGIN)
self._ram_write(RAM_INPOSITION_MARGIN, int(round(value)))
@property
def inposition_margin_eeprom(self):
"""
:getter: Get saved inposition margin from EEPROM
:setter: Save inposition margin in EEPROM
:type: float
"""
return self._eep_read_single(EEP_INPOSITION_MARGIN)
@inposition_margin_eeprom.setter
def inposition_margin_eeprom(self, value):
self._check_range(value, RANGE_INPOSITION_MARGIN)
self._eep_write(EEP_INPOSITION_MARGIN, int(round(value)))
# 41 & 42
@property
def calibration_difference(self):
"""
:getter: Get calibration difference
:setter: Set calibration difference
:type: float
Used to calibrate neutral point of the calibrated position
(calibrated position = absolute position - calibration difference).
It is generally used to make adjustments and compensate assembly
variations.
"""
if self._model_4or6:
data = self._ram_read(RAM_CALIBRATION_DIFFERENCE_LOWER)
return data[1]<<8|data[0]
else:
value = self._ram_read_single(RAM_CALIBRATION_DIFFERENCE_UPPER)
if value>>7:
return (value&0x7F)-0x7F
else:
return value
@calibration_difference.setter
def calibration_difference(self, value):
if self._model_4or6:
self._check_range(value, RANGE_CALIBRATION_DIFFERENCE_2)
value = int(round(value))
if value<0:
value += 0x10000
self._ram_write(RAM_CALIBRATION_DIFFERENCE_LOWER, value&0xFF, value>>8)
else:
self._ram_write(RAM_CALIBRATION_DIFFERENCE_LOWER, value&0xFF, value>>8)
else:
self._check_range(value, RANGE_CALIBRATION_DIFFERENCE_1)
value = int(round(value))
if value<0:
self._ram_write(RAM_CALIBRATION_DIFFERENCE_UPPER, (0x7F-abs(value)&0x7F)|0x80)
else:
self._ram_write(RAM_CALIBRATION_DIFFERENCE_UPPER, value&0x7F)
@property
def calibration_difference_eeprom(self):
"""
:getter: Get saved calibration difference from EEPROM
:setter: Save calibration difference in EEPROM
:type: float
"""
if self._model_4or6:
data = self._eep_read(EEP_CALIBRATION_DIFFERENCE_LOWER)
return data[1]<<8|data[0]
else:
value = self._eep_read_single(EEP_CALIBRATION_DIFFERENCE_UPPER)
if value>>7:
return (value&0x7F)-0x7F
else:
return value
@calibration_difference_eeprom.setter
def calibration_difference_eeprom(self, value):
if self._model_4or6:
self._check_range(value, RANGE_CALIBRATION_DIFFERENCE_2)
value = int(round(value))
if value<0:
value += 0x10000
self._eep_write(EEP_CALIBRATION_DIFFERENCE_LOWER, value&0xFF, value>>8)
else:
self._eep_write(EEP_CALIBRATION_DIFFERENCE_LOWER, value&0xFF, value>>8)
else:
self._check_range(value, RANGE_CALIBRATION_DIFFERENCE_1)
value = int(round(value))
if value<0:
self._eep_write(EEP_CALIBRATION_DIFFERENCE_UPPER, (0x7F-abs(value)&0x7F)|0x80)
else:
self._eep_write(EEP_CALIBRATION_DIFFERENCE_UPPER, value&0x7F)
# 47
@property
def mode(self):
"""
:getter: Get servo control mode
:setter: Set servo control mode
:type: int
Three control mode values are supported:
- :ref:`MODE_FREE <mode-constants>`:
In this mode, the servo shaft is freely movable.
Position control and speed control will not work.
Set MODE_CONTROL before that.
- :ref:`MODE_BRAKE <mode-constants>`:
In this mode, servo motion is prevented.
Position control and speed control will not work.
Set MODE_CONTROL before that.
- :ref:`MODE_CONTROL <mode-constants>`:
In this mode, control instructions are permitted.
"""
return self._ram_read_single(RAM_TORQUE_CONTROL)
@mode.setter
def mode(self, value):
value = int(value)
if value in (MODE_FREE, MODE_BRAKE, MODE_CONTROL):
self._ram_write(RAM_TORQUE_CONTROL, value)
else:
raise PyHerkuleX_Exception("Servo control mode not supported by servo ID: 0x%02X"%self._id)
# 45
[docs] def set_absolute_position_origin(self, option = 'middle'):
"""
Set the absolute position origin from the current position.
:param string option: Only 5 different options are permitted:
- ``'min'``: current position becomes the min absolute position (current absolute position becomes 0)
- ``'half_forward'``: current position plus 180° becomes the middle absolute position (current absolute position becomes 9903)
- ``'middle'`` (default): current position becomes the middle absolute position (current absolute position becomes 16398)
- ``'half_backward'``: current position minus 180° becomes the middle absolute position (current absolute position becomes 22865)
- ``'max'``: current position becomes the max absolute position (current absolute position becomes 32767)
.. note::
This action is permitted only with servo models DRS-0402 and DRS-0602.
"""
if self._magnetic_encoder:
self._ram_write(RAM_AUX_1, {'min':2, 'half_forward':3, 'middle':4, 'half_backward':5, 'max':6}[option])
else:
raise PyHerkuleX_Exception("Can't set absolute position origin for this servo model.")
[docs] def reset_absolute_position_origin(self):
"""
Reset the absolute position origin to its initial state.
.. note::
This action is permitted only with servo models DRS-0402 and DRS-0602.
"""
if self._magnetic_encoder:
self._ram_write(RAM_AUX_1, 1)
else:
raise PyHerkuleX_Exception("Can't reset absolute position origin for this servo model.")
# 51
@property
def speed_mode(self):
"""
:getter: Get speed control mode state
:type: bool
Servo.mode value is MODE_CONTROL and continuous rotation is in progress if speed mode is ``True``.
"""
return bool(self._ram_read_single(RAM_CURRENT_CONTROL_MODE))
# 52
@property
def operating_time(self):
"""
:getter: Get current operating time (max 2.856 second)
:type: float
"""
return 0.0112*self._ram_read_single(RAM_TICK)
# 53
[docs] def control_stop(self, led = LED_OFF):
"""
Stop servo in its current position.
:param int led: LED color among the supported values given as :ref:`LED color constants <led-constants>`.
.. note::
Control mode must be enabled before sending any control instruction to the servo.
"""
self._write(REQ_I_JOG, 0, 0, (led&0x7)<<2|1, self._id, 0)
@property
def position(self):
"""
:getter: Get current (calibrated) position
:type: int
"""
data = self._ram_read(RAM_CALIBRATED_POSITION)
if self._magnetic_encoder:
return data[1]<<8|data[0]
else:
return (data[1]&0x07)<<8|data[0]
@position.setter
def position(self, value):
raise PyHerkuleX_Exception("Can't set position attribute. Use Servo.control_position() method instead.")
[docs] def control_position(self, position, time, led = LED_OFF, velocity_override = True):
"""
Set new (calibrated) position.
:param int position: Desired position.
:param float time: Time taken to move from present
position to desired position.
:param int led: LED color among the supported values given as :ref:`LED color constants <led-constants>`.
:param bool velocity_override: VOR is either ``True`` to enable it or ``False`` to disable it (if the servo model allows to disable VOR)
.. note::
Control mode must be enabled before sending any control instruction to the servo.
"""
position = int(round(position))
self._write(REQ_I_JOG, position&0xFF, position>>8&0xFF, (bool(velocity_override)^1)<<6|(led&0x7)<<2, self._id, int(round(time/0.0112))&0xFF)
#
[docs] def control_position_increment(self, increment, time, led = LED_OFF, velocity_override = True):
"""
Increment current (calibrated) position.
:param int increment: Desired increment to be added to the
current position.
:param int time: Time taken to complete the increment
from current position.
:param int led: LED color among the supported values given as :ref:`LED color constants <led-constants>`.
:param bool velocity_override: either ``True`` to enable it or ``False`` to disable VOR (if the servo model allows it)
.. note::
Control mode must be enabled before sending any control instruction to the servo.
"""
position = int(round(increment+self.position))
self._write(REQ_I_JOG, position&0xFF, position>>8&0xFF, (bool(velocity_override)^1)<<6|(led&0x7)<<2, self._id, int(round(time/0.0112))&0xFF)
@property
def angle(self):
"""
:getter: Get current angular (calibrated) position in degree
:type: float
"""
return self._position_to_angle(self.position)
@angle.setter
def angle(self, value):
raise PyHerkuleX_Exception("Can't set angle attribute. Use control_angle() method instead.")
[docs] def control_angle(self, angle, time, led = LED_OFF, velocity_override = True):
"""
Sets new angular (calibrated) position.
:param float angle: Desired angular position in degree.
:param float time: Time taken to move from present
position to the desired position.
:param int led: LED color among the supported values given as :ref:`LED color constants <led-constants>`.
:param bool velocity_override: VOR is either ``True`` to enable it or ``False`` to disable it (if the servo model allows to disable VOR)
.. note::
Control mode must be enabled before sending any control instruction to the servo.
"""
self.control_position(self._angle_to_position(angle), time, led, velocity_override)
#
[docs] def control_angle_increment(self, increment, time, led = LED_OFF, velocity_override = True):
"""
Increment current angular position.
:param int increment: Desired angular increment to be added to
the current angular position in degree.
:param int time: Time taken to complete the angular increment
from current position.
:param int led: LED color among the supported values given as :ref:`LED color constants <led-constants>`.
:param bool velocity_override: either ``True`` to enable it or ``False`` to disable VOR (if the servo model allows it)
.. note::
Control mode must be enabled before sending any control instruction to the servo.
"""
self.control_angle(self.angle+increment, time, led, velocity_override)
# 55
@property
def native_speed(self):
"""
:getter: Get current native speed
:type: int
"""
data = self._ram_read(RAM_DIFFERENTIAL_POSITION)
if data[1]&0x80:
return -((data[1]<<8|data[0])^0xFFFF)
else:
return (data[1]<<8|data[0])
@native_speed.setter
def native_speed(self, value):
raise PyHerkuleX_Exception("Can't set native_speed attribute. Use control_native_speed() method instead.")
[docs] def control_native_speed(self, speed, time, led = LED_OFF, velocity_override = True):
"""
Set native speed.
:param int speed: Desired speed in native unit.
:param int time: Time taken to reach the desired speed
from the current speed.
:param int led: LED color among the supported values given as :ref:`LED color constants <led-constants>`.
:param bool velocity_override: VOR is either ``True`` to enable it or ``False`` to disable it (if the servo model allows to disable VOR)
.. note::
Control mode must be enabled before sending any control instruction to the servo.
"""
speed = self._signed_native_speed(speed)
self._write(REQ_I_JOG, speed&0xFF, speed>>8&0xFF, 2|(bool(velocity_override)^1)<<6|(led&0x7)<<2, self._id, int(round(time/0.0112))&0xFF)
#
@property
def speed(self):
"""
:getter: Get current speed in step/sec
:type: float
"""
return self._absolute_speed_output_res*self.native_speed
@speed.setter
def speed(self, value):
raise PyHerkuleX_Exception("Can't set speed attribute. Use control_speed() method instead.")
[docs] def control_speed(self, speed, time, led = LED_OFF, velocity_override = True):
"""
Set native speed.
:param float speed: Desired speed in step/second.
:param float time: Time taken to reach the desired speed
from the current speed.
:param int led: LED color among the supported values given as :ref:`LED color constants <led-constants>`.
:param bool velocity_override: VOR is either ``True`` to enable it or ``False`` to disable it (if the servo model allows to disable VOR)
.. note::
Control mode must be enabled before sending any control instruction to the servo.
"""
self.control_native_speed(speed/self._absolute_speed_input_res, time, led, velocity_override)
#
@property
def angular_speed(self):
"""
:getter: Get current angular speed in degree/second
:type: int
"""
return self._angular_speed_output_res*self.native_speed
@angular_speed.setter
def angular_speed(self, value):
raise PyHerkuleX_Exception("Can't set angular_speed attribute. Use control_angular_speed() method instead.")
[docs] def control_angular_speed(self, speed, time, led = LED_OFF, velocity_override = True):
"""
Set angular speed.
:param float speed: Desired speed in degree/second.
:param float time: Time taken to reach the desired speed
from the current speed.
:param int led: LED color among the supported values given as :ref:`LED color constants <led-constants>`.
:param bool velocity_override: VOR is either ``True`` to enable it or ``False`` to disable it (if the servo model allows to disable VOR)
.. note::
Control mode must be enabled before sending any control instruction to the servo.
"""
self.control_native_speed(speed/self._angular_speed_input_res, time, led, velocity_override)
# 54
@property
def absolute_position(self):
"""
:getter: Get current absolute position
:type: int
"""
data = self._ram_read(RAM_ABSOLUTE_POSITION)
return data[1]<<8|data[0]
#
@property
def absolute_angle(self):
"""
:getter: Get current absolute angle
:type: float
"""
data = self._ram_read(RAM_ABSOLUTE_POSITION)
return self._position_res*(data[1]<<8|data[0])
# 57
@property
def absolute_raw_position(self):
"""
:getter: Get current absolute raw position
(from potentiometer)
:type: int
.. note::
This argument is available only with servo models DRS-0402 and DRS-0602.
"""
if self._magnetic_encoder:
data = self._ram_read(RAM_ABSOLUTE_2ND_POSITION)
return data[1]<<8|data[0]
else:
raise PyHerkuleX_Exception("Can't get absolute raw position for this servo model.")
@property
def absolute_raw_angle(self):
"""
:getter: Get current absolute raw angle in degree
(from potentiometer)
:type: float
.. note::
This argument is available only with servo models DRS-0402 and DRS-0602.
"""
if self._magnetic_encoder:
data = self._ram_read(RAM_ABSOLUTE_2ND_POSITION)
return self._raw_position_res*(data[1]<<8|data[0])
else:
raise PyHerkuleX_Exception("Can't get absolute raw angle for this servo model.")
# 58
@property
def absolute_goal_position(self):
"""
:getter: Get current absolute goal position
:type: int
"""
data = self._ram_read(RAM_ABSOLUTE_GOAL_POSITION)
return data[1]<<8|data[0]
#
@property
def absolute_goal_angle(self):
"""
:getter: Get current absolute goal angle
:type: float
"""
data = self._ram_read(RAM_ABSOLUTE_GOAL_POSITION)
return self._position_res*(data[1]<<8|data[0])
# 59
@property
def absolute_intermediate_goal_position(self):
"""
:getter: Get current absolute intermediate goal position
:type: int
"""
data = self._ram_read(RAM_ABSOLUTE_DESIRED_TRAJECTORY_POSITION)
return data[1]<<8|data[0]
#
@property
def absolute_intermediate_goal_angle(self):
"""
:getter: Get current absolute intermediate goal angle
:type: float
"""
data = self._ram_read(RAM_ABSOLUTE_DESIRED_TRAJECTORY_POSITION)
return self._position_res*(data[1]<<8|data[0])
# 60
@property
def intermediate_native_goal_speed(self):
"""
:getter: Get current intermediate native goal speed
:type: int
"""
data = self._ram_read(RAM_DESIRED_VELOCITY)
return data[1]<<8|data[0]
@property
def intermediate_absolute_goal_speed(self):
"""
:getter: Get current intermediate absolute goal speed in step/second
:type: float
"""
data = self._ram_read(RAM_DESIRED_VELOCITY)
return self._absolute_goal_speed_res*(data[1]<<8|data[0])
#
@property
def intermediate_angular_goal_speed(self):
"""
:getter: Get current intermediate angular goal speed in degree/second
:type: float
"""
data = self._ram_read(RAM_DESIRED_VELOCITY)
return self._angular_goal_speed_res*(data[1]<<8|data[0])
# 56
@property
def pwm(self):
"""
:getter: Get current pulse width modulation value.
:type: int from -1023 to 1023
.. tip::
PWM can be considered as a torque like quantity.
"""
data = self._ram_read(RAM_PWM)
if data[1]&0x80:
return -((data[1]<<8|data[0])^0xFFFF)
else:
return data[1]<<8|data[0]
# 49
@property
def voltage(self):
"""
:getter: Get voltage
:type: float
"""
return self._voltage_res*self._ram_read_single(RAM_VOLTAGE)
# 50
@property
def temperature(self):
"""
:getter: Get temperature in degree Celsius
:type: float
"""
if self._model_4or6:
return float(self._ram_read_single(RAM_TEMPERATURE))
else:
return ADC_TEMPERATURE[self._ram_read_single(RAM_TEMPERATURE)]