diff --git a/doc/examples/qubitekk/ex_qubitekk_mc1.py b/doc/examples/qubitekk/ex_qubitekk_mc1.py new file mode 100644 index 000000000..cd124e354 --- /dev/null +++ b/doc/examples/qubitekk/ex_qubitekk_mc1.py @@ -0,0 +1,39 @@ +#!/usr/bin/python +# Qubitekk Motor controller example +from time import sleep + +from instruments.qubitekk import MC1 +import quantities as pq + + +if __name__ == "__main__": + + mc1 = MC1.open_serial(vid=1027, pid=24577, baud=9600, timeout=1) + mc1.step_size = 25*pq.ms + mc1.inertia = 10*pq.ms + print("step size:", mc1.step_size) + print("inertial force: ", mc1.inertia) + + print("Firmware", mc1.firmware) + print("Motor controller type: ", mc1.controller) + print("centering") + + mc1.center() + while mc1.is_centering(): + print(str(mc1.metric_position)+" "+str(mc1.direction)) + pass + + print("Stage Centered") + # for the motor in the mechanical delay line, the travel is limited from + # the full range of travel. Here's how to set the limits. + mc1.lower_limit = -260*pq.ms + mc1.upper_limit = 300*pq.ms + mc1.increment = 5*pq.ms + x_pos = mc1.lower_limit + while x_pos <= mc1.upper_limit: + print(str(mc1.metric_position)+" "+str(mc1.direction)) + mc1.move(x_pos) + while mc1.move_timeout > 0: + sleep(0.5) + sleep(1) + x_pos += mc1.increment diff --git a/doc/source/apiref/ondax.rst b/doc/source/apiref/ondax.rst new file mode 100644 index 000000000..3320c2993 --- /dev/null +++ b/doc/source/apiref/ondax.rst @@ -0,0 +1,16 @@ +.. + TODO: put documentation license header here. + +.. currentmodule:: instruments.ondax + +===== +Ondax +===== + +:class:`LM` Ondax SureLock Laser Module +======================================= + +.. autoclass:: LM + :members: + :undoc-members: + diff --git a/doc/source/apiref/qubitekk.rst b/doc/source/apiref/qubitekk.rst index 6c97a4d34..b86dc6253 100644 --- a/doc/source/apiref/qubitekk.rst +++ b/doc/source/apiref/qubitekk.rst @@ -14,4 +14,9 @@ Qubitekk :members: :undoc-members: +:class:`MC1` Motor Controller +============================= +.. autoclass:: MC1 + :members: + :undoc-members: diff --git a/instruments/ondax/__init__.py b/instruments/ondax/__init__.py new file mode 100644 index 000000000..5b59b2cb7 --- /dev/null +++ b/instruments/ondax/__init__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Module containing Ondax Instruments +""" +from __future__ import absolute_import +from .lm import LM diff --git a/instruments/ondax/lm.py b/instruments/ondax/lm.py new file mode 100644 index 000000000..11070253a --- /dev/null +++ b/instruments/ondax/lm.py @@ -0,0 +1,546 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Provides the support for the Ondax LM Laser. + +Class originally contributed by Catherine Holloway. +""" + +# IMPORTS ##################################################################### + +from __future__ import absolute_import +from __future__ import division + +from enum import IntEnum + +import quantities as pq + +from instruments.abstract_instruments import Instrument +from instruments.util_fns import convert_temperature, assume_units + +# CLASSES ##################################################################### + + +class LM(Instrument): + """ + The LM is the Ondax SureLock VHG-stabilized laser diode. + + The user manual can be found on the `Ondax website`_. + + .. _Ondax website: http://www.ondax.com/Downloads/SureLock/Compact%20laser%20module%20manual.pdf + """ + + def __init__(self, filelike): + super(LM, self).__init__(filelike) + self.terminator = "\r" + self.apc = self._AutomaticPowerControl(self) + self.acc = self._AutomaticCurrentControl(self) + self.tec = self._ThermoElectricCooler(self) + self.modulation = self._Modulation(self) + self._enabled = None + + # ENUMS # + class Status(IntEnum): + """ + Enum containing the valid states of the laser + """ + normal = 1 + inner_modulation = 2 + power_scan = 3 + calibrate = 4 + shutdown_current = 5 + shutdown_overheat = 6 + waiting_stable_temperature = 7 + waiting = 8 + + # INNER CLASSES # + + class _AutomaticCurrentControl(object): + """ + Options and functions related to the laser diode's automatic current + control driver. + + .. warning:: This class is not designed to be accessed directly. It + should be interfaced via `LM.acc` + """ + def __init__(self, parent): + self._parent = parent + self._enabled = False + + @property + def target(self): + """ + Gets the automatic current control target setting. + + This property is accessed via the `LM.acc` namespace. + + Example usage: + + >>> import instruments as ik + >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234) + >>> print(laser.acc.target) + + :return: Current ACC of the Laser + :units: mA + :type: `~quantities.Quantity` + """ + response = float(self._parent.query("rstli?")) + return response*pq.mA + + @property + def enabled(self): + """ + Get/Set the enabled state of the ACC driver. + + This property is accessed via the `LM.acc` namespace. + + Example usage: + + >>> import instruments as ik + >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234) + >>> print(laser.acc.enabled) + >>> laser.acc.enabled = True + + :type: `bool` + """ + return self._enabled + + @enabled.setter + def enabled(self, newval): + if not isinstance(newval, bool): + raise TypeError("ACC driver enabled property must be specified" + "with a boolean, got {}.".format(type(newval))) + if newval: + self._parent.sendcmd("lcen") + else: + self._parent.sendcmd("lcdis") + self._enabled = newval + + def on(self): + """ + Turns on the automatic current control driver. + + This function is accessed via the `LM.acc` namespace. + + Example usage: + + >>> import instruments as ik + >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234) + >>> laser.acc.on() + """ + self._parent.sendcmd("lcon") + + def off(self): + """ + Turn off the automatic current control driver. + + This function is accessed via the `LM.acc` namespace. + + Example usage: + + >>> import instruments as ik + >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234) + >>> laser.acc.off() + """ + self._parent.sendcmd("lcoff") + + class _AutomaticPowerControl(object): + """ + Options and functions related to the laser diode's automatic power + control driver. + + .. warning:: This class is not designed to be accessed directly. It + should be interfaced via `LM.apc` + """ + def __init__(self, parent): + self._parent = parent + self._enabled = False + + @property + def target(self): + """ + Gets the target laser power of the automatic power control in mW. + + This property is accessed via the `LM.apc` namespace. + + Example usage: + + >>> import instruments as ik + >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234) + >>> print(laser.apc.target) + + :return: the target laser power + :units: mW + :type: `~quantities.Quantities` + """ + response = self._parent.query("rslp?") + return float(response)*pq.mW + + @property + def enabled(self): + """ + Get/Set the enabled state of the automatic power control driver. + + This property is accessed via the `LM.apc` namespace. + + Example usage: + + >>> import instruments as ik + >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234) + >>> print(laser.apc.enabled) + >>> laser.apc.enabled = True + + :type: `bool` + """ + return self._enabled + + @enabled.setter + def enabled(self, newval): + if not isinstance(newval, bool): + raise TypeError("APC driver enabled property must be specified " + "with a boolean, got {}.".format(type(newval))) + if newval: + self._parent.sendcmd("len") + else: + self._parent.sendcmd("ldis") + self._enabled = newval + + def start(self): + """ + Start the automatic power control scan. + + This function is accessed via the `LM.apc` namespace. + + Example usage: + + >>> import instruments as ik + >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234) + >>> laser.apc.start() + """ + self._parent.sendcmd("sps") + + def stop(self): + """ + Stop the automatic power control scan. + + This function is accessed via the `LM.apc` namespace. + + Example usage: + + >>> import instruments as ik + >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234) + >>> laser.apc.stop() + """ + self._parent.sendcmd("cps") + + class _Modulation(object): + """ + Options and functions related to the laser's optical output modulation. + + .. warning:: This class is not designed to be accessed directly. It + should be interfaced via `LM.modulation` + """ + def __init__(self, parent): + self._parent = parent + self._enabled = False + + @property + def on_time(self): + """ + Gets/sets the TTL modulation on time, in milliseconds. + + This property is accessed via the `LM.modulation` namespace. + + Example usage: + + >>> import instruments as ik + >>> import quantities as pq + >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234) + >>> print(laser.modulation.on_time) + >>> laser.modulation.on_time = 1 * pq.ms + + :return: The TTL modulation on time + :units: As specified (if a `~quantities.Quantity`) or assumed + to be of units milliseconds. + :type: `~quantities.Quantity` + """ + response = self._parent.query("stsont?") + return float(response)*pq.ms + + @on_time.setter + def on_time(self, newval): + newval = assume_units(newval, pq.ms).rescale(pq.ms).magnitude + self._parent.sendcmd("stsont:"+str(newval)) + + @property + def off_time(self): + """ + Gets/sets the TTL modulation off time, in milliseconds. + + This property is accessed via the `LM.modulation` namespace. + + Example usage: + + >>> import instruments as ik + >>> import quantities as pq + >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234) + >>> print(laser.modulation.on_time) + >>> laser.modulation.on_time = 1 * pq.ms + + :return: The TTL modulation off time. + :units: As specified (if a `~quantities.Quantity`) or assumed + to be of units milliseconds. + :type: `~quantities.Quantity` + """ + response = self._parent.query("stsofft?") + return float(response)*pq.ms + + @off_time.setter + def off_time(self, newval): + newval = assume_units(newval, pq.ms).rescale(pq.ms).magnitude + self._parent.sendcmd("stsofft:"+str(newval)) + + @property + def enabled(self): + """ + Get/Set the TTL modulation output state. + + This property is accessed via the `LM.modulation` namespace. + + Example usage: + + >>> import instruments as ik + >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234) + >>> print(laser.modulation.enabled) + >>> laser.modulation.enabled = True + + :type: `bool` + """ + return self._enabled + + @enabled.setter + def enabled(self, newval): + if not isinstance(newval, bool): + raise TypeError("Modulation enabled property must be specified " + "with a boolean, got {}.".format(type(newval))) + if newval: + self._parent.sendcmd("stm") + else: + self._parent.sendcmd("ctm") + self._enabled = newval + + class _ThermoElectricCooler(object): + """ + Options and functions relating to the laser diode's thermo electric + cooler. + + .. warning:: This class is not designed to be accessed directly. It + should be interfaced via `LM.tec` + """ + def __init__(self, parent): + self._parent = parent + self._enabled = False + + @property + def current(self): + """ + Gets the thermoelectric cooler current setting. + + This property is accessed via the `LM.tec` namespace. + + Example usage: + + >>> import instruments as ik + >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234) + >>> print(laser.tec.current) + + :units: mA + :type: `~quantities.Quantity` + """ + response = self._parent.query("rti?") + return float(response)*pq.mA + + @property + def target(self): + """ + Gets the thermoelectric cooler target temperature. + + This property is acccessed via the `LM.tec` namespace. + + Example usage: + + >>> import instruments as ik + >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234) + >>> print(laser.tec.target) + + :units: Degrees Celcius + :type: `~quantities.Quantity` + """ + response = self._parent.query("rstt?") + return float(response)*pq.degC + + @property + def enabled(self): + """ + Gets/sets the enable state for the thermoelectric cooler. + + This property is accessed via the `LM.tec` namespace. + + Example usage: + + >>> import instruments as ik + >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234) + >>> print(laser.tec.enabled) + >>> laser.tec.enabled = True + + :type: `bool` + """ + return self._enabled + + @enabled.setter + def enabled(self, newval): + if not isinstance(newval, bool): + raise TypeError("TEC enabled property must be specified with " + "a boolean, got {}.".format(type(newval))) + if newval: + self._parent.sendcmd("tecon") + else: + self._parent.sendcmd("tecoff") + self._enabled = newval + + def _ack_expected(self, msg=""): + if msg.find("?") > 0: + return None + else: + return "OK" + + @property + def firmware(self): + """ + Gets the laser system firmware version. + + :type: `str` + """ + response = self.query("rsv?") + return response + + @property + def current(self): + """ + Gets/sets the laser diode current, in mA. + + :units: As specified (if a `~quantities.Quantity`) or assumed + to be of units mA. + :type: `~quantities.Quantity` + """ + response = self.query("rli?") + return float(response)*pq.mA + + @current.setter + def current(self, newval): + newval = assume_units(newval, pq.mA).rescale(pq.mA).magnitude + self.sendcmd("slc:"+str(newval)) + + @property + def maximum_current(self): + """ + Get/Set the maximum laser diode current in mA. If the current is set + over the limit, the laser will shut down. + + :units: As specified (if a `~quantities.Quantity`) or assumed + to be of units mA. + :type: `~quantities.Quantity` + """ + response = self.query("rlcm?") + return float(response)*pq.mA + + @maximum_current.setter + def maximum_current(self, newval): + newval = assume_units(newval, pq.mA).rescale('mA').magnitude + self.sendcmd("smlc:" + str(newval)) + + @property + def power(self): + """ + Get/Set the laser's optical power in mW. + + :units: As specified (if a `~quantities.Quantity`) or assumed + to be of units mW. + :rtype: `~quantities.Quantity` + """ + response = self.query("rlp?") + return float(response)*pq.mW + + @power.setter + def power(self, newval): + newval = assume_units(newval, pq.mW).rescale(pq.mW).magnitude + self.sendcmd("slp:"+str(newval)) + + @property + def serial_number(self): + """ + Gets the laser controller serial number + + :type: `str` + """ + response = self.query("rsn?") + return response + + @property + def status(self): + """ + Read laser controller run status. + + :type: `LM.Status` + """ + response = self.query("rlrs?") + return self.Status(int(response)) + + @property + def temperature(self): + """ + Gets/sets laser diode temperature. + + :units: As specified (if a `~quantities.Quantity`) or assumed + to be of units degrees celcius. + :type: `~quantities.Quantity` + """ + response = self.query("rtt?") + return float(response)*pq.degC + + @temperature.setter + def temperature(self, newval): + newval = convert_temperature(newval, pq.degC).magnitude + self.sendcmd("stt:"+str(newval)) + + @property + def enabled(self): + """ + Gets/sets the laser emission enabled status. + + :type: `bool` + """ + return self._enabled + + @enabled.setter + def enabled(self, newval): + if not isinstance(newval, bool): + raise TypeError("Laser module enabled property must be specified " + "with a boolean, got {}.".format(type(newval))) + if newval: + self.sendcmd("lon") + else: + self.sendcmd("loff") + self._enabled = newval + + def save(self): + """ + Save current settings in flash memory. + """ + self.sendcmd("ssc") + + def reset(self): + """ + Reset the laser controller. + """ + self.sendcmd("reset") diff --git a/instruments/qubitekk/__init__.py b/instruments/qubitekk/__init__.py index bea20273e..e71003a05 100644 --- a/instruments/qubitekk/__init__.py +++ b/instruments/qubitekk/__init__.py @@ -7,3 +7,4 @@ from __future__ import absolute_import from .cc1 import CC1 +from .mc1 import MC1 diff --git a/instruments/qubitekk/mc1.py b/instruments/qubitekk/mc1.py new file mode 100644 index 000000000..98b353962 --- /dev/null +++ b/instruments/qubitekk/mc1.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Provides support for the Qubitekk MC1 Motor Controller. + +MC1 Class originally contributed by Catherine Holloway. +""" + +# IMPORTS ##################################################################### + +from __future__ import absolute_import, division + +from builtins import range, map +from enum import Enum + +import quantities as pq + +from instruments.abstract_instruments import Instrument +from instruments.util_fns import ( + int_property, enum_property, unitful_property, assume_units +) + +# CLASSES ##################################################################### + + +class MC1(Instrument): + """ + The MC1 is a controller for the qubitekk motor controller. Used with a + linear actuator to perform a HOM dip. + """ + def __init__(self, filelike): + super(MC1, self).__init__(filelike) + self.terminator = "\r" + self._increment = 1*pq.ms + self._lower_limit = -300*pq.ms + self._upper_limit = 300*pq.ms + self._firmware = None + self._controller = None + + # ENUMS # + + class MotorType(Enum): + """ + Enum for the motor types for the MC1 + """ + radio = "Radio" + relay = "Relay" + + # PROPERTIES # + + @property + def increment(self): + """ + Gets/sets the stepping increment value of the motor controller + + :units: As specified, or assumed to be of units milliseconds + :type: `~quantities.Quantity` + """ + return self._increment + + @increment.setter + def increment(self, newval): + self._increment = assume_units(newval, pq.ms).rescale(pq.ms) + + @property + def lower_limit(self): + """ + Gets/sets the stepping lower limit value of the motor controller + + :units: As specified, or assumed to be of units milliseconds + :type: `~quantities.Quantity` + """ + return self._lower_limit + + @lower_limit.setter + def lower_limit(self, newval): + self._lower_limit = assume_units(newval, pq.ms).rescale(pq.ms) + + @property + def upper_limit(self): + """ + Gets/sets the stepping upper limit value of the motor controller + + :units: As specified, or assumed to be of units milliseconds + :type: `~quantities.Quantity` + """ + return self._upper_limit + + @upper_limit.setter + def upper_limit(self, newval): + self._upper_limit = assume_units(newval, pq.ms).rescale(pq.ms) + + direction = unitful_property( + name="DIRE", + doc=""" + Get the internal direction variable, which is a function of how far + the motor needs to go. + + :type: `~quantities.Quantity` + :units: milliseconds + """, + units=pq.ms, + readonly=True + ) + + inertia = unitful_property( + name="INER", + doc=""" + Gets/Sets the amount of force required to overcome static inertia. Must + be between 0 and 100 milliseconds. + + :type: `~quantities.Quantity` + :units: milliseconds + """, + format_code='{:.0f}', + units=pq.ms, + valid_range=(0*pq.ms, 100*pq.ms), + set_fmt=":{} {}" + ) + + @property + def internal_position(self): + """ + Get the internal motor state position, which is equivalent to the total + number of milliseconds that voltage has been applied to the motor in + the positive direction minus the number of milliseconds that voltage + has been applied to the motor in the negative direction. + + :type: `~quantities.Quantity` + :units: milliseconds + """ + response = int(self.query("POSI?"))*self.step_size + return response + + metric_position = unitful_property( + name="METR", + doc=""" + Get the estimated motor position, in millimeters. + + :type: `~quantities.Quantity` + :units: millimeters + """, + units=pq.mm, + readonly=True + ) + + setting = int_property( + name="OUTP", + doc=""" + Gets/sets the output port of the optical switch. 0 means input 1 is + directed to output 1, and input 2 is directed to output 2. 1 means that + input 1 is directed to output 2 and input 2 is directed to output 1. + + :type: `int` + """, + valid_set=range(2), + set_fmt=":{} {}" + ) + + step_size = unitful_property( + name="STEP", + doc=""" + Gets/Sets the number of milliseconds per step. Must be between 1 + and 100 milliseconds. + + :type: `~quantities.Quantity` + :units: milliseconds + """, + format_code='{:.0f}', + units=pq.ms, + valid_range=(1*pq.ms, 100*pq.ms), + set_fmt=":{} {}" + ) + + @property + def firmware(self): + """ + Gets the firmware version + + :rtype: `tuple`(Major:`int`, Minor:`int`, Patch`int`) + """ + # the firmware is assumed not to change while the device is active + # firmware is stored locally as it will be gotten often + # pylint: disable=no-member + if self._firmware is None: + while self._firmware is None: + self._firmware = self.query("FIRM?") + value = self._firmware.split(".") + if len(value) < 3: + for _ in range(3-len(value)): + value.append(0) + value = tuple(map(int, value)) + self._firmware = value + return self._firmware + + controller = enum_property( + 'MOTO', + MotorType, + doc=""" + Get the motor controller type. + """, + readonly=True + ) + + @property + def move_timeout(self): + """ + Get the motor's timeout value, which indicates the number of + milliseconds before the motor can start moving again. + + :type: `~quantities.Quantity` + :units: milliseconds + """ + response = int(self.query("TIME?")) + return response*self.step_size + + # METHODS # + + def is_centering(self): + """ + Query whether the motor is in its centering phase + + :return: False if not centering, True if centering + :rtype: `bool` + """ + response = self.query("CENT?") + return True if int(response) == 1 else False + + def center(self): + """ + Commands the motor to go to the center of its travel range + """ + self.sendcmd(":CENT") + + def reset(self): + """ + Sends the stage to the limit of one of its travel ranges + """ + self.sendcmd(":RESE") + + def move(self, new_position): + """ + Move to a specified location. Position is unitless and is defined as + the number of motor steps. It varies between motors. + + :param new_position: the location + :type new_position: `~quantities.Quantity` + """ + if self.lower_limit <= new_position <= self.upper_limit: + new_position = assume_units(new_position, pq.ms).rescale(pq.ms) + clock_cycles = new_position/self.step_size + cmd = ":MOVE "+str(int(clock_cycles)) + self.sendcmd(cmd) + else: + raise ValueError("Location out of range") diff --git a/instruments/tests/test_ondax/test_lm.py b/instruments/tests/test_ondax/test_lm.py new file mode 100644 index 000000000..93c89de29 --- /dev/null +++ b/instruments/tests/test_ondax/test_lm.py @@ -0,0 +1,508 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Unit tests for the Ondax Laser Module +""" + +# IMPORTS ##################################################################### + +from __future__ import absolute_import + +from nose.tools import raises + +import quantities + +from instruments import ondax +from instruments.tests import expected_protocol + +# TESTS ####################################################################### + + +def test_acc_target(): + with expected_protocol( + ondax.LM, + [ + "rstli?" + ], + [ + "100" + ], + sep="\r" + ) as lm: + assert lm.acc.target == 100 * quantities.mA + + +def test_acc_enable(): + with expected_protocol( + ondax.LM, + [ + "lcen" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.acc.enabled = True + assert lm.acc.enabled + + +def test_acc_disable(): + with expected_protocol( + ondax.LM, + [ + "lcdis" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.acc.enabled = False + assert not lm.acc.enabled + + +@raises(TypeError) +def test_acc_enable_not_boolean(): + with expected_protocol( + ondax.LM, + [], + [], + sep="\r" + ) as lm: + lm.acc.enabled = "foobar" + + +def test_acc_on(): + with expected_protocol( + ondax.LM, + [ + "lcon" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.acc.on() + + +def test_acc_off(): + with expected_protocol( + ondax.LM, + [ + "lcoff" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.acc.off() + + +def test_apc_target(): + with expected_protocol( + ondax.LM, + [ + "rslp?" + ], + [ + "100" + ], + sep="\r" + ) as lm: + assert lm.apc.target == 100 * quantities.mW + + +def test_apc_enable(): + with expected_protocol( + ondax.LM, + [ + "len" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.apc.enabled = True + assert lm.apc.enabled + + +def test_apc_disable(): + with expected_protocol( + ondax.LM, + [ + "ldis" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.apc.enabled = False + assert not lm.apc.enabled + + +@raises(TypeError) +def test_apc_enable_not_boolean(): + with expected_protocol( + ondax.LM, + [], + [], + sep="\r" + ) as lm: + lm.apc.enabled = "foobar" + + + +def test_apc_start(): + with expected_protocol( + ondax.LM, + [ + "sps" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.apc.start() + + +def test_apc_stop(): + with expected_protocol( + ondax.LM, + [ + "cps" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.apc.stop() + + +def test_modulation_on_time(): + with expected_protocol( + ondax.LM, + [ + "stsont?", + "stsont:20.0" + ], + [ + "10", + "OK" + ], + sep="\r" + ) as lm: + assert lm.modulation.on_time == 10 * quantities.ms + lm.modulation.on_time = 20 * quantities.ms + + +def test_modulation_off_time(): + with expected_protocol( + ondax.LM, + [ + "stsofft?", + "stsofft:20.0" + ], + [ + "10", + "OK" + ], + sep="\r" + ) as lm: + assert lm.modulation.off_time == 10 * quantities.ms + lm.modulation.off_time = 20 * quantities.ms + + +def test_modulation_enabled(): + with expected_protocol( + ondax.LM, + [ + "stm" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.modulation.enabled = True + assert lm.modulation.enabled + + +def test_modulation_disabled(): + with expected_protocol( + ondax.LM, + [ + "ctm" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.modulation.enabled = False + assert not lm.modulation.enabled + + +@raises(TypeError) +def test_modulation_enable_not_boolean(): + with expected_protocol( + ondax.LM, + [], + [], + sep="\r" + ) as lm: + lm.modulation.enabled = "foobar" + + +def test_tec_current(): + with expected_protocol( + ondax.LM, + [ + "rti?" + ], + [ + "100" + ], + sep="\r" + ) as lm: + assert lm.tec.current == 100 * quantities.mA + + +def test_tec_target(): + with expected_protocol( + ondax.LM, + [ + "rstt?" + ], + [ + "22" + ], + sep="\r" + ) as lm: + assert lm.tec.target == 22 * quantities.degC + + +def test_tec_enable(): + with expected_protocol( + ondax.LM, + [ + "tecon" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.tec.enabled = True + assert lm.tec.enabled + + +def test_tec_disable(): + with expected_protocol( + ondax.LM, + [ + "tecoff" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.tec.enabled = False + assert not lm.tec.enabled + + +@raises(TypeError) +def test_tec_enable_not_boolean(): + with expected_protocol( + ondax.LM, + [], + [], + sep="\r" + ) as lm: + lm.tec.enabled = "foobar" + + +def test_firmware(): + with expected_protocol( + ondax.LM, + [ + "rsv?" + ], + [ + "3.27" + ], + sep="\r" + ) as lm: + assert lm.firmware == "3.27" + + +def test_current(): + with expected_protocol( + ondax.LM, + [ + "rli?", + "slc:100.0" + ], + [ + "120", + "OK" + ], + sep="\r" + ) as lm: + assert lm.current == 120 * quantities.mA + lm.current = 100 * quantities.mA + + +def test_maximum_current(): + with expected_protocol( + ondax.LM, + [ + "rlcm?", + "smlc:100.0" + ], + [ + "120", + "OK" + ], + sep="\r" + ) as lm: + assert lm.maximum_current == 120 * quantities.mA + lm.maximum_current = 100 * quantities.mA + + +def test_power(): + with expected_protocol( + ondax.LM, + [ + "rlp?", + "slp:100.0" + ], + [ + "120", + "OK" + ], + sep="\r" + ) as lm: + assert lm.power == 120 * quantities.mW + lm.power = 100 * quantities.mW + + +def test_serial_number(): + with expected_protocol( + ondax.LM, + [ + "rsn?" + ], + [ + "B099999" + ], + sep="\r" + ) as lm: + assert lm.serial_number == "B099999" + + +def test_status(): + with expected_protocol( + ondax.LM, + [ + "rlrs?" + ], + [ + "1" + ], + sep="\r" + ) as lm: + assert lm.status == lm.Status(1) + + +def test_temperature(): + with expected_protocol( + ondax.LM, + [ + "rtt?", + "stt:40.0" + ], + [ + "35", + "OK" + ], + sep="\r" + ) as lm: + assert lm.temperature == 35 * quantities.degC + lm.temperature = 40 * quantities.degC + + +def test_enable(): + with expected_protocol( + ondax.LM, + [ + "lon" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.enabled = True + assert lm.enabled + + +def test_disable(): + with expected_protocol( + ondax.LM, + [ + "loff" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.enabled = False + assert not lm.enabled + + +@raises(TypeError) +def test_enable_not_boolean(): + with expected_protocol( + ondax.LM, + [], + [], + sep="\r" + ) as lm: + lm.enabled = "foobar" + + +def test_save(): + with expected_protocol( + ondax.LM, + [ + "ssc" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.save() + + +def test_reset(): + with expected_protocol( + ondax.LM, + [ + "reset" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.reset() diff --git a/instruments/tests/test_qubitekk/test_qubitekk_mc1.py b/instruments/tests/test_qubitekk/test_qubitekk_mc1.py new file mode 100644 index 000000000..eff278cf6 --- /dev/null +++ b/instruments/tests/test_qubitekk/test_qubitekk_mc1.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Module containing tests for the Qubitekk MC1 +""" + +# IMPORTS #################################################################### + +from __future__ import absolute_import + +from nose.tools import raises + +import quantities as pq +import instruments as ik +from instruments.tests import expected_protocol + + +# TESTS ###################################################################### + + +def test_mc1_setting(): + with expected_protocol( + ik.qubitekk.MC1, + [ + "OUTP?", + ":OUTP 0" + ], + [ + "1" + ], + sep="\r" + ) as mc: + assert mc.setting == 1 + mc.setting = 0 + + +def test_mc1_internal_position(): + with expected_protocol( + ik.qubitekk.MC1, + [ + "POSI?", + "STEP?" + + ], + [ + "-100", + "1" + ], + sep="\r" + ) as mc: + assert mc.internal_position == -100*pq.ms + + +def test_mc1_metric_position(): + with expected_protocol( + ik.qubitekk.MC1, + [ + "METR?" + ], + [ + "-3.14159" + ], + sep="\r" + ) as mc: + assert mc.metric_position == -3.14159*pq.mm + + +def test_mc1_direction(): + with expected_protocol( + ik.qubitekk.MC1, + [ + "DIRE?" + ], + [ + "-100" + ], + sep="\r" + ) as mc: + assert mc.direction == -100 + + +def test_mc1_firmware(): + with expected_protocol( + ik.qubitekk.MC1, + [ + "FIRM?" + ], + [ + "1.0.1" + ], + sep="\r" + ) as mc: + assert mc.firmware == (1, 0, 1) + + +def test_mc1_inertia(): + with expected_protocol( + ik.qubitekk.MC1, + [ + "INER?" + ], + [ + "20" + ], + sep="\r" + ) as mc: + assert mc.inertia == 20 + + +def test_mc1_step(): + with expected_protocol( + ik.qubitekk.MC1, + [ + "STEP?" + ], + [ + "20" + ], + sep="\r" + ) as mc: + assert mc.step_size == 20*pq.ms + + +def test_mc1_motor(): + with expected_protocol( + ik.qubitekk.MC1, + [ + "MOTO?" + ], + [ + "Radio" + ], + sep="\r" + ) as mc: + assert mc.controller == mc.MotorType.radio + + +def test_mc1_move_timeout(): + with expected_protocol( + ik.qubitekk.MC1, + [ + "TIME?", + "STEP?" + ], + [ + "200", + "1" + ], + sep="\r" + ) as mc: + assert mc.move_timeout == 200*pq.ms + + +def test_mc1_is_centering(): + with expected_protocol( + ik.qubitekk.MC1, + ["CENT?"], + ["1"], + sep="\r" + ) as mc: + assert mc.is_centering() is True + + +def test_mc1_is_centering_false(): + with expected_protocol( + ik.qubitekk.MC1, + ["CENT?"], + ["0"], + sep="\r" + ) as mc: + assert mc.is_centering() is False + + +def test_mc1_center(): + with expected_protocol( + ik.qubitekk.MC1, + [":CENT"], + [""], + sep="\r" + ) as mc: + mc.center() + + +def test_mc1_reset(): + with expected_protocol( + ik.qubitekk.MC1, + [":RESE"], + [""], + sep="\r" + ) as mc: + mc.reset() + + +def test_mc1_move(): + with expected_protocol( + ik.qubitekk.MC1, + ["STEP?", ":MOVE 0"], + ["1"], + sep="\r" + ) as mc: + mc.move(0) + + +@raises(ValueError) +def test_mc1_move_value_error(): + with expected_protocol( + ik.qubitekk.MC1, + [":MOVE -1000"], + [""], + sep="\r" + ) as mc: + mc.move(-1000)