"""
Manipulator class for the Luigs and Neumann SM-5 manipulator controller.
Adapted from Michael Graupner's LandNSM5 class.
Not all commands are implemented.
"""
from __future__ import absolute_import
from __future__ import print_function
import binascii
import time
import threading
import serial
import struct
import warnings
from numpy import sign
from .manipulator import Manipulator
from ..serialdevice import SerialDevice
__all__ = ['LuigsNeumann_SM5']
verbose = False
[docs]class LuigsNeumann_SM5(SerialDevice,Manipulator):
def __init__(self, name = None, stepmoves = True):
'''
A Luigs & Neurmann SM10 controller
Arguments
---------
name : name of serial port
stepmoves : if True, relative moves use steps instead of relative move command
'''
# Note that the port name is arbitrary, it should be set or found out
SerialDevice.__init__(self, name)
Manipulator.__init__(self)
self.stepmoves = stepmoves
# Open the serial port; 1 second time out
self.port.baudrate = 38400
self.port.bytesize = serial.EIGHTBITS
self.port.parity=serial.PARITY_NONE
self.port.stopbits=serial.STOPBITS_ONE
self.port.timeout=0.1 #None is blocking; 0 is non blocking
self.port.open()
self.lock = threading.RLock()
self.established_time = time.time()
self.establish_connection()
# Initialize ramp length of all axes to 210 ms
for axis in range(1,3):
self.set_ramp_length(axis,3)
self.sleep(.05)
[docs] def send_command(self, ID, data, nbytes_answer, ack_ID='', resends=0):
'''
Send a command to the controller
'''
now = time.time()
if now - self.established_time > 3:
self.establish_connection()
self.established_time = now
high, low = self.CRC_16(data,len(data))
# Create hex-string to be sent
# <syn><ID><byte number>
send = '16' + ID + '%0.2X' % len(data)
# <data>
# Loop over length of data to be sent
for i in range(len(data)):
send += '%0.2X' % data[i]
# <CRC>
send += '%0.2X%0.2X' % (high,low)
# Convert hex string to bytes
sendbytes = binascii.unhexlify(send)
expected = binascii.unhexlify('06' + ack_ID)
self.lock.acquire()
try:
self.port.write(sendbytes)
answer = self.port.read(nbytes_answer+6)
finally:
self.lock.release()
if answer[:len(expected)] != expected :
if resends >= 5:
raise serial.SerialException('No expected response received after 5 tries for '
'command with ID ' + ID)
warnings.warn('Did not get expected response for command with ID ' + ID +' ; resending')
# Resend
return self.send_command(ID, data, nbytes_answer, ack_ID, resends=resends+1)
return answer[4:4+nbytes_answer]
[docs] def establish_connection(self):
if verbose:
print("establishing connection")
self.established_time = time.time()
self.send_command('0400', [], 0, ack_ID='040b')
if verbose:
print("connection established")
[docs] def position(self, axis):
'''
Current position along an axis.
Parameters
----------
axis : axis number (starting at 1)
Returns
-------
The current position of the device axis in um.
'''
res = self.send_command('0101', [axis], 4)
return struct.unpack('f', res)[0]
[docs] def position2(self, axis):
'''
Current position along an axis on the second counter.
Parameters
----------
axis : axis number (starting at 1)
Returns
-------
The current position of the device axis in um.
'''
res = self.send_command('0131', [axis], 4)
return struct.unpack('f', res)[0]
[docs] def absolute_move(self, x, axis):
'''
Moves the device axis to position x.
It uses the fast movement command.
Parameters
----------
axis: axis number (starting at 1)
x : target position in um.
speed : optional speed in um/s.
'''
x_hex = binascii.hexlify(struct.pack('>f', x))
data = [axis, int(x_hex[6:], 16), int(x_hex[4:6], 16), int(x_hex[2:4], 16), int(x_hex[:2], 16)]
# TODO: always goes fast (use 0049 for slow)
##### HOANG
#Always goes fasr or slow
#if (axis == 2):
# self.send_command('0049', data, 0)
#else:
# self.send_command('0048', data, 0)
self.send_command('0048', data, 0)
[docs] def absolute_move_group(self, x, axes):
for i in range(len(x)):
self.absolute_move(x[i], axes[i])
self.sleep(0.05)
[docs] def relative_move(self, x, axis):
'''
Moves the device axis by relative amount x in um.
It uses the fast command.
Parameters
----------
axis: axis number
x : position shift in um.
'''
if self.stepmoves:
self.step_move(x, axis)
else:
x_hex = binascii.hexlify(struct.pack('>f', x))
data = [axis, int(x_hex[6:], 16), int(x_hex[4:6], 16), int(x_hex[2:4], 16), int(x_hex[:2], 16)]
self.send_command('004A', data, 0)
[docs] def stop(self, axis):
"""
Stop current movements.
"""
self.send_command('00FF', [axis], 0)
[docs] def zero(self, axes):
"""
Sets the current position of the axes as the zero position.
"""
for axis in axes:
self.send_command('00f0', [axes], 0)
[docs] def set_to_zero_second_counter(self, axes):
"""
Sets the current position of the axes as the zero position on
the second counter.
"""
# # collection command does not seem to work...
# ID = 'A0F0'
# address = group_address(axes)
# self.send_command(ID, address, -1)
ID = '0132'
for axis in axes:
self.send_command(ID, [axis, 2], 0)
[docs] def go_to_zero(self, axes):
"""
Moves axes to zero position.
"""
ID = '0024'
for axis in axes:
self.send_command(ID, [axes], 0)
[docs] def single_step(self, axis, steps):
'''
Moves the given axis by a signed number of steps using the StepIncrement or StepDecrement command.
Using a steps argument different from 1 (or -1) simply sends multiple
StepIncrement/StepDecrement commands.
Uses distance and velocity set by `set_single_step_distance` resp.
`set_single_step_velocity`.
'''
if steps > 0:
ID = '0140'
else:
ID = '0141'
for _ in range(int(abs(steps))):
self.send_command(ID, [axis], 0)
self.wait_until_still([axis])
[docs] def set_single_step_distance(self, axis, distance):
'''
Distance (in um) for `single_step`.
'''
if distance > 255:
print('Step distance too long, setting distance at 255um')
distance = 255
ID = '013a'
data = [axis] + list(bytearray(struct.pack('f', distance)))
self.send_command(ID, data, 0)
[docs] def step_move(self, distance, axis=None, maxstep=255):
'''
Relative move using steps of up to 255 um.
This fixes a bug on L&N controller.
'''
number_step = abs(distance) // maxstep
last_step = abs(distance) % maxstep
if number_step:
self.set_single_step_distance(axis, maxstep)
self.single_step(axis, number_step*sign(distance))
if last_step:
self.set_single_step_distance(axis, last_step)
self.single_step(axis, sign(distance))
[docs] def set_ramp_length(self, axis, length):
"""
Set the ramp length for the chosen axis
:param axis: axis which ramp shall be changed
:param length: 0<length<=16
:return:
"""
self.send_command('003a', [axis, length], 0)
[docs] def wait_until_still(self, axes = None):
"""
Waits for the motors to stop.
"""
res = 1
while res:
res = self.send_command('0120', axes, 7)
res = int(binascii.hexlify(struct.unpack('s', res[6])[0])[1])
if __name__ == '__main__':
sm5 = LuigsNeumann_SM5('COM3')
"""
print 'getting positions:'
for ax in range(1, 9):
print ax, sm5.position(axis=ax)
time.sleep(2)
print 'moving first manipulator (3 axes)'
sm5.relative_move_group([50, 50, 50], [1, 2, 3])
time.sleep(2)
print 'moving second manipulator (3 axes)'
sm5.relative_move_group([50, 50, 50], [4, 5, 6])
time.sleep(2)
print 'moving stage (2 axes)'
sm5.relative_move_group([50, 50], [7, 8])
"""
"""
Apparently: with two successive absolute moves, the second
cancels the first. With two successive relative moves, a sort of random
result is obtained, probably because the second cancels the first at midcourse.
"""
for i in range(5):
print(sm5.position(1))
sm5.absolute_move(1000,1)
time.sleep(1)
print(sm5.position(1))
sm5.absolute_move(1128,1)
print(sm5.position(1))
time.sleep(1)