61LUDL_ERRORS = { -1:
'Unknown command',
62 -2:
'Illegal point type or axis, or module not installed',
63 -3:
'Not enough parameters (e.g. move r=)',
64 -4:
'Parameter out of range',
65 -21:
'Process aborted by HALT command',
68 -10:
'No slides selected',
69 -11:
'End of list reached',
71 -16:
'Motor move error (move not completed successfully due to stall, end limit, etc.',
72 -17:
'Initialization error',
75AXIS_MAPPER = { 1:
'X' ,
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:]
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")
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
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]
218 axisname=AXIS_MAPPER[axis]
219 self.
get_command(bytes(
'SPIN {0}={1}'.format(axisname,speed),
'ascii'))
222 axisname=AXIS_MAPPER[axis]
223 reply=self.
get_command(bytes(
'RDSTAT {0}'.format(axisname),
'ascii'))
224 flags=int(reply.strip()[3:])
228 axisname=AXIS_MAPPER[axis]
229 self.
get_command(bytes(
'SPEED {0}={1}'.format(axisname,speed),
'ascii'))
238 axisname=AXIS_MAPPER[axis]
239 self.
get_command(bytes(
'HERE {0}=0'.format(axisname),
'ascii'))
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
265 previous = self.
_serial.timeout
267 self.
_serial.timeout = new_timeout
270 self.
_serial.timeout = previous
274 def __init__(self, dev_conn: _LudlController, axis: str) ->
None:
293 _logger.warning(
"querying stage axis position but device is busy")
298 def limits(self) -> microscope.AxisLimits:
334 self, conn: _LudlController, **kwargs
364 def axes(self) -> typing.Mapping[str, microscope.abc.StageAxis]:
367 def move_by(self, delta: typing.Mapping[str, float]) ->
None:
368 """Move specified axes by the specified distance. """
369 for axis_name, axis_delta
in delta.items():
370 self.
_dev_conn.move_by_relative_position(
371 int(axis_name), int(axis_delta),
375 def move_to(self, position: typing.Mapping[str, float]) ->
None:
376 """Move specified axes by the specified distance. """
378 for axis_name, axis_position
in position.items():
379 self.
_dev_conn.move_to_absolute_position(
380 int(axis_name), int(axis_position),
430 """Ludl MC 2000 controller.
434 The Ludl MC5000 can control a stage, filter wheels and shutters.
441 self, port: str, baudrate: int = 9600, timeout: float = 0.5, **kwargs
456 def devices(self) -> typing.Mapping[str, microscope.abc.Device]:
None move_to(self, float pos)
microscope.AxisLimits limits(self)
typing.Mapping[str, StageAxis] axes(self)
None __init__(self, str port, int baudrate, float timeout)
None read_until_timeout(self)
None _command_and_validate(self, bytes command, bytes expected)
reset_position(self, bytes axis)
bytes get_description(self, bytes command)
wait_for_motor_stop(self, bytes axis)
None wait_until_idle(self)
int motor_moving(self, bytes axis)
None set_speed(self, bytes axis, int speed)
None move_by_relative_position(self, bytes axis, float delta)
move_to_limit(self, bytes axis, int speed)
bytes get_command(self, bytes command)
None set_command(self, bytes command)
None move_command(self, bytes command)
None move_to_absolute_position(self, bytes axis, float pos)
changed_timeout(self, float new_timeout)
float get_absolute_position(self, bytes axis)
None command(self, bytes command)
None set_speed(self, int speed)
None move_to(self, float pos)
None move_by(self, float delta)
None __init__(self, _LudlController dev_conn, str axis)
microscope.AxisLimits limits(self)
find_limits(self, speed=100000)
None move_to(self, typing.Mapping[str, float] position)
typing.Mapping[str, microscope.abc.StageAxis] homed
None __init__(self, _LudlController conn, **kwargs)
typing.Mapping[str, microscope.abc.StageAxis] axes(self)
None move_by(self, typing.Mapping[str, float] delta)
bool may_move_on_enable(self)
None __init__(self, str port, int baudrate=9600, float timeout=0.5, **kwargs)