Source code for pylab_ml.position_controller.pollux

"""
This script defines the Pollux class, which provides an interface to control the Pollux positioning controller.
The class includes methods for connecting to the device, calibrating it, setting limits, and controlling the position and
speed of each axis (X, Z, and Angular). The status of each axis can also be checked to ensure they are ready for operation.

:Date: |today|
:Author: Semi-ATE <info@Semi-ATE.org>
"""

import warnings
from pylab_ml.base_instrument import logger
from pylab_ml.collate_instrument import Interface
from pylab_ml.base_instrument import Instrument


[docs] class Pollux(Instrument): """ Class to control the Pollux positioning controller.""" interchoices = [Interface.usbserial]
[docs] def __init__(self, **kwargs): """ Initialize the Pollux controller instance and connect to the device, then calibrate the device and set the limits for each axis. """ # kwargs = {"addr" : addr, "interface" : None, "backend" : None, "identify" : identify, "instName" : instName} if 'addr' not in kwargs or kwargs['addr'] is None: kwargs['addr'] = '0403:6001' super().__init__(**kwargs) self.logger = logger logger.debug("Class {}".format(self.__class__.__name__)) self.com._init(self) self._blocking_mode = False
[docs] def setup_inst(self): """Set instrument settings.""" super().setup_inst() if self.inst and self.com.has_visa and not hasattr(self, 'com_para'): self.inst.baud_rate = 19200 self.inst.data_bits = 8 self.inst.parity = self.com.pyvisa.constants.Parity.none self.inst.timeout = 1000 self.inst.flow_control = 0 self.inst.stop_bits = self.com.pyvisa.constants.StopBits.one self.inst.read_termination = '\n' self.inst.write_termination = '\n' elif self.inst and self.com.has_visa: self.inst.baud_rate = self.com_para['baud_rate'] if self.com_para['Parity'] == 'none': parity = self.com.pyvisa.constants.Parity.none elif self.com_para['Parity'] == 'even': parity = self.com.pyvisa.constants.Parity.even elif self.com_para['Parity'] == 'odd': parity = self.com.pyvisa.constants.Parity.odd self.inst.parity = parity self.inst.timeout = 2000 self.inst.read_termination = self.com_para['read_termination'] self.has_visa = self.com.has_visa self.timeout = 5 self.error = False self.errortext = ""
@property def id(self): """Get IDN string.""" msg = '' answ = self.inst.query('1 nidentify') for line in answ.split("\n"): if "Pollux" in line: msg += line+'\n' if msg == '': logger.debug(f'get wrong id = {answ}') return (msg[:-1])
[docs] def disconnect(self): """ Disconnect from the Pollux controller and close the VISA resource. """ if self.inst: self.inst.close() logger.info(f"Disconnected from {self.resource_name}")
def _decode_status(self, status_code): """ Decode the status code from the Pollux controller. Parameters ---------- status_code : int The status code to decode. Returns ------- str A human-readable description of the status code. """ reasons = [] if status_code & 1: reasons.append("Move in progress") if status_code & 4: reasons.append("Machine error occurred") if status_code & 16: reasons.append("Speed mode not active") if status_code & 32: reasons.append("Current position within the target window") if status_code & 64: reasons.append("Motor driver disabled from hardware input") if status_code & 128: reasons.append("Motion disabled event occurred - reset required") if not reasons: return "Unknown state or undocumented code" return " | ".join(reasons)
[docs] def status(self): """ Check the status of each axis (X, Z, Angular) and return True if all are ready, otherwise return False and log the reasons for each axis that is not ready. Returns ------- bool True if all axes are ready, False otherwise. """ status_x = int(self.inst.query("1 nst")) status_z = int(self.inst.query("2 nst")) status_ang = int(self.inst.query("3 nst")) if status_x == 0 and status_z == 0 and status_ang == 0: return True else: if status_x != 0: reason = self._decode_status(status_x) logger.warning(f"X-axis not ready. Code {status_x}: {reason}") if status_z != 0: reason = self._decode_status(status_z) logger.warning(f"Z-axis not ready. Code {status_z}: {reason}") if status_ang != 0: reason = self._decode_status(status_ang) logger.warning(f"Angular axis not ready. Code {status_ang}: {reason}") return False
[docs] def calibrate(self): """ Calibrate each axis of the Pollux controller to determine the origin (lower limit). """ self.stop() self.inst.write("1 ncal") self.inst.write("2 ncal") self.inst.write("3 ncal")
[docs] def base_position(self): """ Set the base position for each axis of the Pollux controller. The base position is set to the following for each axis: - X-axis : 40mm - Z-axis : 100mm - Angular axis : 0 degrees """ self.inst.write("40.0 1 nm") self.inst.write("100.0 2 nm") self.inst.write("0.0 3 nm")
[docs] def maximum_limits(self): """ Determine the maximum limits for each axis of the Pollux controller. """ if self.status(): self.inst.write("1 nrm") self.inst.write("2 nrm")
[docs] def set_limits(self): """ Set the maximum limits for each axis of the Pollux controller. The limits are set to the following for each axis: - X-axis : 60mm - Z-axis : 100mm - Angular axis : 354 degrees """ self.inst.write("0 60 1 setnlimit") self.inst.write("0 100 2 setnlimit") self.inst.write("0 354 3 setnlimit")
[docs] def stop(self): """ Stop any ongoing movement of the Pollux controller. """ self.inst.write("1 nabort") self.inst.write("2 nabort") self.inst.write("3 nabort")
@property def blocking_mode(self): """ Retrieves the current blocking mode status. """ return self._blocking_mode @blocking_mode.setter def blocking_mode(self, value): """ Sets the blocking mode for the controller. """ if not isinstance(value, bool): raise TypeError("blocking_mode must be a boolean (True or False).") self._blocking_mode = value self.logger.debug(f"Blocking mode set to: {self._blocking_mode}") @property def pos_x(self): """ Get the current position of the X-axis in millimeters. """ return self.inst.query("1 np") @pos_x.setter def pos_x(self, value): """ Set the position of the X-axis in millimeters, ensuring it does not exceed the maximum limit of 60mm. """ if value > 60: logger.warning("X-axis position cannot exceed 60mm. Setting to 60mm.") value = 60 if self.status(): self.inst.write(f"{float(value)} 1 nm") if self._blocking_mode: while not self.status(): pass @property def pos_z(self): """ Get the current position of the Z-axis in millimeters. """ return -float(self.inst.query("2 np")) @pos_z.setter def pos_z(self, value): """ Set the position of the Z-axis in millimeters, ensuring it does not exceed the maximum limit of -100mm. """ if value < -100: logger.warning("Z-axis position cannot exceed -100mm. Setting to -100mm.") value = -100 if self.status(): self.inst.write(f"{float(abs(value))} 2 nm") if self._blocking_mode: while not self.status(): pass @property def ang(self): """ Get the current angular position in degrees. """ return self.inst.query("3 np") @ang.setter def ang(self, value): """ Set the angular position in degrees, ensuring it does not exceed the maximum limit of 354 degrees. """ if value > 354: logger.warning("Angular position cannot exceed 354 degrees. Setting to 354 degrees.") value = 354 if self.status(): self.inst.write(f"{float(value)} 3 nm") if self._blocking_mode: while not self.status(): pass @property def speed_x(self): """ Get the current speed of the X-axis in millimeters per second. """ return self.inst.query("1 gnv") @speed_x.setter def speed_x(self, value): """ Set the speed of the X-axis in millimeters per second, ensuring it does not exceed the maximum limit of 13mm/s. """ if value > 13: logger.warning("X-axis speed cannot exceed 13mm/s. Setting to 13mm/s.") value = 13 self.inst.write(f"{float(value)} 1 snv") @property def speed_z(self): """ Get the current speed of the Z-axis in millimeters per second. """ return self.inst.query("2 gnv") @speed_z.setter def speed_z(self, value): """ Set the speed of the Z-axis in millimeters per second, ensuring it does not exceed the maximum limit of 13mm/s. """ if value > 13: logger.warning("Z-axis speed cannot exceed 13mm/s. Setting to 13mm/s.") value = 13 self.inst.write(f"{float(value)} 2 snv") @property def speed_ang(self): """ Get the current angular speed in degrees per second. """ return self.inst.query("3 gnv") @speed_ang.setter def speed_ang(self, value): """ Set the angular speed in degrees per second, ensuring it does not exceed the maximum limit of 26 degrees per second. """ if value > 26: logger.warning("Angular speed cannot exceed 26 deg/s. Setting to 26 deg/s.") value = 26 self.inst.write(f"{float(value)} 3 snv")
[docs] def flush(self): """Flush the USB buffer.""" if self.inst is not None: if self.has_visa is True: logger.debug(f'{self.instName} flush Teensy USB read Buffer...') warnings.simplefilter("ignore") value = '' val = '' while (True): # read out untill empty srting val = '' if self.inst.bytes_in_buffer > 0: try: val = self.inst.read() value = "%s%s" % (value, val) except Exception: value = '' if self.debug: logger.debug('read: {!r}'.format(value)) if val == '': break logger.debug(f'{self.instName} Buffer flushed successfully!') warnings.simplefilter("default") else: self.inst.reset_output_buffer() self.inst.reset_input_buffer() else: logger.debug('{} no instance'.format(self.instName))
[docs] def message(self, message=None): """Device has no display, message display to logger.info.""" if message is None: logger.info("{}".format(self.instName)) else: logger.info("{} {}".format(self.instName, message))
[docs] def reset(self): """Send reset to Pollux-Drive.""" self.flush() self.stop() self.calibrate() self.set_limits() return
if __name__ == "__main__": pos_controller = Pollux() if pos_controller.inst: print("Current X position:", pos_controller.pos_x) print("Current Z position:", pos_controller.pos_z) print("Current Angular position:", pos_controller.ang) pos_controller.pos_x = 30 pos_controller.pos_z = -50 pos_controller.ang = 180 print("Updated X position:", pos_controller.pos_x) print("Updated Z position:", pos_controller.pos_z) print("Updated Angular position:", pos_controller.ang)