From 8502d76b491275cb2f50df33e128a0c6122beb32 Mon Sep 17 00:00:00 2001 From: julienbarrier <1346858+julienbarrier@users.noreply.github.com> Date: Fri, 25 Aug 2023 14:40:05 +0200 Subject: [PATCH 01/30] add driver for Thorlabs PM100D --- .../drivers/Thorlabs/PM100D.py | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py new file mode 100644 index 000000000..e4d96c94c --- /dev/null +++ b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +"""QCoDes-Driver for Thorlab PM100D Handheld Optical Power and Energy Meter Console +https://www.thorlabs.com/newgrouppage9.cfm?objectgroup_id=3341&pn=PM100D + +Authors: + Julien Barrier, +""" + +import logging +import time +from typing import Optional, Any + +from qcodes.instrument import VisaInstrument +from qcodes.parameters import Parameter, create_on_off_val_mapping +import qcodes.validators as vals + +log = logging.getLogger(__name__) + + +class Thorlab_PM100D(VisaInstrument): + """ + Class to represent a Thorlab PM100D optical powermeter + + status: beta-version + + Args: + name (str): name for the instrument + address (str): Visa Resource name to connect + terminator (str): Visa terminator + timeout (float, optional). Visa timeout. default to 20s + """ + def __init__(self, + name: str, + address: Optional[str] = None, + terminator='\n', + timeout: float = 20, + **kwargs: Any): + super().__init__(name, address, terminator=terminator, **kwargs) + self._timeout = timeout + self._timeout_pwr = 120 + + self.averaging = Parameter( + 'averaging', + label='Averaging rate', + get_cmd='AVER?', + set_cmd='AVER', + val_mapping=create_on_off_val_mapping(on_val='1', off_val='0'), + vals=vals.Ints(0, 1), + instrument=self + ) + + self.wavelength = Parameter( + 'wavelength', + label='Detected wavelength', + unit='m', + get_cmd=self._get_wavelength, + set_cmd=self._set_wavelength, + vals=vals.Numbers(185e-9, 25e-6), + instrument=self + ) + + self.power = Parameter( + "power", + label='Measured power', + unit="W", + get_cmd=self._get_power, + vals=vals.Numbers(), + instrument=self + ) + + self.write('STAT:OPER:PTR 512') + self.write('STAT:OPER:NTR 0') + self.ask('STAT:OPER?') + self._check_error() + self.averaging(300) + self._set_conf_power() + + self.connect_message() + + def _check_error(self) -> None: + err = self.ask('SYST:ERR?') + if err[:2] != '+0': + raise RuntimeError(f'PM100D call failed with error: {err}') + + def _set_wavelength(self, value: float) -> None: + value_in_nm = value*1e9 + self.write(f'SENS:CORR:WAV {value_in_nm}') + + def _get_wavelength(self) -> float: + value_in_nm = self.ask('SENS:CORR:WAV?') + return float(value_in_nm)/1e9 + + def _set_conf_power(self) -> None: + """Set configuration to power mode + """ + self.write('CONF:POW') # set config to power mode + self.ask('ABOR;:STAT:OPER?') + self.write('INIT') + return None + + def _get_power(self) -> float: + """Get the power + """ + self._set_conf_power() + oper = self.ask('STAT:OPER?') + start = time.process_time() + time_spent = 0 + while oper != str(512) and time_spent < self._timeout_pwr: + oper = self.ask('STAT:OPER?') + time_spent = time.process_time()-start + power = self.ask('FETC?') + self._check_error() + return float(power) From 8c88557d5f524d449473996fffc7c45a067817cd Mon Sep 17 00:00:00 2001 From: julienbarrier <1346858+julienbarrier@users.noreply.github.com> Date: Fri, 25 Aug 2023 14:59:15 +0200 Subject: [PATCH 02/30] add new HW types --- .../drivers/Thorlabs/APT.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/APT.py b/qcodes_contrib_drivers/drivers/Thorlabs/APT.py index cd027dfed..f83f6f9da 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/APT.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/APT.py @@ -5,8 +5,23 @@ class ThorlabsHWType(enum.Enum): PRM1Z8 = 31 - MFF10x = 48 - K10CR1 = 50 + BSC001 = 11 # 1 ch benchtop stepper driver + BSC101 = 12 # 1 ch benchtop stepper driver + BSC002 = 13 # 2 ch benchtop stepper driver + BDC101 = 14 # 1 ch benchtop dc servo driver + SCC001 = 21 # 1 ch stepper driver card (used within BSC102, 103 units) + DCC001 = 22 # 1 ch DC servo driver card (used within BDC102, 103 units) + ODC001 = 24 # 1 ch DC servo driver cube + OST001 = 25 # 1 ch stepper driver cube + MST601 = 26 # 2 ch modular stepper driver module + TST001 = 29 # 1 ch stepper driver T-cube + TDC001 = 31 # 1 ch DC servo driver T-cube + LTSxxx = 42 # LTS300/LTS150 long travel integrated driver/stages + L490MZ = 43 # L490MZ Integrated Driver/Labjack + BBD10x = 44 # 1/2/3 ch benchtop brushless DC servo driver + MFF10x = 48 # motorized filter flip + K10CR1 = 50 # steper motor rotation mount + KDC101 = 63 # 1 ch Brushed DC servo motor controller K-cube class ThorlabsException(Exception): From 3cabf93418e98876ab3739fb524116f4e84af9b8 Mon Sep 17 00:00:00 2001 From: julienbarrier <1346858+julienbarrier@users.noreply.github.com> Date: Fri, 25 Aug 2023 16:09:28 +0200 Subject: [PATCH 03/30] create private folder to include APT+rotator class --- .../drivers/Thorlabs/K10CR1.py | 15 +- .../drivers/Thorlabs/MFF10x.py | 2 +- .../drivers/Thorlabs/PRM1Z8.py | 2 +- .../drivers/Thorlabs/{ => private}/APT.py | 0 .../drivers/Thorlabs/private/__init__.py | 0 .../drivers/Thorlabs/private/rotators.py | 264 ++++++++++++++++++ 6 files changed, 268 insertions(+), 15 deletions(-) rename qcodes_contrib_drivers/drivers/Thorlabs/{ => private}/APT.py (100%) create mode 100644 qcodes_contrib_drivers/drivers/Thorlabs/private/__init__.py create mode 100644 qcodes_contrib_drivers/drivers/Thorlabs/private/rotators.py diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/K10CR1.py b/qcodes_contrib_drivers/drivers/Thorlabs/K10CR1.py index 5f8ad701d..c1aa169a9 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/K10CR1.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/K10CR1.py @@ -4,19 +4,8 @@ import qcodes.utils.validators as vals from qcodes import Instrument -from .APT import Thorlabs_APT, ThorlabsHWType - - -class RotationDirection(enum.Enum): - """Constants for the rotation direction of Thorlabs K10CR1 rotator""" - FORWARD = "fwd" - REVERSE = "rev" - - -class HomeLimitSwitch(enum.Enum): - """Constants for the home limit switch of Thorlabs K10CR1 rotator""" - REVERSE = "rev" - FORWARD = "fwd" +from .private.APT import Thorlabs_APT, ThorlabsHWType +from .private.rotators import RotationDirection, HomeLimitSwitch class Thorlabs_K10CR1(Instrument): diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/MFF10x.py b/qcodes_contrib_drivers/drivers/Thorlabs/MFF10x.py index 3cfb268d0..7a9dd1530 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/MFF10x.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/MFF10x.py @@ -1,5 +1,5 @@ from qcodes import Instrument -from .APT import Thorlabs_APT, ThorlabsHWType +from .private.APT import Thorlabs_APT, ThorlabsHWType class Thorlabs_MFF10x(Instrument): diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/PRM1Z8.py b/qcodes_contrib_drivers/drivers/Thorlabs/PRM1Z8.py index f856233ae..05fdd1d4d 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/PRM1Z8.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/PRM1Z8.py @@ -1,5 +1,5 @@ from qcodes import Instrument -from .APT import Thorlabs_APT, ThorlabsHWType +from .private.APT import Thorlabs_APT, ThorlabsHWType class Thorlabs_PRM1Z8(Instrument): diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/APT.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/APT.py similarity index 100% rename from qcodes_contrib_drivers/drivers/Thorlabs/APT.py rename to qcodes_contrib_drivers/drivers/Thorlabs/private/APT.py diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/__init__.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/rotators.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/rotators.py new file mode 100644 index 000000000..f02b37249 --- /dev/null +++ b/qcodes_contrib_drivers/drivers/Thorlabs/private/rotators.py @@ -0,0 +1,264 @@ +import enum +from typing import Tuple, Optional + +import qcodes.utils.validators as vals +from qcodes import Instrument +from .APT import Thorlabs_APT + + +class RotationDirection(enum.Enum): + """Constants for the rotation direction of Thorlabs K10CR1 rotator""" + FORWARD = "fwd" + REVERSE = "rev" + +class HomeLimitSwitch(enum.Enum): + """Constants for the home limit switch of Thorlabs K10CR1 rotator""" + REVERSE = "rev" + FORWARD = "fwd" + +class _Thorlabs_rotator(Instrument): + """ + Instrument driver for the Thorlabs K10CR1 rotator. + + Args: + name: Instrument name. + device_id: ID for the desired rotator. + apt: Thorlabs APT server. + + Attributes: + apt: Thorlabs APT server. + serial_number: Serial number of the device. + model: Model description. + version: Firmware version. + """ + + def __init__(self, name: str, device_id: int, hw_type, apt: Thorlabs_APT, **kwargs): + super().__init__(name, **kwargs) + + # Save APT server reference + self.apt = apt + + # initialization + self.serial_number = self.apt.get_hw_serial_num_ex(hw_type, device_id) + self.apt.init_hw_device(self.serial_number) + self.model, self.version, _ = self.apt.get_hw_info(self.serial_number) + + # Set velocity and move-home parameters to previous values. Otherwise the velocity is very + # very low and it is not the velocity stored in the parameters... For whatever reason? + self._set_velocity_parameters() + self._set_home_parameters() + + # Helpers + direction_val_mapping = {RotationDirection.FORWARD: 1, + RotationDirection.FORWARD.value: 1, + RotationDirection.REVERSE: 2, + RotationDirection.REVERSE.value: 2} + lim_switch_val_mapping = {HomeLimitSwitch.REVERSE: 1, + HomeLimitSwitch.REVERSE.value: 1, + HomeLimitSwitch.FORWARD: 4, + HomeLimitSwitch.FORWARD.value: 4} + + # PARAMETERS + # Position + self.add_parameter("position", + get_cmd=self._get_position, + set_cmd=self._set_position, + vals=vals.Numbers(0, 360), + unit=u"\u00b0", + label="Position") + self.add_parameter("position_async", + get_cmd=None, + set_cmd=self._set_position_async, + vals=vals.Numbers(0, 360), + unit=u"\u00b0", + label="Position") + + # Velocity Parameters + self.add_parameter("velocity_min", + set_cmd=self._set_velocity_min, + get_cmd=self._get_velocity_min, + vals=vals.Numbers(0, 25), + unit=u"\u00b0/s", + label="Minimum Velocity") + self.add_parameter("velocity_acceleration", + set_cmd=self._set_velocity_acceleration, + get_cmd=self._get_velocity_acceleration, + vals=vals.Numbers(0, 25), + unit=u"\u00b0/s\u00b2", + label="Acceleration") + self.add_parameter("velocity_max", + set_cmd=self._set_velocity_max, + get_cmd=self._get_velocity_max, + vals=vals.Numbers(0, 25), + unit=u"\u00b0/s", + label="Maximum Velocity") + + # Move home parameters + self.add_parameter("move_home_direction", + set_cmd=self._set_home_direction, + get_cmd=self._get_home_direction, + val_mapping=direction_val_mapping, + label="Direction for Moving Home") + self.add_parameter("move_home_limit_switch", + set_cmd=self._set_home_lim_switch, + get_cmd=self._get_home_lim_switch, + val_mapping=lim_switch_val_mapping, + label="Limit Switch for Moving Home") + self.add_parameter("move_home_velocity", + set_cmd=self._set_home_velocity, + get_cmd=self._get_home_velocity, + vals=vals.Numbers(0, 25), + unit=u"\u00b0/s", + label="Velocity for Moving Home") + self.add_parameter("move_home_zero_offset", + set_cmd=self._set_home_zero_offset, + get_cmd=self._get_home_zero_offset, + vals=vals.Numbers(0, 360), + unit=u"\u00b0", + label="Zero Offset for Moving Home") + + # FUNCTIONS + # Stop motor + self.add_function("stop", + call_cmd=self._stop, + args=[]) + + # Moving direction + self.add_function("move_direction", + call_cmd=self._move_direction, + args=[vals.Enum(*direction_val_mapping)], + arg_parser=lambda val: direction_val_mapping[val]) + + # Enable/disable + self.add_function("enable", + call_cmd=self._enable, + args=[]) + self.add_function("disable", + call_cmd=self._disable, + args=[]) + + # Move home + self.add_function("move_home", + call_cmd=self._move_home, + args=[]) + self.add_function("move_home_async", + call_cmd=self._move_home_async, + args=[]) + + # print connect message + self.connect_message() + + def get_idn(self): + """Returns hardware information of the device.""" + return {"vendor": "Thorlabs", "model": self.model, + "firmware": self.version, "serial": self.serial_number} + + def _get_position(self) -> float: + return self.apt.mot_get_position(self.serial_number) + + def _set_position(self, position: float): + self.apt.mot_move_absolute_ex(self.serial_number, position, True) + + def _set_position_async(self, position: float): + self.apt.mot_move_absolute_ex(self.serial_number, position, False) + + def _get_velocity_parameters(self) -> Tuple[float, float, float]: + return self.apt.mot_get_velocity_parameters(self.serial_number) + + def _set_velocity_parameters(self, + min_vel: Optional[float] = None, accn: Optional[float] = None, max_vel: Optional[float] = None): + if min_vel is None or accn is None or max_vel is None: + old_min_vel, old_accn, old_max_vel = self._get_velocity_parameters() + if min_vel is None: + min_vel = old_min_vel + if accn is None: + accn = old_accn + if max_vel is None: + max_vel = old_max_vel + return self.apt.mot_set_velocity_parameters(self.serial_number, min_vel, accn, max_vel) + + def _get_velocity_min(self) -> float: + min_vel, _, _ = self._get_velocity_parameters() + return min_vel + + def _set_velocity_min(self, min_vel: float): + self._set_velocity_parameters(min_vel=min_vel) + + def _get_velocity_acceleration(self) -> float: + _, accn, _ = self._get_velocity_parameters() + return accn + + def _set_velocity_acceleration(self, accn: float): + self._set_velocity_parameters(accn=accn) + + def _get_velocity_max(self) -> float: + _, _, max_vel = self._get_velocity_parameters() + return max_vel + + def _set_velocity_max(self, max_vel: float): + self._set_velocity_parameters(max_vel=max_vel) + + def _get_home_parameters(self) -> Tuple[int, int, float, float]: + return self.apt.mot_get_home_parameters(self.serial_number) + + def _set_home_parameters(self, direction: Optional[int] = None, lim_switch: Optional[int] = None, + velocity: Optional[float] = None, zero_offset: Optional[float] = None): + if direction is None or lim_switch is None or velocity is None or zero_offset is None: + old_direction, old_lim_switch, old_velocity, old_zero_offset = self._get_home_parameters() + if direction is None: + direction = old_direction + if lim_switch is None: + lim_switch = old_lim_switch + if velocity is None: + velocity = old_velocity + if zero_offset is None: + zero_offset = old_zero_offset + + return self.apt.mot_set_home_parameters(self.serial_number, + direction, lim_switch, velocity, zero_offset) + + def _get_home_direction(self) -> int: + direction, _, _, _ = self._get_home_parameters() + return direction + + def _set_home_direction(self, direction: int): + self._set_home_parameters(direction=direction) + + def _get_home_lim_switch(self) -> int: + _, lim_switch, _, _ = self._get_home_parameters() + return lim_switch + + def _set_home_lim_switch(self, lim_switch: int): + self._set_home_parameters(lim_switch=lim_switch) + + def _get_home_velocity(self) -> float: + _, _, velocity, _ = self._get_home_parameters() + return velocity + + def _set_home_velocity(self, velocity: float): + self._set_home_parameters(velocity=velocity) + + def _get_home_zero_offset(self) -> float: + _, _, _, zero_offset = self._get_home_parameters() + return zero_offset + + def _set_home_zero_offset(self, zero_offset: float): + self._set_home_parameters(zero_offset=zero_offset) + + def _stop(self): + self.apt.mot_stop_profiled(self.serial_number) + + def _move_direction(self, direction: int): + self.apt.mot_move_velocity(self.serial_number, direction) + + def _enable(self): + self.apt.enable_hw_channel(self.serial_number) + + def _disable(self): + self.apt.disable_hw_channel(self.serial_number) + + def _move_home(self): + self.apt.mot_move_home(self.serial_number, True) + + def _move_home_async(self): + self.apt.mot_move_home(self.serial_number, False) \ No newline at end of file From f80bc9f10bf26040dd00f2b26560a46649862ac8 Mon Sep 17 00:00:00 2001 From: julienbarrier <1346858+julienbarrier@users.noreply.github.com> Date: Fri, 25 Aug 2023 16:09:55 +0200 Subject: [PATCH 04/30] add driver for KDC101 rotator --- .../drivers/Thorlabs/KDC101.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py new file mode 100644 index 000000000..b7e758de4 --- /dev/null +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +"""QCoDes-Driver for Thorlab KDC101 K-Cube Brushed DC Servo Motor Controller +https://www.thorlabs.com/thorproduct.cfm?partnumber=KDC101 + +Authors: + Julien Barrier, +""" + +from .private.APT import Thorlabs_APT, ThorlabsHWType +from .private.rotators import _Thorlabs_rotator + + +class Thorlabs_KDC101(_Thorlabs_rotator): + """ + Instrument driver for the Thorlabs KDC101 servo motor controller. + + Args: + name: Instrument name. + device_id: ID for the desired rotator. + apt: Thorlabs APT server. + + Attributes: + apt: Thorlabs APT server. + serial_number: Serial number of the device. + model: Model description. + version: Firmware version. + """ + + def __init__(self, name: str, device_id: int, apt: Thorlabs_APT, **kwargs): + super().__init__(name, device_id=device_id, hwtype=ThorlabsHWType.KDC101, apt=apt, **kwargs) \ No newline at end of file From bee8969e6cab2261c1b742a3d620f1a1b60c04d1 Mon Sep 17 00:00:00 2001 From: julienbarrier <1346858+julienbarrier@users.noreply.github.com> Date: Fri, 25 Aug 2023 16:15:20 +0200 Subject: [PATCH 05/30] variable type (pass mypy) --- qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py index e4d96c94c..3fe932f3c 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py @@ -104,7 +104,7 @@ def _get_power(self) -> float: self._set_conf_power() oper = self.ask('STAT:OPER?') start = time.process_time() - time_spent = 0 + time_spent = 0. while oper != str(512) and time_spent < self._timeout_pwr: oper = self.ask('STAT:OPER?') time_spent = time.process_time()-start From 6ec08c2cea531a447e267799b977cc6ce997d682 Mon Sep 17 00:00:00 2001 From: julienbarrier <1346858+julienbarrier@users.noreply.github.com> Date: Fri, 25 Aug 2023 17:22:05 +0200 Subject: [PATCH 06/30] debug kdc101 --- qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py | 7 +++++-- .../drivers/Thorlabs/private/rotators.py | 10 ++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py index b7e758de4..1b65e2974 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py @@ -26,5 +26,8 @@ class Thorlabs_KDC101(_Thorlabs_rotator): version: Firmware version. """ - def __init__(self, name: str, device_id: int, apt: Thorlabs_APT, **kwargs): - super().__init__(name, device_id=device_id, hwtype=ThorlabsHWType.KDC101, apt=apt, **kwargs) \ No newline at end of file + def __init__(self, name: str, apt: Thorlabs_APT, device_id: int = 0, serial_number=0, **kwargs): + super().__init__(name, device_id=device_id, + hw_type=ThorlabsHWType.KDC101, + serial_number=serial_number, + apt=apt, **kwargs) \ No newline at end of file diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/rotators.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/rotators.py index f02b37249..4398adf78 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/private/rotators.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/private/rotators.py @@ -32,14 +32,20 @@ class _Thorlabs_rotator(Instrument): version: Firmware version. """ - def __init__(self, name: str, device_id: int, hw_type, apt: Thorlabs_APT, **kwargs): + def __init__(self, name: str, + device_id: int = 0, + serial_number: int = 0, + hw_type: enum.EnumType = None, + apt: Thorlabs_APT = None, + **kwargs): super().__init__(name, **kwargs) # Save APT server reference self.apt = apt # initialization - self.serial_number = self.apt.get_hw_serial_num_ex(hw_type, device_id) + if serial_number == 0: + self.serial_number = self.apt.get_hw_serial_num_ex(hw_type, device_id) self.apt.init_hw_device(self.serial_number) self.model, self.version, _ = self.apt.get_hw_info(self.serial_number) From 5e4a4b8def0526dfe0965c2fe6f3dcbec64bcf64 Mon Sep 17 00:00:00 2001 From: julienbarrier <1346858+julienbarrier@users.noreply.github.com> Date: Mon, 28 Aug 2023 17:24:57 +0200 Subject: [PATCH 07/30] add driver for KDC101 --- .../drivers/Thorlabs/KDC101.py | 553 +++++++++++++++++- .../drivers/Thorlabs/private/__init__.py | 39 ++ 2 files changed, 573 insertions(+), 19 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py index 1b65e2974..0c031ff6c 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py @@ -4,30 +4,545 @@ Authors: Julien Barrier, + +TODO: check that pointers are well coded. ctypes.byref(val) if we need to update the value """ +import os +import sys +import ctypes +import logging +from typing import Any +from time import sleep, time -from .private.APT import Thorlabs_APT, ThorlabsHWType -from .private.rotators import _Thorlabs_rotator +from qcodes.instrument import Instrument +from qcodes.parameters import Parameter +from qcodes import validators as vals +from .private import ConnexionErrors, GeneralErrors, MotorErrors -class Thorlabs_KDC101(_Thorlabs_rotator): - """ - Instrument driver for the Thorlabs KDC101 servo motor controller. +log = logging.getLogger(__name__) + +class Thorlabs_KDC101(Instrument): + """Instrument driver for the Thorlabs KDC101 servo motor controller Args: - name: Instrument name. - device_id: ID for the desired rotator. - apt: Thorlabs APT server. - - Attributes: - apt: Thorlabs APT server. - serial_number: Serial number of the device. - model: Model description. - version: Firmware version. + Instrument (_type_): _description_ """ + _CONDITIONS = ['homed', 'moved', 'stopped', 'limit_updated'] + def __init__(self, + name: str, + serial_number: str, + simulation: bool = False, + polling: int = 200, + home: bool = False, + **kwargs): + super().__init__(name, **kwargs) + if sys.platform != 'win32': + self._dll: Any = None + raise OSError('Thorlabs Kinesis only works on Windows') + else: + os.add_dll_directory(r'C:\Program Files\Thorlabs\Kinesis') + self._dll_path = 'Thorlabs.MotionControl.KCube.DCServo.dll' + self._dll = ctypes.cdll.LoadLibrary(self._dll_path) + + self._simulation = simulation + if self._simulation: + self.enable_simulation() + + self._serial_number = ctypes.c_char_p(serial_number.encode('ascii')) + if self._dll.TLI_BuildDeviceList() == 0: + self._dll.CC_Open(self._serial_number) + self._start_polling(polling) + + # self._info = self._get_hardware_info() + #self.model = self._info[0].decode('utf-8') + #self.version = self._info[4] + + #self._device_info = dict(zip( + # ['type ID', 'description', 'PID', 'is known type', 'motor type', + # 'is piezo', 'is laser', 'is custom', 'is rack', 'max_channels'], + # self._get_device_info())) + + self._load_settings() + self._set_limits_approach(1) + + sleep(3) + + self._clear_message_queue() + + if home: + if not self._can_home(): + self.homed = False + raise RuntimeError('Device `{}` is not homeable') + else: + self.go_home() + else: + self.homed = False + + def identify(self) -> None: + self._dll.CC_Identify(self._serial_number) + + def get_idn(self) -> dict: + idparts = ['Thorlabs', self.model, self.version, self.serial_number] + return dict(zip(('vendor', 'model', 'serial', 'firmware'), idparts)) + + def go_home(self, block=True): + self._check_error(self._dll.CC_Home(self._serial_number)) + self.homed = True + if block: + self.wait_for_completion() + + def enable_simulation(self) -> None: + self._dll.TLI_InitializeSimulations() + + def disable_simulation(self) -> None: + self._dll.TLI_UninitializeSimulations() + + def wait_for_completion(self, status: str = 'homed', max_time = 5): + message_type = ctypes.c_ushort() + message_id = ctypes.c_ushort() + message_data = ctypes.c_ulong() + cond = self._CONDITIONS.index(status) + + if status == 'stopped': + if not self.is_moving(): + return None + elif status == 'homed': + max_time = 0 + + while self._dll.CC_MessageQueueSize(self._serial_number) <= 0: + sleep(.2) + + self._dll.CC_WaitForMessage( + self._serial_number, ctypes.byref(message_type), + ctypes.byref(message_id), ctypes.byref(message_data) + ) + + start = time() + while int(message_type.value) != 2 or int(message_id.value) != cond: + end = time() + if end - start > max_time and max_time != 0: + raise RuntimeError(f'waited for {max_time} for {status} to complete' + f'message type: {message_type.value} ({message_id.value})') + + if self._dll.CC_MessageQueueSize(self._serial_number) <= 0: + sleep(.2) + continue + self._dll.CC_WaitForMessage( + self._serial_number, + ctypes.byref(message_type), + ctypes.byref(message_id), + ctypes.byref(message_data) + ) + + def _device_unit_to_real(self, device_unit: int, unit_type: int) -> float: + real_unit = ctypes.c_double() + ret = self._dll.CC_GetRealValueFromDeviceUnit( + self._serial_number, ctypes.c_int(device_unit), + ctypes.byref(real_unit), ctypes.c_int(unit_type) + ) + self._check_error(ret) + return real_unit.value + + def _real_to_device_unit(self, real_unit: float, unit_type: int) -> int: + device_unit = ctypes.c_int() + ret = self._dll.CC_GetDeviceUnitFromRealValue( + self._serial_number, ctypes.c_double(real_unit), + ctypes.byref(device_unit), ctypes.c_int(unit_type) + ) + self._check_error(ret) + return device_unit.value + + def _get_backlash(self): + ret = self._dll.CC_GetBacklash(self._serial_number) + return self._device_unit_to_real(ret, 0) + + def _set_backlash(self, value: float) -> None: + val = self._real_to_device_unit(value, 0) + ret = self._dll.CC_SetBacklash(self._serial_number, ctypes.c_long(val)) + self._check_error(ret) + + def _get_homing_velocity(self): + vel = self._dll.CC_GetHomingVelocity(self._serial_number) + return self._device_unit_to_real(vel, 1) + + def _set_homing_velocity(self, velocity): + vel = self._real_to_device_unit(velocity, 1) + ret = self._dll.CC_SetHomingVelocity(self._serial_number, + ctypes.c_uint(vel)) + self._check_error(ret) + + def _get_jog_mode(self) -> str: + jog_mode = ctypes.c_short() + stop_mode = ctypes.c_short() + ret = self._dll.CC_GetJogMode(self._serial_number, + ctypes.byref(jog_mode), + ctypes.byref(stop_mode)) + self._check_error(ret) + if jog_mode.value == ctypes.c_short(0x01).value: + return 'continuous' + elif jog_mode.value == ctypes.c_short(0x02).value: + return 'stepped' + else: + raise RuntimeError('Unexpected value received from Kinesis') + + def _set_jog_mode(self, mode: str) -> None: + jog_mode = ctypes.c_short(0x00) + stop_mode = ctypes.c_short(0x00) + stop_mode = self._get_stop_mode() + + if mode == 'continuous': + jog_mode = ctypes.c_short(0x01) + elif mode == 'stepped': + jog_mode = ctypes.c_short(0x02) + if stop_mode == 'immediate': + stop_mode = ctypes.c_short(0x01) + elif stop_mode == 'profiled': + stop_mode = ctypes.c_short(0x02) + + ret = self._dll.CC_SetJogMode(self._serial_number, + jog_mode, stop_mode) + self._check_error(ret) + return None + + def _get_jog_step_size(self): + ret = self._dll.CC_GetJogStepSize(self._serial_number) + return self._device_unit_to_real(ret, 0) + + def _set_jog_step_size(self, step_size) -> None: + step = self._real_to_device_unit(step_size, 0) + ret = self._dll.CC_SetJogStepSize(self._serial_number, + ctypes.c_uint(step)) + self._check_error(ret) + return None + + def _check_connection(self) -> bool: + ret = self._dll.CC_CheckConnection(self._serial_number) + return bool(ret) + + def _get_stop_mode(self): + jog_mode = ctypes.c_short() + stop_mode = ctypes.c_short() + ret = self._dll.CC_GetJogMode(self._serial_number, + ctypes.byref(jog_mode), + ctypes.byref(stop_mode)) + self._check_error(ret) + if stop_mode.value == ctypes.c_short(0x01).value: + return 'immediate' + elif stop_mode.value == ctypes.c_short(0x02).value: + return 'profiled' + else: + raise RuntimeError('unexpected value received from Kinesis') + + def _set_stop_mode(self, mode) -> None: + jog_mode = ctypes.c_short(0x00) + stop_mode = ctypes.c_short(0x00) + + jmode = self._get_jog_mode() + if jmode == 'continuous': + jog_mode = ctypes.c_short(0x01) + elif jmode == 'stepped': + jog_mode = ctypes.c_short(0x02) + if mode == 'immediate': + stop_mode = ctypes.c_short(0x01) + elif mode == 'profiled': + stop_mode = ctypes.c_short(0x02) + + ret = self._dll.CC_SetJogMode(self._serial_number, + jog_mode, stop_mode) + self._check_error(ret) + return None + + def _get_max_position(self) -> float: + max_pos = ctypes.c_int() + self._dll.CC_GetStageAxisMaxPos(self._serial_number, max_pos) + return self._device_unit_to_real(max_pos.value, 0) + + def _set_max_position(self, max_val) -> None: + min_val = self._get_min_position() + min_val = self._real_to_device_unit(min_val, 0) + max_val = self._real_to_device_unit(max_val, 0) + ret = self._dll.CC_SetStageAxisLimits(self._serial_number, + ctypes.c_int(min_val), + ctypes.c_int(max_val)) + self._check_error(ret) + self.wait_for_completion('limit_updated') + + def _get_min_position(self) -> float: + min_pos = ctypes.c_int() + self._dll.CC_GetStageAxisMinPos(self._serial_number, min_pos) + return self._device_unit_to_real(min_pos.value, 0) + + def _set_min_position(self, min_val) -> None: + max_val = self._get_max_position() + max_val = self._real_to_device_unit(max_val, 0) + min_val = self._real_to_device_unit(min_val, 0) + ret = self._dll.CC_SetStageAxisLimits(self._serial_number, + ctypes.c_int(min_val), + ctypes.c_int(max_val)) + self._check_error(ret) + self.wait_for_completion('limit_updated') + + def _get_soft_limits_mode(self) -> str: + mode = ctypes.c_int16() + self._dll.CC_GetSoftLimitMode(self._serial_number, ctypes.byref(mode)) + if mode.value == ctypes.c_int16(0).value: + return 'disallow' + elif mode.value == ctypes.c_int16(1).value: + return 'partial' + elif mode.value == ctypes.c_int16(2).value: + return 'all' + else: + raise RuntimeError('unexpected value received from Kinesis') + + def _set_soft_limits_mode(self, mode: str) -> None: + if mode == 'disallow': + lmode = ctypes.c_int16(0) + elif mode == 'partial': + lmode = ctypes.c_int16(1) + elif mode == 'all': + lmode = ctypes.c_int16(2) + + ret = self._dll.CC_SetLimitsSoftwareApproachPolicy(self._serial_number, + lmode) + self._check_error(ret) + + def _get_move_velocity(self) -> float: + acceleration = ctypes.c_int() + velocity = ctypes.c_int() + ret = self._dll.CC_GetVelParams(self._serial_number, + ctypes.byref(acceleration), + ctypes.byref(velocity)) + self._check_error(ret) + return self._device_unit_to_real(velocity.value, 1) + + def _set_move_velocity(self, velocity): + vel = self._real_to_device_unit(velocity, 1) + accel = self._real_to_device_unit(self._get_move_acceleration(), 2) + ret = self._dll.CC_SetVelParams(self._serial_number, + ctypes.c_int(accel), + ctypes.c_int(vel)) + self._check_error(ret) + + def _get_move_acceleration(self) -> float: + acceleration = ctypes.c_int() + velocity = ctypes.c_int() + ret = self._dll.CC_GetVelParams(self._serial_number, + ctypes.byref(acceleration), + ctypes.byref(velocity)) + self._check_error(ret) + return self._device_unit_to_real(acceleration.value, 2) + + def _set_move_acceleration(self, acceleration) -> None: + vel = self._real_to_device_unit(self._get_move_velocity(), 1) + accel = self._real_to_device_unit(acceleration, 2) + ret = self._dll.CC_SetVelParams(self._serial_number, + ctypes.c_int(accel), + ctypes.c_int(vel)) + self._check_error(ret) + + def _get_jog_velocity(self) -> float: + acceleration = ctypes.c_int() + velocity = ctypes.c_int() + ret = self._dll.CC_GetJogVelParams(self._serial_number, + ctypes.byref(acceleration), + ctypes.byref(velocity)) + self._check_error(ret) + return self._device_unit_to_real(velocity.value, 1) + + def _set_jog_velocity(self, velocity) -> None: + vel = self._real_to_device_unit(velocity, 1) + accel = self._real_to_device_unit(self._get_jog_acceleration(), 2) + ret = self._dll.CC_SetJogVelParams(self._serial_number, + ctypes.c_int(accel), + ctypes.c_int(vel)) + self._check_error(ret) + + def _get_jog_acceleration(self) -> float: + acceleration = ctypes.c_int() + velocity = ctypes.c_int() + ret = self._dll.CC_GetJogVelParams(self._serial_number, + ctypes.byref(acceleration), + ctypes.byref(velocity)) + self._check_error(ret) + return self._device_unit_to_real(acceleration.value, 2) + + def _set_jog_acceleration(self, acceleration) -> None: + vel = self._real_to_device_unit(self._get_jog_velocity(), 1) + accel = self._real_to_device_unit(acceleration, 2) + ret = self._dll.CC_SetJogVelParams(self._serial_number, + ctypes.c_int(accel), + ctypes.c_int(vel)) + self._check_error(ret) + + def _get_position(self) -> float: + current_position = ctypes.c_int() + self._dll.CC_GetPosition(self._serial_number, + ctypes.byref(current_position)) + return self._device_unit_to_real(current_position.value, 0) + + def is_moving(self) -> bool: + status_bit = ctypes.c_short() + self._dll.CC_GetStatusBits(self._serial_number, + ctypes.byref(status_bit)) + if status_bit.value & 0x10 or status_bit.value & 0x20: + return True + else: + return False + + def move_to(self, position, block=True) -> None: + pos = self._real_to_device_unit(position, 0) + ret = self._dll.CC_MoveToPosition(self._serial_number, + ctypes.c_int(pos)) + self._check_error(ret) + + if block: + self.wait_for_completion(status='moved', max_time=15) + + def move_by(self, displacement, block: bool = True): + dis = self._real_to_device_unit(displacement, 0) + ret = self._dll.CC_MoveRelative(self._serial_number, + ctypes.c_int(dis)) + self._check_error(ret) + if block: + self.wait_for_completion(status='moved') + + def move_continuous(self, direction = 'forward') -> None: + if direction == 'forward' or direction == 'forwards': + direction = ctypes.c_short(0x01) + elif direction == 'reverse' or direction == 'backward' or direction == 'backwards': + direction = ctypes.c_short(0x02) + else: + raise ValueError('direction unrecognised') + + ret = self._dll.CC_MoveAtVelocity(self._serial_number, direction) + self._check_error(ret) + + def jog(self, direction, block: bool = True) -> None: + if direction == 'forward' or direction == 'forwards': + direction = ctypes.c_short(0x01) + elif direction == 'reverse' or direction == 'backward' or direction == 'backwards': + direction = ctypes.c_short(0x02) + else: + raise ValueError('direction unrecognised') + + ret = self._dll.CC_MoveJog(self._serial_number, direction) + self._check_error(ret) + if self._get_jog_mode() =='stepped': + if block: + self.wait_for_completion(status='moved') + + def stop(self, immediate=False) -> None: + if immediate: + ret = self._dll.CC_StopImmediate(self._serial_number) + else: + ret = self._dll.CC_StopProfiled(self._serial_number) + self._check_error(ret) + self.wait_for_completion(status='stopped') + + def close(self): + if self._simulation: + self.disable_simulation() + if hasattr(self, '_serial_number'): + self._dll.CC_Close(self._serial_number) + + def _get_hardware_info(self) -> list: + """this gives a device not found error. TODO: debug this""" + model = ctypes.c_char_p() + model_size = ctypes.c_ulong() + type_num = ctypes.c_ushort() + channel_num = ctypes.c_ushort() + notes = ctypes.c_char_p() + notes_size = ctypes.c_ulong() + firmware_version = ctypes.c_ulong() + hardware_version = ctypes.c_ushort() + modification_state = ctypes.c_ushort() - def __init__(self, name: str, apt: Thorlabs_APT, device_id: int = 0, serial_number=0, **kwargs): - super().__init__(name, device_id=device_id, - hw_type=ThorlabsHWType.KDC101, - serial_number=serial_number, - apt=apt, **kwargs) \ No newline at end of file + ret = self._dll.CC_GetHardwareInfo( + ctypes.byref(self._serial_number), + ctypes.byref(model), model_size, + ctypes.byref(type_num), ctypes.byref(channel_num), + ctypes.byref(notes), notes_size, ctypes.byref(firmware_version), + ctypes.byref(hardware_version), ctypes.byref(modification_state) + ) + + self._check_error(ret) + return [model.value, type_num.value, channel_num.value, + notes.value, firmware_version.value, hardware_version.value, + modification_state.value] + + def _get_device_info(self): + """this gives a device not found error. TODO: debug this""" + type_id = ctypes.c_ulong() + description = ctypes.c_char() + pid = ctypes.c_ulong() + is_known_type = ctypes.c_bool() + motor_type = ctypes.c_int() + is_piezo_device = ctypes.c_bool() + is_laser = ctypes.c_bool() + is_custom_type = ctypes.c_bool() + is_rack = ctypes.c_bool() + max_channels = ctypes.c_bool() + + ret = self._dll.TLI_GetDeviceInfo( + ctypes.byref(self._serial_number), + ctypes.byref(type_id), + ctypes.byref(description), + ctypes.byref(pid), + ctypes.byref(is_known_type), + ctypes.byref(motor_type), + ctypes.byref(is_piezo_device), + ctypes.byref(is_laser), + ctypes.byref(is_custom_type), + ctypes.byref(is_rack), + ctypes.byref(max_channels) + ) + self._check_error(ret) + return [type_id.value, description.value, pid.value, + is_known_type.value, motor_type.value, is_piezo_device.value, + is_laser.value, is_custom_type.value, is_rack.value, + max_channels.value] + + def _load_settings(self): + self._dll.CC_LoadSettings(self._serial_number) + return None + + def _set_limits_approach(self, limit: int): + disallow_illegal_moves = ctypes.c_int16(0) + allow_partial_moves = ctypes.c_int16(1) + allow_all_moves = ctypes.c_int16(2) + limits_approach_policy = ctypes.c_int16(limit) + + self._dll.CC_SetLimitsSoftwareApproachPolicy( + self._serial_number, ctypes.byref(disallow_illegal_moves), + ctypes.byref(allow_partial_moves), ctypes.byref(allow_all_moves), + ctypes.byref(limits_approach_policy) + ) + return None + + def _start_polling(self, polling: int): + pol = ctypes.c_int(polling) + self._dll.CC_StartPolling(self._serial_number, ctypes.byref(pol)) + return None + + def _clear_message_queue(self) -> None: + self._dll.CC_ClearMessageQueue(self._serial_number) + return None + + def _can_home(self) -> bool: + ret = self._dll.CC_CanHome(self._serial_number) + return bool(ret) + + def _check_error(self, status: int) -> None: + if status != 0: + if status in ConnexionErrors: + raise ConnectionError(f'{ConnexionErrors[status]} ({status})') + elif status in GeneralErrors: + raise OSError(f'{GeneralErrors[status]} ({status})') + elif status in MotorErrors: + raise RuntimeError(f'{MotorErrors[status]} ({status})') + else: + raise ValueError(f'Unknown error code ({status})') + else: + pass + return None \ No newline at end of file diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/__init__.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/__init__.py index e69de29bb..e997ee4c8 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/private/__init__.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/private/__init__.py @@ -0,0 +1,39 @@ +ConnexionErrors = { + 0x00: 'Success', + 0x01: 'The FTDI functions have not been initialized', + 0x02: 'The device could not be found', + 0x03: 'The device must be opened before it can be accessed', + 0x04: 'An I/O error has occured in the FTDI chip', + 0x05: 'There are insufficient resources to run this application', + 0x06: 'An invalid parameter has been supplied to the device', + 0x07: 'The device is no longer present', + 0x08: 'The device detected does not match that expected', + 0x10: 'The library for this device could not be found', + 0x11: 'No functions available for this device', + 0x12: 'The function is not available for this device', + 0x13: 'Bad function pointer detected', + 0x14: 'The generic function failed to complete successfully', + 0x15: 'The specific function failed to complete succesfully' +} + +GeneralErrors = { + 0x20: 'Attempt to open a device that was already open', + 0x21: 'The device has stopped responding', + 0x22: 'This function has not been implemented', + 0x23: 'The device has reported a fault', + 0x24: 'The function could not be completed at this time', + 0x28: 'The function could not be completed because the device is disconnected', + 0x29: 'The firmware has thrown an error', + 0x2A: 'The device has failed to initialize', + 0x2B: 'An invalid channel address was supplied' +} + +MotorErrors = { + 0x25: 'The device cannot perform this function until it has been homed', + 0x26: 'The function cannot be performed as it would result in an illegal position', + 0x27: 'An invalid velocity parameter was supplied. The velocity must be greater than zero', + 0x2C: 'This device does not support homing. Check the limit switch parameters are correct', + 0x2D: 'An invalid jog mode was supplied for the jog function', + 0x2E: 'There is no motor parameters available to convert real world units', + 0x2F: 'Command temporarily unavailable, device may be busy.' +} \ No newline at end of file From 300a79aa67d276b4d1426a8d93ef5a0b41a4404d Mon Sep 17 00:00:00 2001 From: Julien Barrier <1346858+julienbarrier@users.noreply.github.com> Date: Tue, 29 Aug 2023 10:54:30 +0200 Subject: [PATCH 08/30] change ctypes --- .../drivers/Thorlabs/KDC101.py | 174 ++++++++++-------- 1 file changed, 96 insertions(+), 78 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py index 0c031ff6c..8be2fc625 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py @@ -37,6 +37,8 @@ def __init__(self, home: bool = False, **kwargs): super().__init__(name, **kwargs) + + self._serial_number = ctypes.c_char_p(serial_number.encode('ascii')) if sys.platform != 'win32': self._dll: Any = None raise OSError('Thorlabs Kinesis only works on Windows') @@ -44,12 +46,11 @@ def __init__(self, os.add_dll_directory(r'C:\Program Files\Thorlabs\Kinesis') self._dll_path = 'Thorlabs.MotionControl.KCube.DCServo.dll' self._dll = ctypes.cdll.LoadLibrary(self._dll_path) - + self._simulation = simulation if self._simulation: self.enable_simulation() - - self._serial_number = ctypes.c_char_p(serial_number.encode('ascii')) + if self._dll.TLI_BuildDeviceList() == 0: self._dll.CC_Open(self._serial_number) self._start_polling(polling) @@ -57,19 +58,19 @@ def __init__(self, # self._info = self._get_hardware_info() #self.model = self._info[0].decode('utf-8') #self.version = self._info[4] - + #self._device_info = dict(zip( # ['type ID', 'description', 'PID', 'is known type', 'motor type', # 'is piezo', 'is laser', 'is custom', 'is rack', 'max_channels'], # self._get_device_info())) - + self._load_settings() self._set_limits_approach(1) - + sleep(3) - + self._clear_message_queue() - + if home: if not self._can_home(): self.homed = False @@ -78,46 +79,63 @@ def __init__(self, self.go_home() else: self.homed = False - + + + self.position = Parameter( + 'position', + label='position', + get_cmd=self._get_position, + #set_cmd=self._set_position, + vals=vals.Numbers(-1, 360), + unit='\u37b0', + instrument=self + ) + + self.velocity = Parameter( + 'velocity', + label='velocity', + instrument=self + ) + def identify(self) -> None: self._dll.CC_Identify(self._serial_number) - + def get_idn(self) -> dict: idparts = ['Thorlabs', self.model, self.version, self.serial_number] return dict(zip(('vendor', 'model', 'serial', 'firmware'), idparts)) - + def go_home(self, block=True): self._check_error(self._dll.CC_Home(self._serial_number)) self.homed = True if block: self.wait_for_completion() - + def enable_simulation(self) -> None: self._dll.TLI_InitializeSimulations() - + def disable_simulation(self) -> None: self._dll.TLI_UninitializeSimulations() - + def wait_for_completion(self, status: str = 'homed', max_time = 5): message_type = ctypes.c_ushort() message_id = ctypes.c_ushort() message_data = ctypes.c_ulong() cond = self._CONDITIONS.index(status) - + if status == 'stopped': if not self.is_moving(): return None elif status == 'homed': max_time = 0 - + while self._dll.CC_MessageQueueSize(self._serial_number) <= 0: sleep(.2) - + self._dll.CC_WaitForMessage( self._serial_number, ctypes.byref(message_type), ctypes.byref(message_id), ctypes.byref(message_data) ) - + start = time() while int(message_type.value) != 2 or int(message_id.value) != cond: end = time() @@ -134,7 +152,7 @@ def wait_for_completion(self, status: str = 'homed', max_time = 5): ctypes.byref(message_id), ctypes.byref(message_data) ) - + def _device_unit_to_real(self, device_unit: int, unit_type: int) -> float: real_unit = ctypes.c_double() ret = self._dll.CC_GetRealValueFromDeviceUnit( @@ -143,7 +161,7 @@ def _device_unit_to_real(self, device_unit: int, unit_type: int) -> float: ) self._check_error(ret) return real_unit.value - + def _real_to_device_unit(self, real_unit: float, unit_type: int) -> int: device_unit = ctypes.c_int() ret = self._dll.CC_GetDeviceUnitFromRealValue( @@ -152,26 +170,26 @@ def _real_to_device_unit(self, real_unit: float, unit_type: int) -> int: ) self._check_error(ret) return device_unit.value - + def _get_backlash(self): ret = self._dll.CC_GetBacklash(self._serial_number) return self._device_unit_to_real(ret, 0) - + def _set_backlash(self, value: float) -> None: val = self._real_to_device_unit(value, 0) ret = self._dll.CC_SetBacklash(self._serial_number, ctypes.c_long(val)) self._check_error(ret) - + def _get_homing_velocity(self): vel = self._dll.CC_GetHomingVelocity(self._serial_number) return self._device_unit_to_real(vel, 1) - + def _set_homing_velocity(self, velocity): vel = self._real_to_device_unit(velocity, 1) ret = self._dll.CC_SetHomingVelocity(self._serial_number, ctypes.c_uint(vel)) self._check_error(ret) - + def _get_jog_mode(self) -> str: jog_mode = ctypes.c_short() stop_mode = ctypes.c_short() @@ -185,12 +203,12 @@ def _get_jog_mode(self) -> str: return 'stepped' else: raise RuntimeError('Unexpected value received from Kinesis') - + def _set_jog_mode(self, mode: str) -> None: jog_mode = ctypes.c_short(0x00) stop_mode = ctypes.c_short(0x00) stop_mode = self._get_stop_mode() - + if mode == 'continuous': jog_mode = ctypes.c_short(0x01) elif mode == 'stepped': @@ -199,27 +217,27 @@ def _set_jog_mode(self, mode: str) -> None: stop_mode = ctypes.c_short(0x01) elif stop_mode == 'profiled': stop_mode = ctypes.c_short(0x02) - + ret = self._dll.CC_SetJogMode(self._serial_number, jog_mode, stop_mode) self._check_error(ret) return None - + def _get_jog_step_size(self): ret = self._dll.CC_GetJogStepSize(self._serial_number) return self._device_unit_to_real(ret, 0) - + def _set_jog_step_size(self, step_size) -> None: step = self._real_to_device_unit(step_size, 0) ret = self._dll.CC_SetJogStepSize(self._serial_number, ctypes.c_uint(step)) self._check_error(ret) return None - + def _check_connection(self) -> bool: ret = self._dll.CC_CheckConnection(self._serial_number) return bool(ret) - + def _get_stop_mode(self): jog_mode = ctypes.c_short() stop_mode = ctypes.c_short() @@ -233,11 +251,11 @@ def _get_stop_mode(self): return 'profiled' else: raise RuntimeError('unexpected value received from Kinesis') - + def _set_stop_mode(self, mode) -> None: jog_mode = ctypes.c_short(0x00) stop_mode = ctypes.c_short(0x00) - + jmode = self._get_jog_mode() if jmode == 'continuous': jog_mode = ctypes.c_short(0x01) @@ -247,17 +265,17 @@ def _set_stop_mode(self, mode) -> None: stop_mode = ctypes.c_short(0x01) elif mode == 'profiled': stop_mode = ctypes.c_short(0x02) - + ret = self._dll.CC_SetJogMode(self._serial_number, jog_mode, stop_mode) self._check_error(ret) return None - + def _get_max_position(self) -> float: max_pos = ctypes.c_int() self._dll.CC_GetStageAxisMaxPos(self._serial_number, max_pos) return self._device_unit_to_real(max_pos.value, 0) - + def _set_max_position(self, max_val) -> None: min_val = self._get_min_position() min_val = self._real_to_device_unit(min_val, 0) @@ -267,12 +285,12 @@ def _set_max_position(self, max_val) -> None: ctypes.c_int(max_val)) self._check_error(ret) self.wait_for_completion('limit_updated') - + def _get_min_position(self) -> float: min_pos = ctypes.c_int() self._dll.CC_GetStageAxisMinPos(self._serial_number, min_pos) return self._device_unit_to_real(min_pos.value, 0) - + def _set_min_position(self, min_val) -> None: max_val = self._get_max_position() max_val = self._real_to_device_unit(max_val, 0) @@ -282,7 +300,7 @@ def _set_min_position(self, min_val) -> None: ctypes.c_int(max_val)) self._check_error(ret) self.wait_for_completion('limit_updated') - + def _get_soft_limits_mode(self) -> str: mode = ctypes.c_int16() self._dll.CC_GetSoftLimitMode(self._serial_number, ctypes.byref(mode)) @@ -294,7 +312,7 @@ def _get_soft_limits_mode(self) -> str: return 'all' else: raise RuntimeError('unexpected value received from Kinesis') - + def _set_soft_limits_mode(self, mode: str) -> None: if mode == 'disallow': lmode = ctypes.c_int16(0) @@ -302,11 +320,11 @@ def _set_soft_limits_mode(self, mode: str) -> None: lmode = ctypes.c_int16(1) elif mode == 'all': lmode = ctypes.c_int16(2) - + ret = self._dll.CC_SetLimitsSoftwareApproachPolicy(self._serial_number, lmode) self._check_error(ret) - + def _get_move_velocity(self) -> float: acceleration = ctypes.c_int() velocity = ctypes.c_int() @@ -315,7 +333,7 @@ def _get_move_velocity(self) -> float: ctypes.byref(velocity)) self._check_error(ret) return self._device_unit_to_real(velocity.value, 1) - + def _set_move_velocity(self, velocity): vel = self._real_to_device_unit(velocity, 1) accel = self._real_to_device_unit(self._get_move_acceleration(), 2) @@ -323,7 +341,7 @@ def _set_move_velocity(self, velocity): ctypes.c_int(accel), ctypes.c_int(vel)) self._check_error(ret) - + def _get_move_acceleration(self) -> float: acceleration = ctypes.c_int() velocity = ctypes.c_int() @@ -332,7 +350,7 @@ def _get_move_acceleration(self) -> float: ctypes.byref(velocity)) self._check_error(ret) return self._device_unit_to_real(acceleration.value, 2) - + def _set_move_acceleration(self, acceleration) -> None: vel = self._real_to_device_unit(self._get_move_velocity(), 1) accel = self._real_to_device_unit(acceleration, 2) @@ -340,7 +358,7 @@ def _set_move_acceleration(self, acceleration) -> None: ctypes.c_int(accel), ctypes.c_int(vel)) self._check_error(ret) - + def _get_jog_velocity(self) -> float: acceleration = ctypes.c_int() velocity = ctypes.c_int() @@ -349,7 +367,7 @@ def _get_jog_velocity(self) -> float: ctypes.byref(velocity)) self._check_error(ret) return self._device_unit_to_real(velocity.value, 1) - + def _set_jog_velocity(self, velocity) -> None: vel = self._real_to_device_unit(velocity, 1) accel = self._real_to_device_unit(self._get_jog_acceleration(), 2) @@ -357,7 +375,7 @@ def _set_jog_velocity(self, velocity) -> None: ctypes.c_int(accel), ctypes.c_int(vel)) self._check_error(ret) - + def _get_jog_acceleration(self) -> float: acceleration = ctypes.c_int() velocity = ctypes.c_int() @@ -366,7 +384,7 @@ def _get_jog_acceleration(self) -> float: ctypes.byref(velocity)) self._check_error(ret) return self._device_unit_to_real(acceleration.value, 2) - + def _set_jog_acceleration(self, acceleration) -> None: vel = self._real_to_device_unit(self._get_jog_velocity(), 1) accel = self._real_to_device_unit(acceleration, 2) @@ -374,13 +392,13 @@ def _set_jog_acceleration(self, acceleration) -> None: ctypes.c_int(accel), ctypes.c_int(vel)) self._check_error(ret) - + def _get_position(self) -> float: current_position = ctypes.c_int() self._dll.CC_GetPosition(self._serial_number, ctypes.byref(current_position)) return self._device_unit_to_real(current_position.value, 0) - + def is_moving(self) -> bool: status_bit = ctypes.c_short() self._dll.CC_GetStatusBits(self._serial_number, @@ -389,16 +407,16 @@ def is_moving(self) -> bool: return True else: return False - + def move_to(self, position, block=True) -> None: pos = self._real_to_device_unit(position, 0) ret = self._dll.CC_MoveToPosition(self._serial_number, ctypes.c_int(pos)) self._check_error(ret) - + if block: self.wait_for_completion(status='moved', max_time=15) - + def move_by(self, displacement, block: bool = True): dis = self._real_to_device_unit(displacement, 0) ret = self._dll.CC_MoveRelative(self._serial_number, @@ -406,7 +424,7 @@ def move_by(self, displacement, block: bool = True): self._check_error(ret) if block: self.wait_for_completion(status='moved') - + def move_continuous(self, direction = 'forward') -> None: if direction == 'forward' or direction == 'forwards': direction = ctypes.c_short(0x01) @@ -414,10 +432,10 @@ def move_continuous(self, direction = 'forward') -> None: direction = ctypes.c_short(0x02) else: raise ValueError('direction unrecognised') - + ret = self._dll.CC_MoveAtVelocity(self._serial_number, direction) self._check_error(ret) - + def jog(self, direction, block: bool = True) -> None: if direction == 'forward' or direction == 'forwards': direction = ctypes.c_short(0x01) @@ -425,13 +443,13 @@ def jog(self, direction, block: bool = True) -> None: direction = ctypes.c_short(0x02) else: raise ValueError('direction unrecognised') - + ret = self._dll.CC_MoveJog(self._serial_number, direction) self._check_error(ret) if self._get_jog_mode() =='stepped': if block: self.wait_for_completion(status='moved') - + def stop(self, immediate=False) -> None: if immediate: ret = self._dll.CC_StopImmediate(self._serial_number) @@ -439,38 +457,38 @@ def stop(self, immediate=False) -> None: ret = self._dll.CC_StopProfiled(self._serial_number) self._check_error(ret) self.wait_for_completion(status='stopped') - + def close(self): if self._simulation: self.disable_simulation() if hasattr(self, '_serial_number'): self._dll.CC_Close(self._serial_number) - + def _get_hardware_info(self) -> list: """this gives a device not found error. TODO: debug this""" - model = ctypes.c_char_p() - model_size = ctypes.c_ulong() + model = ctypes.create_string_buffer(8) + model_size = ctypes.c_ulong(8) type_num = ctypes.c_ushort() channel_num = ctypes.c_ushort() - notes = ctypes.c_char_p() - notes_size = ctypes.c_ulong() + notes = ctypes.create_string_buffer(48) + notes_size = ctypes.c_ulong(48) firmware_version = ctypes.c_ulong() hardware_version = ctypes.c_ushort() modification_state = ctypes.c_ushort() ret = self._dll.CC_GetHardwareInfo( - ctypes.byref(self._serial_number), + self._serial_number, ctypes.byref(model), model_size, ctypes.byref(type_num), ctypes.byref(channel_num), ctypes.byref(notes), notes_size, ctypes.byref(firmware_version), ctypes.byref(hardware_version), ctypes.byref(modification_state) ) - + self._check_error(ret) return [model.value, type_num.value, channel_num.value, notes.value, firmware_version.value, hardware_version.value, modification_state.value] - + def _get_device_info(self): """this gives a device not found error. TODO: debug this""" type_id = ctypes.c_ulong() @@ -483,7 +501,7 @@ def _get_device_info(self): is_custom_type = ctypes.c_bool() is_rack = ctypes.c_bool() max_channels = ctypes.c_bool() - + ret = self._dll.TLI_GetDeviceInfo( ctypes.byref(self._serial_number), ctypes.byref(type_id), @@ -502,37 +520,37 @@ def _get_device_info(self): is_known_type.value, motor_type.value, is_piezo_device.value, is_laser.value, is_custom_type.value, is_rack.value, max_channels.value] - + def _load_settings(self): self._dll.CC_LoadSettings(self._serial_number) return None - + def _set_limits_approach(self, limit: int): disallow_illegal_moves = ctypes.c_int16(0) allow_partial_moves = ctypes.c_int16(1) allow_all_moves = ctypes.c_int16(2) limits_approach_policy = ctypes.c_int16(limit) - + self._dll.CC_SetLimitsSoftwareApproachPolicy( self._serial_number, ctypes.byref(disallow_illegal_moves), ctypes.byref(allow_partial_moves), ctypes.byref(allow_all_moves), ctypes.byref(limits_approach_policy) ) return None - + def _start_polling(self, polling: int): pol = ctypes.c_int(polling) self._dll.CC_StartPolling(self._serial_number, ctypes.byref(pol)) return None - + def _clear_message_queue(self) -> None: self._dll.CC_ClearMessageQueue(self._serial_number) return None - + def _can_home(self) -> bool: ret = self._dll.CC_CanHome(self._serial_number) return bool(ret) - + def _check_error(self, status: int) -> None: if status != 0: if status in ConnexionErrors: @@ -545,4 +563,4 @@ def _check_error(self, status: int) -> None: raise ValueError(f'Unknown error code ({status})') else: pass - return None \ No newline at end of file + return None From 462723049b80362706a41209d7d19b4fb1ca2a86 Mon Sep 17 00:00:00 2001 From: julienbarrier <1346858+julienbarrier@users.noreply.github.com> Date: Tue, 29 Aug 2023 11:48:18 +0200 Subject: [PATCH 09/30] add parameters --- .../drivers/Thorlabs/KDC101.py | 169 +++++++++++++++--- 1 file changed, 144 insertions(+), 25 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py index 8be2fc625..b298abcb6 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py @@ -37,8 +37,8 @@ def __init__(self, home: bool = False, **kwargs): super().__init__(name, **kwargs) - - self._serial_number = ctypes.c_char_p(serial_number.encode('ascii')) + self.serial_number = serial_number + self._serial_number = ctypes.c_char_p(self.serial_number.encode('ascii')) if sys.platform != 'win32': self._dll: Any = None raise OSError('Thorlabs Kinesis only works on Windows') @@ -55,14 +55,14 @@ def __init__(self, self._dll.CC_Open(self._serial_number) self._start_polling(polling) - # self._info = self._get_hardware_info() - #self.model = self._info[0].decode('utf-8') - #self.version = self._info[4] + self._info = self._get_hardware_info() + self.model = self._info[0].decode('utf-8') + self.version = self._info[4] - #self._device_info = dict(zip( - # ['type ID', 'description', 'PID', 'is known type', 'motor type', - # 'is piezo', 'is laser', 'is custom', 'is rack', 'max_channels'], - # self._get_device_info())) + self._device_info = dict(zip( + ['type_ID', 'description', 'PID', 'is_known_type', 'motor_type', + 'is_piezo', 'is_laser', 'is_custom', 'is_rack', 'max_channels'], + self._get_device_info())) self._load_settings() self._set_limits_approach(1) @@ -83,24 +83,143 @@ def __init__(self, self.position = Parameter( 'position', - label='position', + label='Position', get_cmd=self._get_position, #set_cmd=self._set_position, - vals=vals.Numbers(-1, 360), - unit='\u37b0', + vals=vals.Numbers(0, 360), + unit='\u00b0', + instrument=self + ) + + self.max_position = Parameter( + 'max_position', + label='Maximum position', + set_cmd=self._set_max_position, + get_cmd=self._get_max_position, + parser=float, + vals=vals.Numbers(0, 360), + instrument=self + ) + + self.min_position = Parameter( + 'min_position', + label='Minimum position', + set_cmd=self._set_min_position, + get_cmd=self._get_min_position, + parser=float, + vals=vals.Numbers(0, 360), instrument=self ) self.velocity = Parameter( 'velocity', - label='velocity', + label='Move velocity', + unit='\u00b0/s', + set_cmd=self._set_move_velocity, + get_cmd=self._get_move_velocity, + parser=float, + vals=vals.Numbers(0, 25), + instrument=self + ) + + self.jog_velocity = Parameter( + 'jog_velocity', + label='Jog velocity', + unit='\u00b0/s', + set_cmd=self._set_jog_velocity, + get_cmd=self._get_jog_velocity, + parser=float, + vals=vals.Numbers(), + instrument=self + ) + + self.homing_velocity = Parameter( + 'homing_velocity', + label='Homing velocity', + unit='\u00b0/s', + set_cmd=self._set_homing_velocity, + get_cmd=self._get_homing_velocity, + parser=float, + instrument=self + ) + + self.acceleration = Parameter( + 'acceleration', + label='Move acceleration', + unit='\u00b0/s\u00b2', + set_cmd=self._set_move_acceleration, + get_cmd=self._get_move_acceleration, + parser=float, + vals=vals.Numbers(0, 25), + instrument=self + ) + + self.jog_acceleration = Parameter( + 'jog_acceleration', + label='Jog acceleration', + unit='\u00b0/s\u00b2', + set_cmd=self._set_jog_acceleration, + get_cmd=self._get_jog_acceleration, + parser=float, + vals=vals.Numbers(0, 25), instrument=self ) + self.jog_mode = Parameter( + 'jog_mode', + set_cmd=self._set_jog_mode, + get_cmd=self._get_jog_mode, + parser=str, + vals=vals.Enum('continuous', 'stepped'), + instrument=self + ) + + self.jog_step_size = Parameter( + 'jog_step_size', + set_cmd=self._set_jog_step_size, + get_cmd=self._get_jog_step_size, + parser=float, + instrument=self + ) + + self.stop_mode = Parameter( + 'stop_mode', + set_cmd=self._set_stop_mode, + get_cmd=self._get_stop_mode, + parser=str, + vals=vals.Enum('immediate', 'profiled'), + instrument=self + ) + + self.soft_limits_mode = Parameter( + 'soft_limits_mode', + set_cmd=self._set_soft_limits_mode, + get_cmd=self._get_soft_limits_mode, + parser=str, + vals=vals.Enum('disallow', 'partial', 'all'), + instrument=self + ) + + self.move_home_zero_offset = Parameter( + 'move_home_zero_offset', + instrument=self + ) + + self.backlash = Parameter( + 'backlash', + set_cmd=self._set_backlash, + get_cmd=self._get_backlash, + parser=float, + instrument=self + ) + + self.connect_message() + def identify(self) -> None: self._dll.CC_Identify(self._serial_number) def get_idn(self) -> dict: + """Get device identifier""" idparts = ['Thorlabs', self.model, self.version, self.serial_number] return dict(zip(('vendor', 'model', 'serial', 'firmware'), idparts)) @@ -171,7 +290,7 @@ def _real_to_device_unit(self, real_unit: float, unit_type: int) -> int: self._check_error(ret) return device_unit.value - def _get_backlash(self): + def _get_backlash(self) -> float: ret = self._dll.CC_GetBacklash(self._serial_number) return self._device_unit_to_real(ret, 0) @@ -180,11 +299,11 @@ def _set_backlash(self, value: float) -> None: ret = self._dll.CC_SetBacklash(self._serial_number, ctypes.c_long(val)) self._check_error(ret) - def _get_homing_velocity(self): + def _get_homing_velocity(self) -> float: vel = self._dll.CC_GetHomingVelocity(self._serial_number) return self._device_unit_to_real(vel, 1) - def _set_homing_velocity(self, velocity): + def _set_homing_velocity(self, velocity: float) -> None: vel = self._real_to_device_unit(velocity, 1) ret = self._dll.CC_SetHomingVelocity(self._serial_number, ctypes.c_uint(vel)) @@ -223,11 +342,11 @@ def _set_jog_mode(self, mode: str) -> None: self._check_error(ret) return None - def _get_jog_step_size(self): + def _get_jog_step_size(self) -> float: ret = self._dll.CC_GetJogStepSize(self._serial_number) return self._device_unit_to_real(ret, 0) - def _set_jog_step_size(self, step_size) -> None: + def _set_jog_step_size(self, step_size: float) -> None: step = self._real_to_device_unit(step_size, 0) ret = self._dll.CC_SetJogStepSize(self._serial_number, ctypes.c_uint(step)) @@ -252,7 +371,7 @@ def _get_stop_mode(self): else: raise RuntimeError('unexpected value received from Kinesis') - def _set_stop_mode(self, mode) -> None: + def _set_stop_mode(self, mode: str) -> None: jog_mode = ctypes.c_short(0x00) stop_mode = ctypes.c_short(0x00) @@ -276,7 +395,7 @@ def _get_max_position(self) -> float: self._dll.CC_GetStageAxisMaxPos(self._serial_number, max_pos) return self._device_unit_to_real(max_pos.value, 0) - def _set_max_position(self, max_val) -> None: + def _set_max_position(self, max_val: float) -> None: min_val = self._get_min_position() min_val = self._real_to_device_unit(min_val, 0) max_val = self._real_to_device_unit(max_val, 0) @@ -291,7 +410,7 @@ def _get_min_position(self) -> float: self._dll.CC_GetStageAxisMinPos(self._serial_number, min_pos) return self._device_unit_to_real(min_pos.value, 0) - def _set_min_position(self, min_val) -> None: + def _set_min_position(self, min_val: float) -> None: max_val = self._get_max_position() max_val = self._real_to_device_unit(max_val, 0) min_val = self._real_to_device_unit(min_val, 0) @@ -334,7 +453,7 @@ def _get_move_velocity(self) -> float: self._check_error(ret) return self._device_unit_to_real(velocity.value, 1) - def _set_move_velocity(self, velocity): + def _set_move_velocity(self, velocity: float) -> None: vel = self._real_to_device_unit(velocity, 1) accel = self._real_to_device_unit(self._get_move_acceleration(), 2) ret = self._dll.CC_SetVelParams(self._serial_number, @@ -351,7 +470,7 @@ def _get_move_acceleration(self) -> float: self._check_error(ret) return self._device_unit_to_real(acceleration.value, 2) - def _set_move_acceleration(self, acceleration) -> None: + def _set_move_acceleration(self, acceleration: float) -> None: vel = self._real_to_device_unit(self._get_move_velocity(), 1) accel = self._real_to_device_unit(acceleration, 2) ret = self._dll.CC_SetVelParams(self._serial_number, @@ -368,7 +487,7 @@ def _get_jog_velocity(self) -> float: self._check_error(ret) return self._device_unit_to_real(velocity.value, 1) - def _set_jog_velocity(self, velocity) -> None: + def _set_jog_velocity(self, velocity: float) -> None: vel = self._real_to_device_unit(velocity, 1) accel = self._real_to_device_unit(self._get_jog_acceleration(), 2) ret = self._dll.CC_SetJogVelParams(self._serial_number, @@ -385,7 +504,7 @@ def _get_jog_acceleration(self) -> float: self._check_error(ret) return self._device_unit_to_real(acceleration.value, 2) - def _set_jog_acceleration(self, acceleration) -> None: + def _set_jog_acceleration(self, acceleration: float) -> None: vel = self._real_to_device_unit(self._get_jog_velocity(), 1) accel = self._real_to_device_unit(acceleration, 2) ret = self._dll.CC_SetJogVelParams(self._serial_number, From 785251bb82bffd34501be45c743e7edb997f637b Mon Sep 17 00:00:00 2001 From: julienbarrier <1346858+julienbarrier@users.noreply.github.com> Date: Tue, 29 Aug 2023 11:54:22 +0200 Subject: [PATCH 10/30] debug --- .../drivers/Thorlabs/KDC101.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py index b298abcb6..dd6a9dddb 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py @@ -96,7 +96,6 @@ def __init__(self, label='Maximum position', set_cmd=self._set_max_position, get_cmd=self._get_max_position, - parser=float, vals=vals.Numbers(0, 360), instrument=self ) @@ -106,7 +105,6 @@ def __init__(self, label='Minimum position', set_cmd=self._set_min_position, get_cmd=self._get_min_position, - parser=float, vals=vals.Numbers(0, 360), instrument=self ) @@ -117,7 +115,6 @@ def __init__(self, unit='\u00b0/s', set_cmd=self._set_move_velocity, get_cmd=self._get_move_velocity, - parser=float, vals=vals.Numbers(0, 25), instrument=self ) @@ -128,7 +125,6 @@ def __init__(self, unit='\u00b0/s', set_cmd=self._set_jog_velocity, get_cmd=self._get_jog_velocity, - parser=float, vals=vals.Numbers(), instrument=self ) @@ -139,7 +135,6 @@ def __init__(self, unit='\u00b0/s', set_cmd=self._set_homing_velocity, get_cmd=self._get_homing_velocity, - parser=float, instrument=self ) @@ -149,7 +144,6 @@ def __init__(self, unit='\u00b0/s\u00b2', set_cmd=self._set_move_acceleration, get_cmd=self._get_move_acceleration, - parser=float, vals=vals.Numbers(0, 25), instrument=self ) @@ -160,7 +154,6 @@ def __init__(self, unit='\u00b0/s\u00b2', set_cmd=self._set_jog_acceleration, get_cmd=self._get_jog_acceleration, - parser=float, vals=vals.Numbers(0, 25), instrument=self ) @@ -169,7 +162,6 @@ def __init__(self, 'jog_mode', set_cmd=self._set_jog_mode, get_cmd=self._get_jog_mode, - parser=str, vals=vals.Enum('continuous', 'stepped'), instrument=self ) @@ -178,7 +170,6 @@ def __init__(self, 'jog_step_size', set_cmd=self._set_jog_step_size, get_cmd=self._get_jog_step_size, - parser=float, instrument=self ) @@ -186,7 +177,6 @@ def __init__(self, 'stop_mode', set_cmd=self._set_stop_mode, get_cmd=self._get_stop_mode, - parser=str, vals=vals.Enum('immediate', 'profiled'), instrument=self ) @@ -195,21 +185,14 @@ def __init__(self, 'soft_limits_mode', set_cmd=self._set_soft_limits_mode, get_cmd=self._get_soft_limits_mode, - parser=str, vals=vals.Enum('disallow', 'partial', 'all'), instrument=self ) - self.move_home_zero_offset = Parameter( - 'move_home_zero_offset', - instrument=self - ) - self.backlash = Parameter( 'backlash', set_cmd=self._set_backlash, get_cmd=self._get_backlash, - parser=float, instrument=self ) From 8262286686d317b3994698351d16d6df8188cd78 Mon Sep 17 00:00:00 2001 From: julienbarrier <1346858+julienbarrier@users.noreply.github.com> Date: Tue, 29 Aug 2023 12:34:06 +0200 Subject: [PATCH 11/30] cleaner code --- .../drivers/Thorlabs/K10CR1.py | 13 ++- .../drivers/Thorlabs/KDC101.py | 109 ++++++++++++++++-- 2 files changed, 110 insertions(+), 12 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/K10CR1.py b/qcodes_contrib_drivers/drivers/Thorlabs/K10CR1.py index c1aa169a9..7d628670a 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/K10CR1.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/K10CR1.py @@ -5,7 +5,18 @@ from qcodes import Instrument from .private.APT import Thorlabs_APT, ThorlabsHWType -from .private.rotators import RotationDirection, HomeLimitSwitch + + +class RotationDirection(enum.Enum): + """Constants for the rotation direction of Thorlabs K10CR1 rotator""" + FORWARD = "fwd" + REVERSE = "rev" + + +class HomeLimitSwitch(enum.Enum): + """Constants for the home limit switch of Thorlabs K10CR1 rotator""" + REVERSE = "rev" + FORWARD = "fwd" class Thorlabs_K10CR1(Instrument): diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py index dd6a9dddb..500199dcf 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py @@ -4,8 +4,6 @@ Authors: Julien Barrier, - -TODO: check that pointers are well coded. ctypes.byref(val) if we need to update the value """ import os import sys @@ -191,14 +189,17 @@ def __init__(self, self.backlash = Parameter( 'backlash', + unit='\u00b0', set_cmd=self._set_backlash, get_cmd=self._get_backlash, + vals=vals.Numbers(0, 25), instrument=self ) self.connect_message() def identify(self) -> None: + """Sends a command to the device to make it identify itself""" self._dll.CC_Identify(self._serial_number) def get_idn(self) -> dict: @@ -207,18 +208,31 @@ def get_idn(self) -> dict: return dict(zip(('vendor', 'model', 'serial', 'firmware'), idparts)) def go_home(self, block=True): + """Home the device: set the device to a known state and home position + + Args: + block (bool, optional): will wait for completion. Defaults to True. + """ self._check_error(self._dll.CC_Home(self._serial_number)) self.homed = True if block: self.wait_for_completion() def enable_simulation(self) -> None: + """Initialise a connection to the simulation manager, which must already be running""" self._dll.TLI_InitializeSimulations() def disable_simulation(self) -> None: + """Uninitialize a connection to the simulation manager, which must be running""" self._dll.TLI_UninitializeSimulations() - def wait_for_completion(self, status: str = 'homed', max_time = 5): + def wait_for_completion(self, status: str = 'homed', max_time = 5) -> None: + """Wait for the current function to be finished. + + Args: + status (str, optional): expected status. Defaults to 'homed'. + max_time (int, optional): maximum waiting time for the internal loop. Defaults to 5. + """ message_type = ctypes.c_ushort() message_id = ctypes.c_ushort() message_data = ctypes.c_ulong() @@ -254,8 +268,18 @@ def wait_for_completion(self, status: str = 'homed', max_time = 5): ctypes.byref(message_id), ctypes.byref(message_data) ) + return None def _device_unit_to_real(self, device_unit: int, unit_type: int) -> float: + """Converts a device unit to a real world unit + + Args: + device_unit (int): the device unit. + unit_type (int): the type of unit. Distance: 0, velocity: 1, acceleration: 2. + + Returns: + float: real unit value + """ real_unit = ctypes.c_double() ret = self._dll.CC_GetRealValueFromDeviceUnit( self._serial_number, ctypes.c_int(device_unit), @@ -265,6 +289,15 @@ def _device_unit_to_real(self, device_unit: int, unit_type: int) -> float: return real_unit.value def _real_to_device_unit(self, real_unit: float, unit_type: int) -> int: + """Converts a real world unit to a device unit + + Args: + real_unit (float): the real unit + unit_type (int): the type of unit. Distance: 0, velocity: 1, acceleration: 2 + + Returns: + int: device unit + """ device_unit = ctypes.c_int() ret = self._dll.CC_GetDeviceUnitFromRealValue( self._serial_number, ctypes.c_double(real_unit), @@ -274,10 +307,12 @@ def _real_to_device_unit(self, real_unit: float, unit_type: int) -> int: return device_unit.value def _get_backlash(self) -> float: + """Get the backlash distance setting (used to control hysteresis)""" ret = self._dll.CC_GetBacklash(self._serial_number) return self._device_unit_to_real(ret, 0) def _set_backlash(self, value: float) -> None: + """Set the backlash distance setting (used to control hysteresis)""" val = self._real_to_device_unit(value, 0) ret = self._dll.CC_SetBacklash(self._serial_number, ctypes.c_long(val)) self._check_error(ret) @@ -404,6 +439,7 @@ def _set_min_position(self, min_val: float) -> None: self.wait_for_completion('limit_updated') def _get_soft_limits_mode(self) -> str: + """Gets the software limits mode.""" mode = ctypes.c_int16() self._dll.CC_GetSoftLimitMode(self._serial_number, ctypes.byref(mode)) if mode.value == ctypes.c_int16(0).value: @@ -416,6 +452,7 @@ def _get_soft_limits_mode(self) -> str: raise RuntimeError('unexpected value received from Kinesis') def _set_soft_limits_mode(self, mode: str) -> None: + """Sets the software limits mode""" if mode == 'disallow': lmode = ctypes.c_int16(0) elif mode == 'partial': @@ -502,6 +539,7 @@ def _get_position(self) -> float: return self._device_unit_to_real(current_position.value, 0) def is_moving(self) -> bool: + """check if the motor cotnroller is moving.""" status_bit = ctypes.c_short() self._dll.CC_GetStatusBits(self._serial_number, ctypes.byref(status_bit)) @@ -510,7 +548,14 @@ def is_moving(self) -> bool: else: return False - def move_to(self, position, block=True) -> None: + def move_to(self, position: float, block=True) -> None: + """Move the device to the specified position. + The motor may need to be homed before a position can be set. + + Args: + position (float): the set position + block (bool, optional): will wait until complete. Defaults to True. + """ pos = self._real_to_device_unit(position, 0) ret = self._dll.CC_MoveToPosition(self._serial_number, ctypes.c_int(pos)) @@ -518,16 +563,30 @@ def move_to(self, position, block=True) -> None: if block: self.wait_for_completion(status='moved', max_time=15) + self.position.get() - def move_by(self, displacement, block: bool = True): + def move_by(self, displacement: float, block: bool = True) -> None: + """Move the motor by a relative amount + + Args: + displacement (float): amount to move + block (bool, optional): will wait until complete. Defaults to True. + """ dis = self._real_to_device_unit(displacement, 0) ret = self._dll.CC_MoveRelative(self._serial_number, ctypes.c_int(dis)) self._check_error(ret) if block: self.wait_for_completion(status='moved') + self.position.get() + + def move_continuous(self, direction: str = 'forward') -> None: + """start moving at the current velocity in the specified direction - def move_continuous(self, direction = 'forward') -> None: + Args: + direction (str, optional): the required direction of travel. + Defaults to 'forward'. Accepts 'forward' or 'reverse' + """ if direction == 'forward' or direction == 'forwards': direction = ctypes.c_short(0x01) elif direction == 'reverse' or direction == 'backward' or direction == 'backwards': @@ -537,8 +596,16 @@ def move_continuous(self, direction = 'forward') -> None: ret = self._dll.CC_MoveAtVelocity(self._serial_number, direction) self._check_error(ret) + self.position.get() - def jog(self, direction, block: bool = True) -> None: + def jog(self, direction: str = 'forward', block: bool = True) -> None: + """Performs a jog + + Args: + direction (str): the jog direction. Defaults to 'forward'. + Accepts 'forward' or 'reverse' + block (bool, optional): will wait until complete. Defaults to True. + """ if direction == 'forward' or direction == 'forwards': direction = ctypes.c_short(0x01) elif direction == 'reverse' or direction == 'backward' or direction == 'backwards': @@ -552,7 +619,15 @@ def jog(self, direction, block: bool = True) -> None: if block: self.wait_for_completion(status='moved') - def stop(self, immediate=False) -> None: + def stop(self, immediate: bool = False) -> None: + """Stop the current move + + Args: + immediate (bool, optional): + True: stops immediately (with risk of losing track of position). + False: stops using the current velocity profile. + Defaults to False. + """ if immediate: ret = self._dll.CC_StopImmediate(self._serial_number) else: @@ -567,7 +642,13 @@ def close(self): self._dll.CC_Close(self._serial_number) def _get_hardware_info(self) -> list: - """this gives a device not found error. TODO: debug this""" + """Gets the hardware information from the device + + Returns: + list: [model number, hardware type number, number of channels, + notes describing the device, firmware version, hardware version, + hardware modification state] + """ model = ctypes.create_string_buffer(8) model_size = ctypes.c_ulong(8) type_num = ctypes.c_ushort() @@ -591,8 +672,13 @@ def _get_hardware_info(self) -> list: notes.value, firmware_version.value, hardware_version.value, modification_state.value] - def _get_device_info(self): - """this gives a device not found error. TODO: debug this""" + def _get_device_info(self) -> list: + """Get the device information from the USB port + + Returns: + list: [type id, description, PID, is known type, motor type, + is piezo, is laser, is custom type, is rack, max channels] + """ type_id = ctypes.c_ulong() description = ctypes.c_char() pid = ctypes.c_ulong() @@ -624,6 +710,7 @@ def _get_device_info(self): max_channels.value] def _load_settings(self): + """Update device with stored settings""" self._dll.CC_LoadSettings(self._serial_number) return None From cbdf2ceab0ba1472b516ac726bf041910b4fa52d Mon Sep 17 00:00:00 2001 From: Julien Barrier <1346858+julienbarrier@users.noreply.github.com> Date: Tue, 29 Aug 2023 12:47:49 +0200 Subject: [PATCH 12/30] constraints in parameters --- qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py index 500199dcf..98a699b1f 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py @@ -123,7 +123,7 @@ def __init__(self, unit='\u00b0/s', set_cmd=self._set_jog_velocity, get_cmd=self._get_jog_velocity, - vals=vals.Numbers(), + vals=vals.Numbers(0, 25), instrument=self ) @@ -133,6 +133,7 @@ def __init__(self, unit='\u00b0/s', set_cmd=self._set_homing_velocity, get_cmd=self._get_homing_velocity, + vals=vals.Numbers(0.1, 25), instrument=self ) @@ -168,6 +169,7 @@ def __init__(self, 'jog_step_size', set_cmd=self._set_jog_step_size, get_cmd=self._get_jog_step_size, + vals=vals.Numbers(0.0005, 360) instrument=self ) @@ -192,7 +194,7 @@ def __init__(self, unit='\u00b0', set_cmd=self._set_backlash, get_cmd=self._get_backlash, - vals=vals.Numbers(0, 25), + vals=vals.Numbers(0, 5), instrument=self ) From 2f2a8e37cb40d1b68751c0820f994ad85456d1d7 Mon Sep 17 00:00:00 2001 From: julienbarrier <1346858+julienbarrier@users.noreply.github.com> Date: Tue, 29 Aug 2023 12:56:44 +0200 Subject: [PATCH 13/30] pass mypy --- .../drivers/Thorlabs/KDC101.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py index 98a699b1f..68fdd5d0b 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py @@ -169,7 +169,7 @@ def __init__(self, 'jog_step_size', set_cmd=self._set_jog_step_size, get_cmd=self._get_jog_step_size, - vals=vals.Numbers(0.0005, 360) + vals=vals.Numbers(0.0005, 360), instrument=self ) @@ -590,13 +590,13 @@ def move_continuous(self, direction: str = 'forward') -> None: Defaults to 'forward'. Accepts 'forward' or 'reverse' """ if direction == 'forward' or direction == 'forwards': - direction = ctypes.c_short(0x01) + dir = ctypes.c_short(0x01) elif direction == 'reverse' or direction == 'backward' or direction == 'backwards': - direction = ctypes.c_short(0x02) + dir = ctypes.c_short(0x02) else: raise ValueError('direction unrecognised') - ret = self._dll.CC_MoveAtVelocity(self._serial_number, direction) + ret = self._dll.CC_MoveAtVelocity(self._serial_number, dir) self._check_error(ret) self.position.get() @@ -609,17 +609,18 @@ def jog(self, direction: str = 'forward', block: bool = True) -> None: block (bool, optional): will wait until complete. Defaults to True. """ if direction == 'forward' or direction == 'forwards': - direction = ctypes.c_short(0x01) + dir = ctypes.c_short(0x01) elif direction == 'reverse' or direction == 'backward' or direction == 'backwards': - direction = ctypes.c_short(0x02) + dir = ctypes.c_short(0x02) else: raise ValueError('direction unrecognised') - ret = self._dll.CC_MoveJog(self._serial_number, direction) + ret = self._dll.CC_MoveJog(self._serial_number, dir) self._check_error(ret) if self._get_jog_mode() =='stepped': if block: self.wait_for_completion(status='moved') + self.position.get() def stop(self, immediate: bool = False) -> None: """Stop the current move @@ -636,6 +637,7 @@ def stop(self, immediate: bool = False) -> None: ret = self._dll.CC_StopProfiled(self._serial_number) self._check_error(ret) self.wait_for_completion(status='stopped') + self.position.get() def close(self): if self._simulation: From 11a5b92dda3961b1d3f413de4dd0f855be980f9a Mon Sep 17 00:00:00 2001 From: Julien Barrier <1346858+julienbarrier@users.noreply.github.com> Date: Tue, 29 Aug 2023 16:59:19 +0200 Subject: [PATCH 14/30] set position kdc101.py --- .../drivers/Thorlabs/KDC101.py | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py index 68fdd5d0b..65fa3b687 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py @@ -83,7 +83,7 @@ def __init__(self, 'position', label='Position', get_cmd=self._get_position, - #set_cmd=self._set_position, + #set_cmd=self._set_position, MOT_MoveAbosluteEx() vals=vals.Numbers(0, 360), unit='\u00b0', instrument=self @@ -540,6 +540,14 @@ def _get_position(self) -> float: ctypes.byref(current_position)) return self._device_unit_to_real(current_position.value, 0) + def _set_position(self, position: float) -> None: + pos = self._real_to_device_unit(position, 0) + ret = self._dll.CC_SetMoveAbsolutePosition(self._serial_number, + ctypes.c_int(pos)) + self._check_error(ret) + ret = self._dll.CC_MoveAbsolute(self._serial_number) + self._check_error(ret) + def is_moving(self) -> bool: """check if the motor cotnroller is moving.""" status_bit = ctypes.c_short() @@ -590,13 +598,13 @@ def move_continuous(self, direction: str = 'forward') -> None: Defaults to 'forward'. Accepts 'forward' or 'reverse' """ if direction == 'forward' or direction == 'forwards': - dir = ctypes.c_short(0x01) + direc = ctypes.c_short(0x01) elif direction == 'reverse' or direction == 'backward' or direction == 'backwards': - dir = ctypes.c_short(0x02) + direc = ctypes.c_short(0x02) else: raise ValueError('direction unrecognised') - ret = self._dll.CC_MoveAtVelocity(self._serial_number, dir) + ret = self._dll.CC_MoveAtVelocity(self._serial_number, direc) self._check_error(ret) self.position.get() @@ -609,13 +617,13 @@ def jog(self, direction: str = 'forward', block: bool = True) -> None: block (bool, optional): will wait until complete. Defaults to True. """ if direction == 'forward' or direction == 'forwards': - dir = ctypes.c_short(0x01) + direc = ctypes.c_short(0x01) elif direction == 'reverse' or direction == 'backward' or direction == 'backwards': - dir = ctypes.c_short(0x02) + direc = ctypes.c_short(0x02) else: raise ValueError('direction unrecognised') - ret = self._dll.CC_MoveJog(self._serial_number, dir) + ret = self._dll.CC_MoveJog(self._serial_number, direc) self._check_error(ret) if self._get_jog_mode() =='stepped': if block: From 88c33bb1cdaac812b144e1877363c98534ef16ea Mon Sep 17 00:00:00 2001 From: julienbarrier <1346858+julienbarrier@users.noreply.github.com> Date: Tue, 29 Aug 2023 18:09:40 +0200 Subject: [PATCH 15/30] debug with real instrument --- .../drivers/Thorlabs/KDC101.py | 15 +++++++------ .../drivers/Thorlabs/PM100D.py | 21 ++++++++----------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py index 65fa3b687..e5f8a313f 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py @@ -83,8 +83,8 @@ def __init__(self, 'position', label='Position', get_cmd=self._get_position, - #set_cmd=self._set_position, MOT_MoveAbosluteEx() - vals=vals.Numbers(0, 360), + set_cmd=self._set_position, + vals=vals.Numbers(), unit='\u00b0', instrument=self ) @@ -535,19 +535,18 @@ def _set_jog_acceleration(self, acceleration: float) -> None: self._check_error(ret) def _get_position(self) -> float: - current_position = ctypes.c_int() - self._dll.CC_GetPosition(self._serial_number, - ctypes.byref(current_position)) - return self._device_unit_to_real(current_position.value, 0) + pos = self._dll.CC_GetPosition(self._serial_number) + return self._device_unit_to_real(pos, 0) def _set_position(self, position: float) -> None: - pos = self._real_to_device_unit(position, 0) + pos = self._real_to_device_unit(position % 360, 0) ret = self._dll.CC_SetMoveAbsolutePosition(self._serial_number, ctypes.c_int(pos)) self._check_error(ret) + sleep(.25) ret = self._dll.CC_MoveAbsolute(self._serial_number) self._check_error(ret) - + def is_moving(self) -> bool: """check if the motor cotnroller is moving.""" status_bit = ctypes.c_short() diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py index 3fe932f3c..6b2ec2399 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py @@ -7,7 +7,7 @@ """ import logging -import time +from time import sleep from typing import Optional, Any from qcodes.instrument import VisaInstrument @@ -37,15 +37,15 @@ def __init__(self, **kwargs: Any): super().__init__(name, address, terminator=terminator, **kwargs) self._timeout = timeout - self._timeout_pwr = 120 self.averaging = Parameter( 'averaging', label='Averaging rate', get_cmd='AVER?', set_cmd='AVER', - val_mapping=create_on_off_val_mapping(on_val='1', off_val='0'), - vals=vals.Ints(0, 1), + #val_mapping=create_on_off_val_mapping(on_val='1', off_val='0'), + #vals=vals.Ints(0, 1), + vals=vals.Numbers(), instrument=self ) @@ -69,9 +69,11 @@ def __init__(self, ) self.write('STAT:OPER:PTR 512') + sleep(.2) self.write('STAT:OPER:NTR 0') + sleep(.2) self.ask('STAT:OPER?') - self._check_error() + sleep(.2) self.averaging(300) self._set_conf_power() @@ -102,12 +104,7 @@ def _get_power(self) -> float: """Get the power """ self._set_conf_power() - oper = self.ask('STAT:OPER?') - start = time.process_time() - time_spent = 0. - while oper != str(512) and time_spent < self._timeout_pwr: - oper = self.ask('STAT:OPER?') - time_spent = time.process_time()-start + self.write('MEAS:POW') + sleep(.2) power = self.ask('FETC?') - self._check_error() return float(power) From 778a617148a72dc31cf318e86a8a6e549834c9cb Mon Sep 17 00:00:00 2001 From: Julien Barrier <1346858+julienbarrier@users.noreply.github.com> Date: Tue, 29 Aug 2023 18:34:16 +0200 Subject: [PATCH 16/30] block while setting position --- qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py index e5f8a313f..aa5e67320 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py @@ -538,14 +538,16 @@ def _get_position(self) -> float: pos = self._dll.CC_GetPosition(self._serial_number) return self._device_unit_to_real(pos, 0) - def _set_position(self, position: float) -> None: - pos = self._real_to_device_unit(position % 360, 0) + def _set_position(self, position: float, block: bool=True) -> None: + pos = self._real_to_device_unit(position, 0) ret = self._dll.CC_SetMoveAbsolutePosition(self._serial_number, ctypes.c_int(pos)) self._check_error(ret) - sleep(.25) ret = self._dll.CC_MoveAbsolute(self._serial_number) self._check_error(ret) + if block: + while (self._get_position() - position) < .001: + sleep(.2) def is_moving(self) -> bool: """check if the motor cotnroller is moving.""" From 0c62aa4a868a210b4f58bc161be08577af29ee64 Mon Sep 17 00:00:00 2001 From: Julien Barrier <1346858+julienbarrier@users.noreply.github.com> Date: Tue, 29 Aug 2023 19:05:38 +0200 Subject: [PATCH 17/30] add parameters --- .../drivers/Thorlabs/PM100D.py | 68 +++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py index 6b2ec2399..c283297ce 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py @@ -43,8 +43,6 @@ def __init__(self, label='Averaging rate', get_cmd='AVER?', set_cmd='AVER', - #val_mapping=create_on_off_val_mapping(on_val='1', off_val='0'), - #vals=vals.Ints(0, 1), vals=vals.Numbers(), instrument=self ) @@ -60,13 +58,74 @@ def __init__(self, ) self.power = Parameter( - "power", + 'power', label='Measured power', - unit="W", + unit='W', get_cmd=self._get_power, vals=vals.Numbers(), instrument=self ) + + self.attenuation = Parameter( + 'attenuation', + label='Attenuation', + unit='dB', + get_cmd='SENS:COR:LOSS:INP:MAGN?', + get_parser=float, + vals=vals.Numbers(-60,60), + instrument=self + ) + + self.power_range = Parameter( + 'power_range', + label='Power range', + unit='W', + get_cmd='SENS:POW:RANG:UPP?', + set_cmd='SENS:POW:RANG:UPP {}', + get_parser=float, + set_parser=float, + vals=vals.Numbers(), + instrument=self + ) + + self.auto_range = Parameter( + 'auto_range', + label='Auto range', + get_cmd='SENS:POW:RANG:AUTO?', + set_cmd='SENS:POW:RANG:AUTO {}', + val_mapping=create_on_off_val_mapping(on_val='ON', off_val='OFF'), + vals=vals.Enum('ON', 'OFF'), + instrument=self + ) + + self.frequency = Parameter( + 'frequency', + unit='Hz', + get_cmd='MEAS:FREQ?', + get_parser=float, + vals=vals.Numbers(), + instrument=self + ) + + self.current = Parameter( + 'current', + label='Current', + unit='A', + get_cmd='MEAS:CURR?', + get_parser=float, + vals=vals.Numbers(), + instrument=self + ) + + self.current_range = Parameter( + 'current_range', + label='Current range', + unit='A', + get_cmd='SENS:CURR:RANG:UPP?', + get_parser=float, + vals=vals.Numbers, + instrument=self + ) self.write('STAT:OPER:PTR 512') sleep(.2) @@ -108,3 +167,4 @@ def _get_power(self) -> float: sleep(.2) power = self.ask('FETC?') return float(power) + From 6053fba579083413339e1db498c0d9b34f33cf2a Mon Sep 17 00:00:00 2001 From: Julien Barrier <1346858+julienbarrier@users.noreply.github.com> Date: Tue, 29 Aug 2023 19:30:29 +0200 Subject: [PATCH 18/30] simplify driver with use of lambda functions --- .../drivers/Thorlabs/PM100D.py | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py index c283297ce..d613f15fb 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py @@ -51,8 +51,10 @@ def __init__(self, 'wavelength', label='Detected wavelength', unit='m', - get_cmd=self._get_wavelength, - set_cmd=self._set_wavelength, + get_cmd='SENS:CORR:WAV?', + set_cmd='SENS:CORR:WAV {}', + get_parser=lambda val: float(val)/1e9, + set_parser=lambda val: float(val)*1e9, vals=vals.Numbers(185e-9, 25e-6), instrument=self ) @@ -65,7 +67,7 @@ def __init__(self, vals=vals.Numbers(), instrument=self ) - + self.attenuation = Parameter( 'attenuation', label='Attenuation', @@ -75,7 +77,7 @@ def __init__(self, vals=vals.Numbers(-60,60), instrument=self ) - + self.power_range = Parameter( 'power_range', label='Power range', @@ -87,17 +89,17 @@ def __init__(self, vals=vals.Numbers(), instrument=self ) - + self.auto_range = Parameter( 'auto_range', - label='Auto range', + label='Auto range power', get_cmd='SENS:POW:RANG:AUTO?', set_cmd='SENS:POW:RANG:AUTO {}', val_mapping=create_on_off_val_mapping(on_val='ON', off_val='OFF'), vals=vals.Enum('ON', 'OFF'), instrument=self ) - + self.frequency = Parameter( 'frequency', unit='Hz', @@ -106,7 +108,7 @@ def __init__(self, vals=vals.Numbers(), instrument=self ) - + self.current = Parameter( 'current', label='Current', @@ -116,14 +118,35 @@ def __init__(self, vals=vals.Numbers(), instrument=self ) - + self.current_range = Parameter( 'current_range', label='Current range', unit='A', get_cmd='SENS:CURR:RANG:UPP?', get_parser=float, - vals=vals.Numbers, + vals=vals.Numbers(), + instrument=self + ) + + self.zero_value = Parameter( + 'zero_value', + unit='W', + get_cmd='CORR:COLL:ZERO:MAGN?', + get_parser=float, + vals=vals.Numbers(), + instrument=self + ) + + self.beam_diameter = Parameter( + 'beam_diameter', + label='Beam diameter', + unit='m', + get_cmd='CORR:BEAM?', + set_cmd='CORR:BEAM {}', + get_parser=lambda val: float(val)/1e3, + set_parser=lambda val: float(val)*1e3, + vals=vals.Numbers(), instrument=self ) @@ -143,14 +166,6 @@ def _check_error(self) -> None: if err[:2] != '+0': raise RuntimeError(f'PM100D call failed with error: {err}') - def _set_wavelength(self, value: float) -> None: - value_in_nm = value*1e9 - self.write(f'SENS:CORR:WAV {value_in_nm}') - - def _get_wavelength(self) -> float: - value_in_nm = self.ask('SENS:CORR:WAV?') - return float(value_in_nm)/1e9 - def _set_conf_power(self) -> None: """Set configuration to power mode """ @@ -167,4 +182,3 @@ def _get_power(self) -> float: sleep(.2) power = self.ask('FETC?') return float(power) - From b9e73f31dc2aa4cc4ed7ed265e35acae49205517 Mon Sep 17 00:00:00 2001 From: julienbarrier <1346858+julienbarrier@users.noreply.github.com> Date: Tue, 29 Aug 2023 19:36:32 +0200 Subject: [PATCH 19/30] add general driver for kinesis --- .../drivers/Thorlabs/private/kinesis.py | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py new file mode 100644 index 000000000..8329cbfd7 --- /dev/null +++ b/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +"""QCoDeS base drivers for Thorlabs Kinesis instruments + +Authors: + Julien Barrier, +""" +import os +import sys +import ctypes +from typing import Any +from . import GeneralErrors, MotorErrors, ConnexionErrors + +class _Thorlabs_Kinesis(Instrument): + """A base class for Thorlabs kinesis instruments + + Args: + Instrument (_type_): _description_ + """ + def __init__(self, + name: str, + serial_number: str, + dll_path: str, + simulation: bool = False, + **kwargs): + super().__init__(name, **kwargs) + self.serial_number = serial_number + self._serial_number = ctypes.c_char_p(self.serial_number.encode('ascii')) + self._dll_path = dll_path + if sys.platform != 'win32': + self._dll: Any = None + raise OSError('Thorlabs Kinesis only works on Windows') + else: + os.add_dll_directory(r'C:\Program Files\Thorlabs\Kinesis') + self._dll = ctypes.cdll.LoadLibrary(self._dll_path) + + self._simulation = simulation + if self._simulation: + self.enable_simulation() + + if self._dll.TLI_BuildDeviceList() == 0: + self._dll.CC_Open(self._serial_number) + self._start_polling(polling) + + self._device_info = dict(zip( + ['type_ID', 'description', 'PID', 'is_known_type', 'motor_type', + 'is_piezo', 'is_laser', 'is_custom', 'is_rack', 'max_channels'], + self._get_device_info())) + self._type_ID = self._device_info['type_ID'] + self._description = self._device_info['description'] + self._PID = self._device_info['PID'] + self._is_known_type = self._device_info['is_known_type'] + self._motor_type = self._device_info['motor_type'] + self._is_piezo = self._device_info['is_piezo'] + self._is_laser = self._device_info['is_laser'] + self._is_custom = self._device_info['is_custom'] + self._is_rack = self._device_info['is_rack'] + self._max_channels = self._device_info['max_channels'] + + def _get_device_info(self) -> list: + """Get the device information from the USB port + + Returns: + list: [type id, description, PID, is known type, motor type, + is piezo, is laser, is custom type, is rack, max channels] + """ + type_id = ctypes.c_ulong() + description = ctypes.c_char() + pid = ctypes.c_ulong() + is_known_type = ctypes.c_bool() + motor_type = ctypes.c_int() + is_piezo_device = ctypes.c_bool() + is_laser = ctypes.c_bool() + is_custom_type = ctypes.c_bool() + is_rack = ctypes.c_bool() + max_channels = ctypes.c_bool() + + ret = self._dll.TLI_GetDeviceInfo( + ctypes.byref(self._serial_number), + ctypes.byref(type_id), + ctypes.byref(description), + ctypes.byref(pid), + ctypes.byref(is_known_type), + ctypes.byref(motor_type), + ctypes.byref(is_piezo_device), + ctypes.byref(is_laser), + ctypes.byref(is_custom_type), + ctypes.byref(is_rack), + ctypes.byref(max_channels) + ) + self._check_error(ret) + return [type_id.value, description.value, pid.value, + is_known_type.value, motor_type.value, is_piezo_device.value, + is_laser.value, is_custom_type.value, is_rack.value, + max_channels.value] + + def _check_error(self, status: int) -> None: + if status != 0: + if status in ConnexionErrors: + raise ConnectionError(f'{ConnexionErrors[status]} ({status})') + elif status in GeneralErrors: + raise OSError(f'{GeneralErrors[status]} ({status})') + elif status in MotorErrors: + raise RuntimeError(f'{MotorErrors[status]} ({status})') + else: + raise ValueError(f'Unknown error code ({status})') + else: + pass + return None + + def enable_simulation(self) -> None: + """Initialise a connection to the simulation manager, which must already be running""" + self._dll.TLI_InitializeSimulations() + + def disable_simulation(self) -> None: + """Uninitialize a connection to the simulation manager, which must be running""" + self._dll.TLI_UninitializeSimulations() From d9c479b99966f3ec44a17c3d37e59710727708bd Mon Sep 17 00:00:00 2001 From: julienbarrier <1346858+julienbarrier@users.noreply.github.com> Date: Tue, 29 Aug 2023 19:40:35 +0200 Subject: [PATCH 20/30] pass mypy --- .../drivers/Thorlabs/private/kinesis.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py index 8329cbfd7..d5f4a51da 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py @@ -3,11 +3,13 @@ Authors: Julien Barrier, -""" +""" import os import sys import ctypes from typing import Any + +from qcodes.instrument import Instrument from . import GeneralErrors, MotorErrors, ConnexionErrors class _Thorlabs_Kinesis(Instrument): @@ -37,10 +39,6 @@ def __init__(self, if self._simulation: self.enable_simulation() - if self._dll.TLI_BuildDeviceList() == 0: - self._dll.CC_Open(self._serial_number) - self._start_polling(polling) - self._device_info = dict(zip( ['type_ID', 'description', 'PID', 'is_known_type', 'motor_type', 'is_piezo', 'is_laser', 'is_custom', 'is_rack', 'max_channels'], @@ -55,7 +53,7 @@ def __init__(self, self._is_custom = self._device_info['is_custom'] self._is_rack = self._device_info['is_rack'] self._max_channels = self._device_info['max_channels'] - + def _get_device_info(self) -> list: """Get the device information from the USB port @@ -92,7 +90,7 @@ def _get_device_info(self) -> list: is_known_type.value, motor_type.value, is_piezo_device.value, is_laser.value, is_custom_type.value, is_rack.value, max_channels.value] - + def _check_error(self, status: int) -> None: if status != 0: if status in ConnexionErrors: @@ -106,7 +104,7 @@ def _check_error(self, status: int) -> None: else: pass return None - + def enable_simulation(self) -> None: """Initialise a connection to the simulation manager, which must already be running""" self._dll.TLI_InitializeSimulations() From 59d280400bdb22ae066b7a95cda7e962f772d4cb Mon Sep 17 00:00:00 2001 From: Julien Barrier <1346858+julienbarrier@users.noreply.github.com> Date: Wed, 30 Aug 2023 09:07:07 +0200 Subject: [PATCH 21/30] use base instrument for kinesis and CC --- .../drivers/Thorlabs/KDC101.py | 754 +----------------- .../drivers/Thorlabs/private/CC.py | 697 ++++++++++++++++ .../drivers/Thorlabs/private/kinesis.py | 5 +- 3 files changed, 711 insertions(+), 745 deletions(-) create mode 100644 qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py index aa5e67320..8f2e24bdc 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py @@ -5,26 +5,21 @@ Authors: Julien Barrier, """ -import os -import sys -import ctypes import logging -from typing import Any -from time import sleep, time -from qcodes.instrument import Instrument -from qcodes.parameters import Parameter -from qcodes import validators as vals - -from .private import ConnexionErrors, GeneralErrors, MotorErrors +from .private.CC import _Thorlabs_CC log = logging.getLogger(__name__) -class Thorlabs_KDC101(Instrument): +class Thorlabs_KDC101(_Thorlabs_CC): """Instrument driver for the Thorlabs KDC101 servo motor controller Args: - Instrument (_type_): _description_ + name (str): Instrument name. + serial_number (str): Serial number of the device. + simulation (bool): Enables the simulation manager. Defaults to False. + polling (int): Polling rate in ms. Defaults to 200. + home (bool): Sets the device to home state. Defaults to False. """ _CONDITIONS = ['homed', 'moved', 'stopped', 'limit_updated'] def __init__(self, @@ -34,735 +29,6 @@ def __init__(self, polling: int = 200, home: bool = False, **kwargs): - super().__init__(name, **kwargs) - self.serial_number = serial_number - self._serial_number = ctypes.c_char_p(self.serial_number.encode('ascii')) - if sys.platform != 'win32': - self._dll: Any = None - raise OSError('Thorlabs Kinesis only works on Windows') - else: - os.add_dll_directory(r'C:\Program Files\Thorlabs\Kinesis') - self._dll_path = 'Thorlabs.MotionControl.KCube.DCServo.dll' - self._dll = ctypes.cdll.LoadLibrary(self._dll_path) - - self._simulation = simulation - if self._simulation: - self.enable_simulation() - - if self._dll.TLI_BuildDeviceList() == 0: - self._dll.CC_Open(self._serial_number) - self._start_polling(polling) - - self._info = self._get_hardware_info() - self.model = self._info[0].decode('utf-8') - self.version = self._info[4] - - self._device_info = dict(zip( - ['type_ID', 'description', 'PID', 'is_known_type', 'motor_type', - 'is_piezo', 'is_laser', 'is_custom', 'is_rack', 'max_channels'], - self._get_device_info())) - - self._load_settings() - self._set_limits_approach(1) - - sleep(3) - - self._clear_message_queue() - - if home: - if not self._can_home(): - self.homed = False - raise RuntimeError('Device `{}` is not homeable') - else: - self.go_home() - else: - self.homed = False - - - self.position = Parameter( - 'position', - label='Position', - get_cmd=self._get_position, - set_cmd=self._set_position, - vals=vals.Numbers(), - unit='\u00b0', - instrument=self - ) - - self.max_position = Parameter( - 'max_position', - label='Maximum position', - set_cmd=self._set_max_position, - get_cmd=self._get_max_position, - vals=vals.Numbers(0, 360), - instrument=self - ) - - self.min_position = Parameter( - 'min_position', - label='Minimum position', - set_cmd=self._set_min_position, - get_cmd=self._get_min_position, - vals=vals.Numbers(0, 360), - instrument=self - ) - - self.velocity = Parameter( - 'velocity', - label='Move velocity', - unit='\u00b0/s', - set_cmd=self._set_move_velocity, - get_cmd=self._get_move_velocity, - vals=vals.Numbers(0, 25), - instrument=self - ) - - self.jog_velocity = Parameter( - 'jog_velocity', - label='Jog velocity', - unit='\u00b0/s', - set_cmd=self._set_jog_velocity, - get_cmd=self._get_jog_velocity, - vals=vals.Numbers(0, 25), - instrument=self - ) - - self.homing_velocity = Parameter( - 'homing_velocity', - label='Homing velocity', - unit='\u00b0/s', - set_cmd=self._set_homing_velocity, - get_cmd=self._get_homing_velocity, - vals=vals.Numbers(0.1, 25), - instrument=self - ) - - self.acceleration = Parameter( - 'acceleration', - label='Move acceleration', - unit='\u00b0/s\u00b2', - set_cmd=self._set_move_acceleration, - get_cmd=self._get_move_acceleration, - vals=vals.Numbers(0, 25), - instrument=self - ) - - self.jog_acceleration = Parameter( - 'jog_acceleration', - label='Jog acceleration', - unit='\u00b0/s\u00b2', - set_cmd=self._set_jog_acceleration, - get_cmd=self._get_jog_acceleration, - vals=vals.Numbers(0, 25), - instrument=self - ) - - self.jog_mode = Parameter( - 'jog_mode', - set_cmd=self._set_jog_mode, - get_cmd=self._get_jog_mode, - vals=vals.Enum('continuous', 'stepped'), - instrument=self - ) - - self.jog_step_size = Parameter( - 'jog_step_size', - set_cmd=self._set_jog_step_size, - get_cmd=self._get_jog_step_size, - vals=vals.Numbers(0.0005, 360), - instrument=self - ) - - self.stop_mode = Parameter( - 'stop_mode', - set_cmd=self._set_stop_mode, - get_cmd=self._get_stop_mode, - vals=vals.Enum('immediate', 'profiled'), - instrument=self - ) - - self.soft_limits_mode = Parameter( - 'soft_limits_mode', - set_cmd=self._set_soft_limits_mode, - get_cmd=self._get_soft_limits_mode, - vals=vals.Enum('disallow', 'partial', 'all'), - instrument=self - ) - - self.backlash = Parameter( - 'backlash', - unit='\u00b0', - set_cmd=self._set_backlash, - get_cmd=self._get_backlash, - vals=vals.Numbers(0, 5), - instrument=self - ) - - self.connect_message() - - def identify(self) -> None: - """Sends a command to the device to make it identify itself""" - self._dll.CC_Identify(self._serial_number) - - def get_idn(self) -> dict: - """Get device identifier""" - idparts = ['Thorlabs', self.model, self.version, self.serial_number] - return dict(zip(('vendor', 'model', 'serial', 'firmware'), idparts)) - - def go_home(self, block=True): - """Home the device: set the device to a known state and home position - - Args: - block (bool, optional): will wait for completion. Defaults to True. - """ - self._check_error(self._dll.CC_Home(self._serial_number)) - self.homed = True - if block: - self.wait_for_completion() - - def enable_simulation(self) -> None: - """Initialise a connection to the simulation manager, which must already be running""" - self._dll.TLI_InitializeSimulations() - - def disable_simulation(self) -> None: - """Uninitialize a connection to the simulation manager, which must be running""" - self._dll.TLI_UninitializeSimulations() - - def wait_for_completion(self, status: str = 'homed', max_time = 5) -> None: - """Wait for the current function to be finished. - - Args: - status (str, optional): expected status. Defaults to 'homed'. - max_time (int, optional): maximum waiting time for the internal loop. Defaults to 5. - """ - message_type = ctypes.c_ushort() - message_id = ctypes.c_ushort() - message_data = ctypes.c_ulong() - cond = self._CONDITIONS.index(status) - - if status == 'stopped': - if not self.is_moving(): - return None - elif status == 'homed': - max_time = 0 - - while self._dll.CC_MessageQueueSize(self._serial_number) <= 0: - sleep(.2) - - self._dll.CC_WaitForMessage( - self._serial_number, ctypes.byref(message_type), - ctypes.byref(message_id), ctypes.byref(message_data) - ) - - start = time() - while int(message_type.value) != 2 or int(message_id.value) != cond: - end = time() - if end - start > max_time and max_time != 0: - raise RuntimeError(f'waited for {max_time} for {status} to complete' - f'message type: {message_type.value} ({message_id.value})') - - if self._dll.CC_MessageQueueSize(self._serial_number) <= 0: - sleep(.2) - continue - self._dll.CC_WaitForMessage( - self._serial_number, - ctypes.byref(message_type), - ctypes.byref(message_id), - ctypes.byref(message_data) - ) - return None - - def _device_unit_to_real(self, device_unit: int, unit_type: int) -> float: - """Converts a device unit to a real world unit - - Args: - device_unit (int): the device unit. - unit_type (int): the type of unit. Distance: 0, velocity: 1, acceleration: 2. - - Returns: - float: real unit value - """ - real_unit = ctypes.c_double() - ret = self._dll.CC_GetRealValueFromDeviceUnit( - self._serial_number, ctypes.c_int(device_unit), - ctypes.byref(real_unit), ctypes.c_int(unit_type) - ) - self._check_error(ret) - return real_unit.value - - def _real_to_device_unit(self, real_unit: float, unit_type: int) -> int: - """Converts a real world unit to a device unit - - Args: - real_unit (float): the real unit - unit_type (int): the type of unit. Distance: 0, velocity: 1, acceleration: 2 - - Returns: - int: device unit - """ - device_unit = ctypes.c_int() - ret = self._dll.CC_GetDeviceUnitFromRealValue( - self._serial_number, ctypes.c_double(real_unit), - ctypes.byref(device_unit), ctypes.c_int(unit_type) - ) - self._check_error(ret) - return device_unit.value - - def _get_backlash(self) -> float: - """Get the backlash distance setting (used to control hysteresis)""" - ret = self._dll.CC_GetBacklash(self._serial_number) - return self._device_unit_to_real(ret, 0) - - def _set_backlash(self, value: float) -> None: - """Set the backlash distance setting (used to control hysteresis)""" - val = self._real_to_device_unit(value, 0) - ret = self._dll.CC_SetBacklash(self._serial_number, ctypes.c_long(val)) - self._check_error(ret) - - def _get_homing_velocity(self) -> float: - vel = self._dll.CC_GetHomingVelocity(self._serial_number) - return self._device_unit_to_real(vel, 1) - - def _set_homing_velocity(self, velocity: float) -> None: - vel = self._real_to_device_unit(velocity, 1) - ret = self._dll.CC_SetHomingVelocity(self._serial_number, - ctypes.c_uint(vel)) - self._check_error(ret) - - def _get_jog_mode(self) -> str: - jog_mode = ctypes.c_short() - stop_mode = ctypes.c_short() - ret = self._dll.CC_GetJogMode(self._serial_number, - ctypes.byref(jog_mode), - ctypes.byref(stop_mode)) - self._check_error(ret) - if jog_mode.value == ctypes.c_short(0x01).value: - return 'continuous' - elif jog_mode.value == ctypes.c_short(0x02).value: - return 'stepped' - else: - raise RuntimeError('Unexpected value received from Kinesis') - - def _set_jog_mode(self, mode: str) -> None: - jog_mode = ctypes.c_short(0x00) - stop_mode = ctypes.c_short(0x00) - stop_mode = self._get_stop_mode() - - if mode == 'continuous': - jog_mode = ctypes.c_short(0x01) - elif mode == 'stepped': - jog_mode = ctypes.c_short(0x02) - if stop_mode == 'immediate': - stop_mode = ctypes.c_short(0x01) - elif stop_mode == 'profiled': - stop_mode = ctypes.c_short(0x02) - - ret = self._dll.CC_SetJogMode(self._serial_number, - jog_mode, stop_mode) - self._check_error(ret) - return None - - def _get_jog_step_size(self) -> float: - ret = self._dll.CC_GetJogStepSize(self._serial_number) - return self._device_unit_to_real(ret, 0) - - def _set_jog_step_size(self, step_size: float) -> None: - step = self._real_to_device_unit(step_size, 0) - ret = self._dll.CC_SetJogStepSize(self._serial_number, - ctypes.c_uint(step)) - self._check_error(ret) - return None - - def _check_connection(self) -> bool: - ret = self._dll.CC_CheckConnection(self._serial_number) - return bool(ret) - - def _get_stop_mode(self): - jog_mode = ctypes.c_short() - stop_mode = ctypes.c_short() - ret = self._dll.CC_GetJogMode(self._serial_number, - ctypes.byref(jog_mode), - ctypes.byref(stop_mode)) - self._check_error(ret) - if stop_mode.value == ctypes.c_short(0x01).value: - return 'immediate' - elif stop_mode.value == ctypes.c_short(0x02).value: - return 'profiled' - else: - raise RuntimeError('unexpected value received from Kinesis') - - def _set_stop_mode(self, mode: str) -> None: - jog_mode = ctypes.c_short(0x00) - stop_mode = ctypes.c_short(0x00) - - jmode = self._get_jog_mode() - if jmode == 'continuous': - jog_mode = ctypes.c_short(0x01) - elif jmode == 'stepped': - jog_mode = ctypes.c_short(0x02) - if mode == 'immediate': - stop_mode = ctypes.c_short(0x01) - elif mode == 'profiled': - stop_mode = ctypes.c_short(0x02) - - ret = self._dll.CC_SetJogMode(self._serial_number, - jog_mode, stop_mode) - self._check_error(ret) - return None - - def _get_max_position(self) -> float: - max_pos = ctypes.c_int() - self._dll.CC_GetStageAxisMaxPos(self._serial_number, max_pos) - return self._device_unit_to_real(max_pos.value, 0) - - def _set_max_position(self, max_val: float) -> None: - min_val = self._get_min_position() - min_val = self._real_to_device_unit(min_val, 0) - max_val = self._real_to_device_unit(max_val, 0) - ret = self._dll.CC_SetStageAxisLimits(self._serial_number, - ctypes.c_int(min_val), - ctypes.c_int(max_val)) - self._check_error(ret) - self.wait_for_completion('limit_updated') - - def _get_min_position(self) -> float: - min_pos = ctypes.c_int() - self._dll.CC_GetStageAxisMinPos(self._serial_number, min_pos) - return self._device_unit_to_real(min_pos.value, 0) - - def _set_min_position(self, min_val: float) -> None: - max_val = self._get_max_position() - max_val = self._real_to_device_unit(max_val, 0) - min_val = self._real_to_device_unit(min_val, 0) - ret = self._dll.CC_SetStageAxisLimits(self._serial_number, - ctypes.c_int(min_val), - ctypes.c_int(max_val)) - self._check_error(ret) - self.wait_for_completion('limit_updated') - - def _get_soft_limits_mode(self) -> str: - """Gets the software limits mode.""" - mode = ctypes.c_int16() - self._dll.CC_GetSoftLimitMode(self._serial_number, ctypes.byref(mode)) - if mode.value == ctypes.c_int16(0).value: - return 'disallow' - elif mode.value == ctypes.c_int16(1).value: - return 'partial' - elif mode.value == ctypes.c_int16(2).value: - return 'all' - else: - raise RuntimeError('unexpected value received from Kinesis') - - def _set_soft_limits_mode(self, mode: str) -> None: - """Sets the software limits mode""" - if mode == 'disallow': - lmode = ctypes.c_int16(0) - elif mode == 'partial': - lmode = ctypes.c_int16(1) - elif mode == 'all': - lmode = ctypes.c_int16(2) - - ret = self._dll.CC_SetLimitsSoftwareApproachPolicy(self._serial_number, - lmode) - self._check_error(ret) - - def _get_move_velocity(self) -> float: - acceleration = ctypes.c_int() - velocity = ctypes.c_int() - ret = self._dll.CC_GetVelParams(self._serial_number, - ctypes.byref(acceleration), - ctypes.byref(velocity)) - self._check_error(ret) - return self._device_unit_to_real(velocity.value, 1) - - def _set_move_velocity(self, velocity: float) -> None: - vel = self._real_to_device_unit(velocity, 1) - accel = self._real_to_device_unit(self._get_move_acceleration(), 2) - ret = self._dll.CC_SetVelParams(self._serial_number, - ctypes.c_int(accel), - ctypes.c_int(vel)) - self._check_error(ret) - - def _get_move_acceleration(self) -> float: - acceleration = ctypes.c_int() - velocity = ctypes.c_int() - ret = self._dll.CC_GetVelParams(self._serial_number, - ctypes.byref(acceleration), - ctypes.byref(velocity)) - self._check_error(ret) - return self._device_unit_to_real(acceleration.value, 2) - - def _set_move_acceleration(self, acceleration: float) -> None: - vel = self._real_to_device_unit(self._get_move_velocity(), 1) - accel = self._real_to_device_unit(acceleration, 2) - ret = self._dll.CC_SetVelParams(self._serial_number, - ctypes.c_int(accel), - ctypes.c_int(vel)) - self._check_error(ret) - - def _get_jog_velocity(self) -> float: - acceleration = ctypes.c_int() - velocity = ctypes.c_int() - ret = self._dll.CC_GetJogVelParams(self._serial_number, - ctypes.byref(acceleration), - ctypes.byref(velocity)) - self._check_error(ret) - return self._device_unit_to_real(velocity.value, 1) - - def _set_jog_velocity(self, velocity: float) -> None: - vel = self._real_to_device_unit(velocity, 1) - accel = self._real_to_device_unit(self._get_jog_acceleration(), 2) - ret = self._dll.CC_SetJogVelParams(self._serial_number, - ctypes.c_int(accel), - ctypes.c_int(vel)) - self._check_error(ret) - - def _get_jog_acceleration(self) -> float: - acceleration = ctypes.c_int() - velocity = ctypes.c_int() - ret = self._dll.CC_GetJogVelParams(self._serial_number, - ctypes.byref(acceleration), - ctypes.byref(velocity)) - self._check_error(ret) - return self._device_unit_to_real(acceleration.value, 2) - - def _set_jog_acceleration(self, acceleration: float) -> None: - vel = self._real_to_device_unit(self._get_jog_velocity(), 1) - accel = self._real_to_device_unit(acceleration, 2) - ret = self._dll.CC_SetJogVelParams(self._serial_number, - ctypes.c_int(accel), - ctypes.c_int(vel)) - self._check_error(ret) - - def _get_position(self) -> float: - pos = self._dll.CC_GetPosition(self._serial_number) - return self._device_unit_to_real(pos, 0) - - def _set_position(self, position: float, block: bool=True) -> None: - pos = self._real_to_device_unit(position, 0) - ret = self._dll.CC_SetMoveAbsolutePosition(self._serial_number, - ctypes.c_int(pos)) - self._check_error(ret) - ret = self._dll.CC_MoveAbsolute(self._serial_number) - self._check_error(ret) - if block: - while (self._get_position() - position) < .001: - sleep(.2) - - def is_moving(self) -> bool: - """check if the motor cotnroller is moving.""" - status_bit = ctypes.c_short() - self._dll.CC_GetStatusBits(self._serial_number, - ctypes.byref(status_bit)) - if status_bit.value & 0x10 or status_bit.value & 0x20: - return True - else: - return False - - def move_to(self, position: float, block=True) -> None: - """Move the device to the specified position. - The motor may need to be homed before a position can be set. - - Args: - position (float): the set position - block (bool, optional): will wait until complete. Defaults to True. - """ - pos = self._real_to_device_unit(position, 0) - ret = self._dll.CC_MoveToPosition(self._serial_number, - ctypes.c_int(pos)) - self._check_error(ret) - - if block: - self.wait_for_completion(status='moved', max_time=15) - self.position.get() - - def move_by(self, displacement: float, block: bool = True) -> None: - """Move the motor by a relative amount - - Args: - displacement (float): amount to move - block (bool, optional): will wait until complete. Defaults to True. - """ - dis = self._real_to_device_unit(displacement, 0) - ret = self._dll.CC_MoveRelative(self._serial_number, - ctypes.c_int(dis)) - self._check_error(ret) - if block: - self.wait_for_completion(status='moved') - self.position.get() - - def move_continuous(self, direction: str = 'forward') -> None: - """start moving at the current velocity in the specified direction - - Args: - direction (str, optional): the required direction of travel. - Defaults to 'forward'. Accepts 'forward' or 'reverse' - """ - if direction == 'forward' or direction == 'forwards': - direc = ctypes.c_short(0x01) - elif direction == 'reverse' or direction == 'backward' or direction == 'backwards': - direc = ctypes.c_short(0x02) - else: - raise ValueError('direction unrecognised') - - ret = self._dll.CC_MoveAtVelocity(self._serial_number, direc) - self._check_error(ret) - self.position.get() - - def jog(self, direction: str = 'forward', block: bool = True) -> None: - """Performs a jog - - Args: - direction (str): the jog direction. Defaults to 'forward'. - Accepts 'forward' or 'reverse' - block (bool, optional): will wait until complete. Defaults to True. - """ - if direction == 'forward' or direction == 'forwards': - direc = ctypes.c_short(0x01) - elif direction == 'reverse' or direction == 'backward' or direction == 'backwards': - direc = ctypes.c_short(0x02) - else: - raise ValueError('direction unrecognised') - - ret = self._dll.CC_MoveJog(self._serial_number, direc) - self._check_error(ret) - if self._get_jog_mode() =='stepped': - if block: - self.wait_for_completion(status='moved') - self.position.get() - - def stop(self, immediate: bool = False) -> None: - """Stop the current move - - Args: - immediate (bool, optional): - True: stops immediately (with risk of losing track of position). - False: stops using the current velocity profile. - Defaults to False. - """ - if immediate: - ret = self._dll.CC_StopImmediate(self._serial_number) - else: - ret = self._dll.CC_StopProfiled(self._serial_number) - self._check_error(ret) - self.wait_for_completion(status='stopped') - self.position.get() - - def close(self): - if self._simulation: - self.disable_simulation() - if hasattr(self, '_serial_number'): - self._dll.CC_Close(self._serial_number) - - def _get_hardware_info(self) -> list: - """Gets the hardware information from the device - - Returns: - list: [model number, hardware type number, number of channels, - notes describing the device, firmware version, hardware version, - hardware modification state] - """ - model = ctypes.create_string_buffer(8) - model_size = ctypes.c_ulong(8) - type_num = ctypes.c_ushort() - channel_num = ctypes.c_ushort() - notes = ctypes.create_string_buffer(48) - notes_size = ctypes.c_ulong(48) - firmware_version = ctypes.c_ulong() - hardware_version = ctypes.c_ushort() - modification_state = ctypes.c_ushort() - - ret = self._dll.CC_GetHardwareInfo( - self._serial_number, - ctypes.byref(model), model_size, - ctypes.byref(type_num), ctypes.byref(channel_num), - ctypes.byref(notes), notes_size, ctypes.byref(firmware_version), - ctypes.byref(hardware_version), ctypes.byref(modification_state) - ) - - self._check_error(ret) - return [model.value, type_num.value, channel_num.value, - notes.value, firmware_version.value, hardware_version.value, - modification_state.value] - - def _get_device_info(self) -> list: - """Get the device information from the USB port - - Returns: - list: [type id, description, PID, is known type, motor type, - is piezo, is laser, is custom type, is rack, max channels] - """ - type_id = ctypes.c_ulong() - description = ctypes.c_char() - pid = ctypes.c_ulong() - is_known_type = ctypes.c_bool() - motor_type = ctypes.c_int() - is_piezo_device = ctypes.c_bool() - is_laser = ctypes.c_bool() - is_custom_type = ctypes.c_bool() - is_rack = ctypes.c_bool() - max_channels = ctypes.c_bool() - - ret = self._dll.TLI_GetDeviceInfo( - ctypes.byref(self._serial_number), - ctypes.byref(type_id), - ctypes.byref(description), - ctypes.byref(pid), - ctypes.byref(is_known_type), - ctypes.byref(motor_type), - ctypes.byref(is_piezo_device), - ctypes.byref(is_laser), - ctypes.byref(is_custom_type), - ctypes.byref(is_rack), - ctypes.byref(max_channels) - ) - self._check_error(ret) - return [type_id.value, description.value, pid.value, - is_known_type.value, motor_type.value, is_piezo_device.value, - is_laser.value, is_custom_type.value, is_rack.value, - max_channels.value] - - def _load_settings(self): - """Update device with stored settings""" - self._dll.CC_LoadSettings(self._serial_number) - return None - - def _set_limits_approach(self, limit: int): - disallow_illegal_moves = ctypes.c_int16(0) - allow_partial_moves = ctypes.c_int16(1) - allow_all_moves = ctypes.c_int16(2) - limits_approach_policy = ctypes.c_int16(limit) - - self._dll.CC_SetLimitsSoftwareApproachPolicy( - self._serial_number, ctypes.byref(disallow_illegal_moves), - ctypes.byref(allow_partial_moves), ctypes.byref(allow_all_moves), - ctypes.byref(limits_approach_policy) - ) - return None - - def _start_polling(self, polling: int): - pol = ctypes.c_int(polling) - self._dll.CC_StartPolling(self._serial_number, ctypes.byref(pol)) - return None - - def _clear_message_queue(self) -> None: - self._dll.CC_ClearMessageQueue(self._serial_number) - return None - - def _can_home(self) -> bool: - ret = self._dll.CC_CanHome(self._serial_number) - return bool(ret) - - def _check_error(self, status: int) -> None: - if status != 0: - if status in ConnexionErrors: - raise ConnectionError(f'{ConnexionErrors[status]} ({status})') - elif status in GeneralErrors: - raise OSError(f'{GeneralErrors[status]} ({status})') - elif status in MotorErrors: - raise RuntimeError(f'{MotorErrors[status]} ({status})') - else: - raise ValueError(f'Unknown error code ({status})') - else: - pass - return None + self._dll_path = 'Thorlabs.MotionControl.KCube.DCServo.dll' + super().__init__(name, serial_number, self._dll_path, + simulation, polling, home, **kwargs) \ No newline at end of file diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py new file mode 100644 index 000000000..0af470035 --- /dev/null +++ b/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py @@ -0,0 +1,697 @@ +# -*- coding: utf-8 -*- +"""QCoDes- Base driver for Thorlab instruments using the CC commands +https://www.thorlabs.com/thorproduct.cfm?partnumber=KDC101 + +Authors: + Julien Barrier, +""" +import ctypes +import logging +from time import sleep, time + +from qcodes.parameters import Parameter +from qcodes import validators as vals + +from .kinesis import _Thorlabs_Kinesis + +log = logging.getLogger(__name__) + +class _Thorlabs_CC(_Thorlabs_Kinesis): + """Instrument driver for Thorlabs instruments using the CC commands + + Args: + name (str): Instrument name. + serial_number (str): Serial number of the device. + dll_path (str): Path to the kinesis dll for the instrument to use. + simulation (bool): Enables the simulation manager. Defaults to False. + polling (int): Polling rate in ms. Defaults to 200. + home (bool): Sets the device to home state. Defaults to False. + """ + _CONDITIONS = ['homed', 'moved', 'stopped', 'limit_updated'] + def __init__(self, + name: str, + serial_number: str, + dll_path: str, + simulation: bool = False, + polling: int = 200, + home: bool = False, + **kwargs): + self._dll_path = dll_path + super().__init__(name, serial_number, + self._dll_path, simulation, + **kwargs) + + if self._dll.TLI_BuildDeviceList() == 0: + self._dll.CC_Open(self._serial_number) + self._start_polling(polling) + + self._info = self._get_hardware_info() + self.model = self._info[0].decode('utf-8') + self.version = self._info[4] + + self._load_settings() + self._set_limits_approach(1) + + sleep(3) + + self._clear_message_queue() + + if home: + if not self._can_home(): + self.homed = False + raise RuntimeError('Device `{}` is not homeable') + else: + self.go_home() + else: + self.homed = False + + + self.position = Parameter( + 'position', + label='Position', + get_cmd=self._get_position, + set_cmd=self._set_position, + vals=vals.Numbers(), + unit='\u00b0', + instrument=self + ) + + self.max_position = Parameter( + 'max_position', + label='Maximum position', + set_cmd=self._set_max_position, + get_cmd=self._get_max_position, + vals=vals.Numbers(0, 360), + instrument=self + ) + + self.min_position = Parameter( + 'min_position', + label='Minimum position', + set_cmd=self._set_min_position, + get_cmd=self._get_min_position, + vals=vals.Numbers(0, 360), + instrument=self + ) + + self.velocity = Parameter( + 'velocity', + label='Move velocity', + unit='\u00b0/s', + set_cmd=self._set_move_velocity, + get_cmd=self._get_move_velocity, + vals=vals.Numbers(0, 25), + instrument=self + ) + + self.jog_velocity = Parameter( + 'jog_velocity', + label='Jog velocity', + unit='\u00b0/s', + set_cmd=self._set_jog_velocity, + get_cmd=self._get_jog_velocity, + vals=vals.Numbers(0, 25), + instrument=self + ) + + self.homing_velocity = Parameter( + 'homing_velocity', + label='Homing velocity', + unit='\u00b0/s', + set_cmd=self._set_homing_velocity, + get_cmd=self._get_homing_velocity, + vals=vals.Numbers(0.1, 25), + instrument=self + ) + + self.acceleration = Parameter( + 'acceleration', + label='Move acceleration', + unit='\u00b0/s\u00b2', + set_cmd=self._set_move_acceleration, + get_cmd=self._get_move_acceleration, + vals=vals.Numbers(0, 25), + instrument=self + ) + + self.jog_acceleration = Parameter( + 'jog_acceleration', + label='Jog acceleration', + unit='\u00b0/s\u00b2', + set_cmd=self._set_jog_acceleration, + get_cmd=self._get_jog_acceleration, + vals=vals.Numbers(0, 25), + instrument=self + ) + + self.jog_mode = Parameter( + 'jog_mode', + set_cmd=self._set_jog_mode, + get_cmd=self._get_jog_mode, + vals=vals.Enum('continuous', 'stepped'), + instrument=self + ) + + self.jog_step_size = Parameter( + 'jog_step_size', + set_cmd=self._set_jog_step_size, + get_cmd=self._get_jog_step_size, + vals=vals.Numbers(0.0005, 360), + instrument=self + ) + + self.stop_mode = Parameter( + 'stop_mode', + set_cmd=self._set_stop_mode, + get_cmd=self._get_stop_mode, + vals=vals.Enum('immediate', 'profiled'), + instrument=self + ) + + self.soft_limits_mode = Parameter( + 'soft_limits_mode', + set_cmd=self._set_soft_limits_mode, + get_cmd=self._get_soft_limits_mode, + vals=vals.Enum('disallow', 'partial', 'all'), + instrument=self + ) + + self.backlash = Parameter( + 'backlash', + unit='\u00b0', + set_cmd=self._set_backlash, + get_cmd=self._get_backlash, + vals=vals.Numbers(0, 5), + instrument=self + ) + + self.connect_message() + + def identify(self) -> None: + """Sends a command to the device to make it identify itself""" + self._dll.CC_Identify(self._serial_number) + + def get_idn(self) -> dict: + """Get device identifier""" + idparts = ['Thorlabs', self.model, self.version, self.serial_number] + return dict(zip(('vendor', 'model', 'serial', 'firmware'), idparts)) + + def go_home(self, block=True): + """Home the device: set the device to a known state and home position + + Args: + block (bool, optional): will wait for completion. Defaults to True. + """ + self._check_error(self._dll.CC_Home(self._serial_number)) + self.homed = True + if block: + self.wait_for_completion() + + def wait_for_completion(self, status: str = 'homed', max_time = 5) -> None: + """Wait for the current function to be finished. + + Args: + status (str, optional): expected status. Defaults to 'homed'. + max_time (int, optional): maximum waiting time for the internal loop. Defaults to 5. + """ + message_type = ctypes.c_ushort() + message_id = ctypes.c_ushort() + message_data = ctypes.c_ulong() + cond = self._CONDITIONS.index(status) + + if status == 'stopped': + if not self.is_moving(): + return None + elif status == 'homed': + max_time = 0 + + while self._dll.CC_MessageQueueSize(self._serial_number) <= 0: + sleep(.2) + + self._dll.CC_WaitForMessage( + self._serial_number, ctypes.byref(message_type), + ctypes.byref(message_id), ctypes.byref(message_data) + ) + + start = time() + while int(message_type.value) != 2 or int(message_id.value) != cond: + end = time() + if end - start > max_time and max_time != 0: + raise RuntimeError(f'waited for {max_time} for {status} to complete' + f'message type: {message_type.value} ({message_id.value})') + + if self._dll.CC_MessageQueueSize(self._serial_number) <= 0: + sleep(.2) + continue + self._dll.CC_WaitForMessage( + self._serial_number, + ctypes.byref(message_type), + ctypes.byref(message_id), + ctypes.byref(message_data) + ) + return None + + def _device_unit_to_real(self, device_unit: int, unit_type: int) -> float: + """Converts a device unit to a real world unit + + Args: + device_unit (int): the device unit. + unit_type (int): the type of unit. Distance: 0, velocity: 1, acceleration: 2. + + Returns: + float: real unit value + """ + real_unit = ctypes.c_double() + ret = self._dll.CC_GetRealValueFromDeviceUnit( + self._serial_number, ctypes.c_int(device_unit), + ctypes.byref(real_unit), ctypes.c_int(unit_type) + ) + self._check_error(ret) + return real_unit.value + + def _real_to_device_unit(self, real_unit: float, unit_type: int) -> int: + """Converts a real world unit to a device unit + + Args: + real_unit (float): the real unit + unit_type (int): the type of unit. Distance: 0, velocity: 1, acceleration: 2 + + Returns: + int: device unit + """ + device_unit = ctypes.c_int() + ret = self._dll.CC_GetDeviceUnitFromRealValue( + self._serial_number, ctypes.c_double(real_unit), + ctypes.byref(device_unit), ctypes.c_int(unit_type) + ) + self._check_error(ret) + return device_unit.value + + def _get_backlash(self) -> float: + """Get the backlash distance setting (used to control hysteresis)""" + ret = self._dll.CC_GetBacklash(self._serial_number) + return self._device_unit_to_real(ret, 0) + + def _set_backlash(self, value: float) -> None: + """Set the backlash distance setting (used to control hysteresis)""" + val = self._real_to_device_unit(value, 0) + ret = self._dll.CC_SetBacklash(self._serial_number, ctypes.c_long(val)) + self._check_error(ret) + + def _get_homing_velocity(self) -> float: + vel = self._dll.CC_GetHomingVelocity(self._serial_number) + return self._device_unit_to_real(vel, 1) + + def _set_homing_velocity(self, velocity: float) -> None: + vel = self._real_to_device_unit(velocity, 1) + ret = self._dll.CC_SetHomingVelocity(self._serial_number, + ctypes.c_uint(vel)) + self._check_error(ret) + + def _get_jog_mode(self) -> str: + jog_mode = ctypes.c_short() + stop_mode = ctypes.c_short() + ret = self._dll.CC_GetJogMode(self._serial_number, + ctypes.byref(jog_mode), + ctypes.byref(stop_mode)) + self._check_error(ret) + if jog_mode.value == ctypes.c_short(0x01).value: + return 'continuous' + elif jog_mode.value == ctypes.c_short(0x02).value: + return 'stepped' + else: + raise RuntimeError('Unexpected value received from Kinesis') + + def _set_jog_mode(self, mode: str) -> None: + jog_mode = ctypes.c_short(0x00) + stop_mode = ctypes.c_short(0x00) + stop_mode = self._get_stop_mode() + + if mode == 'continuous': + jog_mode = ctypes.c_short(0x01) + elif mode == 'stepped': + jog_mode = ctypes.c_short(0x02) + if stop_mode == 'immediate': + stop_mode = ctypes.c_short(0x01) + elif stop_mode == 'profiled': + stop_mode = ctypes.c_short(0x02) + + ret = self._dll.CC_SetJogMode(self._serial_number, + jog_mode, stop_mode) + self._check_error(ret) + return None + + def _get_jog_step_size(self) -> float: + ret = self._dll.CC_GetJogStepSize(self._serial_number) + return self._device_unit_to_real(ret, 0) + + def _set_jog_step_size(self, step_size: float) -> None: + step = self._real_to_device_unit(step_size, 0) + ret = self._dll.CC_SetJogStepSize(self._serial_number, + ctypes.c_uint(step)) + self._check_error(ret) + return None + + def _check_connection(self) -> bool: + ret = self._dll.CC_CheckConnection(self._serial_number) + return bool(ret) + + def _get_stop_mode(self): + jog_mode = ctypes.c_short() + stop_mode = ctypes.c_short() + ret = self._dll.CC_GetJogMode(self._serial_number, + ctypes.byref(jog_mode), + ctypes.byref(stop_mode)) + self._check_error(ret) + if stop_mode.value == ctypes.c_short(0x01).value: + return 'immediate' + elif stop_mode.value == ctypes.c_short(0x02).value: + return 'profiled' + else: + raise RuntimeError('unexpected value received from Kinesis') + + def _set_stop_mode(self, mode: str) -> None: + jog_mode = ctypes.c_short(0x00) + stop_mode = ctypes.c_short(0x00) + + jmode = self._get_jog_mode() + if jmode == 'continuous': + jog_mode = ctypes.c_short(0x01) + elif jmode == 'stepped': + jog_mode = ctypes.c_short(0x02) + if mode == 'immediate': + stop_mode = ctypes.c_short(0x01) + elif mode == 'profiled': + stop_mode = ctypes.c_short(0x02) + + ret = self._dll.CC_SetJogMode(self._serial_number, + jog_mode, stop_mode) + self._check_error(ret) + return None + + def _get_max_position(self) -> float: + max_pos = ctypes.c_int() + self._dll.CC_GetStageAxisMaxPos(self._serial_number, max_pos) + return self._device_unit_to_real(max_pos.value, 0) + + def _set_max_position(self, max_val: float) -> None: + min_val = self._get_min_position() + min_val = self._real_to_device_unit(min_val, 0) + max_val = self._real_to_device_unit(max_val, 0) + ret = self._dll.CC_SetStageAxisLimits(self._serial_number, + ctypes.c_int(min_val), + ctypes.c_int(max_val)) + self._check_error(ret) + self.wait_for_completion('limit_updated') + + def _get_min_position(self) -> float: + min_pos = ctypes.c_int() + self._dll.CC_GetStageAxisMinPos(self._serial_number, min_pos) + return self._device_unit_to_real(min_pos.value, 0) + + def _set_min_position(self, min_val: float) -> None: + max_val = self._get_max_position() + max_val = self._real_to_device_unit(max_val, 0) + min_val = self._real_to_device_unit(min_val, 0) + ret = self._dll.CC_SetStageAxisLimits(self._serial_number, + ctypes.c_int(min_val), + ctypes.c_int(max_val)) + self._check_error(ret) + self.wait_for_completion('limit_updated') + + def _get_soft_limits_mode(self) -> str: + """Gets the software limits mode.""" + mode = ctypes.c_int16() + self._dll.CC_GetSoftLimitMode(self._serial_number, ctypes.byref(mode)) + if mode.value == ctypes.c_int16(0).value: + return 'disallow' + elif mode.value == ctypes.c_int16(1).value: + return 'partial' + elif mode.value == ctypes.c_int16(2).value: + return 'all' + else: + raise RuntimeError('unexpected value received from Kinesis') + + def _set_soft_limits_mode(self, mode: str) -> None: + """Sets the software limits mode""" + if mode == 'disallow': + lmode = ctypes.c_int16(0) + elif mode == 'partial': + lmode = ctypes.c_int16(1) + elif mode == 'all': + lmode = ctypes.c_int16(2) + + ret = self._dll.CC_SetLimitsSoftwareApproachPolicy(self._serial_number, + lmode) + self._check_error(ret) + + def _get_move_velocity(self) -> float: + acceleration = ctypes.c_int() + velocity = ctypes.c_int() + ret = self._dll.CC_GetVelParams(self._serial_number, + ctypes.byref(acceleration), + ctypes.byref(velocity)) + self._check_error(ret) + return self._device_unit_to_real(velocity.value, 1) + + def _set_move_velocity(self, velocity: float) -> None: + vel = self._real_to_device_unit(velocity, 1) + accel = self._real_to_device_unit(self._get_move_acceleration(), 2) + ret = self._dll.CC_SetVelParams(self._serial_number, + ctypes.c_int(accel), + ctypes.c_int(vel)) + self._check_error(ret) + + def _get_move_acceleration(self) -> float: + acceleration = ctypes.c_int() + velocity = ctypes.c_int() + ret = self._dll.CC_GetVelParams(self._serial_number, + ctypes.byref(acceleration), + ctypes.byref(velocity)) + self._check_error(ret) + return self._device_unit_to_real(acceleration.value, 2) + + def _set_move_acceleration(self, acceleration: float) -> None: + vel = self._real_to_device_unit(self._get_move_velocity(), 1) + accel = self._real_to_device_unit(acceleration, 2) + ret = self._dll.CC_SetVelParams(self._serial_number, + ctypes.c_int(accel), + ctypes.c_int(vel)) + self._check_error(ret) + + def _get_jog_velocity(self) -> float: + acceleration = ctypes.c_int() + velocity = ctypes.c_int() + ret = self._dll.CC_GetJogVelParams(self._serial_number, + ctypes.byref(acceleration), + ctypes.byref(velocity)) + self._check_error(ret) + return self._device_unit_to_real(velocity.value, 1) + + def _set_jog_velocity(self, velocity: float) -> None: + vel = self._real_to_device_unit(velocity, 1) + accel = self._real_to_device_unit(self._get_jog_acceleration(), 2) + ret = self._dll.CC_SetJogVelParams(self._serial_number, + ctypes.c_int(accel), + ctypes.c_int(vel)) + self._check_error(ret) + + def _get_jog_acceleration(self) -> float: + acceleration = ctypes.c_int() + velocity = ctypes.c_int() + ret = self._dll.CC_GetJogVelParams(self._serial_number, + ctypes.byref(acceleration), + ctypes.byref(velocity)) + self._check_error(ret) + return self._device_unit_to_real(acceleration.value, 2) + + def _set_jog_acceleration(self, acceleration: float) -> None: + vel = self._real_to_device_unit(self._get_jog_velocity(), 1) + accel = self._real_to_device_unit(acceleration, 2) + ret = self._dll.CC_SetJogVelParams(self._serial_number, + ctypes.c_int(accel), + ctypes.c_int(vel)) + self._check_error(ret) + + def _get_position(self) -> float: + pos = self._dll.CC_GetPosition(self._serial_number) + return self._device_unit_to_real(pos, 0) + + def _set_position(self, position: float, block: bool=True) -> None: + pos = self._real_to_device_unit(position, 0) + ret = self._dll.CC_SetMoveAbsolutePosition(self._serial_number, + ctypes.c_int(pos)) + self._check_error(ret) + ret = self._dll.CC_MoveAbsolute(self._serial_number) + self._check_error(ret) + if block: + current_pos = self._get_position() + while abs(current_pos - position) > .001 and abs(abs(current_pos - position) - 360) > .001: + sleep(.2) + + def is_moving(self) -> bool: + """check if the motor cotnroller is moving.""" + status_bit = ctypes.c_short() + self._dll.CC_GetStatusBits(self._serial_number, + ctypes.byref(status_bit)) + if status_bit.value & 0x10 or status_bit.value & 0x20: + return True + else: + return False + + def move_to(self, position: float, block=True) -> None: + """Move the device to the specified position. + The motor may need to be homed before a position can be set. + + Args: + position (float): the set position + block (bool, optional): will wait until complete. Defaults to True. + """ + pos = self._real_to_device_unit(position, 0) + ret = self._dll.CC_MoveToPosition(self._serial_number, + ctypes.c_int(pos)) + self._check_error(ret) + + if block: + self.wait_for_completion(status='moved', max_time=15) + self.position.get() + + def move_by(self, displacement: float, block: bool = True) -> None: + """Move the motor by a relative amount + + Args: + displacement (float): amount to move + block (bool, optional): will wait until complete. Defaults to True. + """ + dis = self._real_to_device_unit(displacement, 0) + ret = self._dll.CC_MoveRelative(self._serial_number, + ctypes.c_int(dis)) + self._check_error(ret) + if block: + self.wait_for_completion(status='moved') + self.position.get() + + def move_continuous(self, direction: str = 'forward') -> None: + """start moving at the current velocity in the specified direction + + Args: + direction (str, optional): the required direction of travel. + Defaults to 'forward'. Accepts 'forward' or 'reverse' + """ + if direction == 'forward' or direction == 'forwards': + direc = ctypes.c_short(0x01) + elif direction == 'reverse' or direction == 'backward' or direction == 'backwards': + direc = ctypes.c_short(0x02) + else: + raise ValueError('direction unrecognised') + + ret = self._dll.CC_MoveAtVelocity(self._serial_number, direc) + self._check_error(ret) + self.position.get() + + def jog(self, direction: str = 'forward', block: bool = True) -> None: + """Performs a jog + + Args: + direction (str): the jog direction. Defaults to 'forward'. + Accepts 'forward' or 'reverse' + block (bool, optional): will wait until complete. Defaults to True. + """ + if direction == 'forward' or direction == 'forwards': + direc = ctypes.c_short(0x01) + elif direction == 'reverse' or direction == 'backward' or direction == 'backwards': + direc = ctypes.c_short(0x02) + else: + raise ValueError('direction unrecognised') + + ret = self._dll.CC_MoveJog(self._serial_number, direc) + self._check_error(ret) + if self._get_jog_mode() =='stepped': + if block: + self.wait_for_completion(status='moved') + self.position.get() + + def stop(self, immediate: bool = False) -> None: + """Stop the current move + + Args: + immediate (bool, optional): + True: stops immediately (with risk of losing track of position). + False: stops using the current velocity profile. + Defaults to False. + """ + if immediate: + ret = self._dll.CC_StopImmediate(self._serial_number) + else: + ret = self._dll.CC_StopProfiled(self._serial_number) + self._check_error(ret) + self.wait_for_completion(status='stopped') + self.position.get() + + def close(self): + if self._simulation: + self.disable_simulation() + if hasattr(self, '_serial_number'): + self._dll.CC_Close(self._serial_number) + + def _get_hardware_info(self) -> list: + """Gets the hardware information from the device + + Returns: + list: [model number, hardware type number, number of channels, + notes describing the device, firmware version, hardware version, + hardware modification state] + """ + model = ctypes.create_string_buffer(8) + model_size = ctypes.c_ulong(8) + type_num = ctypes.c_ushort() + channel_num = ctypes.c_ushort() + notes = ctypes.create_string_buffer(48) + notes_size = ctypes.c_ulong(48) + firmware_version = ctypes.c_ulong() + hardware_version = ctypes.c_ushort() + modification_state = ctypes.c_ushort() + + ret = self._dll.CC_GetHardwareInfo( + self._serial_number, + ctypes.byref(model), model_size, + ctypes.byref(type_num), ctypes.byref(channel_num), + ctypes.byref(notes), notes_size, ctypes.byref(firmware_version), + ctypes.byref(hardware_version), ctypes.byref(modification_state) + ) + + self._check_error(ret) + return [model.value, type_num.value, channel_num.value, + notes.value, firmware_version.value, hardware_version.value, + modification_state.value] + + def _load_settings(self): + """Update device with stored settings""" + self._dll.CC_LoadSettings(self._serial_number) + return None + + def _set_limits_approach(self, limit: int): + disallow_illegal_moves = ctypes.c_int16(0) + allow_partial_moves = ctypes.c_int16(1) + allow_all_moves = ctypes.c_int16(2) + limits_approach_policy = ctypes.c_int16(limit) + + self._dll.CC_SetLimitsSoftwareApproachPolicy( + self._serial_number, ctypes.byref(disallow_illegal_moves), + ctypes.byref(allow_partial_moves), ctypes.byref(allow_all_moves), + ctypes.byref(limits_approach_policy) + ) + return None + + def _start_polling(self, polling: int): + pol = ctypes.c_int(polling) + self._dll.CC_StartPolling(self._serial_number, ctypes.byref(pol)) + return None + + def _clear_message_queue(self) -> None: + self._dll.CC_ClearMessageQueue(self._serial_number) + return None + + def _can_home(self) -> bool: + ret = self._dll.CC_CanHome(self._serial_number) + return bool(ret) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py index d5f4a51da..b3066902c 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py @@ -16,7 +16,10 @@ class _Thorlabs_Kinesis(Instrument): """A base class for Thorlabs kinesis instruments Args: - Instrument (_type_): _description_ + name (str): Instrument name. + serial_number (str): Serial number of the device. + dll_path (str): Path to the kinesis dll for the instrument to use. + simulation (bool): Enables the simulation manager. Defaults to False. """ def __init__(self, name: str, From 87235a05b45babbd7032cbcc51e28b9f7dd8cddf Mon Sep 17 00:00:00 2001 From: julienbarrier <1346858+julienbarrier@users.noreply.github.com> Date: Wed, 30 Aug 2023 11:21:57 +0200 Subject: [PATCH 22/30] debug on real setup --- qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py | 6 +++--- qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py index d613f15fb..70cd8c813 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py @@ -95,8 +95,8 @@ def __init__(self, label='Auto range power', get_cmd='SENS:POW:RANG:AUTO?', set_cmd='SENS:POW:RANG:AUTO {}', - val_mapping=create_on_off_val_mapping(on_val='ON', off_val='OFF'), - vals=vals.Enum('ON', 'OFF'), + val_mapping=create_on_off_val_mapping(on_val='1', off_val='0'), + vals=vals.Ints(0, 1), instrument=self ) @@ -153,7 +153,7 @@ def __init__(self, self.write('STAT:OPER:PTR 512') sleep(.2) self.write('STAT:OPER:NTR 0') - sleep(.2) + sleep(5) self.ask('STAT:OPER?') sleep(.2) self.averaging(300) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py index 0af470035..ae25a61ae 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py @@ -525,9 +525,12 @@ def _set_position(self, position: float, block: bool=True) -> None: ret = self._dll.CC_MoveAbsolute(self._serial_number) self._check_error(ret) if block: - current_pos = self._get_position() - while abs(current_pos - position) > .001 and abs(abs(current_pos - position) - 360) > .001: - sleep(.2) + diff = abs(self._get_position() - position) + diff -= 360 if diff > 180 else diff + while abs(diff) > .001: + sleep(.2) + diff = abs(self._get_position() - position) + diff -= 360 if diff > 180 else diff def is_moving(self) -> bool: """check if the motor cotnroller is moving.""" From 15b2e5766d34d8049661939f7611120220ab137f Mon Sep 17 00:00:00 2001 From: Julien Barrier <1346858+julienbarrier@users.noreply.github.com> Date: Wed, 30 Aug 2023 12:14:01 +0200 Subject: [PATCH 23/30] fix bug acquisition of attenuation --- qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py index 70cd8c813..a98f3d7e9 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py @@ -72,9 +72,11 @@ def __init__(self, 'attenuation', label='Attenuation', unit='dB', - get_cmd='SENS:COR:LOSS:INP:MAGN?', + get_cmd='CORR?', get_parser=float, - vals=vals.Numbers(-60,60), + set_cmd='CORR {}', + set_parser=float, + vals=vals.Numbers(-60, 60), instrument=self ) From 7351e53901c2e676ab0eac4b35dff85f4b447b6f Mon Sep 17 00:00:00 2001 From: julienbarrier <1346858+julienbarrier@users.noreply.github.com> Date: Wed, 30 Aug 2023 18:45:53 +0200 Subject: [PATCH 24/30] add driver for LS instruments + TDC001 --- .../drivers/Thorlabs/TDC001.py | 34 +++ .../drivers/Thorlabs/private/CC.py | 1 - .../drivers/Thorlabs/private/LS.py | 198 ++++++++++++++++++ 3 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 qcodes_contrib_drivers/drivers/Thorlabs/TDC001.py create mode 100644 qcodes_contrib_drivers/drivers/Thorlabs/private/LS.py diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/TDC001.py b/qcodes_contrib_drivers/drivers/Thorlabs/TDC001.py new file mode 100644 index 000000000..4953ab527 --- /dev/null +++ b/qcodes_contrib_drivers/drivers/Thorlabs/TDC001.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +"""QCoDes-Driver for Thorlab TDC001 T-Cube Brushed DC Servo Motor Controller +https://www.thorlabs.com/thorproduct.cfm?partnumber=TDC001 + +Authors: + Julien Barrier, +""" +import logging + +from .private.CC import _Thorlabs_CC + +log = logging.getLogger(__name__) + +class Thorlabs_TDC001(_Thorlabs_CC): + """Instrument driver for the Thorlabs TDC001 servo motor controller + + Args: + name (str): Instrument name. + serial_number (str): Serial number of the device. + simulation (bool): Enables the simulation manager. Defaults to False. + polling (int): Polling rate in ms. Defaults to 200. + home (bool): Sets the device to home state. Defaults to False. + """ + _CONDITIONS = ['homed', 'moved', 'stopped', 'limit_updated'] + def __init__(self, + name: str, + serial_number: str, + simulation: bool = False, + polling: int = 200, + home: bool = False, + **kwargs): + self._dll_path = 'Thorlabs.MotionControl.TCube.DCServo.dll' + super().__init__(name, serial_number, self._dll_path, + simulation, polling, home, **kwargs) \ No newline at end of file diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py index 0af470035..023c7ea10 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- """QCoDes- Base driver for Thorlab instruments using the CC commands -https://www.thorlabs.com/thorproduct.cfm?partnumber=KDC101 Authors: Julien Barrier, diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/LS.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/LS.py new file mode 100644 index 000000000..09b79c892 --- /dev/null +++ b/qcodes_contrib_drivers/drivers/Thorlabs/private/LS.py @@ -0,0 +1,198 @@ +# -*- coding: utf-8 -*- +"""QCoDes- Base driver for Thorlab instruments using the LS commands + +Authors: + Julien Barrier, +""" +import ctypes +import logging +from time import sleep, time + +from qcodes.parameters import Parameter +from qcodes import validators as vals + +from .kinesis import _Thorlabs_Kinesis + +log = logging.getLogger(__name__) + +class _Thorlabs_LS(_Thorlabs_Kinesis): + """Instrument driver for Thorlabs instruments using the CC commands + + Args: + name (str): Instrument name. + serial_number (str): Serial number of the device. + dll_path (str): Path to the kinesis dll for the instrument to use. + simulation (bool): Enables the simulation manager. Defaults to False. + polling (int): Polling rate in ms. Defaults to 200. + home (bool): Sets the device to home state. Defaults to False. + """ + _CONDITIONS = ['homed', 'moved', 'stopped', 'limit_updated'] + def __init__(self, + name: str, + serial_number: str, + dll_path: str, + simulation: bool = False, + polling: int = 200, + home: bool = False, + **kwargs): + self._dll_path = dll_path + super().__init__(name, serial_number, + self._dll_path, simulation, + **kwargs) + + if self._dll.TLI_BuildDeviceList() == 0: + self._open_laser() + self._start_polling(polling) + + self._info = self._get_hardware_info() + self.model = self._info[0].decode('utf-8') + self.version = self._info[4] + + self._load_settings() + self._set_limits_approach(1) + + sleep(3) + + self._clear_message_queue() + + if home: + if not self._can_home(): + self.homed = False + raise RuntimeError('Device `{}` is not homeable') + else: + self.go_home() + else: + self.homed = False + + + self.output_enabled = Parameter( + 'output_enabled', + label='Output enabled', + get_cmd=self._get_output_enabled, + set_cmd=self._set_output_enabled, + vals=vals.Bool(), + docstring='turn laser output off/on. Note that laser key switch must be turned on to turn output on.', + instrument=self + ) + + self.power = Parameter( + 'power', + label='Power output', + unit='W', + get_cmd=self._get_power, + set_cmd=self._set_power, + vals=vals.Numbers(0, .007), + instrument=self + ) + + self.connect_message() + + def identify(self) -> None: + """Sends a command to the device to make it identify itself""" + self._dll.LS_Identify(self._serial_number) + + def get_idn(self) -> dict: + """Get device identifier""" + idparts = ['Thorlabs', self.model, self.version, self.serial_number] + return dict(zip(('vendor', 'model', 'serial', 'firmware'), idparts)) + + def _get_status_bits(self) -> int: + status = self._dll.LS_GetStatusBits(self._serial_number) + if status == 0x40000000: + self._check_error(status) + else: + return int(status) + + def _get_output_enabled(self) -> bool: + return bool(self._get_status_bits & 1) + + def _set_output_enabled(self, value: bool) -> None: + if value: + self.enable_output() + else: + self.disable_output() + + def enable_output(self) -> None: + ret = self._dll.LS_EnableOutput(self._serial_number) + self._check_error(ret) + + def disable_output(self) -> None: + ret = self._dll.LS_DisableOutput(self._serial_number) + self._check_error(ret) + + def _get_power(self) -> float: + max_num = 32767 + max_power = .007 + num = self._dll.LS_GetPowerReading(self._serial_number) + return num/max_num * max_power + + def _set_power(self, power: float) -> None: + max_num = 32767 + max_power = .007 + percent = power / max_power + ret = self._dll.LS_SetPower(self._serial_number, int(percent*max_num)) + self._check_error(ret) + + def close(self): + if self._simulation: + self.disable_simulation() + if hasattr(self, '_serial_number'): + self._stop_polling() + self._dll.LS_Close(self._serial_number) + + def _get_hardware_info(self) -> list: + """Gets the hardware information from the device + + Returns: + list: [model number, hardware type number, number of channels, + notes describing the device, firmware version, hardware version, + hardware modification state] + """ + model = ctypes.create_string_buffer(8) + model_size = ctypes.c_ulong(8) + type_num = ctypes.c_ushort() + channel_num = ctypes.c_ushort() + notes = ctypes.create_string_buffer(48) + notes_size = ctypes.c_ulong(48) + firmware_version = ctypes.c_ulong() + hardware_version = ctypes.c_ushort() + modification_state = ctypes.c_ushort() + + ret = self._dll.LS_GetHardwareInfo( + self._serial_number, + ctypes.byref(model), model_size, + ctypes.byref(type_num), ctypes.byref(channel_num), + ctypes.byref(notes), notes_size, ctypes.byref(firmware_version), + ctypes.byref(hardware_version), ctypes.byref(modification_state) + ) + + self._check_error(ret) + return [model.value, type_num.value, channel_num.value, + notes.value, firmware_version.value, hardware_version.value, + modification_state.value] + + def _load_settings(self): + """Update device with stored settings""" + self._dll.LS_LoadSettings(self._serial_number) + return None + + def _open_laser(self) -> None: + ret = self._dll.LS_Open(self._serial_number) + self._check_error(ret) + + def _close_laser(self) -> None: + ret = self._dll.LS_Close(self._serial_number) + self._check_error(ret) + + def _start_polling(self, polling: int): + pol = ctypes.c_int(polling) + ret = self._dll.LS_StartPolling(self._serial_number, ctypes.byref(pol)) + self._check_error(ret) + return None + + def _stop_polling(self): + self._dll.LS_StopPolling(self._serial_number) + + def _clear_message_queue(self) -> None: + self._dll.LS_ClearMessageQueue(self._serial_number) + return None From 233a6c393e49303340b8454221f9de0a4dcb2b3b Mon Sep 17 00:00:00 2001 From: Julien Barrier <1346858+julienbarrier@users.noreply.github.com> Date: Wed, 30 Aug 2023 21:02:20 +0200 Subject: [PATCH 25/30] refactoring of kinesis --- .../drivers/Thorlabs/KDC101.py | 1 - .../drivers/Thorlabs/KLS1550.py | 113 +++++---------- .../drivers/Thorlabs/Kinesis.py | 132 ------------------ .../drivers/Thorlabs/private/CC.py | 22 ++- .../drivers/Thorlabs/private/LS.py | 3 +- 5 files changed, 54 insertions(+), 217 deletions(-) delete mode 100644 qcodes_contrib_drivers/drivers/Thorlabs/Kinesis.py diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py index 8f2e24bdc..3d6934a07 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py @@ -21,7 +21,6 @@ class Thorlabs_KDC101(_Thorlabs_CC): polling (int): Polling rate in ms. Defaults to 200. home (bool): Sets the device to home state. Defaults to False. """ - _CONDITIONS = ['homed', 'moved', 'stopped', 'limit_updated'] def __init__(self, name: str, serial_number: str, diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py b/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py index 532eff9f1..05cf613af 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py @@ -1,81 +1,32 @@ -from qcodes import Instrument -import qcodes.utils.validators as vals - -from .Kinesis import Thorlabs_Kinesis - - -class Thorlabs_KLS1550(Instrument): - def __init__( - self, - name: str, - serial_number: str, - polling_speed_ms: int, - kinesis: Thorlabs_Kinesis, - **kwargs, - ): - super().__init__(name, **kwargs) - # Save Kinesis server reference - self.kinesis = kinesis - - # Initialization - self.serial_number = serial_number - self.polling_speed_ms = polling_speed_ms - self.kinesis.open_laser(self.serial_number) - self.kinesis.start_polling(self.serial_number, self.polling_speed_ms) - self.info = self.kinesis.laser_info(self.serial_number) - self.model = self.info[0].value.decode("utf-8") - self.version = self.info[4].value - - # Parameters - self.add_parameter( - "output_enabled", - get_cmd=self._get_output_enabled, - set_cmd=self._set_output_enabled, - vals=vals.Bool(), - unit="", - label="Laser output on/off", - docstring="Turn laser output on/off. Note that laser key switch must be on to turn laser output on.", - ) - - self.add_parameter( - "power", - get_cmd=self._get_power, - set_cmd=self._set_power, - vals=vals.Numbers(0, 0.007), # [ATTENTION] max power for simulator is 10mW - unit="W", - label="Power output", - ) - - self.connect_message() - - def get_idn(self): - return { - "vendor": "Thorlabs", - "model": self.model, - "firmware": self.version, - "serial": self.serial_number, - } - - def _get_output_enabled(self): - # First status bit represents 'output enabled' - return bool(self.kinesis.laser_status_bits(self.serial_number) & 1) - - def _set_output_enabled(self, value: bool): - if value: - self.kinesis.laser_enable_output(self.serial_number) - else: - self.kinesis.laser_disable_output(self.serial_number) - - def _get_power(self): - return self.kinesis.get_laser_power(self.serial_number) - - def _set_power(self, power_W: float): - self.kinesis.set_laser_power(self.serial_number, power_W) - - def disconnect(self): - self.kinesis.stop_polling(self.serial_number) - self.kinesis.close_laser(self.serial_number) - - def close(self): - self.disconnect() - super().close() +# -*- coding: utf-8 -*- +"""QCoDeS-Driver for Thorlab KLS1550 Laser source + +Authors: + iago-rst https://github.com/iago-rst, 2023 + Julien Barrier , 2023 +""" +import logging +from .private.LS import _Thorlabs_LS + +log = logging.getLogger(__name__) + +class Thorlabs_KLS1550(_Thorlabs_LS): + """Instrument driver for the Thorlabs KLS1550 + + Args: + name (str): Instrument name. + serial_number (str): Serial number of the device. + simulation (bool): Enables the simulation manager. Defaults to False + polling (int): Polling rate in ms. Defaults to 200. + """ + def __init__(self, + name: str, + serial_number: str, + simulation: bool = False, + polling: int = 200, + **kwargs): + self._dll_path = 'Thorlabs. .dll' + self._polling = kwargs.pop('polling_speed_ms', polling) # back-compatibility + self._simulation = kwargs.pop('sim', simulation) + super().__init__(name, serial_number, self._dll_path, + self._simulation, self._polling, **kwargs) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/Kinesis.py b/qcodes_contrib_drivers/drivers/Thorlabs/Kinesis.py deleted file mode 100644 index d74e8c9c2..000000000 --- a/qcodes_contrib_drivers/drivers/Thorlabs/Kinesis.py +++ /dev/null @@ -1,132 +0,0 @@ -import os -import sys -import ctypes -from ctypes import wintypes - - -class Thorlabs_KinesisException(Exception): - pass - - -class Thorlabs_Kinesis(): - - def __init__(self, dll_path: str, sim: bool) -> None: - if sys.platform == "win32": - os.add_dll_directory(r"C:\Program Files\Thorlabs\Kinesis") - self.dll_path = dll_path - self.dll = ctypes.cdll.LoadLibrary(self.dll_path) - - if sim: - self.enable_simulation() - - # See Thorlabs Kinesis C API documentation for error description - def error_check(self, r: int, source:str) -> int: - if r != 0: - raise Thorlabs_KinesisException("Kinesis: [{}]: Error code {}".format(source, r)) - - return r - - # Connects to simulator (which must be already running but - # without GUI open, only one port) - def enable_simulation(self) -> None: - r = self.dll.TLI_InitializeSimulations() - # Throws error message even if simulation was successfull, - # check the log in the Kinesis simulator for confirmation instead - # self.error_check(r, 'Enable simulation') - - def disable_simulation(self) -> None: - r = self.dll.TLI_UninitializeSimulations() - self.error_check(r, 'Disable simulation') - - # Returns list with all the available (and closed?) devices - def device_list(self) -> list: - r = self.dll.TLI_BuildDeviceList() - if r == 0 or r == 16: - device_list = ctypes.create_string_buffer(250) - r2 = self.dll.TLI_GetDeviceListExt(ctypes.byref(device_list), 250) - self.error_check(r2, 'Get device list') - else: - self.error_check(r, 'Build device list') - - return device_list.value.decode("utf-8").rstrip(',').split(",") - - # Helper convertion function - def to_char_p(self, s: str) -> ctypes.c_char_p: - return ctypes.c_char_p(s.encode('ascii')) - - # Functions for starting and stopping connection to the laser source - def open_laser(self, serial_number: str) -> None: - r = self.dll.LS_Open(self.to_char_p(serial_number)) - self.error_check(r, 'Opening device') - - def start_polling(self, serial_number: str, polling_speed: int) -> None: - # Note: this function returns a boolean (for some reason) - r = self.dll.LS_StartPolling(self.to_char_p(serial_number), ctypes.c_int(polling_speed)) - if r != 1: - # 21 = FT_SpecificFunctionFail - The function failed to complete succesfully. - # If unsuccessful, then r seems to be -1749177856 but this is not explained in the documentation - self.error_check(21, 'Start polling') - - def close_laser(self, serial_number: str) -> None: - self.dll.LS_Close(self.to_char_p(serial_number)) - - def stop_polling(self, serial_number: str) -> None: - self.dll.LS_StopPolling(self.to_char_p(serial_number)) - - # Gets device information from a serial number - def laser_info(self, serial_number: str) -> list: - # Defining variables for information storing - model = ctypes.create_string_buffer(8) - model_size = ctypes.wintypes.DWORD(8) - type_num = ctypes.wintypes.WORD() - channel_num = ctypes.wintypes.WORD() - notes = ctypes.create_string_buffer(48) - notes_size = ctypes.c_ulong(48) - firmware_version = ctypes.wintypes.DWORD() - hardwware_version = ctypes.wintypes.WORD() - modification_state = ctypes.wintypes.WORD() - - r = self.dll.LS_GetHardwareInfo(self.to_char_p(serial_number), ctypes.byref(model), model_size, - ctypes.byref(type_num), ctypes.byref(channel_num), - ctypes.byref(notes), notes_size, ctypes.byref(firmware_version), - ctypes.byref(hardwware_version), ctypes.byref(modification_state)) - self.error_check(r, 'Get hardware info') - - return [model, type_num, channel_num, notes, firmware_version, hardwware_version, modification_state] - - # Returns a string with the status in binary (see doc. for bit meaning) - def laser_status_bits(self, serial_number: str) -> int: - # Note: status bits updated at polling interval, hence no LS_RequestStatusBits - integer = self.dll.LS_GetStatusBits(self.to_char_p(serial_number)) - if integer == 0x40000000: - return self.error_check(integer, 'Get status bits') - else: - return integer - - # Turnng the laser on/off (on only if safety key is on) - def laser_enable_output(self, serial_number: str) -> None: - r = self.dll.LS_EnableOutput(self.to_char_p(serial_number)) - self.error_check(r, 'Enable laser output') - - def laser_disable_output(self, serial_number: str) -> None: - r = self.dll.LS_DisableOutput(self.to_char_p(serial_number)) - self.error_check(r, 'Disable laser output') - - # Reading laser power - def get_laser_power(self, serial_number: str) -> float: - max_num = 32767 - max_power_W = 0.007 - num = self.dll.LS_GetPowerReading(self.to_char_p(serial_number)) - - return num/max_num * max_power_W - - # [ATTENTION] Setting laser power - def set_laser_power(self, serial_number: str, power_W: float) -> None: - # Maximum integer level for laser power - max_num = 32767 - max_power_W = 0.007 - # [ATTENTION] Maximum power is 7mW for actual device but 10mW for the simulator (somehow) - percentage = power_W/max_power_W - r = self.dll.LS_SetPower(self.to_char_p(serial_number), int(percentage*max_num)) - self.error_check(r, 'Set laser power') - diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py index 7d4a017f7..918cf9dc2 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py @@ -188,6 +188,7 @@ def __init__(self, def identify(self) -> None: """Sends a command to the device to make it identify itself""" + self.log.debug('identify the device') self._dll.CC_Identify(self._serial_number) def get_idn(self) -> dict: @@ -201,6 +202,7 @@ def go_home(self, block=True): Args: block (bool, optional): will wait for completion. Defaults to True. """ + self.log.info('home the device.') self._check_error(self._dll.CC_Home(self._serial_number)) self.homed = True if block: @@ -213,6 +215,7 @@ def wait_for_completion(self, status: str = 'homed', max_time = 5) -> None: status (str, optional): expected status. Defaults to 'homed'. max_time (int, optional): maximum waiting time for the internal loop. Defaults to 5. """ + self.log.debug('wait for the current function to be completed') message_type = ctypes.c_ushort() message_id = ctypes.c_ushort() message_data = ctypes.c_ulong() @@ -533,6 +536,7 @@ def _set_position(self, position: float, block: bool=True) -> None: def is_moving(self) -> bool: """check if the motor cotnroller is moving.""" + self.log.info('check if the motor is moving') status_bit = ctypes.c_short() self._dll.CC_GetStatusBits(self._serial_number, ctypes.byref(status_bit)) @@ -549,6 +553,7 @@ def move_to(self, position: float, block=True) -> None: position (float): the set position block (bool, optional): will wait until complete. Defaults to True. """ + self.log.info(f'move to {position}') pos = self._real_to_device_unit(position, 0) ret = self._dll.CC_MoveToPosition(self._serial_number, ctypes.c_int(pos)) @@ -565,6 +570,7 @@ def move_by(self, displacement: float, block: bool = True) -> None: displacement (float): amount to move block (bool, optional): will wait until complete. Defaults to True. """ + self.log.info(f'move by {displacement}') dis = self._real_to_device_unit(displacement, 0) ret = self._dll.CC_MoveRelative(self._serial_number, ctypes.c_int(dis)) @@ -580,6 +586,7 @@ def move_continuous(self, direction: str = 'forward') -> None: direction (str, optional): the required direction of travel. Defaults to 'forward'. Accepts 'forward' or 'reverse' """ + self.log.info(f'move continuously. direction: {direction}') if direction == 'forward' or direction == 'forwards': direc = ctypes.c_short(0x01) elif direction == 'reverse' or direction == 'backward' or direction == 'backwards': @@ -599,6 +606,7 @@ def jog(self, direction: str = 'forward', block: bool = True) -> None: Accepts 'forward' or 'reverse' block (bool, optional): will wait until complete. Defaults to True. """ + self.log.info(f'perform a jog; direction: {direction}') if direction == 'forward' or direction == 'forwards': direc = ctypes.c_short(0x01) elif direction == 'reverse' or direction == 'backward' or direction == 'backwards': @@ -622,6 +630,7 @@ def stop(self, immediate: bool = False) -> None: False: stops using the current velocity profile. Defaults to False. """ + self.log.info('stop the current move') if immediate: ret = self._dll.CC_StopImmediate(self._serial_number) else: @@ -631,9 +640,11 @@ def stop(self, immediate: bool = False) -> None: self.position.get() def close(self): + self.log.info('close the device') if self._simulation: self.disable_simulation() if hasattr(self, '_serial_number'): + self._stop_polling() self._dll.CC_Close(self._serial_number) def _get_hardware_info(self) -> list: @@ -669,10 +680,11 @@ def _get_hardware_info(self) -> list: def _load_settings(self): """Update device with stored settings""" + self.log.info('update the device with the stored settings') self._dll.CC_LoadSettings(self._serial_number) return None - def _set_limits_approach(self, limit: int): + def _set_limits_approach(self, limit: int) -> None: disallow_illegal_moves = ctypes.c_int16(0) allow_partial_moves = ctypes.c_int16(1) allow_all_moves = ctypes.c_int16(2) @@ -685,12 +697,18 @@ def _set_limits_approach(self, limit: int): ) return None - def _start_polling(self, polling: int): + def _start_polling(self, polling: int) -> None: + self.log.info('start polling') pol = ctypes.c_int(polling) self._dll.CC_StartPolling(self._serial_number, ctypes.byref(pol)) return None + def _stop_polling(self) -> None: + self.log.info('stop polling') + self._dll.CC_StopPolling(self._serial_number) + def _clear_message_queue(self) -> None: + self.log.info('clear messages queue') self._dll.CC_ClearMessageQueue(self._serial_number) return None diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/LS.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/LS.py index 09b79c892..27168624b 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/private/LS.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/private/LS.py @@ -2,7 +2,8 @@ """QCoDes- Base driver for Thorlab instruments using the LS commands Authors: - Julien Barrier, + iago-rst https://github.com/iago-rst, 2023 + Julien Barrier , 2023 """ import ctypes import logging From fd51181a3b1d340d120ceeb6ad63ecb87a2c152d Mon Sep 17 00:00:00 2001 From: Julien Barrier <1346858+julienbarrier@users.noreply.github.com> Date: Wed, 30 Aug 2023 21:09:38 +0200 Subject: [PATCH 26/30] clean drivers + pass mypy --- .../drivers/Thorlabs/private/LS.py | 4 +- .../drivers/Thorlabs/private/rotators.py | 270 ------------------ 2 files changed, 2 insertions(+), 272 deletions(-) delete mode 100644 qcodes_contrib_drivers/drivers/Thorlabs/private/rotators.py diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/LS.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/LS.py index 27168624b..5c432233e 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/private/LS.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/private/LS.py @@ -100,12 +100,12 @@ def get_idn(self) -> dict: def _get_status_bits(self) -> int: status = self._dll.LS_GetStatusBits(self._serial_number) if status == 0x40000000: - self._check_error(status) + raise ValueError() else: return int(status) def _get_output_enabled(self) -> bool: - return bool(self._get_status_bits & 1) + return bool(self._get_status_bits() & 1) def _set_output_enabled(self, value: bool) -> None: if value: diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/rotators.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/rotators.py deleted file mode 100644 index 4398adf78..000000000 --- a/qcodes_contrib_drivers/drivers/Thorlabs/private/rotators.py +++ /dev/null @@ -1,270 +0,0 @@ -import enum -from typing import Tuple, Optional - -import qcodes.utils.validators as vals -from qcodes import Instrument -from .APT import Thorlabs_APT - - -class RotationDirection(enum.Enum): - """Constants for the rotation direction of Thorlabs K10CR1 rotator""" - FORWARD = "fwd" - REVERSE = "rev" - -class HomeLimitSwitch(enum.Enum): - """Constants for the home limit switch of Thorlabs K10CR1 rotator""" - REVERSE = "rev" - FORWARD = "fwd" - -class _Thorlabs_rotator(Instrument): - """ - Instrument driver for the Thorlabs K10CR1 rotator. - - Args: - name: Instrument name. - device_id: ID for the desired rotator. - apt: Thorlabs APT server. - - Attributes: - apt: Thorlabs APT server. - serial_number: Serial number of the device. - model: Model description. - version: Firmware version. - """ - - def __init__(self, name: str, - device_id: int = 0, - serial_number: int = 0, - hw_type: enum.EnumType = None, - apt: Thorlabs_APT = None, - **kwargs): - super().__init__(name, **kwargs) - - # Save APT server reference - self.apt = apt - - # initialization - if serial_number == 0: - self.serial_number = self.apt.get_hw_serial_num_ex(hw_type, device_id) - self.apt.init_hw_device(self.serial_number) - self.model, self.version, _ = self.apt.get_hw_info(self.serial_number) - - # Set velocity and move-home parameters to previous values. Otherwise the velocity is very - # very low and it is not the velocity stored in the parameters... For whatever reason? - self._set_velocity_parameters() - self._set_home_parameters() - - # Helpers - direction_val_mapping = {RotationDirection.FORWARD: 1, - RotationDirection.FORWARD.value: 1, - RotationDirection.REVERSE: 2, - RotationDirection.REVERSE.value: 2} - lim_switch_val_mapping = {HomeLimitSwitch.REVERSE: 1, - HomeLimitSwitch.REVERSE.value: 1, - HomeLimitSwitch.FORWARD: 4, - HomeLimitSwitch.FORWARD.value: 4} - - # PARAMETERS - # Position - self.add_parameter("position", - get_cmd=self._get_position, - set_cmd=self._set_position, - vals=vals.Numbers(0, 360), - unit=u"\u00b0", - label="Position") - self.add_parameter("position_async", - get_cmd=None, - set_cmd=self._set_position_async, - vals=vals.Numbers(0, 360), - unit=u"\u00b0", - label="Position") - - # Velocity Parameters - self.add_parameter("velocity_min", - set_cmd=self._set_velocity_min, - get_cmd=self._get_velocity_min, - vals=vals.Numbers(0, 25), - unit=u"\u00b0/s", - label="Minimum Velocity") - self.add_parameter("velocity_acceleration", - set_cmd=self._set_velocity_acceleration, - get_cmd=self._get_velocity_acceleration, - vals=vals.Numbers(0, 25), - unit=u"\u00b0/s\u00b2", - label="Acceleration") - self.add_parameter("velocity_max", - set_cmd=self._set_velocity_max, - get_cmd=self._get_velocity_max, - vals=vals.Numbers(0, 25), - unit=u"\u00b0/s", - label="Maximum Velocity") - - # Move home parameters - self.add_parameter("move_home_direction", - set_cmd=self._set_home_direction, - get_cmd=self._get_home_direction, - val_mapping=direction_val_mapping, - label="Direction for Moving Home") - self.add_parameter("move_home_limit_switch", - set_cmd=self._set_home_lim_switch, - get_cmd=self._get_home_lim_switch, - val_mapping=lim_switch_val_mapping, - label="Limit Switch for Moving Home") - self.add_parameter("move_home_velocity", - set_cmd=self._set_home_velocity, - get_cmd=self._get_home_velocity, - vals=vals.Numbers(0, 25), - unit=u"\u00b0/s", - label="Velocity for Moving Home") - self.add_parameter("move_home_zero_offset", - set_cmd=self._set_home_zero_offset, - get_cmd=self._get_home_zero_offset, - vals=vals.Numbers(0, 360), - unit=u"\u00b0", - label="Zero Offset for Moving Home") - - # FUNCTIONS - # Stop motor - self.add_function("stop", - call_cmd=self._stop, - args=[]) - - # Moving direction - self.add_function("move_direction", - call_cmd=self._move_direction, - args=[vals.Enum(*direction_val_mapping)], - arg_parser=lambda val: direction_val_mapping[val]) - - # Enable/disable - self.add_function("enable", - call_cmd=self._enable, - args=[]) - self.add_function("disable", - call_cmd=self._disable, - args=[]) - - # Move home - self.add_function("move_home", - call_cmd=self._move_home, - args=[]) - self.add_function("move_home_async", - call_cmd=self._move_home_async, - args=[]) - - # print connect message - self.connect_message() - - def get_idn(self): - """Returns hardware information of the device.""" - return {"vendor": "Thorlabs", "model": self.model, - "firmware": self.version, "serial": self.serial_number} - - def _get_position(self) -> float: - return self.apt.mot_get_position(self.serial_number) - - def _set_position(self, position: float): - self.apt.mot_move_absolute_ex(self.serial_number, position, True) - - def _set_position_async(self, position: float): - self.apt.mot_move_absolute_ex(self.serial_number, position, False) - - def _get_velocity_parameters(self) -> Tuple[float, float, float]: - return self.apt.mot_get_velocity_parameters(self.serial_number) - - def _set_velocity_parameters(self, - min_vel: Optional[float] = None, accn: Optional[float] = None, max_vel: Optional[float] = None): - if min_vel is None or accn is None or max_vel is None: - old_min_vel, old_accn, old_max_vel = self._get_velocity_parameters() - if min_vel is None: - min_vel = old_min_vel - if accn is None: - accn = old_accn - if max_vel is None: - max_vel = old_max_vel - return self.apt.mot_set_velocity_parameters(self.serial_number, min_vel, accn, max_vel) - - def _get_velocity_min(self) -> float: - min_vel, _, _ = self._get_velocity_parameters() - return min_vel - - def _set_velocity_min(self, min_vel: float): - self._set_velocity_parameters(min_vel=min_vel) - - def _get_velocity_acceleration(self) -> float: - _, accn, _ = self._get_velocity_parameters() - return accn - - def _set_velocity_acceleration(self, accn: float): - self._set_velocity_parameters(accn=accn) - - def _get_velocity_max(self) -> float: - _, _, max_vel = self._get_velocity_parameters() - return max_vel - - def _set_velocity_max(self, max_vel: float): - self._set_velocity_parameters(max_vel=max_vel) - - def _get_home_parameters(self) -> Tuple[int, int, float, float]: - return self.apt.mot_get_home_parameters(self.serial_number) - - def _set_home_parameters(self, direction: Optional[int] = None, lim_switch: Optional[int] = None, - velocity: Optional[float] = None, zero_offset: Optional[float] = None): - if direction is None or lim_switch is None or velocity is None or zero_offset is None: - old_direction, old_lim_switch, old_velocity, old_zero_offset = self._get_home_parameters() - if direction is None: - direction = old_direction - if lim_switch is None: - lim_switch = old_lim_switch - if velocity is None: - velocity = old_velocity - if zero_offset is None: - zero_offset = old_zero_offset - - return self.apt.mot_set_home_parameters(self.serial_number, - direction, lim_switch, velocity, zero_offset) - - def _get_home_direction(self) -> int: - direction, _, _, _ = self._get_home_parameters() - return direction - - def _set_home_direction(self, direction: int): - self._set_home_parameters(direction=direction) - - def _get_home_lim_switch(self) -> int: - _, lim_switch, _, _ = self._get_home_parameters() - return lim_switch - - def _set_home_lim_switch(self, lim_switch: int): - self._set_home_parameters(lim_switch=lim_switch) - - def _get_home_velocity(self) -> float: - _, _, velocity, _ = self._get_home_parameters() - return velocity - - def _set_home_velocity(self, velocity: float): - self._set_home_parameters(velocity=velocity) - - def _get_home_zero_offset(self) -> float: - _, _, _, zero_offset = self._get_home_parameters() - return zero_offset - - def _set_home_zero_offset(self, zero_offset: float): - self._set_home_parameters(zero_offset=zero_offset) - - def _stop(self): - self.apt.mot_stop_profiled(self.serial_number) - - def _move_direction(self, direction: int): - self.apt.mot_move_velocity(self.serial_number, direction) - - def _enable(self): - self.apt.enable_hw_channel(self.serial_number) - - def _disable(self): - self.apt.disable_hw_channel(self.serial_number) - - def _move_home(self): - self.apt.mot_move_home(self.serial_number, True) - - def _move_home_async(self): - self.apt.mot_move_home(self.serial_number, False) \ No newline at end of file From e3e4fd129f2f456e565e60cdb024c3e097f9f68a Mon Sep 17 00:00:00 2001 From: Julien Barrier <1346858+julienbarrier@users.noreply.github.com> Date: Wed, 30 Aug 2023 21:26:52 +0200 Subject: [PATCH 27/30] debug --- qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py | 2 -- qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py b/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py index 05cf613af..0a52db517 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py @@ -26,7 +26,5 @@ def __init__(self, polling: int = 200, **kwargs): self._dll_path = 'Thorlabs. .dll' - self._polling = kwargs.pop('polling_speed_ms', polling) # back-compatibility - self._simulation = kwargs.pop('sim', simulation) super().__init__(name, serial_number, self._dll_path, self._simulation, self._polling, **kwargs) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py index a98f3d7e9..ee5a48320 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py @@ -31,7 +31,7 @@ class Thorlab_PM100D(VisaInstrument): """ def __init__(self, name: str, - address: Optional[str] = None, + address: str, terminator='\n', timeout: float = 20, **kwargs: Any): From f9faf7aa63c7ebf06f3ce5cc6e110249cb199db2 Mon Sep 17 00:00:00 2001 From: Julien Barrier <1346858+julienbarrier@users.noreply.github.com> Date: Wed, 30 Aug 2023 21:32:46 +0200 Subject: [PATCH 28/30] remove function that ensured back-compatibility to pass mypy --- qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py b/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py index 0a52db517..e5b0e3d23 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py @@ -27,4 +27,4 @@ def __init__(self, **kwargs): self._dll_path = 'Thorlabs. .dll' super().__init__(name, serial_number, self._dll_path, - self._simulation, self._polling, **kwargs) + simulation, polling, **kwargs) From 181336416af428bd7513c24df2c94d3cff1271b4 Mon Sep 17 00:00:00 2001 From: Julien Barrier <1346858+julienbarrier@users.noreply.github.com> Date: Thu, 31 Aug 2023 11:27:18 +0200 Subject: [PATCH 29/30] =?UTF-8?q?resolve=20@astafan8=20=E2=80=99s=20sugges?= =?UTF-8?q?tions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../drivers/Thorlabs/KDC101.py | 25 +++++++---- .../drivers/Thorlabs/KLS1550.py | 21 ++++++--- .../drivers/Thorlabs/PM100D.py | 39 ++++++++-------- .../drivers/Thorlabs/TDC001.py | 25 +++++++---- .../drivers/Thorlabs/private/CC.py | 45 ++++++++++--------- .../drivers/Thorlabs/private/LS.py | 39 ++++++++-------- .../drivers/Thorlabs/private/kinesis.py | 15 ++++--- 7 files changed, 124 insertions(+), 85 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py index 3d6934a07..e78ddf9e7 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py @@ -6,6 +6,7 @@ Julien Barrier, """ import logging +from typing import Optional from .private.CC import _Thorlabs_CC @@ -15,19 +16,27 @@ class Thorlabs_KDC101(_Thorlabs_CC): """Instrument driver for the Thorlabs KDC101 servo motor controller Args: - name (str): Instrument name. - serial_number (str): Serial number of the device. - simulation (bool): Enables the simulation manager. Defaults to False. - polling (int): Polling rate in ms. Defaults to 200. - home (bool): Sets the device to home state. Defaults to False. + name: Instrument name. + serial_number: Serial number of the device. + dll_path: Path to the kinesis dll for the instrument to use. + dll_dir: Directory in which the kinesis dll are stored. + simulation: Enables the simulation manager. + polling: Polling rate in ms. + home: Sets the device to home state. """ def __init__(self, name: str, serial_number: str, + dll_path: Optional[str] = None, + dll_dir: Optional[str] = None, simulation: bool = False, polling: int = 200, home: bool = False, **kwargs): - self._dll_path = 'Thorlabs.MotionControl.KCube.DCServo.dll' - super().__init__(name, serial_number, self._dll_path, - simulation, polling, home, **kwargs) \ No newline at end of file + if dll_path: + self._dll_path = dll_path + else: + self._dll_path = 'Thorlabs.MotionControl.KCube.DCServo.dll' + self._dll_dir: str | None = dll_dir if dll_dir else None + super().__init__(name, serial_number, self._dll_path, self._dll_dir, + simulation, polling, home, **kwargs) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py b/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py index e5b0e3d23..8fc71f88f 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py @@ -6,6 +6,7 @@ Julien Barrier , 2023 """ import logging +from typing import Optional from .private.LS import _Thorlabs_LS log = logging.getLogger(__name__) @@ -14,17 +15,25 @@ class Thorlabs_KLS1550(_Thorlabs_LS): """Instrument driver for the Thorlabs KLS1550 Args: - name (str): Instrument name. - serial_number (str): Serial number of the device. - simulation (bool): Enables the simulation manager. Defaults to False - polling (int): Polling rate in ms. Defaults to 200. + name: Instrument name. + serial_number: Serial number of the device. + dll_path: Path to the kinesis dll for the instrument to use. + dll_dir: Directory in which the kinesis dll are stored. + simulation: Enables the simulation manager. + polling: Polling rate in ms. """ def __init__(self, name: str, serial_number: str, + dll_path: Optional[str] = None, + dll_dir: Optional[str] = None, simulation: bool = False, polling: int = 200, **kwargs): - self._dll_path = 'Thorlabs. .dll' - super().__init__(name, serial_number, self._dll_path, + if dll_path: + self._dll_path = dll_path + else: + self._dll_path = 'Thorlabs.MotionControl.KCube.LaserSource.dll' + self._dll_dir: str | None = dll_dir if dll_dir else None + super().__init__(name, serial_number, self._dll_path, self._dll_dir, simulation, polling, **kwargs) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py index ee5a48320..f8f2cdb1a 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/PM100D.py @@ -8,7 +8,7 @@ import logging from time import sleep -from typing import Optional, Any +from typing import Any from qcodes.instrument import VisaInstrument from qcodes.parameters import Parameter, create_on_off_val_mapping @@ -24,19 +24,19 @@ class Thorlab_PM100D(VisaInstrument): status: beta-version Args: - name (str): name for the instrument - address (str): Visa Resource name to connect - terminator (str): Visa terminator - timeout (float, optional). Visa timeout. default to 20s + name: name for the instrument + address: Visa Resource name to connect + terminator: Visa terminator + timeout. Visa timeout. """ def __init__(self, name: str, address: str, - terminator='\n', + terminator: str = '\n', timeout: float = 20, **kwargs: Any): - super().__init__(name, address, terminator=terminator, **kwargs) - self._timeout = timeout + super().__init__(name, address, terminator=terminator, + timeout=timeout, **kwargs) self.averaging = Parameter( 'averaging', @@ -53,8 +53,7 @@ def __init__(self, unit='m', get_cmd='SENS:CORR:WAV?', set_cmd='SENS:CORR:WAV {}', - get_parser=lambda val: float(val)/1e9, - set_parser=lambda val: float(val)*1e9, + scale=1e9, vals=vals.Numbers(185e-9, 25e-6), instrument=self ) @@ -146,18 +145,12 @@ def __init__(self, unit='m', get_cmd='CORR:BEAM?', set_cmd='CORR:BEAM {}', - get_parser=lambda val: float(val)/1e3, - set_parser=lambda val: float(val)*1e3, + scale=1e3, vals=vals.Numbers(), instrument=self ) - self.write('STAT:OPER:PTR 512') - sleep(.2) - self.write('STAT:OPER:NTR 0') - sleep(5) - self.ask('STAT:OPER?') - sleep(.2) + self._set_transition_filter(512, 0) self.averaging(300) self._set_conf_power() @@ -184,3 +177,13 @@ def _get_power(self) -> float: sleep(.2) power = self.ask('FETC?') return float(power) + + def _set_transition_filter(self, positive: int, negative: int) -> None: + """Apply filters + """ + self.write(f'STAT:OPER:PTR {positive}') + sleep(.2) + self.write(f'STAT:OPER:NTR {negative}') + sleep(5) + self.ask('STAT:OPER?') # clear register + sleep(.2) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/TDC001.py b/qcodes_contrib_drivers/drivers/Thorlabs/TDC001.py index 4953ab527..200964200 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/TDC001.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/TDC001.py @@ -6,6 +6,7 @@ Julien Barrier, """ import logging +from typing import Optional from .private.CC import _Thorlabs_CC @@ -15,20 +16,28 @@ class Thorlabs_TDC001(_Thorlabs_CC): """Instrument driver for the Thorlabs TDC001 servo motor controller Args: - name (str): Instrument name. - serial_number (str): Serial number of the device. - simulation (bool): Enables the simulation manager. Defaults to False. - polling (int): Polling rate in ms. Defaults to 200. - home (bool): Sets the device to home state. Defaults to False. + name: Instrument name. + serial_number: Serial number of the device. + dll_path: Path to the kinesis dll for the instrument to use. + dll_dir: Directory in which the kinesis dll are stored. + simulation: Enables the simulation manager. Defaults to False. + polling: Polling rate in ms. Defaults to 200. + home: Sets the device to home state. Defaults to False. """ _CONDITIONS = ['homed', 'moved', 'stopped', 'limit_updated'] def __init__(self, name: str, serial_number: str, + dll_path: Optional[str] = None, + dll_dir: Optional[str] = None, simulation: bool = False, polling: int = 200, home: bool = False, **kwargs): - self._dll_path = 'Thorlabs.MotionControl.TCube.DCServo.dll' - super().__init__(name, serial_number, self._dll_path, - simulation, polling, home, **kwargs) \ No newline at end of file + if dll_path: + self._dll_path = dll_path + else: + self._dll_path = 'Thorlabs.MotionControl.TCube.DCServo.dll' + self._dll_dir: str | None = dll_dir if dll_dir else None + super().__init__(name, serial_number, self._dll_path, self._dll_dir, + simulation, polling, home, **kwargs) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py index 918cf9dc2..a134d4f53 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/private/CC.py @@ -7,6 +7,7 @@ import ctypes import logging from time import sleep, time +from typing import Optional from qcodes.parameters import Parameter from qcodes import validators as vals @@ -19,25 +20,27 @@ class _Thorlabs_CC(_Thorlabs_Kinesis): """Instrument driver for Thorlabs instruments using the CC commands Args: - name (str): Instrument name. - serial_number (str): Serial number of the device. - dll_path (str): Path to the kinesis dll for the instrument to use. - simulation (bool): Enables the simulation manager. Defaults to False. - polling (int): Polling rate in ms. Defaults to 200. - home (bool): Sets the device to home state. Defaults to False. + name: Instrument name. + serial_number: Serial number of the device. + dll_path: Path to the kinesis dll for the instrument to use. + dll_dir: Directory in which the kinesis dll are stored. + simulation: Enables the simulation manager. Defaults to False. + polling: Polling rate in ms. Defaults to 200. + home: Sets the device to home state. Defaults to False. """ _CONDITIONS = ['homed', 'moved', 'stopped', 'limit_updated'] def __init__(self, name: str, serial_number: str, dll_path: str, + dll_dir: Optional[str], simulation: bool = False, polling: int = 200, home: bool = False, **kwargs): self._dll_path = dll_path super().__init__(name, serial_number, - self._dll_path, simulation, + self._dll_path, dll_dir, simulation, **kwargs) if self._dll.TLI_BuildDeviceList() == 0: @@ -200,7 +203,7 @@ def go_home(self, block=True): """Home the device: set the device to a known state and home position Args: - block (bool, optional): will wait for completion. Defaults to True. + block: will wait for completion. Defaults to True. """ self.log.info('home the device.') self._check_error(self._dll.CC_Home(self._serial_number)) @@ -208,12 +211,12 @@ def go_home(self, block=True): if block: self.wait_for_completion() - def wait_for_completion(self, status: str = 'homed', max_time = 5) -> None: + def wait_for_completion(self, status: str = 'homed', max_time: float = 5) -> None: """Wait for the current function to be finished. Args: - status (str, optional): expected status. Defaults to 'homed'. - max_time (int, optional): maximum waiting time for the internal loop. Defaults to 5. + status: expected status. Defaults to 'homed'. + max_time: maximum waiting time for the internal loop. """ self.log.debug('wait for the current function to be completed') message_type = ctypes.c_ushort() @@ -257,8 +260,8 @@ def _device_unit_to_real(self, device_unit: int, unit_type: int) -> float: """Converts a device unit to a real world unit Args: - device_unit (int): the device unit. - unit_type (int): the type of unit. Distance: 0, velocity: 1, acceleration: 2. + device_unit: the device unit. + unit_type: the type of unit. Distance: 0, velocity: 1, acceleration: 2. Returns: float: real unit value @@ -275,8 +278,8 @@ def _real_to_device_unit(self, real_unit: float, unit_type: int) -> int: """Converts a real world unit to a device unit Args: - real_unit (float): the real unit - unit_type (int): the type of unit. Distance: 0, velocity: 1, acceleration: 2 + real_unit: the real unit + unit_type: the type of unit. Distance: 0, velocity: 1, acceleration: 2 Returns: int: device unit @@ -533,7 +536,7 @@ def _set_position(self, position: float, block: bool=True) -> None: sleep(.2) diff = abs(self._get_position() - position) diff -= 360 if diff > 180 else diff - + def is_moving(self) -> bool: """check if the motor cotnroller is moving.""" self.log.info('check if the motor is moving') @@ -583,7 +586,7 @@ def move_continuous(self, direction: str = 'forward') -> None: """start moving at the current velocity in the specified direction Args: - direction (str, optional): the required direction of travel. + direction: the required direction of travel. Defaults to 'forward'. Accepts 'forward' or 'reverse' """ self.log.info(f'move continuously. direction: {direction}') @@ -602,9 +605,9 @@ def jog(self, direction: str = 'forward', block: bool = True) -> None: """Performs a jog Args: - direction (str): the jog direction. Defaults to 'forward'. + direction: the jog direction. Defaults to 'forward'. Accepts 'forward' or 'reverse' - block (bool, optional): will wait until complete. Defaults to True. + block: will wait until complete. Defaults to True. """ self.log.info(f'perform a jog; direction: {direction}') if direction == 'forward' or direction == 'forwards': @@ -625,7 +628,7 @@ def stop(self, immediate: bool = False) -> None: """Stop the current move Args: - immediate (bool, optional): + immediate: True: stops immediately (with risk of losing track of position). False: stops using the current velocity profile. Defaults to False. @@ -706,7 +709,7 @@ def _start_polling(self, polling: int) -> None: def _stop_polling(self) -> None: self.log.info('stop polling') self._dll.CC_StopPolling(self._serial_number) - + def _clear_message_queue(self) -> None: self.log.info('clear messages queue') self._dll.CC_ClearMessageQueue(self._serial_number) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/LS.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/LS.py index 5c432233e..fc595ad66 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/private/LS.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/private/LS.py @@ -7,7 +7,8 @@ """ import ctypes import logging -from time import sleep, time +from time import sleep +from typing import Optional from qcodes.parameters import Parameter from qcodes import validators as vals @@ -20,27 +21,29 @@ class _Thorlabs_LS(_Thorlabs_Kinesis): """Instrument driver for Thorlabs instruments using the CC commands Args: - name (str): Instrument name. - serial_number (str): Serial number of the device. - dll_path (str): Path to the kinesis dll for the instrument to use. - simulation (bool): Enables the simulation manager. Defaults to False. - polling (int): Polling rate in ms. Defaults to 200. - home (bool): Sets the device to home state. Defaults to False. + name: Instrument name. + serial_number: Serial number of the device. + dll_path: Path to the kinesis dll for the instrument to use. + dll_dir: Directory in which the kinesis dll are stored. + simulation: Enables the simulation manager. Defaults to False. + polling: Polling rate in ms. Defaults to 200. + home: Sets the device to home state. Defaults to False. """ _CONDITIONS = ['homed', 'moved', 'stopped', 'limit_updated'] def __init__(self, name: str, serial_number: str, dll_path: str, + dll_dir: Optional[str], simulation: bool = False, polling: int = 200, home: bool = False, **kwargs): self._dll_path = dll_path super().__init__(name, serial_number, - self._dll_path, simulation, + self._dll_path, dll_dir, simulation, **kwargs) - + if self._dll.TLI_BuildDeviceList() == 0: self._open_laser() self._start_polling(polling) @@ -75,7 +78,7 @@ def __init__(self, docstring='turn laser output off/on. Note that laser key switch must be turned on to turn output on.', instrument=self ) - + self.power = Parameter( 'power', label='Power output', @@ -103,10 +106,10 @@ def _get_status_bits(self) -> int: raise ValueError() else: return int(status) - + def _get_output_enabled(self) -> bool: return bool(self._get_status_bits() & 1) - + def _set_output_enabled(self, value: bool) -> None: if value: self.enable_output() @@ -116,17 +119,17 @@ def _set_output_enabled(self, value: bool) -> None: def enable_output(self) -> None: ret = self._dll.LS_EnableOutput(self._serial_number) self._check_error(ret) - + def disable_output(self) -> None: ret = self._dll.LS_DisableOutput(self._serial_number) self._check_error(ret) - + def _get_power(self) -> float: max_num = 32767 max_power = .007 num = self._dll.LS_GetPowerReading(self._serial_number) return num/max_num * max_power - + def _set_power(self, power: float) -> None: max_num = 32767 max_power = .007 @@ -176,11 +179,11 @@ def _load_settings(self): """Update device with stored settings""" self._dll.LS_LoadSettings(self._serial_number) return None - + def _open_laser(self) -> None: ret = self._dll.LS_Open(self._serial_number) self._check_error(ret) - + def _close_laser(self) -> None: ret = self._dll.LS_Close(self._serial_number) self._check_error(ret) @@ -190,7 +193,7 @@ def _start_polling(self, polling: int): ret = self._dll.LS_StartPolling(self._serial_number, ctypes.byref(pol)) self._check_error(ret) return None - + def _stop_polling(self): self._dll.LS_StopPolling(self._serial_number) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py index b3066902c..22a07f8a2 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py @@ -7,7 +7,7 @@ import os import sys import ctypes -from typing import Any +from typing import Any, Optional from qcodes.instrument import Instrument from . import GeneralErrors, MotorErrors, ConnexionErrors @@ -16,26 +16,29 @@ class _Thorlabs_Kinesis(Instrument): """A base class for Thorlabs kinesis instruments Args: - name (str): Instrument name. - serial_number (str): Serial number of the device. - dll_path (str): Path to the kinesis dll for the instrument to use. - simulation (bool): Enables the simulation manager. Defaults to False. + name: Instrument name. + serial_number: Serial number of the device. + dll_path: Path to the kinesis dll for the instrument to use. + dll_dir: Directory in which the kinesis dll are stored. + simulation: Enables the simulation manager. Defaults to False. """ def __init__(self, name: str, serial_number: str, dll_path: str, + dll_dir: Optional[str] = None, simulation: bool = False, **kwargs): super().__init__(name, **kwargs) self.serial_number = serial_number self._serial_number = ctypes.c_char_p(self.serial_number.encode('ascii')) self._dll_path = dll_path + self._dll_dir: str | None = dll_dir if dll_dir else r'C:\Program Files\Thorlabs\Kinesis' if sys.platform != 'win32': self._dll: Any = None raise OSError('Thorlabs Kinesis only works on Windows') else: - os.add_dll_directory(r'C:\Program Files\Thorlabs\Kinesis') + os.add_dll_directory(self._dll_dir) self._dll = ctypes.cdll.LoadLibrary(self._dll_path) self._simulation = simulation From 8d8a367818880afa9dfcdd8a20b7b98259493eee Mon Sep 17 00:00:00 2001 From: Julien Barrier <1346858+julienbarrier@users.noreply.github.com> Date: Thu, 31 Aug 2023 11:40:17 +0200 Subject: [PATCH 30/30] use Optional instead of | None for python < 3.10 --- qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py | 2 +- qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py | 2 +- qcodes_contrib_drivers/drivers/Thorlabs/TDC001.py | 2 +- qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py index e78ddf9e7..91dedf4e6 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KDC101.py @@ -37,6 +37,6 @@ def __init__(self, self._dll_path = dll_path else: self._dll_path = 'Thorlabs.MotionControl.KCube.DCServo.dll' - self._dll_dir: str | None = dll_dir if dll_dir else None + self._dll_dir: Optional[str] = dll_dir if dll_dir else None super().__init__(name, serial_number, self._dll_path, self._dll_dir, simulation, polling, home, **kwargs) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py b/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py index 8fc71f88f..2c42de72a 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/KLS1550.py @@ -34,6 +34,6 @@ def __init__(self, self._dll_path = dll_path else: self._dll_path = 'Thorlabs.MotionControl.KCube.LaserSource.dll' - self._dll_dir: str | None = dll_dir if dll_dir else None + self._dll_dir: Optional[str] = dll_dir if dll_dir else None super().__init__(name, serial_number, self._dll_path, self._dll_dir, simulation, polling, **kwargs) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/TDC001.py b/qcodes_contrib_drivers/drivers/Thorlabs/TDC001.py index 200964200..dcba4a894 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/TDC001.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/TDC001.py @@ -38,6 +38,6 @@ def __init__(self, self._dll_path = dll_path else: self._dll_path = 'Thorlabs.MotionControl.TCube.DCServo.dll' - self._dll_dir: str | None = dll_dir if dll_dir else None + self._dll_dir: Optional[str] = dll_dir if dll_dir else None super().__init__(name, serial_number, self._dll_path, self._dll_dir, simulation, polling, home, **kwargs) diff --git a/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py b/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py index 22a07f8a2..06eff4ebc 100644 --- a/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py +++ b/qcodes_contrib_drivers/drivers/Thorlabs/private/kinesis.py @@ -33,7 +33,7 @@ def __init__(self, self.serial_number = serial_number self._serial_number = ctypes.c_char_p(self.serial_number.encode('ascii')) self._dll_path = dll_path - self._dll_dir: str | None = dll_dir if dll_dir else r'C:\Program Files\Thorlabs\Kinesis' + self._dll_dir: Optional[str] = dll_dir if dll_dir else r'C:\Program Files\Thorlabs\Kinesis' if sys.platform != 'win32': self._dll: Any = None raise OSError('Thorlabs Kinesis only works on Windows')