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/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/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()