81 """Connection to a Ludl Controller and wrapper to its commands.
83 Tested with MC2000 controller and xy stage.
86 This class also implements the logic to parse and validate
87 commands so it can be shared between multiple devices.
89 This class has only been tested on a MAC2000 controller from the
90 1990's however newer controllers should be compatible.
94 def __init__(self, port: str, baudrate: int, timeout: float) ->
None:
102 bytesize=serial.EIGHTBITS,
103 stopbits=serial.STOPBITS_TWO,
104 parity=serial.PARITY_NONE,
119 print(
"Unable to read configuration. Is Ludl connected?")
126 for line
in answer[4:-1]:
129 devinfo=re.split(
r"\s{2,}",line.decode(
'ascii'))
131 self.
_devlist[devinfo[0]]=devinfo[1:]
139 def get_number_axes(self):
143 """Send command to device."""
145 self.
_serial.write(command + b
"\r")
148 """Read a line from the device connection until '\n'."""
150 return self.
_serial.read_until(b
"\n")
152 def read_multiline(self):
157 output.append(line.strip())
158 if line==b
'N' or line[0:2] == b
':A' :
162 elif line[0] == b
'N':
164 error=line[2:].strip()
165 raise(
'Ludl controller error: %s,%s' % (error,LUDL_ERRORS[error]))
169 """Read until timeout; used to clean buffer if in an unknown state."""
176 """Keep sending the 'STATUS' comand until the respnce
180 def _command_and_validate(self, command: bytes, expected: bytes) ->
None:
183 if answer == b
':A \n' :
190 """Send get command and return the answer."""
196 """Send a move command and check return value."""
207 """Send a realtive movement command to stated axis"""
208 axisname=AXIS_MAPPER[axis]
209 self.
move_command(bytes(
'MOVREL {0}={1}'.format(axisname,
210 str(delta)),
'ascii'))
213 """Send a realtive movement command to stated axis"""
214 axisname=AXIS_MAPPER[axis]
217 def move_to_limit(self,axis: bytes,speed: int):
218 axisname=AXIS_MAPPER[axis]
219 self.
get_command(bytes(
'SPIN {0}={1}'.format(axisname,speed),
'ascii'))
221 def motor_moving(self,axis: bytes) -> int:
222 axisname=AXIS_MAPPER[axis]
223 reply=self.
get_command(bytes(
'RDSTAT {0}'.format(axisname),
'ascii'))
224 flags=int(reply.strip()[3:])
227 def set_speed(self, axis: bytes, speed: int) ->
None:
228 axisname=AXIS_MAPPER[axis]
229 self.
get_command(bytes(
'SPEED {0}={1}'.format(axisname,speed),
'ascii'))
232 def wait_for_motor_stop(self,axis: bytes):
237 def reset_position(self, axis: bytes):
238 axisname=AXIS_MAPPER[axis]
239 self.
get_command(bytes(
'HERE {0}=0'.format(axisname),
'ascii'))
241 def get_absolute_position(self, axis: bytes) -> float:
242 axisname=AXIS_MAPPER[axis]
243 position=self.
get_command(bytes(
'WHERE {0}'.format(axisname),
'ascii'))
244 if position[3:4]==b
'N':
245 print(
"Error: {0} : {1}".format(position,
246 LUDL_ERRORS[int(position[4:6])]))
248 return float(position.strip()[2:])
251 """Send a set command and check return value."""
258 """Send a get description command and return it."""
261 return self.
_serial.read_until(b
"\rEND\r")
263 @contextlib.contextmanager
264 def changed_timeout(self, new_timeout: float):
265 previous = self.
_serial.timeout
267 self.
_serial.timeout = new_timeout
270 self.
_serial.timeout = previous